第五章

Agent 执行循环

🎧 语音播放

理解 Hermes 如何"思考"并执行任务

🎯 本章目标

  • 理解 Agent 循环的核心概念
  • 掌握 run_conversation 的完整执行流程
  • 理解 IterationBudget 的作用

📚 前置知识

  • 第四章的启动流程
  • 知道 LLM 是"根据输入返回输出"的

💡 学完收获

  • 能解释 Agent 循环的原理
  • 理解为什么 Agent 需要多次调用
  • 掌握调试循环的方法

5.1 什么是 Agent 循环?

普通程序:输入 → 处理 → 输出(一次完成)

Agent:输入 → 思考 → 行动 → 观察结果 → 再思考 → 再行动 → ... → 输出

💡 类比:侦探破案

侦探不会看一眼现场就得出结论。他会:

  • 问一些问题(调用工具)
  • 得到答案(观察结果)
  • 再问新问题
  • 直到证据链完整

Agent 循环就是"侦探思维"的程序化实现。

5.2 核心流程图

以下是 run_conversation 的完整流程:

Agent 执行循环 Agent Loop

flowchart TD Start(["🧠 run_conversation() 开始"]) --> Check{"budget\n还有吗?"} Check -->|还有| Build["📝 构建 Prompt\n系统 + 记忆 + 用户输入"] Check -->|没了| Fail["❌ 达到最大迭代\n返回失败"] Build --> Call["📡 调用 LLM API"] Call --> Parse{"解析响应"} Parse --> HasCall{"tool_calls?"} HasCall -->|有| Execute["⚡ 执行工具\nhandle_function_call()"] Execute --> Log["📝 记录工具结果\n到 messages"] Log --> Check HasCall -->|没有| Success["✅ 返回最终响应"] Success --> End(["🏁 完成"]) style Start fill:#dbeafe,stroke:#2563eb style Check fill:#ede9fe,stroke:#7c3aed style Execute fill:#d1fae5,stroke:#059669 style Fail fill:#fee2e2,stroke:#ef4444 style Success fill:#d1fae5,stroke:#059669

5.3 关键组件

5.3.1 IterationBudget

迭代预算控制最大循环次数,防止无限循环:

⚙️ max_iterations
最大迭代次数(默认 90)
🔢 remaining
剩余可用迭代次数
run_agent.py
class IterationBudget:
    def __init__(self, max_iterations: int = 90):
        self.max_iterations = max_iterations
        self.remaining = max_iterations
    
    def use(self) -> bool:
        """消耗一次迭代,返回是否还有剩余"""
        self.remaining -= 1
        return self.remaining >= 0
    
    def is_exhausted(self) -> bool:
        return self.remaining <= 0
⚠️ 为什么是 90?

90 次迭代看起来很多,但一个复杂任务可能需要几十次工具调用。每次工具调用都会消耗一次迭代。如果达到 90 次还没完成,说明任务可能有问题。

5.3.2 消息构建

每次循环,Hermes 都会构建完整的消息列表:

消息构建 Message Building

flowchart LR A["👤 用户输入"] --> D[Messages] B["🤖 System Prompt"] --> D C["💾 记忆"] --> D D --> E["📋 完整消息列表"] style A fill:#dbeafe,stroke:#2563eb style B fill:#ede9fe,stroke:#7c3aed style C fill:#d1fae5,stroke:#059669 style E fill:#fef3c7,stroke:#f59e0b

5.4 循环执行详解

第一步:检查预算

run_agent.py
async def run_conversation(self, new_message: str):
    # 检查迭代预算
    if not self.iteration_budget.use():
        return "错误:达到最大迭代次数"
    
    # 构建消息
    messages = self._build_messages(new_message)
    
    # 调用 LLM
    response = await self._call_llm(messages)
    
    # ...处理响应

第二步:调用 LLM

run_agent.py
async def _call_llm(self, messages: list[dict]) -> str:
    # 调用 OpenAI 兼容 API
    response = self.client.chat.completions.create(
        model=self.model,
        messages=messages,
        tools=self.tool_schemas,  # 工具定义
        **self._make_extra_kwargs()  # temperature, max_tokens 等
    )
    return response

第三步:解析工具调用

run_agent.py
def _parse_response(self, response) -> tuple[bool, list, str]:
    """解析 LLM 响应"""
    if hasattr(response.choices[0].message, 'tool_calls'):
        tool_calls = response.choices[0].message.tool_calls
        return (True, tool_calls, None)
    else:
        text = response.choices[0].message.content
        return (False, [], text)

5.5 工具执行流程

当 LLM 返回工具调用时:

工具执行 Tool Execution

flowchart TD A["🔧 tool_calls"] --> B["🔍 查找工具"] B --> C{"工具存在?"} C -->|存在| D["⚡ 调用工具"] C -->|不存在| E["❌ 返回错误"] D --> F["📝 记录结果"] F --> G["🔄 下一轮循环"] E --> G style D fill:#d1fae5,stroke:#059669 style E fill:#fee2e2,stroke:#ef4444
run_agent.py
async def _execute_tool_calls(self, tool_calls):
    results = []
    for tool_call in tool_calls:
        name = tool_call.function.name
        args = json.loads(tool_call.function.arguments)
        
        # 调用工具注册表执行
        result = await dispatch_tool_call(name, args)
        results.append(result)
    
    # 将工具结果添加到消息历史
    self.messages.append(...)

5.6 完整消息流程

一个完整的对话,消息列表如何演变:

第一轮
初始状态
messages = [system, memory, user]
第一轮结束
Tool Call
messages = [system, memory, user, assistant(tool_call), tool_result]
第二轮
继续思考
messages = [system, memory, user, assistant(tool_call), tool_result, assistant(tool_call_2), tool_result_2]
最终
完成
messages = [system, memory, user, ..., assistant(text)]

5.7 为什么需要循环?

看一个具体例子:用户说"帮我分析 GitHub 趋势"

1
搜索
调用 web_search
2
提取
调用 web_extract
3
分析
LLM 分析数据
4
输出
返回分析报告

这 4 步就是 4 次迭代。没有循环,就无法完成复杂任务。

5.8 易错点

⚠️ 无限循环

如果 Agent 反复调用同一个工具,可能是:

  • 工具返回的结果没有帮助
  • Prompt 没有告诉 Agent 什么时候停止
  • 工具本身有 bug

⚠️ 预算耗尽

如果达到最大迭代次数,检查:

  • 任务是否太复杂?
  • 工具是否正常工作?
  • 是否需要增加 max_iterations?

📚 本章总结

本章重点:

  • Agent 循环 = 思考 → 行动 → 观察 → 重复
  • 每次循环构建完整消息列表发给 LLM
  • IterationBudget 防止无限循环
  • 工具调用结果加入消息历史

一句话记忆点: Agent 循环就是"AI 的自我对话"——它不是一次问答,而是一场多轮对话

延伸思考

  1. 如果 LLM 返回了多个工具调用,Hermes 是串行还是并行执行?
  2. 如何让 Agent 在某些条件下提前结束,而不是等预算耗尽?
  3. 对比:ReAct、Plan-and-Execute、AutoGPT 的循环策略有什么不同?