AI 会回答还不够:ProofOps 业务研判平台落地实战

我的博客:www.k-thoughts.site

公众号:Geek漫游指南

我做 ProofOps 时,最早遇到的问题并不是模型不会回答,而是它回答得太像那么回事。

业务同学拿着一个订单号来问:"为什么一直没出库?"模型很容易给出一份排查清单:查库存、查任务、查日志、查下游接口。句子都对,但继续追问就会发现,它不知道订单现在处于什么状态,也没有证据说明问题到底卡在哪一步。

真正的线上研判不是知识问答。一个结论通常要同时经得起四个问题:

  1. 你查了哪些实时数据?
  2. 这些字段在业务上代表什么?
  3. 代码里的状态流转和当前数据是否一致?
  4. 如果判断错了,别人能不能沿着证据重新核对?

所以 ProofOps 从一开始就不是一个"给大模型套个聊天页面"的项目。我更愿意把它理解成一套业务研判工作台:模型负责规划和解释,MCP 负责受控取证,代码知识库负责核对实现,Java Web 负责权限、会话和审计,最后把过程和结论一起留下来。

先说结论:这个项目真正难的部分,不是把模型接进来,而是把一条不稳定的 Agent 调用,变成一条可以持续运行、可以重新连接、可以约束权限、也可以解释失败的工程链路。

先别急着做 Agent,先把问题定义对

传统客服系统更擅长记录问题,监控系统更擅长发现指标异常,日志平台更擅长检索文本。业务研判往往横跨这三类工具。

以仓储履约为例,一个"订单为什么没出库"的问题,背后可能需要同时确认:

  • 订单当前状态和业务类型;
  • 库存是否足够,是否存在冻结;
  • 波次、拣货、复核等任务有没有生成;
  • 某个接口是否失败,失败时的请求和响应是什么;
  • 代码里什么条件会进入这个分支;
  • 同类问题以前有没有出现过。

这些信息散落在数据库、日志、链路平台、代码仓库和人的经验里。模型只看到用户的一句话时,能做的主要还是推测。

我给 ProofOps 定下的第一条规则因此很简单:没有证据,就不要把可能性写成根因。

这条规则后来影响了整个系统。实时数据必须通过只读工具获取,代码判断要能指出来源,历史案例只是参考,工具失败要明确展示,最终结论还要区分"已确认""较大可能"和"仍需补充"。

最终架构没有很玄,关键是各层不要越界

当前系统可以压缩成下面这条链路:

这套结构里,我刻意把"业务控制面"和"Agent 执行面"拆开了。

Spring Boot Web 不负责推理,它负责登录、RBAC、会话、附件、运行状态、SSE 转发、案例和 MCP 配置。Hermes Bridge 不负责业务账号体系,它只把一次研判请求交给 Agent Runtime,并把模型、工具和进度事件流式返回。

这样拆的好处不是架构看起来更完整,而是出了问题以后知道该去哪里找:

现象 优先检查的位置
用户无法访问某项能力 Web 登录态、角色和菜单权限
页面一直等待 Web SSE、Bridge 流、代理缓冲
工具数量为 0 MCP 配置、子进程、环境变量和协议握手
模型没有使用历史 会话 ID、历史加载和上下文预算
结论没有实时数据 Skill 是否命中、MCP 是否可用、工具是否实际调用

如果所有事情都塞在一个 Python 进程里,早期开发可能更快,但权限、状态恢复和故障归因会越来越混乱。把边界拆清以后,复杂度没有消失,只是终于有地方安放了。

一次研判到底是怎么跑起来的

用户在工作台里输入问题后,系统不会直接把这句话原样扔给模型。

第一步是建立本次运行。每次研判都有独立的 runId,同一个会话则保持稳定的 convId。前者用来追踪这一次执行,后者用来承接多轮上下文。原始问题先落库,后续补充的系统提示、知识片段和安全规则只存在于本次请求里,不覆盖用户原话。

第二步是组装研判上下文。平台会结合本次目标、角色视角、选中的 Skill、附件元数据和有限的历史消息生成请求。这里有个看起来不起眼但很重要的选择:当前实现只加载同一会话最近完成的若干轮,并设置总字符预算。多轮不是把所有历史无限塞给模型,而是保留足够上下文,同时避免旧信息持续污染新判断。

第三步是 Agent 取证。模型可以根据问题选择业务数据库、日志、链路或代码检索工具,但这些工具并不拥有任意系统权限。它拿到的是一个经过限制的工具集合,而不是生产环境的万能终端。

第四步是把过程流式返回。Bridge 会产生开始、步骤、工具、文本增量、结果、错误和完成等事件;Java 侧把关键事件放入缓冲并持久化,再通过 SSE 发给前端。浏览器断线后可以重新挂载,已经完成的运行也可以回放。

第五步才是形成结论。最终报告和过程日志会保存在会话里,经过确认的内容可以进入案例库,后续再被检索出来辅助相似问题。

把这五步串起来以后,ProofOps 才从"一次模型请求"变成了"一个可管理的研判任务"。

图 1:问真台把查证过程、核心结论、可直接回复和问题上下文放在同一个工作区。

