本文是「深挖 Hermes 源码」系列第一篇。从 CLI 入口到核心推理循环,完整拆解工业级 Agent 框架的 ReAct 实现。下期将深挖 Skill 系统设计原理,建议关注、收藏。
你是否也曾经用 LangChain、AutoGPT 这样的框架,手搓过各种 Agent 系统?
你可能试过构建 ReAct 框架来实现思维链推理,也许用过 Self-Reflection 来让 Agent 自我纠正,也许甚至探索过 Plan-and-Execute 或 Chain-of-Thought 这样复杂的模式。每一次都感到这些框架很复杂——能自己思考、自己规划、自己执行。
但接下来,你看到了 Hermes 这样的工业级 Agent 框架,运行得十分流畅高效。你不禁想问:Hermes 到底是如何实现这些 Agent 核心框架的?底层逻辑有多复杂?
今天,我要告诉你的真相是:所有的"复杂",其实源自一个看似简单的 while 循环。

我决定撰写一系列深挖 Hermes 源码的文章,逐步看懂工业级 Agent 框架 Hermes 的设计哲学和实现细节。从底层推理引擎到上层应用接口,每一层都值得我们仔细研究。
这是第一篇。我花了时间把 Hermes 的核心源码从头梳理了一遍,从 CLI 入口、Agent 初始化、到核心推理循环再到工具调用——整个链路都摸清楚了。让我为你拆解 Hermes 是怎么做到的。
第一步:用户在终端输入一句话
# cli.py(命令行入口)class HermesCLI: def run(self): while True: user_input = prompt(">> ") # 等待用户输入 self.process_command(user_input)用户在终端敲下:帮我总结一下 /Users/me/report.txt 里面的内容
这句话被 process_command() 接住。如果是斜杠命令(比如 /help),本地直接处理。如果是普通对话,就打包往下传给 AIAgent.chat()。
第二步:AIAgent 做个"翻译官"
# run_agent.py(AI Agent 主类)class AIAgent: def chat(self, message: str) -> str: """最简单的调用接口""" # 把用户的纯文本组装成标准格式 final_response = self.run_conversation(user_message=message) return final_responsechat() 的工作很简单:接收用户的一句话,组装成符合 OpenAI 标准的格式,然后丢给真正的处理引擎 run_conversation()。
第三步:心脏跳动 —— 开始循环(核心!)
现在到了最关键的部分。这就是 ReAct 的全部秘密:
# run_agent.py 的 run_conversation() 方法核心逻辑def run_conversation(self, user_message: str): messages = [ {"role": "system", "content": "你是个智能助手..."}, {"role": "user", "content": user_message} ] api_call_count = 0 # ========== ReAct 的 while 循环 ========== while api_call_count < self.max_iterations: # 最多90次 # 1. 请求大模型,带上聊天记录 + 可用工具列表 response = self.client.chat.completions.create( model="claude-opus", messages=messages, tools=get_tool_definitions(), max_tokens=2000 ) # 2. 看大模型的回答 # 情况A:大模型直接说话了,任务完成 if not response.tool_calls: return response.content # 循环结束 # 情况B:大模型要调用工具 tool_results = [] for tool_call in response.tool_calls: tool_name = tool_call.name tool_args = tool_call.arguments # 3. 在本地执行工具 result = handle_function_call(tool_name, tool_args) tool_results.append({ "tool_call_id": tool_call.id, "result": result }) # 4. 把工具执行结果塞回聊天记录 for result in tool_results: messages.append({ "role": "tool", "content": result["result"] }) api_call_count += 1 # 循环回到 while 开头,大模型继续思考...这就是 ReAct 的全部。 没有黑魔法,就是:
① 给大模型工具列表
② 大模型决定要不要用
③ 如果用,本地跑代码
④ 结果塞回去,大模型再看一遍
⑤ 重复,直到大模型说完
第四步:工具怎么执行?
当大模型输出工具调用的 JSON 时,谁来接住?
答案是 handle_function_call():
# model_tools.pydef handle_function_call(tool_name: str, args: dict, task_id=None): """工具路由器 - 根据工具名称,找到对应的函数执行""" # 从注册表里找对应的工具实现 tool = get_tool_from_registry(tool_name) if not tool: return json.dumps({"error": f"工具不存在: {tool_name}"}) try: result = tool["handler"](args, task_id=task_id) return json.dumps({"success": True, "data": result}) except Exception as e: return json.dumps({"error": str(e)})工具是怎么注册进来的?很简单,每个工具文件顶部都有一句:
# tools/file_tools.pyfrom tools.registry import registrydef read_file_impl(path: str) -> str: with open(path, 'r') as f: return f.read()# 注册这个工具registry.register( name="read_file", schema={ "name": "read_file", "description": "读取文件内容", "parameters": { "type": "object", "properties": { "path": {"type": "string", "description": "文件路径"} } } }, handler=lambda args, **kw: read_file_impl(args["path"]))就这么简单。大模型不用知道这个函数怎么实现的,它只知道:"我有个叫 read_file 的工具,我可以传一个文件路径给它。"

