第一次搭 Hermes Skill,我把工具调用想得太简单了
大概在两三周前,我试着用 Hermes 搭一个能自动整理简报的小助手,一开始想的很简单:让模型调用一个函数,把输入文本按固定格式切成几个字段,再输出成表格。写起来确实很快,一个函数体加一个 Decision 定义,注册进 Agent 就能跑。但真正开始跑之后,问题就来了:模型有时候根本不调用这个 Skill,有时候调用了却传错参数,有时候函数执行成功了但模型给出的最终回答完全不像预期。
这个过程的挫败感不在于“代码写不出来”,而在于你很难判断问题到底出在哪一层。
后来我把 Hermes 的整体机制重新梳理了一遍,才发现这个框架真正需要理解的地方不在“写函数”,而在“函数和模型之间如何衔接”。Hermes Skill 的搭建,本质上是把一段普通 Python 函数变成一个模型可感知、可调用、可反馈、可恢复的工程单元。它解决的并不是“少写几行代码”的问题,而是让 Agent 的工具调用从“碰运气”变成“可控流程”。
这篇文章,我想把这套思路完整写出来:从最小 Skill 的定义,到运行参数,到排查链路,再到工程化改造。不是为了介绍一遍文档,而是把我实际踩过的坑和验证过的顺序整理给你。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
1. 先搞清楚 Hermes Skill 到底在解决什么问题
1.1 从 Agent 框架中的“工具”概念说起
只要接触过 Agent 类框架,你一定会碰到一个核心问题:模型怎么知道什么时候该调用工具?什么时候该直接回答?
在普通函数调用里,是人来决定调用顺序。但在 Agent 场景里,模型可能在每个回合都要判断一次“现在是不是应该调用这个函数”。这个判断依赖的并不是函数本身有多强大,而是模型对这一组“决策信息”的理解。
Hermes 把这个环节拆得很清楚:Agent 运行循环里,模型看到的是系统的指令、对话历史、可用工具的描述,以及当前这一轮的输入。它需要根据这些信息产生一个结构化输出,这个输出里包含了“该调用哪个函数”和“传给函数的参数是什么”。工具只是模型决策后执行的一步。
所以 Hermes Skill 在这里面扮演的角色,更像是一个“经过封装的工具协议”:既有模型能理解的描述信息,又有 Python 侧能执行的函数体,还有错误处理、反馈、恢复机制。
理解了这一点,你就知道为什么给它起名叫“Skill”而不是简单叫“Tool”。Tool 强调的是一个可以执行的函数,Skill 强调的是模型可以信任并重复使用的一套能力。
1.2 Hermes 的 Skill 是怎么被模型感知的
在 Hermes 里,Skill 通常通过 Decision 结构暴露给模型。这个 Decision 里至少包含几个关键信息:
- 能力的名称,也就是模型在决策时会看到的标识。
- 一段功能描述,越具体越好,它决定了模型什么时候选择这个技能。
- 输入参数的模型结构,比如参数名、类型、是否必填、含义说明。
- 对应 Python 函数实现,真正跑业务逻辑的部分。
有一个点容易忽略:模型不会像人一样“读代码”,它看到的只是你给它的描述。你函数写得再漂亮,如果描述写得模糊,模型就可能在错误的时候调用它,甚至在应该调用的时候忽略它。
这也是为什么我在后面搭建时,把 Decision 里的描述当成核心配置来写,而不是随便填一句“通用工具函数”。
经验建议:你在写 Skill 描述的时候,可以把自己当成一个完全不了解内部实现的产品经理。描述里必须写清楚“这个方法适合什么场景”“输入有什么要求”“输出是什么样”。
1.3 Hermes Skill 和普通 Function Calling 的差别
很多人会问,既然主流模型都支持 Function Calling,为什么还要用 Hermes 自己定义的 Skill?
我的理解是,Function Calling 解决的是“模型能不能输出结构化调用指令”的问题,但 Hermes Skill 解决的是“调用之后整条链路如何处理”的问题。
在 Hermes 里,Skill 执行完毕后,结果会回到上下文中,模型会继续判断结果是否满足需求。如果执行出错,Feedback 机制可以把错误信息封装给模型,让它重新修正调用。如果出现异常,Recovery 流程还可以尝试恢复。
也就是说,你要对比的并不是“函数定义方式”,而是“工具调用的完整闭环”。Hermes 的价值在于它把决策、执行、反馈、恢复都纳入了同一个框架,而 Skill 就是这整个闭环里模型与外部能力交互的接口。
1.4 本文的主判断
在展开具体步骤前,我想先把整篇文章的核心观点说清楚:
Hermes Skill 真正提高效率的地方,并不是“让模型多了一个函数可以调”,而是把一次性的工具调用,变成了可描述、可验证、可反馈、可恢复的标准化流程。
如果你只是为了演示一个 Demo,那随便写个函数注册进去都能跑。但如果你想做一个能长期稳定运行的 Agent 应用,你必须在 Skill 的描述、参数结构、错误反馈、输入边界这几个维度上花力气。这篇文章后面所有内容,都是围绕这个判断展开的。
2. 搭建前先把环境与结构过一次
2.1 安装与依赖确认
Hermes 的安装方式在官方仓库里有明确说明,一般通过 pip 直接安装即可。我自己的建议是,在安装之前先确认几件事:
- Python 版本是否满足项目要求,不同分支可能对 Python 版本有不同要求。
- 是否要用到流式输出,如果需要,就要同时确认 SSE 相关接口。
- 是否要接 Azure 或本地模型端点,这会直接影响你初始化 Agent 时配置模型的方式。
- 是否用到长期记忆,如果用到,数据库和向量化相关依赖也要提前装好。
如果你只是在本地做功能验证,用默认配置跑通一遍并不难。但如果要放进生产项目,依赖版本、环境隔离、模型服务地址这些基础项,建议一开始就固定下来,不要每次在新机器上现试。
2.2 目录组织:Skill 不要全堆在一个文件里
我第一次搭的时候,把几个 Skill 直接写在一个文件里,因为总数少,跑起来没问题。但当你开始有十个、二十个 Skill 的时候,会明显发现这种写法非常影响排查效率。
我更建议按职责拆分:
text复制agent_project/
├── skills/
│ ├── __init__.py
│ ├── text/
│ │ ├── __init__.py
│ │ ├── decision.py
│ │ └── handler.py
│ └── file_utils/
│ ├── __init__.py
│ ├── decision.py
│ └── handler.py
├── agents/
│ ├── __init__.py
│ └── setup.py
└── configs/
└── config.yaml
这种结构的价值在于,一个 Skill 的所有相关代码都集中在一个目录里。你改参数结构时不会误动其他技能,别人接手项目时也能快速定位某一个能力在哪里定义。
2.3 运行参数的关键顺序
Hermes 在运行时有几个参数非常关键,但不一定需要从第一天就全部配置到位。我按照经验,建议按下面的顺序去确认:
- 第一步先确认模型接入和基础对话能跑通。
- 第二步注册一个最简单的 Skill,验证模型能否感知到它。
- 第三步再打开流式、记忆、反馈、恢复这些高级能力。
如果你一次性把所有开关都打开,遇到问题时很难分辨是模型配置的问题,还是 Skill 定义的问题。
3. 一个最小 Skill 的最小闭环
3.1 Decision 定义:给模型一份清晰的使用说明
我们用一个常见场景来演示:假设你要给模型一个“按字符合并文本片段”的能力,可以定义类似下面的结构(这里用通用写法,具体字段以你实际使用的版本为准):
python复制from pydantic import BaseModel
class MergeInput(BaseModel):
segments: str
separator: str = "\\n"
class MergeSkill:
function_name = "merge_text_segments"
function_description = (
"将多个文本片段合并为一个完整文本,"
"用于处理分块输出或分段整理场景。"
"输入 segments 是待合并的文本片段,separator 是连接符。"
)
input_args = MergeInput
这里重点不是代码本身,而是 function_description。模型是通过这段文字来判断什么时候用这个函数的。如果你写得含糊,模型很可能在需要合并文本时选择了其他方案。
3.2 实现函数体:输出类型要稳定
函数体本身可以是任何 Python 逻辑,但有一个原则值得记住:输出结构越稳定,模型就越容易理解结果。
python复制class MergeSkillHandler:
@staticmethod
def run(input_data: MergeInput) -> str:
seg_list = input_data.segments.split("\\n")
merged = input_data.separator.join([seg.strip() for seg in seg_list if seg.strip()])
return merged
我在实际使用时发现,如果 Skill 的返回结果是简单的字符串或 JSON,模型下一步处理会顺畅很多。如果你返回的是一个自定义对象,模型在理解输出时可能丢失细节,尤其是当输出比较长的时候。
3.3 注册进 Agent:把 Skill 挂到决策函数列表里
Hermes 的 Agent 在构建时需要接入多个组件,包括模型、用户输入处理、决策函数列表、反馈处理器、记忆缓冲区等。
一种常见的注册方式是:把 Decider(也就是模型决策函数)接收到的工具列表传入构建 Agent 的配置中,然后构造一个 Agent 实例:
python复制agent = Agent(
llm=model,
desicion_functions=[MergeSkill()],
default_memory_buffer=ShortTermBuffer(...),
)
这里的决策函数列表就是 Agent 能感知到的全部 Skill 集合。我建议最少先只注册一个,跑通之后再逐步添加。这样一旦行为异常,你可以更准确地判断是不是某一个 Skill 干扰了模型判断。
注意:如果你在同一个 Agent 里注册了很多功能类似的 Skill,模型很可能会用错。保持每个 Skill 的功能边界清晰,甚至可以在描述里写明它“不负责什么”,这样可以减少误调用。
3.4 最小闭环的验证方法
跑通最小闭环后,不要急着看结果。先做一个最简单的验证:
- 输入一句话,明确要求模型调用这个 Skill。
- 观察模型是否输出了对应的工具调用指令。
- 观察 Skill 是否被真正执行。
- 观察最终回复是否把这个函数的结果用上了。
如果这几个节点有一个断裂,说明链条还没完全打通。不要堆新功能,先把这条链路修好。
4. 从单任务跑通到批量可用:运行时参数才是分水岭
4.1 持续运行与流式反馈
如果用 Hermes 做演示,一次运行结束就退出也很正常。但要长期运行,至少要考虑持续运行和流式反馈。
Hermes 里有类似 run_continuously 这样的开关,用来决定 Agent 是否持续处理输入。如果你的场景是一次性问答,可以不开。如果要把它做成常驻服务,就需要打开持续运行模式,并配合超时和中断机制来避免线程一直挂住。
另一个关键项是流式反馈。开启后,Agent 运行过程会通过事件流逐步输出状态,比如调用了哪个 Skill、执行结果是什么、模型准备输出什么。这个在调试阶段尤其重要,因为它能让你直接看到 Agent 内部决策过程,而不是只看到最终结果。
4.2 短期记忆与长期记忆要分开对待
Hermes 使用默认的短期记忆缓冲区和外部记忆库来管理上下文。短期记忆默认内容较多时,会影响模型对当前任务的注意力。
举个例子:如果一个 Agent 连续处理了 50 轮对话,每轮的函数执行结果都被堆在上下文里,那么模型在第 51 轮做决策时,可能已经分不清哪些是历史记录,哪些是当前输入。
我建议短期记忆设置一个合理的交互窗口,比如只保留最近 5 到 10 轮关键信息,更早的信息交给长期记忆库去检索。这样既保留了连续性,又不会让每一步决策都拖着沉重的背景。
长期记忆如果开启了,要注意给记忆写入操作单独留出处理时间。尤其在批量任务里,短期记忆本身已足够工作时,长时间运行仍会不断增加记忆体量,建议定期对记忆缓冲做一次清理或归档。
4.3 反馈机制:错误不会直接结束,但会反馈给模型
Hermes 的一个特点是:当 Skill 执行出错时,错误信息不会直接中断,而是可以通过 Feedback 机制返回给模型。模型会看到“这个调用失败了,原因是……”然后决定是否需要重新调整参数,或是换一个 Skill。
这个机制非常实用,但需要你在 Skill 实现中做到两件事:
- 错误信息要具体,比如明确告诉模型“参数 separator 必须是字符串,不能为 None”。
- 不要在函数内部把所有异常都吞掉,否则反馈机制没有意义。
经验建议:写 Skill 时尽量让异常信息带有“可修正建议”。这样模型在下一轮反馈里更容易做出正确调整。
4.4 一个偏工程化的配置参考
下面是运行时可能用到的一组配置结构,供参考,不要直接照抄,因为不同版本字段名称会有差异:
python复制agent = Agent(
llm=model,
desicion_functions=[skill_a, skill_b],
default_memory_buffer=ShortTermBuffer(
max_tokens=3000,
default_message=...,
),
# 是否持续运行
run_continuously=True,
# 是否启用流式反馈
enable_streaming=True,
)
如果你发现模型调用 Skill 频繁失败,可以先关闭流式,把日志打全,逐条观察 Agent 内部输出;等到逻辑稳定后再开启流式做交互展示。这是一个非常实用的节奏控制方式。
5. 排查链路:Skill 没有被正确调用时,按这个顺序查
5.1 问题最常见在决策层,而不是执行层
如果模型该调用 Skill 却没调用,或者调用了错误的 Skill,绝大多数情况下问题出在决策层:模型“不知道”这个 Skill 存在,或者“不理解”它适合当前场景。
这时候要检查:
- Skill 是否已注册到 Agent 的决策函数列表里。
function_name是否与其他 Skill 重复。function_description是否清晰说明了触发场景。- 输入参数结构是否合理,模型能否理解每个字段的含义。
- 模型本身是否支持函数调用。
5.2 检查输入层:参数解析失败
如果模型确实发起了工具调用,但执行报错,先查参数解析。
常见情况包括:
- 必填参数缺失。
- 参数类型不匹配,比如模型传了字符串,函数却期望 List。
- 参数名不一致,比如 Decision 里定义的是
text_segments,函数实现里却用的是segments。
这个阶段最有效的办法是把模型的原始输出打印出来,直接看它传了什么参数。不要猜,Hero 体验就是一边改代码一边猜,效率非常低。
5.3 检查兼容层:新接口和旧配置混用
Hermes 的 API 迭代速度不慢,如果你在 GitHub 上看到的最新示例和自己安装的版本不一致,很容易出现“照着示例写却报错”的情况。
排查方式是:
- 先确认本地安装版本。
- 再对照官方仓库里对应版本 README 示例。
- 注意
desicion_functions这类字段在不同版本中可能的名称变化。
如果一个参数确实在当前版本里不存在,优先参考官方迁移说明,不要强行 import 旧模块。
5.4 检查运行层:执行超时、上下文截断、输出不可读
如果决策层和输入层都正常,但最终回复异常,问题可能出在运行层:
- Skill 执行时间过长,超出了模型等待时间。
- 返回值太长,超出了上下文窗口,结果被截断。
- 返回值类型过于复杂,模型无法从中提取有效信息。
- 模型的最终输出被记忆缓冲截断。
这一步可以从日志里看执行耗时和上下文 token 数,再决定是否缩短返回内容,或者拆成多个更小的 Skill。
5.5 排查优先级参考表
下面这个表格是我实际操作时用的优先级排序,适用于大多数“模型不调用 Skill”或“调用结果异常”的问题:
| 排查顺序 | 检查点 | 常见问题 |
|---|---|---|
| 1 | Skill 是否注册成功 | 漏注册、列表写错 |
| 2 | 描述是否足够清晰 | 描述太短、没有场景 |
| 3 | 参数结构是否可解析 | 类型错误、字段不匹配 |
| 4 | 函数内部是否异常 | 异常被吞掉、没有反馈 |
| 5 | 返回结果是否超出上下文 | 返回太长、信息被截断 |
| 6 | 模型配置是否正确 | 模型不支持工具调用 |
| 7 | 版本是否一致 | 新旧 API 混用、字段改名 |
如果你遇到问题,建议从上到下排查,不要一上来就怀疑模型能力。
6. 进阶:把 Skill 从 Demo 变成可维护模块
6.1 同步与异步:不是每个任务都要同步等结果
Hermes 提供了多种 Agent 类型,有些通过 run_workflow 一次性输出结果,有些支持持续交互。在真实应用里,我的建议是:
- 同步模式适合:用户发一个请求,返回最终回答。比如问答助手、内容生成。
- 异步模式适合:多个任务并行处理、批量内容处理、长时间运行的任务。
如果你要在异步模式中用 Skill,注意函数的线程安全性。不要在一个共享全局变量上同时写入,否则并发一高就会出现数据异常。
6.2 批量任务:先拆粒度,再做重试
批量任务往往不是“把一个 Skill 跑一千遍”,而是“对不同数据多次调用同一套流程”。这里有一个很关键的原则:
一次调用尽量只处理一个明确子任务。
不要为了省事,让一个 Skill 内部循环处理大量数据。虽然这样可以减少调用次数,但一旦出问题,很难判断是数据的问题,还是 Skill 逻辑的问题。
更好的做法是:
- 先用 1 到 2 条样例数据验证。
- 再把批次数从 3、5 逐步增加到 10。
- 观察内存、上下文和耗时变化。
- 出现问题后先固定能复现的最小样本,再排查。
6.3 日志、异常与参数校验,一个都不能少
这次搭建过程中我体会最深的一点是:如果只是写一个教学用 Skill,异常处理可以很随意。但如果你在做一个实际项目,就必须补上几个工程要素:
- 日志记录:每个 Skill 执行时,记录调用时间、输入摘要、输出摘要、耗时。
- 参数校验:在函数入口先做输入校验,避免模型传错参数时产生难以理解的报错。
- 异常分类:哪些错误是可修正的,比如参数类型错误;哪些是不可修正的,比如服务不可用。
- 可测试性:把 Skill 的核心逻辑抽成纯函数,方便单测,不依赖 Agent 环境。
这一步会明显增加代码量,但它是从“演示”走向“稳定运行”的必经环节。Hermes 本身提供了反馈与恢复机制,但你不能只依赖框架,自己的异常处理逻辑也要足够清晰。
建议:每个 Skill 的 handler 里都写一个
validate_input方法,把所有必填字段先检查一遍。模型偶尔会传默认值或 None,提前校验可以少很多麻烦。
6.4 组合 Skill:尽量不要让一个 Skill 依赖另一个 Skill 的内部细节
一个 Skill 也可以调用其他函数或服务,但要谨慎让一个 Skill 直接依赖另一个 Skill 的特殊返回结构。
比较好的方式是:Skill 之间通过标准数据格式进行传递,比如统一用 JSON 字符串或 Markdown 文本。这样某个 Skill 调整内部实现时,不会破坏其他 Skill 的调用链。
如果你需要做一个“多步骤任务”,比如先检索再总结,我更建议在 Agent 的层面编排,而不是把两个 Skill 的调用逻辑耦合在一起。Agent 的决策能力恰恰体现在它能根据中间结果决定下一步如何调用,这种编排价值比硬编码组合高得多。
6.5 工程化改造清单
我把上文提到的要素汇总成一份检查清单,适合你在正式上线前逐项核对:
- [ ] Skill 的描述是否包含触发场景和边界。
- [ ] 输入参数是否有校验逻辑。
- [ ] 函数返回类型是否稳定、简短、易理解。
- [ ] 异常信息是否能指导模型修正。
- [ ] 是否记录执行日志和耗时。
- [ ] 是否设计了重试策略。
- [ ] 是否合理设置上下文窗口长度。
- [ ] 是否对长期记忆做归档或清理。
- [ ] 是否限制 Skill 的并发度。
- [ ] 是否在本地用小样本回归测试。
如果这些项你都处理了,你的 Hermes Skill 就不再是单个脚本里的临时函数,而是一个可以交给其他人维护的模块化能力。
7. 适用边界:哪些场景适合 Hermes Skill,哪些不该用
7.1 适合什么人、什么场景
Hermes Skill 最适合的场景,通常要满足这几个条件:
- 你需要让模型根据用户输入动态选择调用什么能力。
- 你希望工具调用失败后,模型能基于反馈修正而不是直接崩溃。
- 你希望把工具能力的“描述”和“实现”分离,便于扩展。
- 你正在做的是多轮交互式 Agent,而不是单轮的规则脚本。
从这个角度讲,它的适用人群比较明确:有一定 Python 基础,理解模型与工具间的协作关系,愿意花时间调试边界条件的开发者。
7.2 不适合什么人、什么场景
有些场景我不建议硬上 Hermes Skill:
- 只是做简单自动化脚本,不涉及模型决策,直接用普通 Python 函数更简单。
- 对实时性要求极高,且逻辑完全固定,不需要模型判断策略,用规则系统更可靠。
- 团队里没有人愿意维护模型决策的边界问题,这类 Agent 系统需要长期调优。
- 任务本身不需要工具调用,只是文本生成,引入 Skill 反而增加复杂度。
还有一个很实际的问题:如果你使用的模型不具备可靠的结构化输出能力,Skill 的建立并不会让它“突然变聪明”。模型本身的工具调用能力决定了 Skill 能不能被正确触发。这一点,框架解决不了,只能靠选模型和调配置来解决。
7.3 长期使用时的判断标准
如果你问我自己怎么判断这一套方案是否值得继续投入,我一般看三个信号:
- 新增一个 Skill 的成本是否在下降。如果每加一个都要改框架代码,说明抽象层还不够好。
- 调试一个失败调用能否快速定位。如果能通过日志和反馈信息快速修正,说明工程基础到位了。
- 模型误调用率是否在可接受范围。如果经常在不需要工具时调用工具,描述和参数设计就要重新审视。
这三个信号比任何性能指标都更能反映项目的健康度。
最后说几句实际操作层面的经验
我重新整理 Hermes Skill 的过程里,最有价值的一步,其实不是写代码,而是先想清楚“模型要怎样才能理解并信任这个工具”。
很多文章会直接给你一个完整配置,让你照着跑通。但跑通只是第一步。真正拉开差距的,是在后续使用中你能否稳定地处理以下三类问题:模型什么时候调用、怎么传参数、出错之后怎么修正。
如果你正准备开始搭建 Hermes Skill,我的建议是从一个不超过 50 行代码的最小 Skill 入手,完整走一遍“定义 Decision -> 实现函数体 -> 注册进 Agent -> 验证调用 -> 调试异常 -> 添加反馈”这个流程。中间不要贪多,不要同时注册一堆技能。
先把一个 Skill 的气候打通,再慢慢扩展。这个项目最有意思的地方,也恰恰在这里:当你把一个个离散的工具变成模型能主动调用的能力时,你能明显感觉到,Agent 不再只是“能聊天”,而是开始真正“能干活”了。
