目录

什么是 AG-UI:AI Agent 与前端应用之间的交互协议

2025 年 5 月,一个名叫 AG-UI 的协议悄悄出现在 GitHub 上,两个月内已有 LangGraph、CrewAI、Mastra 等主流 Agent 框架相继接入。

它要解决的问题并不复杂,却是每个做 AI 应用的团队迟早会碰到的:Agent 后端和前端界面之间,用什么格式说话?

/img/what-is-ag-ui/0101.png
AIAgent协议三层架构


三层协议,各司其职

在 AG-UI 出现之前,AI Agent 生态里已经有两个协议占据了重要位置:

  • MCP(Model Context Protocol,Anthropic 主导):解决 Agent 访问外部工具和数据源的问题
  • A2A(Agent to Agent,Google 主导):解决多 Agent 之间协作和任务委托的问题

这两个协议覆盖了"Agent 对外"的两个方向,但都没有管"Agent 对用户"的这一段。

AG-UI 补的就是这个缺口——Agent 和前端应用(用户界面)之间的通信标准

三个协议拼在一起,才构成一套完整的 Agent 协议栈。


AG-UI 是什么

全称 Agent User Interaction Protocol,由 CopilotKit 社区发起,2025 年 5 月正式开源。

一句话概括:一套开放的、轻量的、基于事件的协议,定义了 AI Agent 后端如何向前端应用发送信息,以及前端如何把用户输入传回给 Agent。

它不绑定任何框架,不要求特定的传输方式(SSE、WebSocket、Webhook 都可以),也不限制 Agent 内部用什么模型或技术栈。

唯一的要求是:当 Agent 需要和用户界面交互时,双方遵守同一套事件格式。

/img/what-is-ag-ui/0102.png
AG-UI核心架构

典型的请求链路是这样的:

  1. 用户在前端输入消息
  2. 前端通过 AG-UI Client 把消息发给 Agent 后端
  3. Agent 处理请求,调用 LLM 或工具
  4. Agent 把结果以事件流的形式返回给前端
  5. 前端根据事件类型实时更新 UI

整个过程双向流动:Agent 向前端下发事件,前端向 Agent 上报消息和工具调用结果。


核心:16 种标准事件

AG-UI 定义了 16 种标准事件类型,覆盖一次 Agent 交互的完整生命周期。

/img/what-is-ag-ui/0103.png
AG-UI16种标准事件类型

生命周期事件管理整个 Agent 运行流程:

RUN_STARTED → STEP_STARTED → STEP_FINISHED → RUN_FINISHED

出错时发送 RUN_ERROR。前端收到 RUN_STARTED 就可以开始显示"正在思考"状态,收到 RUN_FINISHED 再收起来。

文本消息事件处理流式文字输出,类似 OpenAI streaming API 的拆包形式:

TEXT_MESSAGE_START → TEXT_MESSAGE_CONTENT(多次)→ TEXT_MESSAGE_END

工具调用事件让前端清楚地知道 Agent 正在调什么工具、参数是什么:

TOOL_CALL_START → TOOL_CALL_ARGS → TOOL_CALL_END

AG-UI 的设计里,工具列表由前端告知 Agent,调用结果由前端传回 Agent

这样前端可以选择是否弹出确认框让用户审批,实现 Human-in-the-Loop。

状态管理事件解决前后端状态同步的问题:

  • STATE_SNAPSHOT:发送完整状态,用于初始化或恢复
  • STATE_DELTA:发送增量变化,减少数据量

典型场景:Agent 正在实时修改一份文档或表单,状态事件驱动前端 UI 实时更新,不需要轮询。

16 种事件类型详细示例

1. 生命周期事件示例

// RUN_STARTED - Agent 开始处理请求
{
  "type": "RUN_STARTED",
  "threadId": "thread_123",
  "runId": "run_456",
  "timestamp": "2026-03-29T10:00:00Z"
}

