不用 LangChain,手写了一个最小 Agent Loop。目标并不复杂,只支持 3 个工具:

  1. read_file(path)
  2. write_file(path, content)
  3. run_shell(command)

然后让模型自己决定什么时候调用工具,什么时候直接回答用户。

真正写起来之后,我发现 Agent Loop 和普通 Chatbot 的区别,比我原来想得更大。普通 Chatbot 更像是“一问一答”,而 Agent Loop 更像是“模型决策一次,程序执行一次,再把结果反馈回去继续决策”的循环。

也正因为这样,很多平时看起来像小细节的问题,在 Agent 里都会被放大。

我设计了哪些工具?

这次我故意把工具收得很小,只保留读取文件、写文件和执行 shell 三种能力。

这样做的原因不是因为功能够少,而是因为最小 Agent Loop 最重要的不是“工具全”,而是“边界清楚”。read_file 就只负责读文件,write_file 就只负责写文件,run_shell 则提供一个最基础的系统入口。

我后来感觉,工具设计得越清楚,模型越不容易在“该不该调用这个工具”上犹豫。反过来,如果一个工具描述太宽泛,模型就很容易把它当成万能入口,最后什么都想试一下。

模型什么时候会选错工具?

一开始我以为模型选错工具,主要是因为工具描述写得不够详细。后来发现不完全是这样。

很多时候,模型不是“不知道调用什么”,而是“明明已经可以结束了,但还是继续调用工具”。比如任务只是读取 README 并总结项目内容,理论上 read_file 一次就够了,但模型有时还会继续调用 run_shell 去看目录,甚至想通过 shell 去输出所谓的 final。

这让我意识到,模型选错工具这件事,很多时候背后不是工具定义有问题,而是退出协议设计得不够自然。如果程序一直暗示模型“你必须用某种特殊格式退出”,那模型就可能把“结束任务”也误解成一种需要执行的动作。

参数错误怎么处理?

这次我也第一次更具体地感受到,工具参数校验不能只停留在“模型应该会传对”这种假设上。因为模型依然可能:

  • 漏掉必须参数
  • 传错参数类型
  • 调用一个不存在的工具

所以程序侧还是要自己做一层校验。工具定义能减少错误,但不能代替运行时校验。

这一点很像后端接口开发。你不能因为前端理论上会按接口文档传参,就完全不做服务端校验。到了 Agent 这里,这个“前端”其实就是模型本身。

工具执行失败后模型能不能恢复?

这是我觉得 Agent Loop 最像“系统设计”的地方。

普通脚本里,一步失败往往就意味着整体失败;但 Agent Loop 不是。工具执行失败后,更合理的处理方式通常不是直接退出,而是把失败结果包装成工具返回值,再交回给模型。

比如找不到文件、参数不合法、shell 超时,这些都可以先变成结构化结果,然后继续喂给模型,让它自己决定下一步是重试、换工具,还是直接告诉用户失败原因。

工具调用本质上很像一种受约束的“请求分发”。程序负责把请求路由到正确工具,再把执行结果包装回上下文里。模型真正依赖的,不只是工具有没有执行成功,而是它能不能拿到一份足够清楚的执行反馈。

循环什么时候应该停止?

这次我踩得最明显的坑,反而不是工具调用本身,而是停止条件。

我一开始把退出协议设计得太死了,要求模型必须输出严格的 final JSON,程序才承认它结束。但实际 trace 里能看到,模型其实已经没有继续调用工具了,而且正文里也已经给出了总结,只是因为前面还带了 <think>...</think>,所以 Harness 没认出来。

后来我才慢慢想明白:在 native tool calling 模式下,更自然的退出条件应该是:

  1. 如果模型还有 tool_calls,就继续执行。
  2. 如果模型没有 tool_calls,并且有可见内容,就把它当最终答案。
  3. 如果内容里有 <think>,先清理掉再判断。

也就是说,Agent Loop 的停止条件不应该只是“程序员最喜欢什么格式”,而应该尽量贴近模型在这个调用模式下的自然行为。

这和普通 Chatbot 有什么区别?

写完这个最小 Agent 之后,我最大的感受是,普通 Chatbot 的重点是“生成回答”,而 Agent 的重点是“围绕回答组织一个可执行的循环”。

普通 Chatbot 通常只需要关心 prompt 和输出质量;但 Agent Loop 还要多关心几件事:

  • 工具边界是否清楚
  • 参数校验是否完整
  • 错误能不能回传给模型
  • 循环什么时候停
  • trace 是否足够完整

这些部分如果没处理好,模型就算本身能力不错,整个 Agent 也可能表现得很不稳定。