目录

从指标知识库到对话式分析平台:一个 Agent WebUI 的设计与实践

如何统一多种 Agent、流式事件、知识检索与数据分析工作流

很多 AI 数据分析项目最先做出来的是一个聊天框:用户输入问题,模型返回一段 SQL,看起来已经完成了从自然语言到数据查询的跨越。

但真正把它交给分析师使用后,很快会遇到另一组问题:这段 SQL 参考了哪套口径?Agent 正在检索还是卡住了?刷新页面后任务还在不在?查询结果放在哪里?同一个问题下周能否接着分析?

这篇文章分享我在一个指标知识库项目中构建 WebUI 的过程。它不是给大模型套一层聊天界面,而是尝试把知识检索、Agent 执行、SQL 生成、查询结果、图表和分析报告组织成一条可追溯的工作流。

全文已对业务名称、指标、表名、编码和内部平台信息做泛化处理,重点讨论可复用的架构思路与工程取舍。

1. 从命令行 Agent 到分析工作台

项目最初的形态是一套 Git 管理的指标知识库,以及运行在本地的 Agent Skills。

知识库里沉淀了标准 SQL、指标目录、术语定义和表元数据。用户可以在命令行里向 Agent 提问,Agent 先检索知识库,再读取命中的标准 SQL,最后给出解释或生成查询。

这套方式已经解决了一个关键问题:不让模型凭训练数据猜指标口径

但命令行并不是分析工作的终点。

问题命令行阶段的表现
使用门槛用户需要理解 CLI、工作目录、模型和会话参数
过程不可见正文、思考、工具调用和日志混在同一输出流里
会话难管理多个分析任务依赖零散的 session ID 或终端历史
产物割裂SQL、查询结果、图表和报告散落在不同目录与工具中
状态难恢复页面或终端中断后,不容易恢复正在运行的任务
知识不可浏览Catalog 和表结构对 Agent 可读,对普通用户却不够友好

因此,WebUI 的目标并不是简单地“把 CLI 搬进浏览器”,而是建立一个面向分析任务的工作台:

  1. 用户用自然语言描述问题;
  2. Agent 从权威知识库中检索口径;
  3. 系统实时展示检索、工具调用和生成过程;
  4. SQL、查询结果和分析报告自动关联到当前会话;
  5. 用户可以随时回到历史任务继续分析。

我更愿意把它称为 Agentic Data Workspace,而不只是 Chat UI。


2. 先确定边界:WebUI 不负责“变聪明”

在设计之初,我刻意把 WebUI 和 Agent 的知识能力分开。

WebUI 负责:

  • 浏览器交互;
  • Agent 选择与请求编排;
  • 流式事件展示;
  • 会话和运行状态管理;
  • SQL、结果、图表与报告的组织;
  • 知识库和检索状态的可视化。

Agent Skills 负责:

  • 判断问题类型;
  • 检索指标目录和术语;
  • 读取标准 SQL;
  • 约束允许修改的参数;
  • 调用查询和分析工具;
  • 在没有权威来源时拒绝编造。
flowchart LR U["用户问题"] W["WebUI<br/>交互与编排"] A["Agent<br/>推理与工具调用"] K["知识库<br/>Catalog / 标准 SQL / 表元数据"] D["分析产物<br/>SQL / 结果 / 图表 / 报告"] U --> W W --> A A --> K K --> A A --> D D --> W

这个边界非常重要。

如果把知识检索、口径判断和 SQL 生成规则都写进 Web 后端,WebUI 很快会变成第二套 Agent 框架;如果让前端直接理解每个工具的业务语义,新增 Agent 或 Skill 时又要同步修改大量界面代码。

最终的分工是:

Agent 决定做什么,WebUI 负责把过程和产物可靠地呈现出来。


3. 总体架构:一套界面,接入多种 Agent

整个系统由浏览器前端、Web 后端、Agent 接入层和知识资产层组成。

