Agent Loop:Agent 的核心执行循环

从 Hermes 源码看 Agent 如何通过循环调用 LLM 和工具来完成任务

系列:通过 Hermes 探秘 Agent 工程 | 第 1 篇


为什么需要一个"循环"?

如果你用过 ChatGPT 或 Claude 的聊天界面,你会发现它们的工作方式是一次性的:你发一条消息,模型回一条消息,对话结束。

但 Agent 不同。Agent 的任务往往不是"问一句答一句"能解决的。一个典型的 Agent 请求可能是:

“帮我查一下今天 A 股涨幅前 10 的板块,把结果写成 CSV 文件,然后分析一下这些板块的共同特征。”

这个任务需要:

  1. 调用工具查询数据
  2. 把数据写入文件
  3. 读取文件内容进行分析
  4. 输出最终结论

每一步都依赖上一步的结果,模型需要"看到"工具执行的结果,才能决定下一步做什么。这就是 Agent Loop——一个让 LLM 和工具反复交互的 while 循环。

从入口开始

Hermes 的 Agent 入口是一个叫 run_conversation 的函数。从名字就能看出,它处理的是一轮"对话"(一个用户请求),但内部的轮数远不止一轮。

用户消息 → [循环开始] → 构建消息 → 调用 LLM → 有工具调用?→ 执行工具 → 结果回灌 → 再次调用
                                                        ↓
                                              [循环结束] ← 无工具调用 → 输出最终回复

整个循环可以分成三个阶段:初始化、迭代、终结

阶段一:系统 Prompt 的构建

在循环开始之前,Agent 需要构建一个系统 Prompt。这个 Prompt 不是用户写的,是 Agent 自己"拼装"出来的,目的只有一个:告诉 LLM 你是谁、你能做什么、你现在处于什么环境

Hermes 把它拆成三层:

  • Stable(稳定层):身份声明、工具使用指南、环境信息、Skill 索引。这部分每个会话只构建一次,因为它是"不变的"。

  • Context(上下文层):用户自定义的规则文件(比如项目根目录的 AGENTS.md),以及一些平台特有的提示。这部分是"半稳定"的——用户改了规则文件,下次会话会刷新。

  • Volatile(易变层):记忆快照、用户画像、当前时间、会话 ID、当前模型。这部分每个会话都会不同,甚至有时会动态刷新。

这三层拼在一起,就是一个完整的系统 Prompt。比如它的典型结构可能是:

[身份] 你是一个运行在终端里的 AI Agent...
[工具使用] 你可以调用以下工具:terminal, read_file, write_file...
[环境] 操作系统 Linux,当前目录 /root,后端终端 local...
[技能] 已安装技能:akshare-data-fetcher, github-pr-workflow...
[记忆] 用户偏好:简洁高效,不喜欢废话...

阶段二:循环的"燃料"——迭代预算

进入循环之前,Agent 需要知道一件事:最多能循环多少次?

如果没有任何限制,Agent 可能陷入死循环:模型不断调用工具,工具结果不理想,模型再尝试,再失败,无限循环下去。

Hermes 用一个叫做 IterationBudget 的对象来管理这个预算。它有两个维度:

  • max_total:整个会话的总循环次数上限(默认 90 次)
  • 单轮预算:每轮对话消耗 1 个预算

每次调用 LLM 之前,预算会被"消耗"一次。当预算耗尽时,循环强制终止。

但还有一个细节:execute_code 工具可以"退款"。因为 execute_code 本质上是一个程序化的调用(程序生成代码 → Agent 自动执行 → 返回结果),不算真正的"对话轮次"。所以调用这个工具时,预算会被退还。

调用 execute_code → 消耗预算 → 执行完毕 → 退还预算

这就是为什么你能用 Agent 跑很多代码,但不会快速耗尽预算。

阶段三:每次迭代内部发生了什么

每次循环内部,其实分成几个子步骤:

1. 中断检查

在调用 LLM 之前,先检查用户是否发来了新消息或 /stop 命令。如果有,立刻中断循环,返回迄今为止的结果。这种设计很实用——你可以在 Agent 执行到一半时喊停。

2. 调用 LLM

构建好消息列表(系统 Prompt + 历史对话 + 当前工具结果)后,Agent 调用 LLM 的 API。这一步可能失败(网络超时、限流、模型错误),所以有一套重试和退避机制。

3. 解析响应

LLM 返回的响应有两种可能:

  • 纯文本:模型认为任务已经完成,不再需要调用工具
  • 带工具调用:模型输出了工具调用指令(function call)

4. 工具调度的"三道安检"

如果模型要求调用工具,Agent 不会立即执行,而是经过三层验证:

  1. 名称校验:检查工具名是否存在。如果模型产生幻觉(叫了一个不存在的工具名),尝试自动修复(比如 fuzzy 匹配相似的工具名)。如果修不好,最多重试 3 次,然后终止。

  2. 参数校验:检查工具的参数是否是合法的 JSON。如果是空字符串,自动补成 {}(模型常见行为)。如果是损坏的 JSON,先尝试重试 3 次,还不行就注入一个"错误结果"让模型看到,让它自己修正。

  3. 去重与限制:检查并去除重复的调用(比如同一个工具被连续调两次相同的参数)。同时限制 delegate_task 子代理的调用数量,防止无限递归。

5. 执行与结果回灌

通过验证的工具调用会被分发执行。Hermes 支持并行执行(如果模型一次返回多个独立的工具调用),也支持串行。

执行结果会被格式化为统一的 tool role 消息,追加到历史对话中。下一次循环时,模型就能看到这些结果。

6. 压缩检查

每次工具执行完毕,Agent 会估算当前上下文(历史对话 + 工具结果)的 token 数。如果接近上下文窗口的阈值(默认 50%),就会触发压缩——用一个便宜的模型把早期的对话摘要替换掉,保留关键信息,释放空间。

这就是为什么你可以在一个会话里聊很久,而不会遇到"上下文太长"的错误。

什么时候循环结束?

循环终止的条件有四个:

  1. 模型返回纯文本(没有工具调用)→ 任务完成,返回最终回复
  2. 预算耗尽(达到 max_total)→ 强制停止,返回已完成的进度
  3. 用户中断(发送新消息或 /stop)→ 优雅退出,保留当前状态
  4. 工具护栏触发(检测到危险操作)→ 立即停止,输出安全警告

工程启示

从 Hermes 的实现中,我们可以看到几个 Agent 工程的设计原则:

1. 循环必须有预算

没有预算限制的 Agent 是不可靠的。预算不仅是"保护伞",也是"进度表"——用户可以据此判断任务的复杂度。

2. 工具执行是"窗口",不是"黑盒"

Agent 不是盲目信任模型的输出。名称校验、参数校验、去重、限流——这些"安检"步骤确保工具调用是安全、合理、可预测的。

3. 错误恢复是"对话式"的

当工具调用失败时,Agent 不是直接报错退出,而是把错误信息作为 tool result 喂回给模型,让它有机会自己修正。这种"容错对话"是 Agent 智能的核心体现。

4. 上下文是"有价商品"

Agent 的每一次 API 调用都在消耗 token,而上下文窗口是有限的。压缩机制把"长对话"变成"摘要 + 近期内容",让 Agent 能处理远超窗口容量的任务。

总结

Agent Loop 不是一个高深的概念,它就是一个 while 循环。但这个循环内部的设计——迭代预算、中断处理、工具调度、错误恢复、上下文压缩——决定了 Agent 的可靠性、效率和智能程度。

下一个系列文章,我们将深入工具系统:Hermes 是如何让 50+ 个工具自动注册、发现、分发和执行的。