// STEP_STARTED - Agent 开始执行一个步骤
{
  "type": "STEP_STARTED",
  "threadId": "thread_123",
  "runId": "run_456",
  "stepId": "step_789",
  "stepType": "llm_call",
  "timestamp": "2026-03-29T10:00:01Z"
}

// STEP_FINISHED - 步骤执行完成
{
  "type": "STEP_FINISHED",
  "threadId": "thread_123",
  "runId": "run_456",
  "stepId": "step_789",
  "timestamp": "2026-03-29T10:00:05Z"
}

// RUN_FINISHED - 整个运行完成
{
  "type": "RUN_FINISHED",
  "threadId": "thread_123",
  "runId": "run_456",
  "timestamp": "2026-03-29T10:00:10Z"
}

// RUN_ERROR - 运行出错
{
  "type": "RUN_ERROR",
  "threadId": "thread_123",
  "runId": "run_456",
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "API rate limit exceeded"
  },
  "timestamp": "2026-03-29T10:00:08Z"
}

2. 文本消息事件示例

// TEXT_MESSAGE_START - 开始发送文本消息
{
  "type": "TEXT_MESSAGE_START",
  "messageId": "msg_001",
  "role": "assistant",
  "timestamp": "2026-03-29T10:00:02Z"
}

// TEXT_MESSAGE_CONTENT - 流式文本内容(多次发送)
{
  "type": "TEXT_MESSAGE_CONTENT",
  "messageId": "msg_001",
  "delta": "AG-UI 是一套",
  "timestamp": "2026-03-29T10:00:02.100Z"
}

{
  "type": "TEXT_MESSAGE_CONTENT",
  "messageId": "msg_001",
  "delta": "开放的事件协议",
  "timestamp": "2026-03-29T10:00:02.300Z"
}

// TEXT_MESSAGE_END - 文本消息结束
{
  "type": "TEXT_MESSAGE_END",
  "messageId": "msg_001",
  "timestamp": "2026-03-29T10:00:02.500Z"
}

3. 工具调用事件示例

// TOOL_CALL_START - 开始工具调用
{
  "type": "TOOL_CALL_START",
  "toolCallId": "tool_001",
  "toolName": "search_weather",
  "timestamp": "2026-03-29T10:00:03Z"
}

// TOOL_CALL_ARGS - 工具调用参数
{
  "type": "TOOL_CALL_ARGS",
  "toolCallId": "tool_001",
  "args": {
    "city": "北京",
    "date": "2026-03-29"
  },
  "timestamp": "2026-03-29T10:00:03.100Z"
}

// TOOL_CALL_END - 工具调用结束
{
  "type": "TOOL_CALL_END",
  "toolCallId": "tool_001",
  "timestamp": "2026-03-29T10:00:03.500Z"
}

4. 状态管理事件示例

// STATE_SNAPSHOT - 完整状态快照
{
  "type": "STATE_SNAPSHOT",
  "state": {
    "currentDocument": {
      "title": "项目报告",
      "content": "这是项目报告的初始内容...",
      "lastModified": "2026-03-29T09:30:00Z"
    },
    "userPreferences": {
      "theme": "dark",
      "language": "zh-CN"
    }
  },
  "timestamp": "2026-03-29T10:00:00Z"
}

// STATE_DELTA - 状态增量变化
{
  "type": "STATE_DELTA",
  "delta": {
    "currentDocument": {
      "content": "这是项目报告的更新内容..."
    }
  },
  "timestamp": "2026-03-29T10:00:15Z"
}

一个典型的事件流

以简单的对话为例,Agent 收到用户问题后,发出的完整事件序列大概是:

[
  { "type": "RUN_STARTED",          "threadId": "t1", "runId": "r1" },
  { "type": "TEXT_MESSAGE_START",   "messageId": "m1", "role": "assistant" },
  { "type": "TEXT_MESSAGE_CONTENT", "messageId": "m1", "delta": "AG-UI 是一套" },
  { "type": "TEXT_MESSAGE_CONTENT", "messageId": "m1", "delta": "开放的事件协议" },
  { "type": "TEXT_MESSAGE_END",     "messageId": "m1" },
  { "type": "RUN_FINISHED",         "threadId": "t1", "runId": "r1" }
]

