2026-03-16 16:11:40 +08:00
|
|
|
|
# 前后端数据流通指南(Agent Chat)
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
本文档仅描述**当前后端实现**的 runs/events/history 数据流,不定义视觉细节。
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
## 1) 总体流程
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
1. 客户端 `POST /api/v1/agent/runs` 提交 `RunAgentInput`
|
|
|
|
|
|
2. 后端返回 `202` + `taskId/threadId/runId/created`
|
|
|
|
|
|
3. 客户端 `GET /api/v1/agent/runs/{threadId}/events` 订阅 SSE
|
|
|
|
|
|
4. 后端输出 AG-UI 事件(如 `RUN_STARTED`、`TOOL_CALL_RESULT`、`TEXT_MESSAGE_END`)
|
|
|
|
|
|
5. 客户端按需 `GET /api/v1/agent/history` 拉取历史快照(按天)
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
## 2) `/runs` 请求与响应
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
### 请求
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
- Body: `RunAgentInput`
|
|
|
|
|
|
- user message 可为纯文本,也可为文本+binary(图片 URL)
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
### 响应
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"taskId": "...",
|
|
|
|
|
|
"threadId": "...",
|
|
|
|
|
|
"runId": "...",
|
|
|
|
|
|
"created": true
|
|
|
|
|
|
}
|
2026-03-16 09:01:01 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
`created` 语义:是否在本次请求中创建了新会话。
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
## 3) `/runs/{threadId}/events` 事件流
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
### SSE 形式
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
```text
|
|
|
|
|
|
id: <stream-id>
|
|
|
|
|
|
event: <EVENT_TYPE>
|
|
|
|
|
|
data: <json>
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
### 事件类型
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
以 `docs/protocols/agent/sse-events.md` 为准。当前重点是:
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
- 运行生命周期:`RUN_STARTED` / `RUN_FINISHED` / `RUN_ERROR`
|
|
|
|
|
|
- 阶段:`STEP_STARTED` / `STEP_FINISHED`
|
|
|
|
|
|
- 工具:`TOOL_CALL_START` / `TOOL_CALL_ARGS` / `TOOL_CALL_END` / `TOOL_CALL_RESULT`
|
|
|
|
|
|
- 文本完成:`TEXT_MESSAGE_END`
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
### 文本流策略
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
当前后端不提供 token 级 `TEXT_MESSAGE_CONTENT` 增量流作为主路径;
|
|
|
|
|
|
而是在 worker 完成后通过 `TEXT_MESSAGE_END` 一次性携带完整语义结果。
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
## 4) `/history` 快照
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
`GET /api/v1/agent/history` 返回 `HistorySnapshotResponse`:
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"scope": "history_day",
|
|
|
|
|
|
"threadId": "...",
|
|
|
|
|
|
"day": "2026-03-16",
|
|
|
|
|
|
"hasMore": false,
|
|
|
|
|
|
"messages": []
|
2026-03-16 09:01:01 +08:00
|
|
|
|
}
|
2026-03-16 16:11:40 +08:00
|
|
|
|
```
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
说明:
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
- 这是普通 JSON 响应,不是 SSE 事件包装。
|
|
|
|
|
|
- `messages` 已按 seq 升序组织。
|
|
|
|
|
|
- `before` 采用 `YYYY-MM-DD`,语义是向更早日期翻页。
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
---
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
## 5) events 与 history 的一致性机制
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
### 5.1 语义来源一致
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
两条链路都来自同一运行时输出(worker/tool output)及其持久化元数据。
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
### 5.2 UI 编译器一致
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-17 12:18:09 +08:00
|
|
|
|
两条链路都使用后端 `ui_compiler.compile(...)` 将 **worker** 的 `ui_hints` 编译为可渲染结构:
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
- events:在 runtime 发送事件前编译,字段名为 `ui_schema`
|
|
|
|
|
|
- history:在历史转换时编译,字段名为 `ui_schema`
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-17 14:12:44 +08:00
|
|
|
|
tool 结果不再走 UI 编译链路:`TOOL_CALL_RESULT` 提供 `tool_call_args` + `result` 组合。
|
|
|
|
|
|
|
|
|
|
|
|
- `metadata.tool_agent_output` 是 tool 消息的完整信源(用于 runtime observation 与 history replay)。
|
|
|
|
|
|
- `message.content` 保持轻量摘要(当前等于 `result`)。
|
|
|
|
|
|
- `tool_call_args` 记录输入参数,`result` 记录输出事实,二者不做冗余重复。
|
2026-03-17 12:18:09 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
### 5.3 当前命名差异(实现现状)
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
两条链路字段命名已统一:
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
- events: `ui_schema`(snake_case)
|
|
|
|
|
|
- history: `ui_schema`(snake_case)
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
## 6) 推荐消费顺序(面向客户端重构)
|
2026-03-16 09:01:01 +08:00
|
|
|
|
|
2026-03-16 16:11:40 +08:00
|
|
|
|
1. 先以 `/history` 获取首屏快照
|
|
|
|
|
|
2. 再接入 `/events` 处理后续增量
|
|
|
|
|
|
3. 以 `runId` + `messageId/toolCallId` 做去重与合并
|
|
|
|
|
|
4. 统一消费 `ui_schema`
|
2026-03-18 13:35:25 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 7) Navigation Action 数据流(ui_schema.actions)
|
|
|
|
|
|
|
|
|
|
|
|
### 7.1 后端生成
|
|
|
|
|
|
|
|
|
|
|
|
- runtime 使用 `ui_hints.action.type = navigation` 产出导航动作。
|
|
|
|
|
|
- 编译后在 `ui_schema` 中保持 `action.type = navigation`、`action.path`、`action.params`。
|
|
|
|
|
|
- 路由来源应受后端静态路由目录约束:
|
|
|
|
|
|
- `backend/src/core/config/static/route/frontend_routes.yaml`
|
|
|
|
|
|
|
|
|
|
|
|
### 7.2 前端消费(统一解析规则)
|
|
|
|
|
|
|
|
|
|
|
|
- 对 `type = navigation`,前端仅走一条解析路径:
|
|
|
|
|
|
1. 读取 `path` 作为内部路由目标;
|
|
|
|
|
|
2. 将 `params` 仅视为 query 参数(不用于 path 模板替换);
|
|
|
|
|
|
3. 执行 GoRouter 跳转(建议 `context.go(...)`)。
|
|
|
|
|
|
- `path` 必须是已落地页面路由,且应是已实参化路径(如 `/todo/123`,而非 `/todo/:id`)。
|
|
|
|
|
|
|
2026-03-19 00:52:16 +08:00
|
|
|
|
### 7.3 路由表达粒度(Route-First 约束)
|
|
|
|
|
|
|
|
|
|
|
|
- 关键业务动作(创建、编辑、分享、处理邀请等)应优先设计为可深链页面路由,而不是仅存在于临时弹层。
|
|
|
|
|
|
- 若 UI 采用 sheet 风格展示,也应由页面路由承载状态,再以页面内 surface 呈现 sheet 视觉。
|
|
|
|
|
|
- `todo.edit` 必须落地为独立子页面(`/todo/{id}/edit`),不应通过详情页内弹窗承载编辑主流程。
|
|
|
|
|
|
- 推荐后端优先使用以下 route_id 生成导航(示例):
|
|
|
|
|
|
- `calendar.event_create` -> `/calendar/events/new`
|
|
|
|
|
|
- `calendar.event_edit` -> `/calendar/events/{id}/edit`
|
|
|
|
|
|
- `calendar.event_share` -> `/calendar/events/{id}/share`
|
|
|
|
|
|
- `todo.create` -> `/todo/new`
|
|
|
|
|
|
- `todo.edit` -> `/todo/{id}/edit`
|
|
|
|
|
|
|
|
|
|
|
|
### 7.4 约束建议
|
2026-03-18 13:35:25 +08:00
|
|
|
|
|
|
|
|
|
|
- 为了让前端只保留一种解析逻辑,推荐强约束:
|
|
|
|
|
|
- `path` 只接受内部路由;
|
|
|
|
|
|
- `params` 只接受标量值(string/number/boolean);
|
|
|
|
|
|
- 禁止在 `params` 里放嵌套对象数组。
|