用 AI 知识库提问时,有一种体验特别劝退。

用户问:

我们合同里的赔偿责任上限是多少?有哪些例外?

系统没有报错,也没有卡死。

但接下来的十几秒里,页面只有一个 loading 图标。用户看不到系统是否正在检索知识库、有没有查到合同原文、是否又调用了联网搜索,也不知道答案还要等多久。

十几秒后,一整段文字突然出现。

答案也许是对的,但用户仍然会怀疑:

  • 它真的查了我的知识库吗?
  • 中间调用了哪些工具?
  • 结论来自哪份文档、哪一页?
  • 如果等太久,我能不能取消?

这不是模型能力的问题,而是 Agent 和前端之间的通信方式出了问题。

传统接口习惯“发出请求,等待完整 JSON”。但一个真正的 Agent,可能要经历多轮推理、知识库检索、联网搜索、引用整理和状态更新。把这些过程全部藏在一个 loading 后面,用户看到的就只剩下漫长的沉默。

这也是我们在做企业级 AI 知识库「有谷大脑」时,为什么没有把问答功能做成普通的 HTTP 请求,而是使用 AG-UI 协议连接前端和 Deep Agent。

一、普通 HTTP 为什么接不住 Agent 问答?

普通问答接口通常是这样的:

前端发送问题
    ↓
后端生成完整答案
    ↓
返回一份 JSON
    ↓
前端一次性渲染

这种模式适合“输入确定、输出确定”的接口,比如查询一条订单、读取一个用户资料。

但 Agent 的执行过程并不确定。

面对同一个问题,它可能:

  1. 先检索企业知识库;
  2. 发现资料不足,再调用联网搜索;
  3. 根据检索结果继续组织答案;
  4. 整理引用,让用户能够回到原文核对;
  5. 中途失败、重试,或者被用户取消。

前端不仅要拿到最终答案,还要实时知道:当前 Run 是否开始、文本生成到了哪里、调用了哪个工具、工具返回了什么、引用如何变化,以及任务最终成功还是失败。

如果仍然只返回一个最终 JSON,这些过程状态就全部丢了。

所以 Agent 问答真正需要的,不只是“接口能返回答案”,而是:

Agent 每发生一件事,前端都能收到对应事件,并立即更新界面。

二、AG-UI 到底解决了什么?

AG-UI(Agent-User Interaction Protocol)可以理解为 Agent 与用户界面之间的事件协议。

有谷大脑的问答后端由 LangGraph + deepagents 构建,前端通过 SSE 订阅 AG-UI 事件流。后端不是等全部工作结束后才交付一个大 JSON,而是一边执行,一边把文本、工具调用、状态和引用推给前端。

有谷大脑 AG-UI 问答链路:前端通过 SSE 接收 Deep Agent 推送的文本、工具调用、状态与引用事件

图 1:有谷大脑 AG-UI 问答链路。前端通过 SSE 接收 Deep Agent 推送的文本、工具调用、状态与引用事件。

如果没有这一层协议抽象,每换一个 Agent 框架、每增加一种前端,都要重新处理 SSE 格式、工具事件和状态合并,很容易变成 M × N 的对接困境。

AG-UI 把这些交互统一成标准事件。前端不必理解 Agent 内部每一步是怎么实现的,只要按事件类型更新界面。

三、用户看到的“正在回答”,背后其实有四类事件

1. 文本不是最后一次性出现,而是逐段流出来

一次回答可以依次产生:

TEXT_MESSAGE_START
    ↓
TEXT_MESSAGE_CONTENT { delta }
    ↓
TEXT_MESSAGE_CONTENT { delta }
    ↓
TEXT_MESSAGE_END

前端每收到一个 delta 就追加一段文字,打字机效果自然形成。

这不仅让等待显得更短,也让用户确认系统已经开始工作。对 Agent 产品来说,“及时反馈”本身就是体验的一部分。

