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