flowchart TB subgraph Browser["浏览器"] CHAT["对话与会话"] SQLUI["SQL 与查询结果"] CHART["图表与分析报告"] LIB["知识库浏览"] end subgraph Backend["Web 后端"] API["HTTP API"] STREAM["统一流式事件"] SESSION["会话与运行时状态"] ARTIFACT["分析产物索引"] RUNNER["Runner 调度层"] end subgraph Engines["Agent 引擎"] CLI1["Agent CLI A"] CLI2["Agent CLI B"] ACP["ACP 常驻 Agent"] end subgraph Knowledge["权威知识资产"] CAT["指标 Catalog"] SQL["标准 SQL"] META["术语与表元数据"] SKILL["Agent Skills"] end Browser --> API Browser --> STREAM API --> RUNNER RUNNER --> CLI1 RUNNER --> CLI2 RUNNER --> ACP RUNNER --> SESSION API --> ARTIFACT CLI1 --> SKILL CLI2 --> SKILL ACP --> SKILL SKILL --> CAT SKILL --> SQL SKILL --> META

前端使用 React 和 TypeScript 构建,后端使用 FastAPI。技术选型本身并不特别,真正需要解决的是三个连接问题:

  1. 如何让不同 Agent 对前端表现为同一种协议;
  2. 如何让长时间运行的 Agent 任务可观察、可取消、可恢复;
  3. 如何让对话与 SQL、结果、图表等产物保持稳定关联。

4. Runner 层:隔离不同 Agent 的差异

Agent 的接入方式并不统一。

有的 Agent 通过命令行子进程运行,每次请求启动一个进程;有的支持恢复历史会话;还有的实现了 ACP(Agent Client Protocol),可以通过长连接维护常驻会话。

它们的输出格式也不一样:

  • 有的输出普通文本;
  • 有的输出逐行 JSON;
  • 有的将文本增量、工具调用和最终结果分成不同消息;
  • 有的在协议层维护独立的会话 ID。

如果让前端分别适配每一种 Agent,聊天组件会迅速堆满条件判断。因此我在后端增加了 Runner 层,每个 Runner 只负责五件事:

  1. 构造该 Agent 需要的请求;
  2. 启动进程或复用连接;
  3. 解析原始输出;
  4. 转换成统一事件;
  5. 实现取消和异常处理。

前端只认识统一事件,不关心背后是 CLI 还是常驻 Agent。

这层抽象带来的好处不仅是“方便新增引擎”。它还允许不同运行方式共享同一套会话列表、消息组件、工具状态和错误处理逻辑。


5. 用类型化事件描述 Agent 的执行过程

普通聊天应用通常只需要处理文本流,但 Agent 的输出不只有文本。

一次完整任务可能依次发生:

接收问题
  → 检索知识库
  → 读取标准 SQL
  → 调用查询工具
  → 等待查询完成
  → 读取结果
  → 生成解释或图表

如果所有内容都被压成字符串,前端只能把它们混在一起展示。用户既看不清 Agent 正在做什么,也无法判断任务究竟是运行中、已完成还是失败。

因此,后端将不同 Agent 的原始输出归一化为一组类型化事件:

事件类别用途
元信息标识会话、轮次、Agent 和模型
状态表示初始化、检索、执行、保存等阶段
文本增量流式呈现最终回复
思考增量单独呈现可展示的推理过程
工具开始告知用户正在调用什么能力
工具参数展示或记录结构化输入
工具结果呈现工具成功、失败和返回摘要
最终结果表示当前轮次正常结束
错误提供统一异常信息
会话保存通知前端刷新本地会话状态
sequenceDiagram participant U as 用户 participant F as 前端 participant B as Web 后端 participant A as Agent participant T as 工具 U->>F: 提交问题 F->>B: 创建对话请求 B->>A: 调度 Agent A-->>B: 状态 / 文本 / 思考 B-->>F: 流式事件 A->>T: 调用检索或查询工具 T-->>A: 工具结果 A-->>B: 工具事件与最终回答 B-->>F: 结果与会话保存事件

浏览器与后端之间使用 SSE(Server-Sent Events)传输这些事件。

