AI Agent实现推理过程

从“黑箱 Agent”到“可观察 CLI”:我是怎么补上推理摘要,并彻底拦住错误 HTML 的

前段时间,我在自己的 RG CLI 项目里连续踩中了两个很影响体验的问题。

第一个问题是:模型其实已经在“想”,但终端里完全看不到任何推理摘要。用户只能看到工具调用和最终答案,整个过程像一个黑箱。

第二个问题是:在接入 OpenAI-compatible 的 responses 能力后,如果 baseUrl 配错了,或者上游网关返回了错误页,程序拿到的可能是一整段 HTML,而不是我真
正需要的 JSON/SSE 响应。对用户来说,这种体验非常差;对开发者来说,排查成本也很高。

这次改造,我主要做了两件事:一是把“推理摘要”真正接进来,并在 CLI 里以合适的方式展示;二是把所有“错误 HTML”在进入业务逻辑前拦住,让系统只处理它该处
理的数据。

项目 改造前 改造后
推理过程 终端看不到,只有最终答案 支持实时 Thinking 和最终“思考摘要”
工具调用顺序 思考信息和工具信息容易脱节 思考摘要按轮次插入,和工具调用时间顺序一致
HTML 错页 容易变成模糊报错,甚至污染响应链路 在 JSON/SSE 两条链路统一拦截,明确提示 baseUrl 或网关问题
排障体验 只能猜哪里错了 Debug 下直接看到请求体、状态码、content-type 和响应片段

一、推理摘要:不是“显示更多文本”,而是把能力链路补完整

改造前的问题

在最早的实现里,查询主循环只关心三件事:发消息、拿工具调用、拿最终文本。也就是说,模型返回的“推理摘要”根本没有正式的数据通道。

这会带来两个直接问题:

  1. 用户只能看到结果,看不到思路,Agent 的行为不够可解释。
  2. 即便底层模型已经支持 reasoning summary,CLI 这一层也接不住,功能等于白开。

更关键的是,推理信息不是简单 console.log 一下就行。它至少要经过四层:

  1. 配置层要能打开它。
  2. LLM 请求层要真正向上游声明需要 reasoning summary。
  3. 流式解析层要能识别 summary 相关事件。
  4. 会话/UI 层要知道哪些该实时显示,哪些该持久化保存。

少一层,这个功能都不算真正完成。

我是怎么改的

这次改造里,我先把配置打通了,新增了 reasoningEffort 和 reasoningSummary,并让它们能从默认值、用户配置、环境变量、命令行参数一路透传到模型请求层。

然后在 OpenAI-compatible 的 responses 请求里,显式带上 reasoning 配置,并补上 include 与 text 控制。这样不是“等模型碰运气返回”,而是主动告诉上游:
我要 reasoning summary,而且我要按可解析的结构给我。

接着我单独补了一条 responses 的流式解析链路,专门处理这类 SSE 事件:

  • response.reasoning_summary_part.added
  • response.reasoning_summary_text.delta

这一层做的事情很关键:不是只收文本,而是把分段 summary 重新拼起来,并且在需要时发出 section break,这样 UI 才知道什么时候该换段、什么时候该继续追
加。

最后,QueryEngine 和 UI 也一起改了:

  • 运行中用 ThinkingPanel 展示实时思考文本
  • 结束后把它整理成“思考摘要”
  • 只持久化摘要,不持久化零散的实时推理片段

这里我刻意做了一个取舍:保留“可观察性”,但不把完整思考流原样落盘。这比一股脑全存下来更稳,也更符合 CLI 场景。

和改造前的对比

改造前,用户看到的是:

“提问 -> 工具调用 -> 最终答案”

改造后,用户看到的是:

“提问 -> 实时 Thinking -> 工具调用 -> 对应轮次的思考摘要 -> 最终答案”

而且我后面又补了一次修正,把“思考摘要”和“工具调用”的展示顺序按时间线重新排了一遍。否则即使有摘要,也会出现“所有思考都堆到最后”的问题,信息是有了,
但时序是错的,读起来还是别扭。

这也是为什么我后来又提交了一次顺序修复:功能实现只是第一步,展示顺序正确才算真正可用。

二、错误 HTML:本质不是返回错了,而是没有在边界处拦住脏数据

改造前的问题

这个问题表面上看是“接口返回了 HTML”,本质上其实是:程序没有把“模型响应”和“网站错误页”当成两种完全不同的数据处理。

尤其是在接入 responses 流式能力之后,链路比原来更复杂了:

  • 普通 JSON 请求是一条入口
  • 流式 SSE 请求又是一条入口

如果只在普通请求里做保护,而流式入口没有校验,那么一旦 baseUrl 指向了网站首页、反向代理页、网关错误页,程序就可能拿到一段 HTML。接下来要么解析失
败,要么给出很模糊的异常信息,最糟糕时还可能让错误页混进正常响应流程。

我是怎么改的

这次我做的不是“补一个 if”,而是统一了边界校验策略。

先在请求层抽出 HTML 检测逻辑,只要 payload 是字符串,并且以 或 <html 开头,就直接判定这不是模型响应。

然后分两条链路处理:

  1. 对普通 JSON 请求,在拿到响应文本后先安全解析;如果发现是 HTML,不进入业务解析,而是直接构造明确错误信息。
  2. 对流式 SSE 请求,不仅检查状态码,还额外检查 content-type 是否真的是 text/event-stream。哪怕 HTTP 200,只要内容类型不对,也不继续往下读。

这一步很重要。很多“错误 HTML”并不是 500,它可能是一个“成功返回的网页”,如果你只看状态码,根本拦不住。

另外我还把 debug 信息补全了。现在一旦遇到 HTML,错误里能直接看到这些关键信息:

  • 请求 URL
  • 请求体
  • HTTP 状态码
  • content-type
  • 响应片段

这样排查时基本不用再猜,看到信息就知道是:

  • baseUrl 指错了
  • 路径拼错了
  • 还是上游网关直接吐了个错误页

和改造前的对比

改造前,遇到 HTML 更像是“程序突然坏了”,你只能顺着异常一点点猜。

改造后,系统会明确告诉你:

“这里返回的不是 JSON/SSE,而是 HTML;你的 llm.baseUrl 很可能不是 API 根地址,或者上游返回了网关错误页。”

这两种体验差别非常大。前者是被动排错,后者是带诊断信息的失败。

三、这次改造里,我最看重的其实不是功能,而是边界

如果只从提交记录看,这次最显眼的两个点是:

  • ee9f25d:实现思考过程摘要功能
  • 2fbee16:修正思考过程和工具调用的时间顺序

但如果从工程角度看,我真正想解决的是两个更底层的问题:

  1. 模型的新能力,不能只停留在 API 层,要一直接到用户真正看得见的地方。
  2. 任何非预期响应,都必须在边界处被识别和隔离,不能流进核心业务。

推理摘要的本质,是把 Agent 从“黑箱”变成“可观察系统”;错误 HTML 的修复,本质,是把系统从“脆弱链路”变成“有防线的链路”。

这两个问题看起来一个偏体验,一个偏稳定性,但最后都指向同一件事:一个能用的 CLI Agent,不只是能答题,更要让人知道它在做什么,以及它为什么会失败。


AI Agent实现推理过程
https://sdueryrg.github.io/2026/04/11/AI-Agent实现推理过程/
作者
yrg
发布于
2026年4月11日
许可协议