上一篇记录了 AgentHub 跑通图片处理的过程。任务能够完成以后,新的问题也出现了:如果结果不对,我怎么知道是哪一步出了问题?

模型回复、解析器、工具和外部服务连在同一条调用链里,只看最终答案,很难定位原因。因此接下来的改动集中在两个地方:让文本解析能应对已知的格式变化,再把每轮调用记录下来。

这篇记录的是 2026 年 9 月 22 日对解析器、运行轨迹和工程计划的完善。

文本协议需要处理实际出现的格式

这一版 AgentHub 用 Action、Action Input 和 Final Answer 从模型回复里提取决策。最开始的实现主要针对格式规整的文本。

但标签可能出现不同写法。例如,以下都是用于说明解析行为的样例:

1
Action:echo
1
**Action**: echo

还有内容放到下一行的情况:

1
2
3
4
Action: echo
Action Input:
第一行
第二行

如果解析器只认最规整的单行格式,这些变化就可能造成工具名没有提取出来,或输入内容丢失。

这次修复的做法很直接:先统一全角冒号,再按行拆分标签和值,清理标签周围的空白和星号;遇到 Action Input 或 Final Answer 时,把当前值和后续行合并起来。

这里的重点是把已经发现的问题落成明确规则,而不是期待提示词让所有输出永远保持一致。

八个测试把解析行为固定下来

Parse 接收字符串并返回 Decision,没有网络、文件和模型调用。这让它适合用固定输入检查行为。

这次为解析器补了八个测试,覆盖的情况包括:

情况检查的行为
全角冒号能提取 echo 工具名
Markdown 加粗标签能识别 Action
输入从下一行开始能提取后续内容
多行最终答案保留换行和完整文本
多行工具输入保留多行输入
输入后又出现工具标签已提取的工具名不被覆盖
完整工具请求工具名和输入都正确
完整最终回答正确判断运行可以结束

测试的意义在于把“这个样例应该怎样解析”保存下来。以后修改解析器时,可以检查这些已知行为是否发生变化。

但也要认真看断言覆盖到哪里。例如,“输入后又出现工具标签”的测试,只检查先前提取的工具名仍为 echo。当前实现会把 Action Input 后面的全部文本收进输入,它没有证明后面的标签一定被排除。

同样,这八个测试也不意味着任意非法回复都能处理。字段冲突、缺失字段和复杂的分隔规则,仍然需要另外定义行为。

解析测试和运行过程记录解决不同的问题

解析测试能回答:给定一段文本,程序会提取出什么。

实际运行时,我还想知道:模型这一轮返回了什么,工具有没有被调用,传进去的参数是什么,以及等待时间花在哪里。

因此,我增加了 Observer。在这一版里,它有两个观察入口:

1
2
3
4
5
type Observer interface {
OnLLMCall(step int, reply string, err error, elapsed time.Duration)
OnToolCall(step int, name string, input string,
output string, err error, elapsed time.Duration)
}

Agent 内核在模型调用和工具调用结束后,把结果交给 Observer。具体怎么输出,放在 ConsoleObserver 里实现。

这样做之后,循环仍然负责执行和消息回传,控制台输出负责展示发生了什么。以后如果需要换一种记录方式,至少有一个可以替换的接口。

看见时间,也看见输入和结果

控制台轨迹主要记录这些信息:

  • 当前轮次。
  • 模型调用耗时、回复或错误。
  • 工具名称、输入、执行耗时、结果或错误。

模型计时围绕一次 Chat 调用,工具计时围绕一次 Execute 调用。因此它们表示的是对应调用经历的时间,包含请求和处理过程,不能直接当成模型内部计算时间。

有了这些记录,排查问题时就能沿着调用链检查:模型是否输出了预期工具名,解析后传入的参数是否合理,工具是否返回错误,以及下一轮是否收到了反馈。

未知工具的情况也会记录到 Observer,并生成错误 Observation 交回模型。这样,工具名称错误本身也成为可查看的事件。

空 Observer 让观察能力保持可选

构造 Agent 时,如果没有传入 Observer,代码会使用 NopObserver。

它实现同一个接口,方法体为空。这样内核仍然可以在固定位置调用观察方法,不需要每次都检查 Observer 是否为空。

这个设计在当前规模下很实用:命令行运行时可以打开控制台轨迹,其他调用场景也可以选择不输出。

不过,这里的 Observer 是事后观察。工具已经执行完,才把结果交给它。它没有负责批准或拒绝工具调用,因此不能把它当成执行前的权限检查。

工程计划要和实现状态分别记录

同一天,我还整理了 AgentHub 的工程化计划,列出了后续的功能方向和验收任务。

计划的作用是把接下来需要解决的问题写清楚。至于哪些已经完成,仍然要回到对应代码和实际运行情况来看。

截至这一阶段,解析器有了八个固定输入测试,运行过程有了 Observer 和控制台轨迹。但是 Agent 循环的完整失败路径还没有相应的自动化测试,记录日志也不能替代这些测试。