这里选择 SSE,主要是因为当前场景的数据方向非常明确:用户通过普通 HTTP 提交请求,服务端持续向浏览器推送事件。相比为所有交互都引入双向 WebSocket,SSE 的实现和调试成本更低,也天然适合文本增量和状态通知。

而在后端与 ACP Agent 之间,长连接和双向协议更重要,因此使用 WebSocket。两个链路解决的是不同问题,没有必要强行统一。


6. 常驻 Agent 与冷启动问题

CLI 模式简单、兼容性好,但每轮启动新进程可能带来明显的冷启动延迟:

  • 加载 Agent 配置;
  • 初始化 MCP 工具;
  • 读取 Skills;
  • 恢复会话上下文;
  • 建立外部连接。

对于偶尔使用的单轮任务,这个成本可以接受;对于连续的数据分析,会频繁打断用户节奏。

ACP 模式的思路是让 Agent 作为常驻进程存在,Web 后端通过长连接向它发送请求。后端维护连接池,并将浏览器会话映射到底层 Agent 会话。

flowchart LR WEB["Web 会话 ID"] MAP["会话映射"] ACPID["Agent 会话 ID"] CONN["连接池"] AGENT["常驻 Agent"] WEB --> MAP MAP --> ACPID ACPID --> CONN CONN --> AGENT

为什么需要两套 ID?

Web 会话代表用户看到的分析任务,它可以被重命名、置顶、搜索和归档;Agent 会话则由底层协议维护,关注上下文延续。将二者解耦后,界面层不需要暴露底层协议细节,也可以在 Agent 实现变化时保持自己的会话模型稳定。

CLI 和常驻 Agent 并不是非此即彼:

方式更适合的场景
CLI 子进程低频使用、调试、兼容已有工具、任务隔离
常驻 Agent连续多轮分析、频繁工具调用、关注响应延迟

统一 Runner 之后,二者可以在同一个 WebUI 中共存。


7. 页面刷新之后,任务应该继续存在

数据查询和深度分析经常需要几十秒甚至更久。浏览器刷新、网络抖动或前端组件重载,不应该导致后端任务中断。

我把“Agent 是否在运行”和“浏览器是否在线”拆成两个状态。

后端运行时存储会记录:

  • 当前任务状态;
  • 已产生的事件;
  • 单调递增的事件序号;
  • 会话与 Agent 的关联;
  • 完成、取消或异常状态。

前端重新连接时,携带自己已经处理到的事件序号。后端只补发后续事件,避免重复渲染。

sequenceDiagram participant F as 浏览器 participant R as 运行时事件存储 participant A as Agent A->>R: 写入事件 seq=1..20 R-->>F: 推送 seq=1..12 Note over F: 页面刷新 A->>R: 继续写入 seq=21..30 F->>R: 从 seq=12 之后恢复 R-->>F: 补发 seq=13..30

这实际上是一个轻量级的事件重放机制。它没有引入复杂消息队列,却解决了单机 WebUI 中最常见的断线恢复问题。

需要注意的是,运行时事件和永久会话不是同一个概念:

  • 运行时事件用于恢复“正在发生的事情”;
  • 会话存储用于保存“已经发生的事情”。

把两者混在一起,会让持久化结构被大量细粒度事件污染;完全不保存运行时状态,则无法处理刷新恢复。


8. 从聊天记录到分析产物图谱

对数据分析来说,一段最终回复只是任务的一部分。真正有价值的产物还包括:

  • Agent 生成或修改的 SQL;
  • 一次查询的状态和结果;
  • 根据结果生成的图表;
  • 深度分析过程和最终报告。

如果这些产物只靠文件名和时间戳管理,用户很难回答:“这份结果是哪次对话生成的?”

因此,会话模型除了消息,还维护与分析产物的关联:

flowchart TB S["分析会话"] Q1["SQL 草稿 A"] Q2["SQL 草稿 B"] R1["查询结果 A"] R2["查询结果 B"] C1["趋势图"] C2["分组对比图"] REPORT["分析报告"] S --> Q1 S --> Q2 Q1 --> R1 Q2 --> R2 R1 --> C1 R2 --> C2 R1 --> REPORT R2 --> REPORT

