有谷大脑
返回博客

有谷大脑的问答界面,为什么用 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 响应。

有谷大脑 AG-UI 问答流:前端 Client 订阅 SSE 事件,后端 Deep Agent 推送 token、工具调用与状态。

REST 不够用的四个场景

传统 HTTPAgent 问答
短请求 / 短响应长连接,几十秒到几分钟
结果是确定的 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_basesearch_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 是让检索结果可见、可点、可验证的那一层