为什么我把只读放在了智能前面

Agent 能调用工具以后,最先增加的不是能力,而是风险。

业务数据库里有真实订单和库存,配置中心里有环境参数,日志里可能带用户信息。即使模型没有恶意,错误理解一句话、选错环境或者生成一条带副作用的操作,都可能造成实际影响。

所以当前方案使用了多层约束:

  1. 浏览器只访问 Web,不直接连接 Bridge、数据库或 MCP 子进程。
  2. 管理能力由 RBAC 控制,普通研判用户不能随意修改工具配置。
  3. 数据工具默认只开放查询,插入、更新、删除和 DDL 明确关闭。
  4. 生产数据通过受审计的只读接口访问,不把数据库凭据交给模型。
  5. 附件同时校验当前用户、会话归属和受控路径,不能凭路径读取任意本地文件。
  6. 代码库和历史文档被标记为"不可信参考数据",其中的文字不能覆盖系统规则。

最后一条值得单独说一下。RAG 找回来的内容未必都是事实,也可能包含过期文档、错误结论,甚至是一段看起来像系统指令的文本。ProofOps 会明确告诉 Agent:这些材料只能作为待核验事实;一旦与实时证据冲突,以实时、可复核的信息为准。

这不是一句提示词就能完全解决的安全问题,但至少把信任边界写进了系统,而不是默认"进了知识库的都是真的"。

第一个坑:端口开着,不代表系统真的好了

项目第一次稳定运行时,我很快遇到一个看似简单的问题:服务明明启动了,页面却不能完成研判。

早期判断服务状态时,我也会先看端口。后来发现,对 Agent 系统来说,"进程存在"和"能力就绪"中间隔着很长一段距离。

Web 服务需要确认数据库、登录态和研判引擎配置;Bridge 还要确认模型 Provider、MCP 和记忆组件。于是健康检查不再只返回一个 200 OK,而是分别暴露:

  • Bridge 本身是否正常;
  • 模型配置是否可用;
  • MCP 是否完成初始化;
  • 记忆组件是否就绪;
  • Web 当前使用的是否是真实 Agent,而不是 Mock。

还有一个容易误判的细节:Java 服务启动时要初始化 Spring、JPA、数据库结构和基础数据,冷启动并不一定立刻监听端口。连续重启只会让问题更难判断。更稳妥的方式是给启动留出时间窗口,同时观察服务状态、日志和健康接口。

这件事让我重新定义了"启动成功":不是进程号存在,也不是端口能连,而是整条最小研判链路满足运行前提。

第二个坑:一次 Connection closed,问题不一定在数据库

一次 MCP 测试返回了 Connection closed。这个错误很容易让人先怀疑数据库密码、网络或 KMS,但当时真正的问题更朴素:平台里保存的启动命令仍然指向一个已经删除的旧目录。

我没有直接在页面里反复点"应用",而是把链路拆成三层验证:

  1. 先用当前启动器做一次不落库的直接协议探测;
  2. 确认子进程能够启动,并能返回真实工具列表;
  3. 再更新平台配置,通过应用接口触发热加载,最后从应用层重新测试。

直接探测成功以后,数据库凭据、运行环境和 MCP 协议基本可以排除,剩下的才是平台保存的命令与实际文件布局不一致。修复路径后,应用层测试也成功返回了只读查询工具,增删改和 DDL 开关仍保持关闭。

这个过程里还踩到另一个坑:我曾经让直接数据库修改和 JPA 应用流程同时更新同一条配置,结果触发了记录状态冲突。数据库里显示"已应用",也不等于运行中的 Bridge 已经加载新配置。

最后留下的规则是:让一个应用流程拥有配置更新,更新后既读回持久化状态,也执行一次真实工具探测。 只看页面提示、数据库字段或进程存在,任何一个都不够。

第三个坑:看到"研判流异常",别急着怪模型

另一次故障更典型。页面只显示"研判流异常",而当时 Web、Bridge 和 MCP 的健康检查全部正常。

沿着对应 runId 查日志后,我能确认的是:Java 向 Bridge 发出了取消请求,Bridge 接收成功,正在进行的模型调用随后被中断。

但这些证据只能证明"这次运行被取消了",不能证明是谁触发的。可能是用户点击取消,可能是前端断开后触发清理,也可能是网络或上游事件造成的连锁反应。当时没有完整复现,所以我没有把它写成模型故障,也没有假装已经找到根因。

这次排查反过来推动了事件流设计:

  • 每次运行使用稳定的 runId 串联前端、Java、Bridge 和模型日志;
  • 心跳与业务事件分开,避免心跳把持久化日志淹没;
  • 步骤、结果、取消和错误等关键事件进入缓冲与会话记录;
  • 前端重连时既能接后续事件,也能回放已经发生的部分;
  • 失败状态要区分系统繁忙、工具异常、模型异常、用户取消和连接中断。

Agent 系统的错误经常出现在边界上。没有贯穿链路的运行标识,最后看到的就只剩一句"请求失败"。

多轮、案例和 Skill,解决的是三个不同问题

做到这里以后,系统已经能完成一次研判,但还谈不上知识沉淀。

