有谷大脑的问答界面,为什么用 AG-UI 而不是普通 HTTP
Agent 问答不是「发请求等 JSON」——要流式 token、工具调用可见、状态同步、引用可点。有谷大脑用 AG-UI 协议把 Deep Agent 和前端接在一起。
在知识库里问一个问题,等十秒,然后整段答案一次性弹出来——体验已经比早期「转圈等全文」好不少。但如果 Agent 中间调了知识库检索、又调了联网搜索、还在组织引用,用户这十秒看到的是空白,不知道系统在干什么,也不知道能不能中途取消。
Agent 应用和传统 REST API 的根本差别在于:交互是长连接、流式、非确定性的。 同一个问题,Agent 可能走完全不同的步骤;UI 需要一边收 token 一边展示工具调用,还要把检索到的引用实时挂到回答上。
有谷大脑的问答功能,后端是 LangGraph + deepagents 构建的 Deep Agent,前端通过 AG-UI(Agent-User Interaction Protocol) 订阅事件流——而不是等一个完整的 JSON 响应。
REST 不够用的四个场景
| 传统 HTTP | Agent 问答 |
|---|---|
| 短请求 / 短响应 | 长连接,几十秒到几分钟 |
| 结果是确定的 JSON | 非确定性——步骤每次可能不同 |
| UI 只渲染最终结果 | 需要展示中间过程(检索、思考、工具) |
| 一次调用结束 | 同一 thread 可多轮、可 regenerate |
如果不做协议层抽象,每接一个 Agent 框架、每加一种前端,都要重写 SSE 解析、事件格式和状态合并——典型的 M × N 对接困境。
AG-UI 在中间定义 ~16 种标准事件,任何符合规范的后端都能被任何符合规范的前端接入。
有谷大脑里 AG-UI 管什么
流式文本。 TEXT_MESSAGE_START → 多个 TEXT_MESSAGE_CONTENT { delta } → TEXT_MESSAGE_END。前端拿到 delta 就 append,打字机效果天然出来,不用自己猜「还有没有更多」。
工具调用可见。 Agent 调 query_knowledge_base 或 search_web 时,前端收到 TOOL_CALL_START / TOOL_CALL_ARGS / TOOL_CALL_RESULT,可以展示「正在检索知识库…」「找到 5 条结果」——用户知道系统在依据资料作答,而不是凭空生成。
生命周期。 RUN_STARTED 禁用输入框、RUN_FINISHED 恢复、RUN_ERROR 展示错误。regenerate 和 edit-user 流程也在 Run 边界上对齐 LangGraph checkpoint。
引用与状态。 检索到的 chunk 引用、citation 列表通过 State 事件(STATE_SNAPSHOT / STATE_DELTA)同步到前端,回答里的 [1] [2] 可以点到原文——这和有谷「回答附带出处」的产品承诺直接相关。
和 MCP、A2A 的分工
完整 Agent 应用通常三层协议各管一块,互不重叠:
- AG-UI — Agent ↔ 用户界面(有谷网页端、桌面端的问答 UI)
- MCP — Agent ↔ 工具与数据(企业版把知识库接入 Cursor 等 AI 客户端)
- A2A — Agent ↔ Agent(多 Agent 协作场景)
有谷大脑网页问答走 AG-UI;开放平台 MCP 服务走 MCP。它们解决不同边界,不需要互相替代。
后端:Deep Agent + Checkpointer
后端 Agent 在 API 进程启动时构建一次,带 Postgres checkpointer。同一 thread_id 的请求在进程内串行——避免并发写 checkpoint 导致对话状态错乱。
工具层面,问答优先 query_knowledge_base;知识库信息不足时可以 search_web 补充。工具返回的结构化结果(chunk 内容、来源 metadata)经 AG-UI 事件推到前端,而不是塞在一坨 markdown 字符串里让前端自己解析。
regenerate / 编辑用户消息后重跑,会先把 LangGraph checkpoint 对齐到正确分支,再持久化到 chat_message 表——协议层负责实时推流,持久化层负责历史记录,两者通过 thread_id 关联。
前端:订阅事件流,而不是轮询
前端 AG-UI Client 对 /ag-run 发起 SSE 连接,subscribe 事件流后分发给:
- 聊天气泡组件(文本 delta)
- 工具调用卡片(检索进度)
- 引用侧栏(citation 列表)
- 全局 loading / error 状态
这比「每 500ms 轮询一次任务状态」简单得多,也更省资源——事件到了就渲染,Run 结束就关闭连接。
工程上几个实际取舍
同一 thread 串行。 用户快速连点两次发送,第二次要等第一次 Run 结束或取消——这是为了避免 checkpoint 竞争。产品上可以展示「上一条还在生成中」。
Checkpointer 与 chat_message 双写。 实时对话状态在 LangGraph checkpoint;落库的历史消息在 PostgreSQL。regenerate 时要两边对齐,否则 UI 看到的和数据库不一致。
错误要可恢复。 RUN_ERROR 带 message 和 code,前端提示后允许重试,而不是白屏。
引用必须结构化。 如果只靠模型在 markdown 里写「据第 3 章」,前端无法跳转。State 事件里带 chunk id、文档路径、页码,点击引用才能打开阅读器定位。
和入库链路的关系
AG-UI 解决的是「问」这一侧的体验;Excel 切块、PDF 版面检测、代码 AST 切分 解决的是「存」这一侧的质量。两边通过 RAG 检索衔接:
入库切得对 → 检索命中准 → Agent 工具返回好 chunk → AG-UI 把引用推到 UI → 用户能回到原文核对。
Agent 协议层再精巧,也救不了「垃圾进、垃圾出」的索引。但有好的索引,如果前端只能等一整段 JSON,用户依然感受不到「有据可依」——AG-UI 是让检索结果可见、可点、可验证的那一层。