在界面上,这些内容可以出现在同一个分析上下文中:

  • 左侧是历史会话;
  • 中间是自然语言对话;
  • 右侧是 SQL、结果、图表和报告;
  • 知识库和检索状态作为辅助入口。

这使得 WebUI 不再只是“输入问题、输出答案”,而是逐渐成为分析过程的索引。


9. 知识库为什么也需要可视化

Agent 可以直接读取 YAML、SQL 和 Markdown,但人类用户未必愿意在目录树中逐个寻找文件。

知识库浏览器提供了另一种视角:

  • 按业务域浏览指标;
  • 按文档类型筛选;
  • 搜索指标名、别名和关键词;
  • 在线查看标准 SQL 和方法说明;
  • 查看表字段、分区和枚举;
  • 检查检索索引是否更新。

这项能力看似只是“文件预览”,实际解决了两个问题。

第一,它让 Agent 的依据可见。用户可以从回答跳回知识源,而不是只能相信模型总结。

第二,它降低了知识维护者和普通使用者之间的鸿沟。维护者仍然可以通过 Git 管理结构化资产,普通用户则通过 WebUI 阅读和检索同一份内容。

RAG 状态面板也遵循同样的思路:检索系统不应该是完全的黑盒。索引是否存在、更新时间是否落后、服务是否健康,都应该有明确状态。


10. 一条完整的数据分析链路

将这些模块组合起来后,一次典型任务可以是:

用户提出指标问题
  → Agent 检索指标 Catalog
  → 读取权威口径与标准 SQL
  → 根据时间范围生成查询
  → 调用数据查询工具
  → WebUI 展示执行状态与结果
  → 用户要求绘制趋势或分组对比
  → 图表与查询结果关联保存
  → Agent 基于多份结果继续分析
  → 生成可回溯的分析报告

例如,分析师可以提出:

最近三个月指标 X 出现下降,请按渠道和用户类型拆解原因。

系统不会立刻凭空生成结论,而是沿着一条受约束的路径工作:

  1. 在知识库中确认指标 X 的定义和标准 SQL;
  2. 生成只修改日期等运行参数的查询;
  3. 执行查询并保存结构化结果;
  4. 根据问题选择趋势、对比或贡献拆解;
  5. 将关键图表和结论写入报告;
  6. 保留从结论到结果、SQL 和知识源的引用链。

这里最重要的不是“自动画了一张图”,而是分析过程不再依赖临时复制粘贴。


11. 典型应用场景

11.1 指标口径问答

业务人员可以询问指标定义、统计粒度、过滤条件和数据来源。Agent 从知识库中组织答案,WebUI 展示引用来源。

11.2 标准 SQL 生成

分析师用自然语言描述时间范围、维度和输出要求。Agent 命中标准 SQL 后按白名单修改运行参数,降低重新编写业务逻辑带来的口径风险。

11.3 交互式取数

用户在对话中发起查询,并在同一界面查看执行状态、结果预览和完整数据,不再需要在聊天工具、代码编辑器和查询平台之间反复切换。

11.4 数据可视化

查询结果可以继续生成趋势图、分组对比图、分布图或散点图。图表配置作为独立产物保存,并与原始结果关联。

11.5 深度分析与报告

对于需要多轮下钻的任务,Agent 可以在同一个会话中引用多份查询结果,逐步形成分析结论和报告。

11.6 新人自助学习

新人可以通过自然语言和知识库浏览器了解团队有哪些指标、指标如何计算、常用表有哪些字段,不再完全依赖口头传递。

11.7 检索与 Agent 运维

维护者可以查看 Agent 可用性、知识索引更新时间和检索服务状态,快速区分“模型问题”“工具问题”和“知识没有收录”。


12. 安全边界与工程取舍

这套方案目前主要面向个人或内网环境。它解决的是 Agent 分析工作流的可用性,还不是一个完整的企业级多租户平台。

