当 AI 长出了手脚:深度解析 Hermes Agent 的「工具调用宇宙」
如果说大语言模型是一个满腹经纶却四肢不协调的学者,那么 Hermes Agent 就是给他配备了机械臂、望远镜、键盘和一张全球通票——而且还装了个"防止他把自己搞崩溃"的安全气囊。
一、开篇:为什么你需要了解 Hermes Agent
2024 年以来,"AI Agent"这个词的出镜率比任何流量明星都高。各路大厂、开源社区都在喊:「我们的 Agent 能自主完成任务!」但真正打开代码一看,很多不过是用 LangChain 套了几个函数调用,美其名曰"智能体"。
Nous Research 的 Hermes Agent 不一样。这是一个从生产级多平台部署出发设计的完整 AI 代理框架,代码量超过 15 万行,支持从命令行、Telegram、Discord、Slack、飞书到微信、DingTalk 等十余种消息平台,内置工具数量超过 70 个,还有一套精密的错误恢复、上下文压缩和插件机制。
更难得的是,整个系统的设计理念非常清晰:**让模型尽量专注"思考",让框架负责"手脚协调"**。本文将带你深入其架构核心,一探这套"让 AI 长出手脚"的工程哲学。
二、项目全景:一张地图先看懂
在钻进代码细节之前,先把 Hermes Agent 的整体版图摆出来:
用户层
├── CLI (交互式终端) ← cli.py / hermes_cli/
├── TUI (Ink 终端 UI) ← ui-tui/ + tui_gateway/
├── 消息网关 (多平台) ← gateway/
└── ACP Server (IDE 插件) ← acp_adapter/
核心引擎层
├── AIAgent (对话主循环) ← run_agent.py
├── 工具系统 ← model_tools.py + tools/
└── Agent 内核 ← agent/
基础设施层
├── 工具注册表 ← tools/registry.py
├── 工具集定义 ← toolsets.py
├── 会话存储 (SQLite FTS5) ← hermes_state.py
└── 插件系统 ← hermes_cli/plugins.py
这个架构有一个显著特点:分层清晰,依赖单向。tools/registry.py 没有任何上层依赖,工具模块只依赖注册表,model_tools.py 负责发现和调度,run_agent.py 在最上面消费一切。这种设计使得任何一层都可以独立测试,任何一个工具的改动都不会在项目里引发蝴蝶效应。
三、大脑:AIAgent 的对话主循环
3.1 一切的起点
run_agent.py 里的 AIAgent 类是整个项目的大脑,足足有 15,000+ 行代码。别被这个数字吓到,核心循环其实逻辑非常清晰:
# 极简版伪代码,展示核心逻辑
while iterations < max_iterations and budget.remaining > 0:
if interrupt_requested:
break
# 调用 LLM
response = llm_client.chat.completions.create(
model=model, messages=messages, tools=tool_schemas
)
if response.tool_calls:
# 执行工具,把结果追加到对话
for tool_call in response.tool_calls:
result = handle_function_call(tool_call.name, tool_call.args)
messages.append(tool_result_message(result))
iterations += 1
else:
# 模型不再调用工具,返回最终答案
return response.content
这个循环有几个关键设计决策值得展开讲。
3.2 预算管理:防止 AI "无限刷卡"
IterationBudget 是一个线程安全的迭代计数器,初始化时设置最大迭代次数(默认 90 次):
class IterationBudget:
def consume(self) -> bool:
"""尝试消耗一次迭代,返回是否允许继续"""
with self._lock:
if self._used >= self.max_total:
return False
self._used += 1
return True
def refund(self) -> None:
"""退还一次迭代(用于 execute_code 这类不消耗预算的调用)"""
with self._lock:
if self._used > 0:
self._used -= 1
这里有个有趣的细节:execute_code 工具(让模型写 Python 脚本批量调用其他工具)调用完后会 退还 本次迭代预算。为什么?因为 execute_code 的目的是减少 LLM 轮次,如果反而消耗预算,就本末倒置了。
父 Agent 最大 90 次,每个子 Agent 独立最大 50 次——这样既防止了无限循环,又让复杂的委托任务有足够的空间完成。
3.3 并行工具调用:让 AI 学会"同时做多件事"
当 LLM 一次性返回多个工具调用时,Hermes 会智能判断能否并行执行:
_PARALLEL_SAFE_TOOLS = frozenset({
"read_file", "search_files", "web_search", "web_extract",
"vision_analyze", "session_search", ...
})
_PATH_SCOPED_TOOLS = frozenset({"read_file", "write_file", "patch"})
_NEVER_PARALLEL_TOOLS = frozenset({"clarify"}) # 需要用户交互,必须串行
对于路径相关的工具,系统会额外检查两个操作是否针对不同路径——如果都要读同一个文件,自然没必要并行,而且可能会出问题。这个判断逻辑虽然朴素,但在工程实践中足够实用。
最大并发工作线程数限制在 8 个,避免线程爆炸。
3.4 "Grace Call":给 AI 最后一次发言机会
有个不起眼但很贴心的细节:当预算用完时,系统会给模型一次 额外的最终调用机会(_budget_grace_call),但这次不允许再使用工具,只能输出文字。这样即使任务没完全完成,模型也能礼貌地告知用户当前进度,而不是突然断电般消失。
四、手脚:工具系统的精妙设计
4.1 注册表模式:自动发现,无需手动维护
工具系统的核心是 tools/registry.py 里的 ToolRegistry 单例。每个工具文件在模块顶层调用 registry.register(),在 Python 导入时自动完成注册:
# tools/web_tool.py 示例(简化)
from tools.registry import registry
registry.register(
name="web_search",
toolset="web",
schema={
"name": "web_search",
"description": "Search the web for information",
"parameters": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"]
}
},
handler=lambda args, **kw: web_search(query=args["query"]),
check_fn=lambda: True, # 总是可用
)
注册表通过 AST 分析(而非执行)来检测哪些文件包含 registry.register() 调用,再按需导入:
def _module_registers_tools(module_path: Path) -> bool:
"""用 AST 扫描而非执行,安全高效"""
source = module_path.read_text(encoding="utf-8")
tree = ast.parse(source)
return any(_is_registry_register_call(stmt) for stmt in tree.body)
这样,添加一个新工具只需:
-
创建
tools/your_tool.py并调用registry.register() -
把工具名加入
toolsets.py里的某个工具集
不需要修改任何主程序文件。这是教科书级别的开闭原则实践。
4.2 工具集:分层的权限管理
toolsets.py 定义了一套工具集体系,核心是 _HERMES_CORE_TOOLS 列表——这是默认给所有平台的基础工具包,包含:
-
网络:
web_search、web_extract -
终端:
terminal、process -
文件:
read_file、write_file、patch、search_files -
浏览器自动化:
browser_navigate、browser_click、browser_type等 12 个 -
内存与规划:
todo、memory -
委托:
delegate_task(启动子 Agent) -
智能家居:
ha_*(Home Assistant 全套,条件可用) -
多 Agent 协调:
kanban_*(看板任务系统)
每个工具支持 check_fn 做可用性检查。比如 Home Assistant 工具只在设置了 HASS_TOKEN 环境变量时才出现在工具列表里,避免模型在没有 HA 的环境中乱用。
4.3 防止 AI "犯强迫症":工具护栏系统
agent/tool_guardrails.py 是一个完全无副作用的纯函数护栏控制器,专门对付 AI 的"重复行为":
| 情况 | 警告阈值 | 阻断阈值 |
|---|---|---|
| 完全相同的调用失败 | 2次 | 5次 |
| 同一工具持续失败 | 3次 | 8次 |
| 幂等工具无进展 | 2次 | 5次 |
这套系统能识别两类问题:
-
精确循环:模型用相同参数反复调用同一个工具(通常意味着卡住了)
-
无效探索:反复读同一个文件但每次都没推进对话(原地打转)
护栏控制器只返回"建议",不直接执行。真正的决策权在 run_agent.py 里,这保证了护栏逻辑的可测试性——你可以独立对 ToolCallGuardrailController 写单元测试,完全不需要启动 LLM。
五、记忆:跨会话持久化的艺术
5.1 上下文压缩:不得不说的工程难题
长对话是 AI 代理最头疼的问题之一。上下文超出限制怎么办?直接截断会让模型失去前文——就像让人从第200页开始读一本书。
Hermes 的 ContextCompressor 用一个辅助的小模型(便宜+快速)来总结中间的对话轮次,同时保护头尾。总结有精确的 token 预算控制:
_MIN_SUMMARY_TOKENS = 2000 # 总结最少要这么详细
_SUMMARY_RATIO = 0.20 # 总结占压缩内容的20%
_SUMMARY_TOKENS_CEILING = 12_000 # 再长也不超过这个
总结注入时会有明确的前缀标记:
[CONTEXT COMPACTION — REFERENCE ONLY] Earlier turns were compacted... Your current task is identified in the '## Active Task' section...
这个前缀非常关键:它告诉模型"这是历史摘要,不是新指令,请不要重复做已完成的事"。这个细节展现了工程师对 LLM 行为模式的深刻理解。
5.2 记忆管理:记住什么,忘记什么
agent/prompt_builder.py 里有一段对记忆系统的精妙指导,值得原文引用:
"Do NOT save task progress, session outcomes, completed-work logs, or temporary TODO state to memory... If a fact will be stale in a week, it does not belong in memory."
"Write memories as declarative facts, not instructions to yourself. 'User prefers concise responses' ✓ — 'Always respond concisely' ✗"
这不只是工程约束,更是一种认知架构哲学:记忆应该是关于"世界是什么样"的事实,而非"我该怎么做"的指令。指令式记忆在后续会话中会被模型错误地当作系统指令执行,造成行为漂移。
5.3 提示词注入防护
prompt_builder.py 里还有一个默默保护用户安全的扫描器,在加载 AGENTS.md、SOUL.md 等上下文文件前,会检测可能的 prompt 注入攻击:
_CONTEXT_THREAT_PATTERNS = [
(r'ignore\s+(previous|all|above|prior)\s+instructions', "prompt_injection"),
(r'do\s+not\s+tell\s+the\s+user', "deception_hide"),
(r'curl\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)', "exfil_curl"),
(r'cat\s+[^\n]*(\.env|credentials|\.netrc|\.pgpass)', "read_secrets"),
# ... 更多模式
]
如果文件里藏着试图让 AI 泄露密钥或覆盖指令的恶意文本,系统会直接拦截并记录日志。在多平台网关场景下,这个保护尤其重要——你的 Telegram 机器人读取的是网络上的文档,随时可能遭遇"越狱陷阱"。
六、消息网关:把 AI 送进每个聊天软件
6.1 平台支持的广度令人叹为观止
Gateway 层支持的平台数量,看了真让人感叹开源社区的力量:
| 类别 | 平台 |
|---|---|
| 国际社交 | Telegram、Discord、Slack、Signal、Matrix、Mattermost |
| 国内平台 | 微信(Weixin)、企业微信(WeCom)、飞书(Feishu)、钉钉(DingTalk)、QQ机器人 |
| 特殊用途 | Home Assistant、Email、SMS、BlueBubbles(iMessage)、Webhook、API Server |
| 内部平台 | 元宝(YuanBao) |
每个平台适配器继承自 gateway/platforms/base.py,实现统一的接口。这意味着核心 Agent 逻辑完全不感知自己是在回复 Telegram 消息还是企业微信消息——完美的抽象。
6.2 会话缓存:长驻 Gateway 的内存管理
Gateway 是个长驻进程,同时服务多个用户/频道。每个会话对应一个 AIAgent 实例(包含 LLM 客户端、工具 Schema、记忆提供者等),所以需要精心的缓存管理:
_AGENT_CACHE_MAX_SIZE = 128 # 最多缓存128个活跃会话
_AGENT_CACHE_IDLE_TTL_SECS = 3600 # 1小时无活动就驱逐
LRU 驱逐 + 空闲 TTL,避免长期运行的网关吃光内存。
七、错误韧性:当一切都在出错时
7.1 错误分类与分级响应
agent/error_classifier.py 定义了一套 API 错误分类体系,将各种奇形怪状的错误归类为标准的 FailoverReason:
auth → 刷新 token / 轮换凭证
billing → 立即切换到下一个 API key
rate_limit → 退避等待,然后轮换
context_overflow → 压缩上下文,不是 failover
timeout → 重建客户端,重试
model_not_found → 降级到备用模型
注意 context_overflow 的处理方式:不是换一个 API key,而是压缩上下文再重试。分类器理解了不同错误的本质原因,而不是一律"换个密钥试试"。
7.2 凭证池:多 Key 自动轮换
agent/credential_pool.py 实现了生产级的凭证管理:
-
策略:
fill_first(用满再换)、round_robin(轮询)、random(随机)、least_used(最少使用) -
状态:每个 key 独立追踪是否被限速(429)、余额耗尽(402)、认证失败(401)
-
冷却时间:认证失败冷却 5 分钟,限速/余额问题冷却 1 小时
-
Provider 级:支持的 reset_at 时间戳会精确覆盖默认冷却时间
对于需要部署多个 API key 以提高吞吐量的生产场景,这套机制即插即用。
7.3 "安全写":防止破管道崩溃
有个藏在底层却至关重要的小设计:_SafeWriter——一个 sys.stdout/sys.stderr 的透明代理:
class _SafeWriter:
def write(self, data):
try:
return self._inner.write(data)
except (OSError, ValueError):
# 管道破了?没关系,静默失败,不崩溃
return len(data) if isinstance(data, str) else 0
当 Hermes 运行在 Docker 容器、systemd 服务或无头守护进程中,stdout 管道可能随时断开。没有这个包装,任何一个 print() 都可能让整个 Agent 崩溃。这种防御性编程在生产环境中才能体会到它的价值——等到线上崩了再加,已经晚了。
八、插件系统:让 Hermes 长出无限可能
8.1 四个插件来源
插件系统支持从四处发现插件:
-
仓库内置:
plugins/<name>/(随项目发布) -
用户插件:
~/.hermes/plugins/<name>/(个人定制) -
项目插件:
./.hermes/plugins/<name>/(项目级,需开关) -
pip 包:通过
hermes_agent.pluginsentry-point 分发
后来的覆盖先来的——你的个人插件可以完全替换内置插件的同名版本。
8.2 钩子系统:无侵入式扩展
插件可以注册以下生命周期钩子:
-
pre_tool_call/post_tool_call:工具调用前后 -
pre_llm_call/post_llm_call:LLM 调用前后 -
on_session_start/on_session_end:会话开始/结束
想加一个"所有工具调用都记录到你的数据库"的可观测性插件?三行代码注册一个 post_tool_call 钩子就够了,完全不需要动主程序。
8.3 记忆提供者插件
记忆系统是独立的插件体系,支持多个后端:honcho、mem0、supermemory、byterover、hindsight 等。每个实现 MemoryProvider ABC,提供标准的 sync_turn()、prefetch() 和 shutdown() 接口。
有趣的限制:只允许同时激活一个外部记忆提供者。这是有意为之——防止多个记忆后端向 LLM 的上下文窗口注入重复或冲突的信息,导致 schema 膨胀和行为混乱。
九、皮肤引擎:认真对待"颜值"
不少严肃的工程师可能觉得 CLI 的外观不重要,但 Hermes 有一套完整的皮肤引擎 hermes_cli/skin_engine.py——纯数据驱动,无需改代码就能定制整个界面风格。
内置四套皮肤:
-
default:经典 Hermes 金/卡哇伊风格 -
ares:深红战神主题,带自定义旋转翅膀 -
mono:简洁灰度单色 -
slate:冷蓝开发者风格
用户可以在 ~/.hermes/skins/ 放置 YAML 文件定制一切:颜色、加载动画表情、旋转动词("forging"、"plotting"...)、工具前缀符号、品牌名称……
# cyberpunk.yaml
name: cyberpunk
colors:
banner_border: "#FF00FF"
banner_title: "#00FFFF"
spinner:
thinking_verbs: ["jacking in", "decrypting", "uploading"]
branding:
agent_name: "Cyber Agent"
/skin cyberpunk 即时生效。这不是炫技,这是让工具成为工具使用者自己的一部分——当你的 AI 助手有着你自己定制的外观,你和它的关系就不一样了。
十、TUI 与 Dashboard:浏览器里跑终端
Hermes 有一个基于 Ink(React for terminal)的现代 TUI,通过 hermes --tui 启动。更有意思的是 hermes dashboard 命令——它会启动一个本地 Web 服务,在浏览器里嵌入真实的 hermes --tui 进程,通过 xterm.js 渲染。
关键设计决策在于:Dashboard 嵌入的不是一个 Web 版仿品,而是真实的终端进程。前后端通过 WebSocket PTY 桥接(ptyprocess on POSIX)。
AGENTS.md 里有一段对这个架构的警告,语气相当严肃:
"Do not re-implement the primary chat experience in React. The main transcript, composer/input flow... belong to the embedded hermes --tui — anything new you add to Ink shows up in the dashboard automatically. If you find yourself rebuilding the transcript or composer for the dashboard, stop and extend Ink instead."
这体现了一种工程纪律:不要因为"我在做 Web 前端"就去重新实现已经有的东西,应当找到正确的抽象层次去扩展。
十一、实际应用场景
场景一:个人全能助理
配置 Telegram Bot,Hermes Agent 立刻变成你的私人助理:
-
早上问今天的新闻摘要 →
web_search+ 总结 -
让它监控某个网页变化 →
cronjob定时任务 -
远程执行服务器命令 →
terminal+ 安全审批 -
告诉它你的习惯 →
memory持久化偏好
场景二:团队协作自动化
接入企业微信或飞书,让 Hermes 参与团队日常:
-
用 Kanban 工具集拆解复杂项目到多个 Worker Agent 并行完成
-
send_message工具让 Agent 在完成任务后主动通知相关人员 -
session_search让 Agent 记住上次讨论的技术决策
场景三:开发流水线助手
结合代码仓库,Hermes 可以:
-
读文件 → 分析代码 → 写文件 → 运行测试(一条龙)
-
delegate_task让主 Agent 把子任务分配给多个并行的子 Agent -
skill_manage让 Agent 把学到的调试技巧存成可复用的"技能文档"
场景四:智能家居中枢
接入 Home Assistant,Hermes 变成能理解自然语言的智能家居控制器:
-
"开灯" →
ha_call_service -
"现在客厅温度多少" →
ha_get_state -
"每天晚上十点把卧室灯调暗" →
cronjob+ha_call_service
十二、未来展望:架构蕴含的可能性
强化学习集成
项目里有一个 environments/ 目录,包含针对 Atropos 训练框架的 RL 环境实现。这意味着 Hermes Agent 正在探索通过实际任务执行来优化 Agent 行为的路径——不只是让 LLM 更强,而是让整个 Agent 循环更高效。
多 Agent 协作升级
现有的 Kanban 任务系统已经支持多个 Worker Agent 并行处理看板任务,并通过 SQLite 共享状态。这套机制的边界远未触到天花板:更复杂的任务依赖图、动态 Agent 生成、跨机器的分布式执行……
模型无关性的价值
目前已支持 OpenAI、Anthropic、Google Gemini、AWS Bedrock、Moonshot、本地 Ollama 等众多提供商,凭证池让多 Key 轮换无缝衔接。随着 AI 模型市场持续演化,这种提供商无关性会变得越来越值钱。
十三、总结:工程艺术与哲学的交汇点
读完 Hermes Agent 的代码,有几点感悟挥之不去:
一是"防御性"贯穿始终。 从 _SafeWriter 防管道崩溃,到护栏系统防模型无限循环,再到 prompt 注入扫描,处处体现出"假设一切都会出错"的工程心态。这不是悲观主义,这是经验。
二是"抽象边界"的坚守。 工具注册表不知道 Agent 的存在,Agent 不知道具体跑在哪个消息平台上,TUI 的 Ink 层不知道 Python 的业务逻辑——每一层都在做好自己的事,通过清晰的接口对话。
三是"数据驱动 vs 代码驱动"的智慧选择。 皮肤系统、工具集定义、命令注册表都选择了数据驱动,让扩展成本降为零。而核心的 Agent 循环、错误恢复逻辑则坚守代码,保持精确控制。
四是对人机交互的尊重。 无论是 clarify 工具让 AI 在不确定时主动问用户,还是 _budget_grace_call 让 AI 在预算耗尽时体面地道别,都体现了"AI 是来辅助人的,不是来替代人判断的"这一根本立场。
Hermes Agent 不是一个"玩具级"的 AI 代理演示,它是一个认真思考过生产部署、安全边界、可扩展性和用户体验的系统。无论你是想学习 Agent 架构设计,还是直接把它当作自己的 AI 基础设施,都值得深入研究。
项目地址:Hermes Agent - Nous Research
官方文档:https://hermes-agent.nousresearch.com/docs
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)