2. 工具调用不再藏在黑盒里

有谷大脑的 Agent 可能调用:

  • query_knowledge_base:检索企业知识库;
  • search_web:知识库信息不足时补充联网资料。

工具执行过程中,前端会接收到类似下面的事件:

TOOL_CALL_START
TOOL_CALL_ARGS
TOOL_CALL_RESULT

于是界面可以展示:

正在检索知识库……
已找到 5 条相关内容
正在整理引用……

用户看到的就不再是一个沉默的 loading,而是一条可理解的执行过程。

更重要的是,工具返回的 chunk、来源和 metadata 都可以保持结构化,不需要先塞进一大段 Markdown,再让前端反向猜测哪些文字属于引用。

3. Run 的开始、结束和错误都有明确边界

Agent 问答不是一次普通的“请求—响应”,而是一次 Run。

RUN_STARTED
RUN_FINISHED
RUN_ERROR

收到 RUN_STARTED 后,前端可以切换输入状态;收到 RUN_FINISHED 后恢复;出现 RUN_ERROR 时,则展示明确的错误信息并允许重试。

重新生成答案、编辑上一条用户消息再运行,也可以围绕同一个 Run 边界与 LangGraph checkpoint 对齐。

如果没有统一的生命周期事件,前端很容易遇到这些问题:按钮提前恢复、上一轮回答还没结束就开始下一轮、报错后页面永远停在 loading。

4. 引用不是模型写出来的角标,而是结构化状态

企业知识库问答里,答案后面出现一个 [1] 并不难。

难的是用户点击 [1] 后,真的能打开对应文件,并定位到具体页码或原文片段。

如果只让模型在 Markdown 中写“根据第 3 章”,前端并不知道它对应哪个文档、哪个 chunk、哪一页。

因此,有谷大脑会通过 STATE_SNAPSHOT、STATE_DELTA 等状态事件,同步 citation 列表及来源信息。引用可以携带 chunk id、文档路径、页码等字段,前端再把回答中的角标与阅读器关联起来。

这样,“回答附带出处”才不是视觉装饰,而是一条真正可点击、可追溯、可核验的证据链。

四、AG-UI、MCP、A2A 有什么区别?

这三个协议经常被放在一起讨论,但解决的并不是同一个问题。

协议连接谁主要解决什么
AG-UIAgent ↔ 用户界面流式文本、工具过程、运行状态、引用同步
MCPAgent ↔ 工具与数据让 Agent 调用知识库、搜索及其他外部能力
A2AAgent ↔ Agent多 Agent 之间的通信与协作

有谷大脑网页端问答使用 AG-UI;开放平台通过 MCP 把知识库能力接入 Cursor 等 AI 客户端。

一个负责“Agent 怎么把过程展示给人”,一个负责“Agent 怎么连接工具和数据”。两者边界不同,并不是谁替代谁。

五、有谷大脑的完整问答链路是怎样的?

后端 Agent 在 API 进程启动时构建,并使用 Postgres checkpointer 保存对话执行状态。

当用户发起提问时,大致会经过:

用户提问
    ↓
前端 AG-UI Client 连接 /ag-run
    ↓
Deep Agent 开始 Run
    ↓
优先调用 query_knowledge_base
    ↓
知识库资料不足时调用 search_web
    ↓
通过 SSE 持续推送文本、工具、引用与状态事件
    ↓
前端分别更新聊天气泡、工具卡片、引用侧栏和全局状态

同一个 thread_id 的请求会在进程内串行,避免多个 Run 同时写入 checkpoint,造成对话分支竞争。

实时状态和历史记录则各自承担不同职责:

  • LangGraph checkpoint 保存执行中的对话状态;
  • PostgreSQL 的 chat_message 保存用户可见的历史消息;
  • 两者通过 thread_id 关联。