前端只需要处理事件类型,不用关心 Agent 内部用了哪个 LLM 或框架——这就是协议的价值。


传输方式:协议无关

AG-UI 本身不规定传输层。

常见的接法有三种:

传输方式 适合场景
SSE(Server-Sent Events) 最常见,单向流式推送,HTTP 友好
WebSocket 双向低延迟,适合需要高频交互的场景
Webhook 异步回调,适合任务型 Agent

绝大多数 Web 应用选 SSE,实现最简单,不需要额外的基础设施。

前后端代码实现示例

1. 后端 Agent 实现(Node.js + Express)

// server.js - AG-UI 后端实现
const express = require('express');
const app = express();

app.use(express.json());

// 模拟 LLM 调用
async function callLLM(prompt) {
  // 这里可以替换为实际的 OpenAI、Claude 等 API 调用
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("AG-UI 是一套开放的事件协议,专门解决 AI Agent 后端与前端应用之间的通信标准化问题。");
    }, 1000);
  });
}

// SSE 端点 - 处理 AG-UI 事件流
app.get('/api/ag-ui/stream', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*'
  });

  const threadId = `thread_${Date.now()}`;
  const runId = `run_${Date.now()}`;

  // 发送 RUN_STARTED 事件
  res.write(`data: ${JSON.stringify({
    type: 'RUN_STARTED',
    threadId,
    runId,
    timestamp: new Date().toISOString()
  })}\n\n`);

  // 模拟 Agent 处理过程
  setTimeout(async () => {
    // 发送 STEP_STARTED 事件
    res.write(`data: ${JSON.stringify({
      type: 'STEP_STARTED',
      threadId,
      runId,
      stepId: 'step_llm',
      stepType: 'llm_call',
      timestamp: new Date().toISOString()
    })}\n\n`);

    // 发送 TEXT_MESSAGE_START
    res.write(`data: ${JSON.stringify({
      type: 'TEXT_MESSAGE_START',
      messageId: 'msg_001',
      role: 'assistant',
      timestamp: new Date().toISOString()
    })}\n\n`);

    // 模拟流式文本输出
    const response = await callLLM(req.query.prompt || '什么是 AG-UI?');
    const chunks = response.match(/.{1,10}/g) || [response];
    
    for (let i = 0; i < chunks.length; i++) {
      res.write(`data: ${JSON.stringify({
        type: 'TEXT_MESSAGE_CONTENT',
        messageId: 'msg_001',
        delta: chunks[i],
        timestamp: new Date().toISOString()
      })}\n\n`);
      
      // 模拟流式输出的延迟
      await new Promise(resolve => setTimeout(resolve, 100));
    }

    // 发送 TEXT_MESSAGE_END
    res.write(`data: ${JSON.stringify({
      type: 'TEXT_MESSAGE_END',
      messageId: 'msg_001',
      timestamp: new Date().toISOString()
    })}\n\n`);

    // 发送 STEP_FINISHED
    res.write(`data: ${JSON.stringify({
      type: 'STEP_FINISHED',
      threadId,
      runId,
      stepId: 'step_llm',
      timestamp: new Date().toISOString()
    })}\n\n`);

    // 发送 RUN_FINISHED
    res.write(`data: ${JSON.stringify({
      type: 'RUN_FINISHED',
      threadId,
      runId,
      timestamp: new Date().toISOString()
    })}\n\n`);

    res.end();
  }, 500);
});

app.listen(3000, () => {
  console.log('AG-UI Server running on port 3000');
});