真实执行流程演示
用户问:总结一下 /Users/me/report.txt
循环第 1 轮
[用户输入] - [CLI] - [AIAgent.chat()] |[LLM 看到] messages + tools = [read_file, write_file, ...] |[LLM 思考] 用户让我总结文件,但我不知道文件内容。 我看到工具列表里有 read_file,那我先读一下吧。 |[LLM 输出] tool_calls: [{"name": "read_file", ...}] |[本地执行] 结果:"第一季度销售额增长 30%,其中..." |[更新 messages] 塞回工具结果 |api_call_count = 1,循环继续循环第 2 轮
[LLM 再次请求] messages 里多了工具返回的文件内容 |[LLM 思考] 现在我看到了文件内容,我可以直接总结了。 |[LLM 输出] content: "总结:贵公司第一季度销售额增长30%..." (这次没有 tool_calls,只有 content) |[检查] if not response.tool_calls: 是的! |[返回] return response.content循环结束为什么 run_agent.py 有 14000 行?
理论上,ReAct 这么简单,代码不应该这么多。但一旦上生产环境,事情就复杂了。除了常见的容错处理外,Hermes 还需要适配 OpenAI、Anthropic、Gemini、DeepSeek 等多种模型的 API 格式差异。
每个模型的接口、参数、返回格式都不同。Hermes 需要在底层做大量的适配代码,让上层逻辑无感知地支持所有这些模型。
具体来说,这 14000 行的开销包括:
1. 模型适配层 —— Claude 用 thinking_blocks,Gemini 用不同格式,需要统一提取推理过程
2. JSON 修复器 —— 大模型经常输出破损的 JSON(缺少双引号),代码得硬编码正则修复
3. 上下文压缩 —— 工具返回 10 万行日志时,不压缩下一轮 LLM 请求会 Token 超限
4. 死循环防护 —— 大模型可能一直调用错误的工具,最多 90 次强制终止
5. 并发执行 —— 多个工具可以并发执行(读 3 个文件同时进行),需要判断依赖关系
总结:ReAct 就是这么简单
写到这儿,你应该看出来了:
📌 ReAct 三步总结
① 思考(Reasoning) = 大模型看着工具列表,输出"我想用这个工具"
② 行动(Acting) = 本地代码找到对应的 Python 函数并执行
③ 循环 = 把执行结果塞回聊天记录,大模型再看一遍,接着想
没有什么黑魔法。所有的 AI Agent 框架,本质都是这个逻辑。
区别只在于:谁的 error handling 做得更硬核,谁的模型适配做得更全面,谁就能上生产环境不崩盘。

深度思考:为什么 ReAct 如此有效?
你可能会问:一个这么简单的框架,为什么能做到那么好的效果?
答案其实很简单:因为大模型本身能力提升了。
在过去的 Agent 框架时代,开发者们需要在框架代码里手搓大量的复杂逻辑:如何判断是否需要调用工具?手搓规则引擎。工具调用失败了怎么办?手搓重试逻辑。多个工具怎么组织执行顺序?手搓编排引擎。大模型的输出格式不对怎么办?手搓解析和修复。
这些都是框架级别的问题,占用了大量工程师的时间。
但现在不同了。 今天的大模型(Claude 3.5、GPT-4、Gemini 2.0)已经可以自己判断什么时候需要用工具、自己规划工具调用的顺序、自己从错误中学习和修正、自己按照格式输出工具调用。
这些原本需要框架处理的事情,现在大模型可以直接处理。所以 Hermes 的框架变得极其简单——它只需要做好"翻译"和"执行"这两件事,其他的都交给大模型。
这是 ReAct 框架仍然有效的根本原因。不是框架变得更聪明,而是大模型变得更聪明。框架反而可以变得更简单。
换句话说:Hermes 的优秀不在于框架的复杂度,而在于它充分信任大模型的能力,让大模型真正成为 Agent 的"脑子"。
这只是开始
好了,ReAct 的秘密我们都看透了。
下期,我将继续为你深挖 Hermes 是怎么支持 Skill 功能的。 你知道吗?Hermes 能让任何 Skill(脚本、代码、文档)自动被 Agent 理解和调用,这背后的机制是什么?它是如何做到像"插件系统"一样灵活的?
通过这一系列深挖 Hermes 源码的文章,我们将逐步看懂工业级 Agent 框架 Hermes 的设计哲学和实现细节。 从底层推理引擎,到中层技能管理,再到上层应用接口——每一层都值得我们仔细研究。

💬 关注熵息茶馆,回复"进群"加入交流群,一起探讨 AI Agent 开发技巧和 Hermes 源码。
如果你觉得这篇文章有收获,请点个赞和在看!
关注 熵息茶馆,我们下期继续深挖 Hermes!
干掉技术焦虑,看懂工业级 AI Agent。
夜雨聆风