我后来把连续性拆成了三层:

多轮会话解决当前问题的上下文。 同一个 convId 下,用户可以只补充一个订单号、时间点或新现象,不需要每次重新描述背景。历史加载有轮数和字符预算,查询失败时则明确降级为无历史模式。

案例库解决已经验证过的结论。 一次模型回答不会自动变成标准答案。只有经过确认、带适用范围和根因说明的案例,才适合在后续问题中作为"已验证答案"参与召回。

图 2:研判结果经过确认后可以归档为案例,保留业务链路和证据链。

Skill 解决稳定的排查方法。 比如先查什么、字段怎么解释、哪些状态需要交叉验证,这些更适合写成领域 SOP,由问题特征触发,而不是每次都期待模型临场发挥。

三者不能混为一谈。聊天历史不是长期知识,案例不是系统规则,Skill 也不保证依赖的实时工具一定可用。把它们分开,反而更容易知道答案为什么变好,出了错又该改哪一层。

我也没有把这套机制包装成"系统已经会自主进化"。目前更准确的说法是:它具备受控的历史恢复、知识检索和 Skill 扩展能力;真正进入长期复用的知识,仍然需要验证、版本和边界。

从能运行到能常驻,中间还有一套运维工程

开发阶段,我最初从终端直接启动 Web 和 Bridge。功能验证没有问题,但终端会话被回收以后,进程也跟着消失了。

这不是业务代码故障,只是启动方式不具备持久性。本地环境后来交给系统级的用户服务管理,生产部署则由服务管理器托管 Web、Bridge 和可选的企业协作入口,并为每个进程配置独立工作目录、环境文件、启动重试和日志。

真正的发布验收不只检查服务是否为 active,至少还包括:

  1. Web 健康接口返回真实研判引擎;
  2. Bridge 同时满足模型、MCP 和记忆就绪;
  3. MCP 不仅显示已连接,还能完成一次最小只读调用;
  4. 页面可以持续收到步骤、心跳、结果和完成事件;
  5. 最终报告、会话历史和案例可以正常读取;
  6. 日志中没有密码、Token、完整鉴权头和数据库连接串;
  7. 新版本发布前有可执行的 JAR、配置和 Skill 回滚路径。

这里最重要的变化,是把"我电脑上跑通过"改成了一组别人也能重复执行的验收条件。落地不是把服务启动一次,而是让下一次发布、下一次故障和下一位维护者都有路可走。

回头看,真正落地的是一条证据链

ProofOps 现在已经有聊天工作台、历史会话、案例库、Skill 管理、MCP 管理、权限体系、知识检索和企业协作入口。但如果只按菜单介绍这个项目,很容易把重点带偏。

对我来说,真正有价值的是下面这条链路:

rust 复制代码
业务问题
  -> 明确环境和实体
  -> 选择受控工具
  -> 获取实时证据
  -> 对照代码与领域规则
  -> 输出有边界的结论
  -> 保存过程
  -> 验证后沉淀为案例或 Skill

模型只是其中一环。没有权限控制,它可能越界;没有工具,它只能猜;没有代码知识,它很难解释状态为什么这样流转;没有运行记录,失败时无法复盘;没有验证环节,错误答案还会被当成知识继续传播。

如果重新做一遍,我仍然会按这个顺序:先做最小的只读取证链路,再补运行标识和可观测性,然后处理重连、会话与知识沉淀,最后才扩展更多模型和自动化能力。

我暂时不会给它写"排障效率提升多少倍"。项目目前能证明的是架构、能力和几次真实故障的处理过程,还没有一组长期、可对照的 MTTR 或人工成本数据。比起先写一个漂亮数字,我更愿意先把系统做到:它给出的每个重要判断,都知道证据从哪里来;它失败的时候,也知道下一步该去哪里看。

这大概就是我对业务 Agent 落地最实际的理解:不是让模型更敢回答,而是让整个系统更有资格回答。

相关推荐
SimonKing30 分钟前
白嫖国产多模态大模型:商汤 SenseNova 接入指南
java·后端·程序员
小江的记录本30 分钟前
【ORM框架】MyBatis核心原理、ORM思想、MyBatis vs JPA
java·数据库·后端·spring·spring cloud·oracle·mybatis
卷无止境43 分钟前
FastAPI生产环境密钥管理全解析,从一个.env文件说起
后端·python·fastapi
祀爱1 小时前
C# MQTT 连接服务
后端·c#·.net
卷无止境1 小时前
SigV4与HTTPS,两套完全不同维度的安全机制
后端·python·fastapi
青石路1 小时前
好好的OceanBase官方驱动你不用,非要用第三方驱动,ArrayIndexOutOfBoundsException了吧
java·后端
深念Y1 小时前
微服务抽取路线图:从胖单体到 ARM 集群
前端·arm开发·数据库·后端·微服务·云原生·架构
苏灿烤鱼1 小时前
一个用 Nim 写的隐私优先 Twitter 替代前端,源码级技术剖析
后端·github·twitter
灯澜忆梦1 小时前
【基于GO的Web开发14】gin请求重定向
前端·后端·golang·gin