2. 前端实现(原生 JavaScript)

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>AG-UI 示例</title>
    <style>
        .chat-container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .messages { border: 1px solid #ccc; height: 400px; overflow-y: auto; padding: 10px; }
        .message { margin: 10px 0; padding: 8px; border-radius: 4px; }
        .user { background: #e3f2fd; text-align: right; }
        .assistant { background: #f5f5f5; }
        .thinking { color: #666; font-style: italic; }
        .input-area { display: flex; margin-top: 10px; }
        .input-area input { flex: 1; padding: 8px; }
        .input-area button { padding: 8px 16px; }
    </style>
</head>
<body>
    <div class="chat-container">
        <h2>AG-UI 聊天示例</h2>
        <div id="messages" class="messages"></div>
        <div class="input-area">
            <input type="text" id="userInput" placeholder="输入你的问题..." />
            <button onclick="sendMessage()">发送</button>
        </div>
    </div>

    <script>
        class AGUIClient {
            constructor() {
                this.eventSource = null;
                this.currentMessage = '';
                this.isThinking = false;
            }

            connect(prompt) {
                if (this.eventSource) {
                    this.eventSource.close();
                }

                this.eventSource = new EventSource(`/api/ag-ui/stream?prompt=${encodeURIComponent(prompt)}`);
                
                this.eventSource.onmessage = (event) => {
                    const data = JSON.parse(event.data);
                    this.handleEvent(data);
                };

                this.eventSource.onerror = (error) => {
                    console.error('AG-UI 连接错误:', error);
                    this.addMessage('系统', '连接出错,请重试', 'error');
                };
            }

            handleEvent(event) {
                console.log('收到 AG-UI 事件:', event);
                
                switch (event.type) {
                    case 'RUN_STARTED':
                        this.isThinking = true;
                        this.addMessage('系统', 'Agent 开始思考...', 'thinking');
                        break;
                        
                    case 'TEXT_MESSAGE_START':
                        this.currentMessage = '';
                        break;
                        
                    case 'TEXT_MESSAGE_CONTENT':
                        this.currentMessage += event.delta;
                        this.updateAssistantMessage(this.currentMessage);
                        break;
                        
                    case 'TEXT_MESSAGE_END':
                        this.currentMessage = '';
                        break;
                        
                    case 'RUN_FINISHED':
                        this.isThinking = false;
                        break;
                        
                    case 'RUN_ERROR':
                        this.addMessage('系统', `错误: ${event.error.message}`, 'error');
                        this.isThinking = false;
                        break;
                }
            }

            addMessage(role, content, type = 'normal') {
                const messagesDiv = document.getElementById('messages');
                const messageDiv = document.createElement('div');
                messageDiv.className = `message ${role} ${type}`;
                messageDiv.textContent = `${role}: ${content}`;
                messagesDiv.appendChild(messageDiv);
                messagesDiv.scrollTop = messagesDiv.scrollHeight;
            }

            updateAssistantMessage(content) {
                const messagesDiv = document.getElementById('messages');
                let messageDiv = messagesDiv.lastChild;
                
                if (!messageDiv || !messageDiv.classList.contains('assistant')) {
                    messageDiv = document.createElement('div');
                    messageDiv.className = 'message assistant';
                    messagesDiv.appendChild(messageDiv);
                }
                
                messageDiv.textContent = `Assistant: ${content}`;
                messagesDiv.scrollTop = messagesDiv.scrollHeight;
            }
        }

        const aguiClient = new AGUIClient();

        function sendMessage() {
            const input = document.getElementById('userInput');
            const message = input.value.trim();
            
            if (message) {
                aguiClient.addMessage('User', message);
                aguiClient.connect(message);
                input.value = '';
            }
        }

        // 支持回车键发送
        document.getElementById('userInput').addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                sendMessage();
            }
        });
    </script>
</body>
</html>

3. 工具调用完整示例

// 工具调用示例 - 后端实现
app.post('/api/ag-ui/tools', (req, res) => {
  const { toolName, args } = req.body;
  
  // 模拟工具调用
  switch (toolName) {
    case 'search_weather':
      // 这里可以调用实际的天气 API
      const weatherData = {
        city: args.city,
        temperature: '25°C',
        condition: '晴朗',
        humidity: '60%'
      };
      res.json({ success: true, data: weatherData });
      break;
      
    case 'calculate':
      const result = eval(args.expression); // 注意:生产环境不要用 eval
      res.json({ success: true, data: result });
      break;
      
    default:
      res.status(400).json({ success: false, error: '未知的工具' });
  }
});

// 前端工具调用处理
class ToolHandler {
  async handleToolCall(toolCall) {
    // 显示确认对话框(Human-in-the-Loop)
    const confirmed = confirm(`是否允许调用工具: ${toolCall.toolName}?`);
    
    if (confirmed) {
      try {
        const response = await fetch('/api/ag-ui/tools', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            toolName: toolCall.toolName,
            args: toolCall.args
          })
        });
        
        const result = await response.json();
        return result;
      } catch (error) {
        return { success: false, error: error.message };
      }
    } else {
      return { success: false, error: '用户取消了工具调用' };
    }
  }
}

