Technical Guide

06. Agent 主循环:一次消息如何变成模型调用和工具调用

从 run_agent.py 和 agent/conversation_loop.py 看 Hermes 的核心执行链路。

这篇解决什么问题

Hermes 最核心的源码,不是某一个工具,也不是某个平台适配。

真正的主干是:一条用户消息进来后,Hermes 怎么构造上下文、调用模型、执行工具,再把工具结果送回模型。

这条链路看懂了,后面 Tools、Skills、Memory、Cron、Gateway 都会变清楚。

先看哪几个文件

核心文件是:

run_agent.py
agent/conversation_loop.py
agent/tool_executor.py
model_tools.py

当前源码里,agent/conversation_loop.py 文件开头已经说明:它是从 run_agent.AIAgent 里拆出来的主循环,run_agent.py 里仍保留薄转发和兼容入口。

也就是说:

run_agent.AIAgent.run_conversation
→ agent.conversation_loop.run_conversation

读主循环时,重点看 agent/conversation_loop.py

一轮对话的简化流程

可以先把它理解成这样:

1. 用户消息进入 AIAgent
2. 构建 turn context
3. 拼系统提示词、记忆、技能、项目规则
4. 计算 token 和上下文预算
5. 调用模型
6. 如果模型返回文本,结束本轮
7. 如果模型返回 tool_calls,执行工具
8. 把工具结果作为 tool message 追加回 messages
9. 再次调用模型
10. 直到模型给出最终回复或达到迭代上限

这就是 Agent loop 的基本形状。

为什么不是一次模型调用就结束

因为 Hermes 是 tool-calling agent。

模型可能会先说:我要调用 read_file。Hermes 执行文件读取,把结果塞回上下文。模型再根据文件内容决定下一步,可能继续调用 patchterminalweb_search

所以一次用户请求,内部可能对应多次模型调用和多次工具调用。

主循环里真正难的部分

主循环不只是 while loop。

它还要处理:

  • provider 报错和 failover;
  • 上下文过长;
  • 压缩;
  • 工具调用参数修复;
  • 用户中断;
  • usage 统计;
  • billing / entitlement 错误提示;
  • 多模态消息;
  • 记忆和 skill nudges;
  • 最大迭代次数。

所以 conversation_loop.py 很长。读的时候不要试图一次看完。

推荐读法

第一遍只找主干:

def run_conversation(...)
模型调用在哪里发生
tool_calls 在哪里被判断
工具执行在哪里被调用
工具结果如何追加回 messages
什么时候 return 最终回复

第二遍再看异常处理和上下文压缩。

第三遍才看 provider 特殊分支。

一个重要边界

Agent loop 不直接实现每个工具。

它只负责调度:

模型说要调什么工具
→ tool_executor 执行
→ model_tools / registry 找到 handler
→ 具体 tool 文件执行真实动作

这个边界很重要。否则你会在主循环里找不到 web_searchterminal 的具体实现。

如果你要改 Agent 行为

先判断你想改的是哪一层:

改模型调用策略        → conversation_loop / provider adapter
改系统提示词          → prompt_builder / system_prompt
改工具执行并发/展示    → tool_executor
改具体工具行为        → tools/<tool>.py
改工具是否暴露        → toolsets.py / registry / tool config

不要一上来就在 run_agent.py 里硬改。

下一篇看什么

下一篇看 Prompt 与上下文。

因为模型能不能做对事,很多时候不是模型能力问题,而是系统提示词、记忆、技能、项目规则和可用工具共同塑造出来的。