# 前后端数据流通指南(Agent Chat) 本文档仅描述**当前后端实现**的 runs/events/history 数据流,不定义视觉细节。 --- ## 1) 总体流程 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` 拉取历史快照(按天) --- ## 2) `/runs` 请求与响应 ### 请求 - Body: `RunAgentInput` - user message 可为纯文本,也可为文本+binary(图片 URL) ### 响应 ```json { "taskId": "...", "threadId": "...", "runId": "...", "created": true } ``` `created` 语义:是否在本次请求中创建了新会话。 --- ## 3) `/runs/{threadId}/events` 事件流 ### SSE 形式 ```text id: event: data: ``` ### 事件类型 以 `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`: ```json { "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(...)` 将 **tool** 的 `ui_hints` 编译为可渲染结构: - events:在 runtime 发送事件前编译,字段名为 `ui_schema` - history:在历史转换时编译,字段名为 `ui_schema` tool 结果走 UI 编译链路:`TOOL_CALL_RESULT` 在保留 `tool_call_args` + `result` 的同时可携带 `ui_schema`。 - `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) 推荐消费顺序(面向客户端重构) 1. 先以 `/history` 获取首屏快照 2. 再接入 `/events` 处理后续增量 3. 以 `runId` + `messageId/toolCallId` 做去重与合并 4. 统一消费 `ui_schema` --- ## 7) Navigation Action 数据流(ui_schema.actions) ### 7.1 后端生成 - runtime 基于 tool 输出中的 `ui_hints.action.type = navigation` 产出导航动作。 - 编译后在 `ui_schema` 中保持 `action.type = navigation`、`action.path`、`action.params`。 - 路由由工具能力直接给出 concrete path,agent 本身不需要维护 route_id 概念。 ### 7.2 前端消费(统一解析规则) - 对 `type = navigation`,前端仅走一条解析路径: 1. 读取 `path` 作为内部路由目标; 2. 将 `params` 仅视为 query 参数(不用于 path 模板替换); 3. 执行 GoRouter 跳转(建议 `context.go(...)`)。 - `path` 必须是已落地页面路由,且应是已实参化路径(如 `/todo/123`,而非 `/todo/:id`)。 ### 7.3 路由表达粒度(Route-First 约束) - 关键业务动作(创建、编辑、分享、处理邀请等)应优先设计为可深链页面路由,而不是仅存在于临时弹层。 - 若 UI 采用 sheet 风格展示,也应由页面路由承载状态,再以页面内 surface 呈现 sheet 视觉。 - `todo.edit` 必须落地为独立子页面(`/todo/{id}/edit`),不应通过详情页内弹窗承载编辑主流程。 - 推荐工具能力优先输出以下 concrete path(示例): - `/calendar/events/new` - `/calendar/events/{id}/edit` - `/calendar/events/{id}/share` - `/todo/new` - `/todo/{id}/edit` ### 7.4 约束建议 - 为了让前端只保留一种解析逻辑,推荐强约束: - `path` 只接受内部路由; - `params` 只接受标量值(string/number/boolean); - 禁止在 `params` 里放嵌套对象数组。