<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Agent Loop on Weiuou的博客</title><link>https://blog.weiuou.top/tags/agent-loop/</link><description>Recent content in Agent Loop on Weiuou的博客</description><image><title>Weiuou的博客</title><url>https://blog.weiuou.top/avatar.png</url><link>https://blog.weiuou.top/avatar.png</link></image><generator>Hugo</generator><language>zh-cn</language><copyright>Weiuou</copyright><lastBuildDate>Mon, 29 Jun 2026 23:53:28 +0800</lastBuildDate><atom:link href="https://blog.weiuou.top/tags/agent-loop/index.xml" rel="self" type="application/rss+xml"/><item><title>Agent开发笔记（1）我第一次手写 Agent Loop 遇到的问题</title><link>https://blog.weiuou.top/posts/my-first-agent-loop-problems/</link><pubDate>Mon, 29 Jun 2026 23:53:28 +0800</pubDate><guid>https://blog.weiuou.top/posts/my-first-agent-loop-problems/</guid><description>第一次不用 LangChain 手写最小 Agent Loop 时，我在工具设计、参数校验、错误恢复和退出协议上踩到的一些坑。</description><content:encoded><![CDATA[<p>不用 LangChain，手写了一个最小 Agent Loop。目标并不复杂，只支持 3 个工具：</p>
<ol>
<li><code>read_file(path)</code></li>
<li><code>write_file(path, content)</code></li>
<li><code>run_shell(command)</code></li>
</ol>
<p>然后让模型自己决定什么时候调用工具，什么时候直接回答用户。</p>
<p>真正写起来之后，我发现 Agent Loop 和普通 Chatbot 的区别，比我原来想得更大。普通 Chatbot 更像是“一问一答”，而 Agent Loop 更像是“模型决策一次，程序执行一次，再把结果反馈回去继续决策”的循环。</p>
<p>也正因为这样，很多平时看起来像小细节的问题，在 Agent 里都会被放大。</p>
<h2 id="我设计了哪些工具">我设计了哪些工具？</h2>
<p>这次我故意把工具收得很小，只保留读取文件、写文件和执行 shell 三种能力。</p>
<p>这样做的原因不是因为功能够少，而是因为最小 Agent Loop 最重要的不是“工具全”，而是“边界清楚”。<code>read_file</code> 就只负责读文件，<code>write_file</code> 就只负责写文件，<code>run_shell</code> 则提供一个最基础的系统入口。</p>
<p>我后来感觉，工具设计得越清楚，模型越不容易在“该不该调用这个工具”上犹豫。反过来，如果一个工具描述太宽泛，模型就很容易把它当成万能入口，最后什么都想试一下。</p>
<h2 id="模型什么时候会选错工具">模型什么时候会选错工具？</h2>
<p>一开始我以为模型选错工具，主要是因为工具描述写得不够详细。后来发现不完全是这样。</p>
<p>很多时候，模型不是“不知道调用什么”，而是“明明已经可以结束了，但还是继续调用工具”。比如任务只是读取 README 并总结项目内容，理论上 <code>read_file</code> 一次就够了，但模型有时还会继续调用 <code>run_shell</code> 去看目录，甚至想通过 shell 去输出所谓的 final。</p>
<p>这让我意识到，模型选错工具这件事，很多时候背后不是工具定义有问题，而是退出协议设计得不够自然。如果程序一直暗示模型“你必须用某种特殊格式退出”，那模型就可能把“结束任务”也误解成一种需要执行的动作。</p>
<h2 id="参数错误怎么处理">参数错误怎么处理？</h2>
<p>这次我也第一次更具体地感受到，工具参数校验不能只停留在“模型应该会传对”这种假设上。因为模型依然可能：</p>
<ul>
<li>漏掉必须参数</li>
<li>传错参数类型</li>
<li>调用一个不存在的工具</li>
</ul>
<p>所以程序侧还是要自己做一层校验。工具定义能减少错误，但不能代替运行时校验。</p>
<p>这一点很像后端接口开发。你不能因为前端理论上会按接口文档传参，就完全不做服务端校验。到了 Agent 这里，这个“前端”其实就是模型本身。</p>
<h2 id="工具执行失败后模型能不能恢复">工具执行失败后模型能不能恢复？</h2>
<p>这是我觉得 Agent Loop 最像“系统设计”的地方。</p>
<p>普通脚本里，一步失败往往就意味着整体失败；但 Agent Loop 不是。工具执行失败后，更合理的处理方式通常不是直接退出，而是把失败结果包装成工具返回值，再交回给模型。</p>
<p>比如找不到文件、参数不合法、shell 超时，这些都可以先变成结构化结果，然后继续喂给模型，让它自己决定下一步是重试、换工具，还是直接告诉用户失败原因。</p>
<p>工具调用本质上很像一种受约束的“请求分发”。程序负责把请求路由到正确工具，再把执行结果包装回上下文里。模型真正依赖的，不只是工具有没有执行成功，而是它能不能拿到一份足够清楚的执行反馈。</p>
<h2 id="循环什么时候应该停止">循环什么时候应该停止？</h2>
<p>这次我踩得最明显的坑，反而不是工具调用本身，而是停止条件。</p>
<p>我一开始把退出协议设计得太死了，要求模型必须输出严格的 final JSON，程序才承认它结束。但实际 trace 里能看到，模型其实已经没有继续调用工具了，而且正文里也已经给出了总结，只是因为前面还带了 <code>&lt;think&gt;...&lt;/think&gt;</code>，所以 Harness 没认出来。</p>
<p>后来我才慢慢想明白：在 native tool calling 模式下，更自然的退出条件应该是：</p>
<ol>
<li>如果模型还有 <code>tool_calls</code>，就继续执行。</li>
<li>如果模型没有 <code>tool_calls</code>，并且有可见内容，就把它当最终答案。</li>
<li>如果内容里有 <code>&lt;think&gt;</code>，先清理掉再判断。</li>
</ol>
<p>也就是说，Agent Loop 的停止条件不应该只是“程序员最喜欢什么格式”，而应该尽量贴近模型在这个调用模式下的自然行为。</p>
<h2 id="这和普通-chatbot-有什么区别">这和普通 Chatbot 有什么区别？</h2>
<p>写完这个最小 Agent 之后，我最大的感受是，普通 Chatbot 的重点是“生成回答”，而 Agent 的重点是“围绕回答组织一个可执行的循环”。</p>
<p>普通 Chatbot 通常只需要关心 prompt 和输出质量；但 Agent Loop 还要多关心几件事：</p>
<ul>
<li>工具边界是否清楚</li>
<li>参数校验是否完整</li>
<li>错误能不能回传给模型</li>
<li>循环什么时候停</li>
<li>trace 是否足够完整</li>
</ul>
<p>这些部分如果没处理好，模型就算本身能力不错，整个 Agent 也可能表现得很不稳定。</p>
]]></content:encoded></item></channel></rss>