已接入的框架

截至 2026 年初,已官方支持 AG-UI 的 Agent 框架包括:LangGraph、Mastra、CrewAI、AG2(AutoGen 的后续版本)。

微软 Azure Agent Framework 也在官方文档里收录了 AG-UI 集成指南。

前端侧目前有 CopilotKit 提供的 React 客户端库,WhatsApp 和微信的消息客户端在开发中。


AG-UI 解决了什么问题

在 AG-UI 出现之前,一个典型 AI 应用的前后端对接通常是这样的:后端用自己约定的格式推送数据,前端写一堆解析逻辑,换个 Agent 框架就要重写一遍。

协议不统一,对接成本高,代码也难复用。

AG-UI 的思路是:把前端和 Agent 之间的"约定"提升成开放标准

前端只需要会处理 16 种事件,就能接任何遵守协议的 Agent;Agent 只需要会发这 16 种事件,就能对接任何遵守协议的前端。

前后端彻底解耦,各自专注自己的事情。


传输层实现对比

SSE vs WebSocket 实现差异

SSE 实现(推荐用于简单场景)

// 后端 SSE 实现
app.get('/ag-ui/sse', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });

  // 发送 AG-UI 事件
  const sendEvent = (event) => {
    res.write(`data: ${JSON.stringify(event)}\n\n`);
  };

  sendEvent({ type: 'RUN_STARTED', threadId: 't1', runId: 'r1' });
  // ... 更多事件
});

// 前端 SSE 客户端
const eventSource = new EventSource('/ag-ui/sse');
eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  handleAGUIEvent(data);
};

WebSocket 实现(适合双向高频交互)

// 后端 WebSocket 实现(使用 ws 库)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  ws.on('message', (message) => {
    const data = JSON.parse(message);
    
    // 处理前端发送的消息
    if (data.type === 'USER_MESSAGE') {
      // 处理用户输入,发送 AG-UI 事件
      ws.send(JSON.stringify({ 
        type: 'RUN_STARTED', 
        threadId: data.threadId, 
        runId: 'r1' 
      }));
    }
    
    // 处理工具调用结果
    if (data.type === 'TOOL_RESULT') {
      // 继续 Agent 执行流程
    }
  });
});