12.1 本地文件存储

会话、SQL 和分析结果使用本地结构化文件存储,优点是简单、透明、容易调试,也方便 Agent 直接读取。

它适合:

  • 单用户或小规模内网使用;
  • 本地优先的 Agent 工作流;
  • 原型验证和快速迭代。

它不适合:

  • 多实例并发写入;
  • 复杂权限和共享;
  • 大规模历史数据查询;
  • 严格事务一致性要求。

当系统进入多用户阶段,会话和产物索引应迁移到数据库或对象存储,但文件作为 Agent 与外部工具之间的产物协议仍然有价值。

12.2 WebUI 不是安全边界

即使界面隐藏了某些操作,也不能替代后端权限检查。真正对外服务时仍需要:

  • 身份认证和角色权限;
  • 数据源访问控制;
  • 查询成本与并发限制;
  • 敏感字段识别和脱敏;
  • 高风险操作审批;
  • 完整审计日志。

12.3 自动化不能替代口径治理

WebUI 可以让标准 SQL 更容易被检索和执行,却不能自动判断一个业务口径是否合理。指标治理仍需要知识 Owner、版本管理和人工 Review。

Agent 的价值是缩短从问题到证据的路径,而不是取消人的责任。


13. 我从这个项目中得到的几个结论

13.1 Agent 产品的核心不是聊天框

聊天框只是入口。真正决定可用性的,是运行状态、工具反馈、会话恢复和产物管理。

13.2 协议统一比引擎统一更重要

不必要求所有 Agent 使用同一种运行方式。只要能够在边界处统一事件协议,就可以让 CLI、长连接 Agent 和未来的新引擎共用上层体验。

13.3 对话历史不等于任务状态

消息记录只能说明用户和 Agent 说过什么,不能完整表达任务是否运行、产生了哪些文件、关联了哪些结果。分析型 Agent 需要独立的运行时状态和产物模型。

13.4 恢复能力应该从第一天设计

长任务一旦出现,刷新恢复就不再是锦上添花。事件序号和补发机制的实现成本不高,却能显著改善可靠性。

13.5 可追溯比“像人一样回答”更重要

数据分析的结论最终要落到证据上。知识来源、SQL、查询结果、图表和报告之间的关联,往往比回复措辞是否自然更重要。


14. 后续演进方向

当前版本已经能够完成从知识问答到分析产物管理的基本闭环,下一阶段更值得投入的是平台化能力:

方向目标
多用户与权限支持身份认证、角色和数据域隔离
任务队列管理长时间任务、并发、重试和资源配额
服务端存储支持多实例、团队共享和历史检索
查询治理增加成本估算、审批、限流和脱敏
分析编排将多轮查询、计算、图表和报告组织成可复用流程
质量监控观察检索命中率、拒答率、查询成功率和人工修正
团队协作支持分享、评论、报告版本和知识反馈

但这些能力应该建立在两个前提之上:

  1. 知识资产已经有明确的权威来源;
  2. Agent 的每一步操作都可以被观察和追溯。

如果这两个基础没有建立,平台化只会把不可控的自动化放大。


15. 小结

从命令行 Agent 到对话式分析平台,真正需要补齐的不是一个网页,而是一整套任务基础设施:

  1. 用 Runner 层统一不同 Agent 的调用差异;
  2. 用类型化流式事件呈现文本、状态和工具过程;
  3. 用常驻 Agent 降低连续分析中的冷启动开销;
  4. 用运行时事件重放支持页面刷新和断线恢复;
  5. 用会话关联 SQL、查询结果、图表和分析报告;
  6. 用知识库浏览和来源引用保持结果可核对;
  7. 明确 WebUI、Agent Skills 和知识治理各自的责任边界。

一句话总结:

一个可靠的 Agent WebUI,不只是让用户能和模型说话,而是让一次分析从问题、知识、执行到结论都有迹可循。

如果你也在构建面向数据分析、研发或内部知识工作的 Agent,希望这些设计取舍能提供一些参考。