用户重新生成答案或编辑历史问题时,需要先把 checkpoint 对齐到正确分支,再更新持久化消息。否则就可能出现“界面显示的是新回答,后端上下文却还停在旧分支”的错乱。

六、真正落地时,最容易踩的四个坑

坑 1:同一会话允许多个 Run 同时写状态

用户快速点击两次发送,如果两个 Run 同时修改同一个 checkpoint,很容易产生竞争。

更稳妥的做法是:同一 thread_id 串行执行,或者先取消上一轮,再开始下一轮。产品界面也应明确提示“上一条仍在生成中”。

坑 2:checkpoint 和历史消息只更新了一边

重新生成或编辑消息后,如果只改了数据库记录,没有同步 checkpoint,下一轮 Agent 仍可能沿着旧上下文继续回答。

协议层负责实时事件,持久化层负责历史记录,但两者必须在同一条对话分支上保持一致。

坑 3:错误只结束连接,不告诉前端发生了什么

SSE 连接断开,不等于用户知道发生了什么。

RUN_ERROR 应携带可处理的 message 和 code。前端收到后要停止 loading、展示原因,并提供重试入口,而不是留下一个永远转圈的页面。

坑 4:引用被当成普通文本

模型生成一个 [3] 很容易,但如果没有与真实来源建立结构化关联,用户仍然无法核对结论。

对企业知识库来说,引用至少应该尽可能关联:文档、chunk、页码或章节路径。否则“有引用”和“可验证”之间,还隔着很远。

七、怎么判断 Agent 问答链路是否真的做好了?

比起只测试“最后能不能答对”,下面几种操作更容易暴露协议层问题。

长答案测试

问一个需要综合多份资料的问题。

看首个 token 是否及时出现,而不是等待十几秒后整段弹出。

工具调用测试

问一个必须查知识库的问题。

看界面能否展示正在检索、命中结果和工具结束状态,而不是只有统一的 loading。

引用测试

点击答案中的 [1]、[2]。

看它能否打开真实来源,并定位到对应原文,而不是只显示一个无法追溯的脚注。

连续点击测试

在上一轮还没结束时再次发送。

看系统是否正确排队或取消,是否会把两轮 token 混进同一个回答。

失败恢复测试

让工具超时或主动中断连接。

看界面能否结束 loading、说明错误并允许重试。

这些场景,比“请求最终返回了 200”更能说明一个 Agent 应用是否真正可用。

八、最后:Agent 问答最怕的,不只是答得慢,而是让用户不知道它在做什么

Agent 的价值,在于它能够检索资料、调用工具、组织答案并给出依据。

但如果这些过程都被藏在一个 loading 后面,用户看到的仍然只是一个偶尔很慢、偶尔报错、无法解释答案来源的聊天框。

所以在有谷大脑里,我们用 AG-UI 把 Deep Agent 的执行过程转成前端可消费的事件:

token 实时流出,工具调用看得见,运行状态能同步,答案引用可以点回原文。

不过,AG-UI 解决的是“问”的体验。企业资料入库时怎么解析、怎么切块,决定的则是 Agent 能不能检索到完整且可靠的内容。

有谷大脑也在针对不同企业文档设计对应的解析方式:

  • Excel 表格怎样切块,才不会把表头和数据拆散?
  • PDF 复杂版面怎样识别,才不会打乱阅读顺序?
  • 代码怎样按 AST 切分,才不会把函数从中间截断?
  • 合同怎样保留条款、定义和交叉引用?

入库结构正确,检索才能命中好 chunk;检索结果可靠,AG-UI 才能把依据实时、清楚地呈现给用户。

如果你也在做企业知识库、RAG、Agent 应用或企业 AI 落地,可以关注后续更新。

想体验这套“过程可见、引用可点”的知识库问答,可以访问:

有谷大脑:https://brain.yogu.pro

Agent 问答不应该是“发请求,等 JSON”。真正可用的 Agent,需要让每一步都被用户看见。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