// 前端 WebSocket 客户端
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
  // 发送用户消息
  ws.send(JSON.stringify({
    type: 'USER_MESSAGE',
    content: '你好',
    threadId: 't1'
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  handleAGUIEvent(data);
};

完整的工作流示例

带工具调用和状态管理的完整 Agent 工作流

// 完整的 AG-UI Agent 实现
class AGUIAgent {
  constructor() {
    this.state = {
      conversationHistory: [],
      currentDocument: null,
      userPreferences: {}
    };
  }

  async processMessage(userMessage, sendEvent) {
    const threadId = `thread_${Date.now()}`;
    const runId = `run_${Date.now()}`;

    // 1. 发送运行开始事件
    sendEvent({
      type: 'RUN_STARTED',
      threadId,
      runId,
      timestamp: new Date().toISOString()
    });

    try {
      // 2. 分析用户意图
      sendEvent({
        type: 'STEP_STARTED',
        threadId,
        runId,
        stepId: 'step_analysis',
        stepType: 'intent_analysis',
        timestamp: new Date().toISOString()
      });

      const intent = await this.analyzeIntent(userMessage);
      
      sendEvent({
        type: 'STEP_FINISHED',
        threadId,
        runId,
        stepId: 'step_analysis',
        timestamp: new Date().toISOString()
      });

      // 3. 根据意图执行相应操作
      if (intent.requiresTool) {
        await this.handleToolCall(intent, sendEvent, threadId, runId);
      } else {
        await this.generateResponse(userMessage, sendEvent, threadId, runId);
      }

      // 4. 发送运行完成事件
      sendEvent({
        type: 'RUN_FINISHED',
        threadId,
        runId,
        timestamp: new Date().toISOString()
      });

    } catch (error) {
      // 5. 错误处理
      sendEvent({
        type: 'RUN_ERROR',
        threadId,
        runId,
        error: {
          code: 'PROCESSING_ERROR',
          message: error.message
        },
        timestamp: new Date().toISOString()
      });
    }
  }

  async handleToolCall(intent, sendEvent, threadId, runId) {
    // 发送工具调用开始事件
    sendEvent({
      type: 'TOOL_CALL_START',
      toolCallId: 'tool_001',
      toolName: intent.toolName,
      timestamp: new Date().toISOString()
    });

    // 发送工具参数
    sendEvent({
      type: 'TOOL_CALL_ARGS',
      toolCallId: 'tool_001',
      args: intent.parameters,
      timestamp: new Date().toISOString()
    });

    // 这里会等待前端返回工具调用结果
    // 前端通过 Human-in-the-Loop 确认后调用工具
    // 并将结果通过 TOOL_RESULT 事件传回

    // 工具调用结束
    sendEvent({
      type: 'TOOL_CALL_END',
      toolCallId: 'tool_001',
      timestamp: new Date().toISOString()
    });
  }

  async generateResponse(userMessage, sendEvent, threadId, runId) {
    sendEvent({
      type: 'STEP_STARTED',
      threadId,
      runId,
      stepId: 'step_llm',
      stepType: 'llm_call',
      timestamp: new Date().toISOString()
    });

    // 开始文本消息
    sendEvent({
      type: 'TEXT_MESSAGE_START',
      messageId: 'msg_001',
      role: 'assistant',
      timestamp: new Date().toISOString()
    });

    // 模拟流式响应
    const response = "这是一个基于 AG-UI 协议的响应示例。";
    const chunks = response.match(/.{1,5}/g) || [response];
    
    for (const chunk of chunks) {
      sendEvent({
        type: 'TEXT_MESSAGE_CONTENT',
        messageId: 'msg_001',
        delta: chunk,
        timestamp: new Date().toISOString()
      });
      await new Promise(resolve => setTimeout(resolve, 50));
    }

    // 结束文本消息
    sendEvent({
      type: 'TEXT_MESSAGE_END',
      messageId: 'msg_001',
      timestamp: new Date().toISOString()
    });

    sendEvent({
      type: 'STEP_FINISHED',
      threadId,
      runId,
      stepId: 'step_llm',
      timestamp: new Date().toISOString()
    });
  }

  // 状态同步示例
  syncState(sendEvent) {
    sendEvent({
      type: 'STATE_SNAPSHOT',
      state: this.state,
      timestamp: new Date().toISOString()
    });
  }

  updateState(delta, sendEvent) {
    // 更新内部状态
    this.state = { ...this.state, ...delta };
    
    // 发送状态增量
    sendEvent({
      type: 'STATE_DELTA',
      delta: delta,
      timestamp: new Date().toISOString()
    });
  }
}

快速开始指南

1. 安装依赖

# 后端(Node.js)
npm install express

# 前端(可选使用 CopilotKit)
npm install @copilotkit/react-components

2. 创建最简单的 AG-UI 应用

后端(server.js)

const express = require('express');
const app = express();

app.get('/ag-ui/chat', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });

  // 发送简单的 AG-UI 事件流
  res.write(`data: ${JSON.stringify({
    type: 'RUN_STARTED',
    threadId: 'demo',
    runId: 'run1'
  })}\n\n`);

  res.write(`data: ${JSON.stringify({
    type: 'TEXT_MESSAGE_START',
    messageId: 'msg1',
    role: 'assistant'
  })}\n\n`);

  res.write(`data: ${JSON.stringify({
    type: 'TEXT_MESSAGE_CONTENT',
    messageId: 'msg1',
    delta: '你好!这是一个 AG-UI 示例。'
  })}\n\n`);

  res.write(`data: ${JSON.stringify({
    type: 'TEXT_MESSAGE_END',
    messageId: 'msg1'
  })}\n\n`);

  res.write(`data: ${JSON.stringify({
    type: 'RUN_FINISHED',
    threadId: 'demo',
    runId: 'run1'
  })}\n\n`);

  res.end();
});

