<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>工程原理 | 有志者事竟成</title><link>https://www.liwenshen.com/tags/%E5%B7%A5%E7%A8%8B%E5%8E%9F%E7%90%86/</link><atom:link href="https://www.liwenshen.com/tags/%E5%B7%A5%E7%A8%8B%E5%8E%9F%E7%90%86/index.xml" rel="self" type="application/rss+xml"/><description>工程原理</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>zh</language><lastBuildDate>Wed, 01 Jul 2026 10:00:00 +0800</lastBuildDate><image><url>https://www.liwenshen.com/media/icon_hu_dd5d76fef920c49e.png</url><title>工程原理</title><link>https://www.liwenshen.com/tags/%E5%B7%A5%E7%A8%8B%E5%8E%9F%E7%90%86/</link></image><item><title>Agent Loop：Agent 的核心执行循环</title><link>https://www.liwenshen.com/post/hermes-agent-engineering/01-agent-loop/</link><pubDate>Wed, 01 Jul 2026 10:00:00 +0800</pubDate><guid>https://www.liwenshen.com/post/hermes-agent-engineering/01-agent-loop/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;系列：通过 Hermes 探秘 Agent 工程 | 第 1 篇&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="为什么需要一个循环"&gt;为什么需要一个&amp;quot;循环&amp;quot;？&lt;/h2&gt;
&lt;p&gt;如果你用过 ChatGPT 或 Claude 的聊天界面，你会发现它们的工作方式是一次性的：你发一条消息，模型回一条消息，对话结束。&lt;/p&gt;
&lt;p&gt;但 Agent 不同。Agent 的任务往往不是&amp;quot;问一句答一句&amp;quot;能解决的。一个典型的 Agent 请求可能是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;帮我查一下今天 A 股涨幅前 10 的板块，把结果写成 CSV 文件，然后分析一下这些板块的共同特征。&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个任务需要：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;调用工具查询数据&lt;/li&gt;
&lt;li&gt;把数据写入文件&lt;/li&gt;
&lt;li&gt;读取文件内容进行分析&lt;/li&gt;
&lt;li&gt;输出最终结论&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每一步都依赖上一步的结果，模型需要&amp;quot;看到&amp;quot;工具执行的结果，才能决定下一步做什么。这就是 Agent Loop——一个让 LLM 和工具反复交互的 while 循环。&lt;/p&gt;
&lt;h2 id="从入口开始"&gt;从入口开始&lt;/h2&gt;
&lt;p&gt;Hermes 的 Agent 入口是一个叫 &lt;code&gt;run_conversation&lt;/code&gt; 的函数。从名字就能看出，它处理的是一轮&amp;quot;对话&amp;quot;（一个用户请求），但内部的轮数远不止一轮。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;用户消息 → [循环开始] → 构建消息 → 调用 LLM → 有工具调用？→ 执行工具 → 结果回灌 → 再次调用
↓
[循环结束] ← 无工具调用 → 输出最终回复
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;整个循环可以分成三个阶段：&lt;strong&gt;初始化、迭代、终结&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="阶段一系统-prompt-的构建"&gt;阶段一：系统 Prompt 的构建&lt;/h2&gt;
&lt;p&gt;在循环开始之前，Agent 需要构建一个系统 Prompt。这个 Prompt 不是用户写的，是 Agent 自己&amp;quot;拼装&amp;quot;出来的，目的只有一个：&lt;strong&gt;告诉 LLM 你是谁、你能做什么、你现在处于什么环境&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Hermes 把它拆成三层：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stable（稳定层）&lt;/strong&gt;：身份声明、工具使用指南、环境信息、Skill 索引。这部分每个会话只构建一次，因为它是&amp;quot;不变的&amp;quot;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Context（上下文层）&lt;/strong&gt;：用户自定义的规则文件（比如项目根目录的 &lt;code&gt;AGENTS.md&lt;/code&gt;），以及一些平台特有的提示。这部分是&amp;quot;半稳定&amp;quot;的——用户改了规则文件，下次会话会刷新。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Volatile（易变层）&lt;/strong&gt;：记忆快照、用户画像、当前时间、会话 ID、当前模型。这部分每个会话都会不同，甚至有时会动态刷新。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这三层拼在一起，就是一个完整的系统 Prompt。比如它的典型结构可能是：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[身份] 你是一个运行在终端里的 AI Agent...
[工具使用] 你可以调用以下工具：terminal, read_file, write_file...
[环境] 操作系统 Linux，当前目录 /root，后端终端 local...
[技能] 已安装技能：akshare-data-fetcher, github-pr-workflow...
[记忆] 用户偏好：简洁高效，不喜欢废话...
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="阶段二循环的燃料迭代预算"&gt;阶段二：循环的&amp;quot;燃料&amp;quot;——迭代预算&lt;/h2&gt;
&lt;p&gt;进入循环之前，Agent 需要知道一件事：&lt;strong&gt;最多能循环多少次？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果没有任何限制，Agent 可能陷入死循环：模型不断调用工具，工具结果不理想，模型再尝试，再失败，无限循环下去。&lt;/p&gt;
&lt;p&gt;Hermes 用一个叫做 &lt;code&gt;IterationBudget&lt;/code&gt; 的对象来管理这个预算。它有两个维度：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;max_total&lt;/strong&gt;：整个会话的总循环次数上限（默认 90 次）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单轮预算&lt;/strong&gt;：每轮对话消耗 1 个预算&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每次调用 LLM 之前，预算会被&amp;quot;消耗&amp;quot;一次。当预算耗尽时，循环强制终止。&lt;/p&gt;
&lt;p&gt;但还有一个细节：&lt;strong&gt;execute_code 工具可以&amp;quot;退款&amp;quot;&lt;/strong&gt;。因为 execute_code 本质上是一个程序化的调用（程序生成代码 → Agent 自动执行 → 返回结果），不算真正的&amp;quot;对话轮次&amp;quot;。所以调用这个工具时，预算会被退还。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;调用 execute_code → 消耗预算 → 执行完毕 → 退还预算
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这就是为什么你能用 Agent 跑很多代码，但不会快速耗尽预算。&lt;/p&gt;
&lt;h2 id="阶段三每次迭代内部发生了什么"&gt;阶段三：每次迭代内部发生了什么&lt;/h2&gt;
&lt;p&gt;每次循环内部，其实分成几个子步骤：&lt;/p&gt;
&lt;h3 id="1-中断检查"&gt;1. 中断检查&lt;/h3&gt;
&lt;p&gt;在调用 LLM 之前，先检查用户是否发来了新消息或 /stop 命令。如果有，立刻中断循环，返回迄今为止的结果。这种设计很实用——你可以在 Agent 执行到一半时喊停。&lt;/p&gt;
&lt;h3 id="2-调用-llm"&gt;2. 调用 LLM&lt;/h3&gt;
&lt;p&gt;构建好消息列表（系统 Prompt + 历史对话 + 当前工具结果）后，Agent 调用 LLM 的 API。这一步可能失败（网络超时、限流、模型错误），所以有一套重试和退避机制。&lt;/p&gt;
&lt;h3 id="3-解析响应"&gt;3. 解析响应&lt;/h3&gt;
&lt;p&gt;LLM 返回的响应有两种可能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;纯文本&lt;/strong&gt;：模型认为任务已经完成，不再需要调用工具&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;带工具调用&lt;/strong&gt;：模型输出了工具调用指令（function call）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4-工具调度的三道安检"&gt;4. 工具调度的&amp;quot;三道安检&amp;quot;&lt;/h3&gt;
&lt;p&gt;如果模型要求调用工具，Agent 不会立即执行，而是经过三层验证：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;名称校验&lt;/strong&gt;：检查工具名是否存在。如果模型产生幻觉（叫了一个不存在的工具名），尝试自动修复（比如 fuzzy 匹配相似的工具名）。如果修不好，最多重试 3 次，然后终止。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;参数校验&lt;/strong&gt;：检查工具的参数是否是合法的 JSON。如果是空字符串，自动补成 &lt;code&gt;{}&lt;/code&gt;（模型常见行为）。如果是损坏的 JSON，先尝试重试 3 次，还不行就注入一个&amp;quot;错误结果&amp;quot;让模型看到，让它自己修正。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;去重与限制&lt;/strong&gt;：检查并去除重复的调用（比如同一个工具被连续调两次相同的参数）。同时限制 &lt;code&gt;delegate_task&lt;/code&gt; 子代理的调用数量，防止无限递归。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="5-执行与结果回灌"&gt;5. 执行与结果回灌&lt;/h3&gt;
&lt;p&gt;通过验证的工具调用会被分发执行。Hermes 支持并行执行（如果模型一次返回多个独立的工具调用），也支持串行。&lt;/p&gt;
&lt;p&gt;执行结果会被格式化为统一的 tool role 消息，追加到历史对话中。下一次循环时，模型就能看到这些结果。&lt;/p&gt;
&lt;h3 id="6-压缩检查"&gt;6. 压缩检查&lt;/h3&gt;
&lt;p&gt;每次工具执行完毕，Agent 会估算当前上下文（历史对话 + 工具结果）的 token 数。如果接近上下文窗口的阈值（默认 50%），就会触发压缩——用一个便宜的模型把早期的对话摘要替换掉，保留关键信息，释放空间。&lt;/p&gt;
&lt;p&gt;这就是为什么你可以在一个会话里聊很久，而不会遇到&amp;quot;上下文太长&amp;quot;的错误。&lt;/p&gt;
&lt;h2 id="什么时候循环结束"&gt;什么时候循环结束？&lt;/h2&gt;
&lt;p&gt;循环终止的条件有四个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;模型返回纯文本&lt;/strong&gt;（没有工具调用）→ 任务完成，返回最终回复&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;预算耗尽&lt;/strong&gt;（达到 max_total）→ 强制停止，返回已完成的进度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用户中断&lt;/strong&gt;（发送新消息或 /stop）→ 优雅退出，保留当前状态&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具护栏触发&lt;/strong&gt;（检测到危险操作）→ 立即停止，输出安全警告&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="工程启示"&gt;工程启示&lt;/h2&gt;
&lt;p&gt;从 Hermes 的实现中，我们可以看到几个 Agent 工程的设计原则：&lt;/p&gt;
&lt;h3 id="1-循环必须有预算"&gt;1. 循环必须有预算&lt;/h3&gt;
&lt;p&gt;没有预算限制的 Agent 是不可靠的。预算不仅是&amp;quot;保护伞&amp;quot;，也是&amp;quot;进度表&amp;quot;——用户可以据此判断任务的复杂度。&lt;/p&gt;
&lt;h3 id="2-工具执行是窗口不是黑盒"&gt;2. 工具执行是&amp;quot;窗口&amp;quot;，不是&amp;quot;黑盒&amp;quot;&lt;/h3&gt;
&lt;p&gt;Agent 不是盲目信任模型的输出。名称校验、参数校验、去重、限流——这些&amp;quot;安检&amp;quot;步骤确保工具调用是安全、合理、可预测的。&lt;/p&gt;
&lt;h3 id="3-错误恢复是对话式的"&gt;3. 错误恢复是&amp;quot;对话式&amp;quot;的&lt;/h3&gt;
&lt;p&gt;当工具调用失败时，Agent 不是直接报错退出，而是把错误信息作为 tool result 喂回给模型，让它有机会自己修正。这种&amp;quot;容错对话&amp;quot;是 Agent 智能的核心体现。&lt;/p&gt;
&lt;h3 id="4-上下文是有价商品"&gt;4. 上下文是&amp;quot;有价商品&amp;quot;&lt;/h3&gt;
&lt;p&gt;Agent 的每一次 API 调用都在消耗 token，而上下文窗口是有限的。压缩机制把&amp;quot;长对话&amp;quot;变成&amp;quot;摘要 + 近期内容&amp;quot;，让 Agent 能处理远超窗口容量的任务。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;Agent Loop 不是一个高深的概念，它就是一个 while 循环。但这个循环内部的设计——迭代预算、中断处理、工具调度、错误恢复、上下文压缩——决定了 Agent 的可靠性、效率和智能程度。&lt;/p&gt;
&lt;p&gt;下一个系列文章，我们将深入工具系统：Hermes 是如何让 50+ 个工具自动注册、发现、分发和执行的。&lt;/p&gt;</description></item></channel></rss>