前后端通信协议
HTTP Agent 完全兼容 AG-UI 协议,使用 SSE 实现前后端实时流式通信。
可直接使用 AG-UI TypeScript SDK 的 HttpAgent 进行接入:
import { HttpAgent } from "@ag-ui/client";
const agent = new HttpAgent({
url: "https://your-endpoint/send-message",
headers: {
Authorization: "Bearer your-api-key",
},
});
POST /send-message
请求体
请求体遵循 AG-UI 协议的 RunAgentInput 结构:
interface RunAgentInput {
threadId: string; // 会话线程 ID
runId: string; // 本次运行的唯一 ID
parentRunId?: string; // 可选,父运行 ID(用于分支/回溯场景)
state?: any; // 可选,当前状态
messages: Message[]; // 消息历史
tools: Tool[]; // 客户端工具列表
context: Context[]; // 上下文信息
forwardedProps?: any; // 可选,透传属性
}
说明:
threadId整个对话保持一致,服务端通过该 ID 维护会话状态runId每次调用生成新的唯一 IDmessages消息列表
消息类型
type Message =
| DeveloperMessage
| SystemMessage
| AssistantMessage
| UserMessage
| ToolMessage;
interface DeveloperMessage {
id: string;
role: "developer";
content: string;
name?: string;
}
interface SystemMessage {
id: string;
role: "system";
content: string;
name?: string;
}
interface AssistantMessage {
id: string;
role: "assistant";
content?: string;
name?: string;
toolCalls?: ToolCall[];
}
interface UserMessage {
id: string;
role: "user";
content: string | InputContent[];
name?: string;
}
interface ToolMessage {
id: string;
role: "tool";
content: string;
toolCallId: string;
error?: string;
}
interface ToolCall {
id: string;
type: "function";
function: { name: string; arguments: string };
}
// 多模态输入内容
type InputContent =
| { type: "text"; text: string }
| { type: "binary"; mimeType: string; id?: string; url?: string; data?: string; filename?: string };
工具定义
interface Tool {
name: string;
description: string;
parameters: any; // JSON Schema
}
上下文定义
interface Context {
description: string;
value: string;
}
示例请求:
{
"threadId": "550e8400-e29b-41d4-a716-446655440000",
"runId": "run_001",
"messages": [
{ "id": "msg_1", "role": "user", "content": "今天北京天气怎么样?" }
],
"tools": [
{
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
],
"context": [],
"state": {}
}
响应事件
响应为 SSE(Server-Sent Events) 流,格式如下:
- 每个事件:
data: {json}\n\n(JSON 后跟两个换行符)
AG-UI 协议定义了以下事件类型:
| 事件类型 | 说明 |
|---|---|
RUN_STARTED | Agent 运行开始 |
RUN_FINISHED | Agent 运行结束 |
RUN_ERROR | Agent 运行错误 |
TEXT_MESSAGE_START | 文本消息开始 |
TEXT_MESSAGE_CONTENT | 文本内容增量 |
TEXT_MESSAGE_END | 文本消息结束 |
TOOL_CALL_START | 工具调用开始 |
TOOL_CALL_ARGS | 工具参数增量 |
TOOL_CALL_END | 工具调用结束 |
TOOL_CALL_RESULT | 工具执行结果 |
STATE_SNAPSHOT | 状态快照 |
STATE_DELTA | 状态增量更新(JSON Patch) |
MESSAGES_SNAPSHOT | 消息历史快照 |
STEP_STARTED | 步骤开始 |
STEP_FINISHED | 步骤结束 |
TOOL_CALL_ARGS 拼接规则:
工具参数以流式增量传输,客户端需将同一 toolCallId 的多个 TOOL_CALL_ARGS 事件的 delta 字段按顺序拼接,最终得到完整的 JSON 字符串。
// 事件流
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_1","delta":"{\"cit"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_1","delta":"y\":\"北"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_1","delta":"京\"}"}
// 拼接结果
"{\"city\":\"北京\"}" → JSON.parse() → { city: "北京" }
注意: 当存在多个并行工具调用时,不同 toolCallId 的事件可能交错出现,客户端应使用 toolCallId 区分和追踪每个工具调用。
事件结构
// 生命周期事件
interface RunStartedEvent {
type: "RUN_STARTED";
threadId: string;
runId: string;
parentRunId?: string;
timestamp?: number;
}
interface RunFinishedEvent {
type: "RUN_FINISHED";
threadId: string;
runId: string;
result?: any;
timestamp?: number;
}
interface RunErrorEvent {
type: "RUN_ERROR";
message: string;
code?: string;
timestamp?: number;
}
// 文本消息事件
interface TextMessageStartEvent {
type: "TEXT_MESSAGE_START";
messageId: string;
role: "developer" | "system" | "assistant" | "user";
}
interface TextMessageContentEvent {
type: "TEXT_MESSAGE_CONTENT";
messageId: string;
delta: string; // 非空文本增量
}
interface TextMessageEndEvent {
type: "TEXT_MESSAGE_END";
messageId: string;
}
// 工具调用事件
interface ToolCallStartEvent {
type: "TOOL_CALL_START";
toolCallId: string;
toolCallName: string;
parentMessageId?: string;
}
interface ToolCallArgsEvent {
type: "TOOL_CALL_ARGS";
toolCallId: string;
delta: string;
}
interface ToolCallEndEvent {
type: "TOOL_CALL_END";
toolCallId: string;
}
interface ToolCallResultEvent {
type: "TOOL_CALL_RESULT";
messageId: string;
toolCallId: string;
content: string;
}
// 状态管理事件
interface StateSnapshotEvent {
type: "STATE_SNAPSHOT";
snapshot: any;
}
interface StateDeltaEvent {
type: "STATE_DELTA";
delta: JsonPatchOperation[]; // RFC 6902 JSON Patch
}
interface MessagesSnapshotEvent {
type: "MESSAGES_SNAPSHOT";
messages: Message[];
}
// 步骤事件
interface StepStartedEvent {
type: "STEP_STARTED";
stepName: string;
}
interface StepFinishedEvent {
type: "STEP_FINISHED";
stepName: string;
}
响应示例
data: {"type":"RUN_STARTED","threadId":"thread_001","runId":"run_001"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_1","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_1","delta":"让我查询一下"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_1"}
data: {"type":"TOOL_CALL_START","toolCallId":"call_123","toolCallName":"get_weather","parentMessageId":"msg_1"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_123","delta":"{\"city\":\"北京\"}"}
data: {"type":"TOOL_CALL_END","toolCallId":"call_123"}
data: {"type":"TOOL_CALL_RESULT","messageId":"msg_2","toolCallId":"call_123","content":"晴天,25°C"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_3","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_3","delta":"北京今天是晴天,温度25°C。"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_3"}
data: {"type":"RUN_FINISHED","threadId":"thread_001","runId":"run_001"}
前端工具 vs 服务端工具
Agent 可以同时使用前端工具和服务端工具:
| 特征 | 前端工具 | 服务端工具 |
|---|---|---|
| 定义位置 | 前端,通过 tools 参数传入 | 服务端 Agent 内部 |
| 执行位置 | 前端(浏览器) | 服务端 |
| 结果传递 | 前端通过 ToolMessage 发回 | 服务端返回 TOOL_CALL_RESULT 事件 |
前端判断逻辑:
- 收到
TOOL_CALL_END后,检查工具名称是否在前端传入的tools列表中 - 如果在列表中:这是前端工具,等待
RUN_FINISHED,然后前端执行工具,将结果作为ToolMessage发送 - 如果不在列表中:这是服务端工具,等待
TOOL_CALL_RESULT事件
时序图
基本文本响应
前端工具调用流程
前端通过 tools 参数传递工具定义。当 Agent 调用前端工具时,前端负责执行并将结果发回。
服务端工具调用流程
服务端工具由服务端定义和执行,前端无需处理工具执行逻辑。
人机交互(Human-in-the-Loop)
通过前端工具实现人机交互。当需要用户输入时,Agent 调用一个前端定义的工具,前端执行该工具时向用户展示 UI 并收集输入。
示例:确认工具定义
const confirmTool = {
name: "confirmAction",
description: "请求用户确认操作",
parameters: {
type: "object",
properties: {
action: { type: "string", description: "需要确认的操作" },
importance: { type: "string", enum: ["low", "medium", "high", "critical"] }
},
required: ["action"]
}
};
场景说明
场景 1:纯对话
请求:
{
"threadId": "thread_001",
"runId": "run_001",
"messages": [
{ "id": "msg_1", "role": "user", "content": "你好" }
],
"tools": [],
"context": []
}
响应:
data: {"type":"RUN_STARTED","threadId":"thread_001","runId":"run_001"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_2","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_2","delta":"你好"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_2","delta":"!有什么可以帮你的吗?"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_2"}
data: {"type":"RUN_FINISHED","threadId":"thread_001","runId":"run_001"}
场景 2:前端工具调用
前端定义了 search_local_files 工具(只有浏览器能访问本地文件)。
第一轮请求:
{
"threadId": "thread_003",
"runId": "run_003",
"messages": [
{ "id": "msg_1", "role": "user", "content": "帮我搜索本地的报告文件" }
],
"tools": [
{
"name": "search_local_files",
"description": "搜索用户本地文件",
"parameters": { "type": "object", "properties": { "keyword": { "type": "string" } } }
}
],
"context": []
}
第一轮响应:
data: {"type":"RUN_STARTED","threadId":"thread_003","runId":"run_003"}
data: {"type":"TOOL_CALL_START","toolCallId":"call_002","toolCallName":"search_local_files"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_002","delta":"{\"keyword\":\"报告\"}"}
data: {"type":"TOOL_CALL_END","toolCallId":"call_002"}
data: {"type":"RUN_FINISHED","threadId":"thread_003","runId":"run_003"}
前端处理:
- 从事件中收集
toolCalls:{id: "call_002", type: "function", function: {name: "search_local_files", arguments: "{\"keyword\":\"报告\"}"}} - 执行工具,获得结果
- 发送第二轮请求
第二轮请求:
{
"threadId": "thread_003",
"runId": "run_004",
"messages": [
{ "id": "msg_1", "role": "user", "content": "帮我搜索本地的报告文件" },
{
"id": "msg_2",
"role": "assistant",
"toolCalls": [
{ "id": "call_002", "type": "function", "function": { "name": "search_local_files", "arguments": "{\"keyword\":\"报告\"}" } }
]
},
{
"id": "msg_3",
"role": "tool",
"toolCallId": "call_002",
"content": "[\"2024年度报告.pdf\", \"Q3报告.docx\"]"
}
],
"tools": [...],
"context": []
}
第二轮响应:
data: {"type":"RUN_STARTED","threadId":"thread_003","runId":"run_004"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_4","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_4","delta":"找到了 2 个文件:2024年度报告.pdf 和 Q3报告.docx"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_4"}
data: {"type":"RUN_FINISHED","threadId":"thread_003","runId":"run_004"}
场景 3:服务端工具调用
服务端定义了 get_weather 工具,前端无需传递该工具。
请求:
{
"threadId": "thread_002",
"runId": "run_002",
"messages": [
{ "id": "msg_1", "role": "user", "content": "北京天气怎么样?" }
],
"tools": [],
"context": []
}
响应:
data: {"type":"RUN_STARTED","threadId":"thread_002","runId":"run_002"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_2","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_2","delta":"让我查一下"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_2"}
data: {"type":"TOOL_CALL_START","toolCallId":"call_001","toolCallName":"get_weather","parentMessageId":"msg_2"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_001","delta":"{\"city\":\"北京\"}"}
data: {"type":"TOOL_CALL_END","toolCallId":"call_001"}
data: {"type":"TOOL_CALL_RESULT","messageId":"msg_tool_1","toolCallId":"call_001","content":"晴天,25°C"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_3","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_3","delta":"北京今天晴天,25°C。"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_3"}
data: {"type":"RUN_FINISHED","threadId":"thread_002","runId":"run_002"}
说明:
- 前端收到
TOOL_CALL_START,工具名get_weather不在前端tools列表中 - 前端等待
TOOL_CALL_RESULT事件,直接展示结果 - 无需前端执行工具或发送额外请求
场景 4:人机交互(Human-in-the-Loop)
通过前端工具实现用户确认。
第一轮请求:
{
"threadId": "thread_004",
"runId": "run_005",
"messages": [
{ "id": "msg_1", "role": "user", "content": "删除所有临时文件" }
],
"tools": [
{
"name": "confirmAction",
"description": "请求用户确认危险操作",
"parameters": {
"type": "object",
"properties": {
"action": { "type": "string" },
"count": { "type": "number" }
},
"required": ["action"]
}
}
],
"context": []
}
第一轮响应:
data: {"type":"RUN_STARTED","threadId":"thread_004","runId":"run_005"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_2","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_2","delta":"即将删除 15 个临时文件"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_2"}
data: {"type":"TOOL_CALL_START","toolCallId":"call_003","toolCallName":"confirmAction","parentMessageId":"msg_2"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_003","delta":"{\"action\":\"删除临时文件\",\"count\":15}"}
data: {"type":"TOOL_CALL_END","toolCallId":"call_003"}
data: {"type":"RUN_FINISHED","threadId":"thread_004","runId":"run_005"}
客户端处理:
- 解析
confirmAction工具调用参数 - 显示确认对话框
- 用户点击"确认"
- 发送包含工具结果的新请求
第二轮请求:
{
"threadId": "thread_004",
"runId": "run_006",
"messages": [
{ "id": "msg_1", "role": "user", "content": "删除所有临时文件" },
{
"id": "msg_2",
"role": "assistant",
"content": "即将删除 15 个临时文件",
"toolCalls": [
{ "id": "call_003", "type": "function", "function": { "name": "confirmAction", "arguments": "{\"action\":\"删除临时文件\",\"count\":15}" } }
]
},
{
"id": "msg_3",
"role": "tool",
"toolCallId": "call_003",
"content": "confirmed"
}
],
"tools": [...],
"context": []
}
第二轮响应:
data: {"type":"RUN_STARTED","threadId":"thread_004","runId":"run_006"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_4","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_4","delta":"已删除 15 个临时文件。"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_4"}
data: {"type":"RUN_FINISHED","threadId":"thread_004","runId":"run_006"}