app.listen(3000, () => {
  console.log('AG-UI 示例服务运行在 http://localhost:3000');
});

前端(index.html)

<!DOCTYPE html>
<html>
<body>
  <button onclick="startChat()">开始对话</button>
  <div id="output"></div>
  
  <script>
    function startChat() {
      const eventSource = new EventSource('/ag-ui/chat');
      
      eventSource.onmessage = (event) => {
        const data = JSON.parse(event.data);
        const output = document.getElementById('output');
        
        switch(data.type) {
          case 'TEXT_MESSAGE_CONTENT':
            output.innerHTML += data.delta;
            break;
          case 'RUN_STARTED':
            output.innerHTML = 'Agent 开始处理...<br>';
            break;
          case 'RUN_FINISHED':
            output.innerHTML += '<br>对话完成!';
            eventSource.close();
            break;
        }
      };
    }
  </script>
</body>
</html>

3. 运行应用

node server.js

然后在浏览器中打开 http://localhost:3000 即可体验 AG-UI 的基本功能。

常见问题

AG-UI 和 MCP 的区别是什么?

MCP 解决 Agent 调用外部工具和数据的问题(如查数据库、调 API);

AG-UI 解决 Agent 和用户界面通信的问题。

两者不冲突,一个 Agent 可以同时用 MCP 对接工具、用 AG-UI 对接前端。

AG-UI 和 OpenAI 的 streaming API 有什么不同?

OpenAI streaming API 是厂商私有格式,只适用于 OpenAI 的服务;

AG-UI 是开放协议,不绑定任何 LLM 厂商,任何 Agent 框架都可以实现。

AG-UI 的事件类型也比 token 流更丰富,支持工具调用、状态同步等场景。

前端必须用 React 才能用 AG-UI 吗?

不是。

AG-UI 是协议,不限制前端框架。

CopilotKit 提供了 React 的参考实现,但 Vue、Svelte 或者原生 JS 同样可以解析 AG-UI 的事件流,官方也在开发更多语言的客户端库。

AG-UI 适合什么规模的项目?

不限规模。

简单的聊天机器人和复杂的多 Agent 工作流都可以用。

协议本身很轻量,接入成本低,小项目不会因为用它显得过度设计。


写在最后

AI Agent 的协议生态还在快速演变。

MCP 管工具、A2A 管 Agent 间协作、AG-UI 管用户交互——三块拼图各就其位,才让复杂 Agent 应用的工程化有了标准可循。

如果你在做 AI 应用的前端集成,AG-UI 值得花半天时间研究一下。


参考资料:AG-UI 官方文档GitHub: ag-ui-protocol/ag-ui

版权声明

未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!

本文原文链接: https://fiveyoboy.com/articles/what-is-ag-ui/

备用原文链接: https://blog.fiveyoboy.com/articles/what-is-ag-ui/