你每天做的最多的工作,是不是那种“不复杂但特别固定”的事情:把一堆聊天记录整理成会议纪要,把后台数据导出来改成固定格式的日报,把零散的产品反馈归类成需求清单。每次做这些事,你都得打开聊天窗口,重新描述需求、贴范例、强调输出格式。Agent 一开始确实能听懂,但它记不住,你教完这次,下次还得从头教一遍。
后来我接触到了 Hermes 这个智能体项目,才找到一个更顺手的解法:把重复工作流程保存为自定义技能。说白了,就是给 Agent 打造一个“专属工具箱”,你只要做一次技能封装,之后它就能自己判断什么时候该用、怎么用,输出格式稳定,还能自动调用外部工具。这篇文章我会从环境部署讲到技能原理,再带你完整创建一个自定义技能,最后把我在实际操作中踩过的坑和排查思路一并整理出来。适合刚接触 Agent 开发、或者已经在用类 Agent 工具但觉得“每次都重新写提示词太麻烦”的人。
1. 为什么 Agent 需要自定义技能
1.1 重复工作流程的痛点:固定指令模板不够用
先回到最基础的场景。大部分 AI 工具都支持“自定义指令”或者“预设 Prompt”,你可以把一段固定指令模板保存下来,下次复制粘贴就行。这个方法确实解决了一部分问题,但它有几个明显的短板。
第一个短板是触发靠人,不靠语义。固定指令模板放在笔记软件里,每次要用还得自己想起来翻,Agent 不会根据你说话的意图自动匹配这段模板。第二个短板是完全没有参数化能力。同样是“生成周报”,本周和上周的时间范围不一样,本周的原始材料可能是一段聊天记录,也可能是表格文件,固定指令模板没有办法把这些变量拆出来。第三个短板更实际——它调用不了工具。复杂一点的重复流程往往不只是“写一段话”那么简单,可能还要查数据库、跑一个 RPA 脚本、读取某个文件夹里的最新文件。固定提示词本质上是文本,它不具备执行能力。
我自己的习惯是把这些重复工作流程先列一个清单,标出频率、耗时、输出格式是否固定,只要三项都满足,就值得做成一个技能。这个思路不仅适用于 Hermes,也适用于任何支持技能机制的 Agent 项目。
1.2 技能与普通指令模板的根本区别
很多人第一次听说“自定义技能”这个词,会误以为它就是“高级版预设提示词”。我在实际使用中的体感差距非常大,技能和指令模板最核心的区别有三点。
第一,技能有结构化元信息。Hermes 的技能通常由一个描述文件、一个提示词模板和若干个执行脚本组成。描述文件里写着这个技能叫什么、什么时候触发、需要哪些参数,这些信息会被加载进 Agent 的上下文,让模型自主判断是否调用。第二,技能能真正操作外部世界。它不只是输出文本,还可以调用内置工具、执行 Python 脚本、触发浏览器自动化流程、读写文件。也就是说,技能可以完成“取数—处理—生成—发送”这条完整链路。第三,技能是可持续迭代和组合的。一个技能可以被其他技能调用,你可以像拼积木一样搭出更复杂的工作流。
用一个生活化的类比,固定指令模板是备忘录,技能是插件。备忘录提醒你做什么,插件直接替你把事情办了。你给 Agent 装上一个“周报技能”,它就能在你说“写本周周报”的时候,自动打开上一步生成的工作记录、按模板整理成四段式结构、最后输出一份可以直接粘贴到邮件里的版本。
1.3 什么时候该做技能,什么时候不该做
不是什么东西都适合封装成技能,我在最开始也走过弯路,什么事都想做成技能,结果维护成本比手动做还高。后来总结出几个判断标准,你可以在动手之前先过一遍。
适合做技能的场景有三个特征:高频重复,每周至少用一次,甚至每天都用;输出边界清晰,结果有固定格式或者明确结构;流程可被显式描述,你能把每一步列出来,写成人能看懂的规则。比如日报生成、邮件分类、订单异常排查、多系统数据汇总,都很合适。
不适合做技能的场景也有三个特征:每次需求差异太大,比如写一篇广告创意文案,虽然高频,但每次方向、人群、风格完全不同,技能反而会限制发挥;主观判断占比过高,比如面试评估、用户访谈分析,需要大量上下文理解,不适合做成固定模板;输出不可验证,如果技能跑完你都不知道结果对不对,那就先别自动化。记住一个原则:技能是给“稳定流程”用的,不是给“灵感创作”用的。
2. Hermes 环境准备:本地部署与配置
2.1 部署形态怎么选:Docker、Python 环境还是桌面版
Hermes 这类智能体项目的部署方式一般有三种:源码 Python 环境运行、Docker 容器化运行、以及图形化的桌面版本。我在不同机器上的使用体验可以给你做参考。
如果你的电脑是 Windows,并且不想折腾依赖冲突,我建议优先考虑 Docker Desktop。好处是环境隔离,项目自带的依赖、Python 版本、模型配置都封装在镜像里,不会污染系统环境。坏处是第一次拉镜像比较慢,而且 Docker Desktop 本身对内存有要求。
如果你在 Linux 服务器上跑,直接用 Python 虚拟环境更轻量,启动速度也更快。我自己生产的做法是:日常调试用 Python 虚拟环境,部署到长期运行的服务器上时用 Docker Compose。至于桌面版,它适合完全不碰命令行的用户,但做技能开发时不方便看日志和改配置,所以我不太推荐重度开发者依赖桌面版。
2.2 Windows 系统部署 Hermes 的具体步骤
从下载到配置跑通,我整理了一份可以直接照着做的流程。第一步是准备基础环境:安装 Python 3.10 或更高版本,并且勾选“Add Python to PATH”;如果选择 Docker 路线,安装 Docker Desktop 并启用 WSL2 后端。这一步里最容易踩的坑是 Python 版本太低,Hermes 的依赖里有不少库对 3.9 以下的版本已经不作兼容了。
第二步是克隆或下载项目仓库,建议放到一个路径里没有中文和空格的纯英文目录下,比如D:\dev\hermes。我自己第一次部署就是因为文件夹叫“我的项目”,导致不少脚本报编码错误,折腾了大半天。随后创建虚拟环境:
cd D:\dev\hermes python -m venv venv venv\Scripts\activate pip install -r requirements.txt第三步是配置模型服务。Hermes 本身是一个 Agent 框架,它需要接一个底层的大模型来完成文本理解。你在配置里需要指定模型的服务地址、API Key 和模型名称。很多人在这一步卡住的原因是搞不清楚“模型服务”和“Hermes”的分工:Hermes 负责调度、技能加载、工具调用,模型负责理解语言和生成回复。可以简单理解成 Hermes 是驾驶系统,大模型是引擎。
# config.yaml 示例片段 model: provider: openai_compatible base_url: "http://127.0.0.1:8000/v1" api_key: "sk-local-test" name: "your-model-name" temperature: 0.3这里我建议显式设置temperature: 0.3或更低,因为技能执行场景需要稳定的输出,温度越高越容易跑偏。
2.3 验证安装:跑通一个冒烟测试
环境配置完别急着写技能,先跑一个冒烟测试,确认 Agent 能正常回应。我在里约项目里通常会执行一条最简单的技能列表命令:
hermes skill list这条命令会读取技能目录,并把已经加载的技能名称显示出来。如果命令报错,优先检查技能目录路径是否正确;如果输出为空,说明目录里还没有任何技能,这是正常的。然后再启动 Agent 服务,在对话里随便问一句“你好,请介绍一下你自己”。只要模型有正常回复,说明链路是通的。所谓冒烟测试,就是在正式使用之前,用最小成本验证关键路径能跑通。我的习惯是每次改完配置都跑一遍冒烟测试,几十秒就能定位是不是基础环境出了问题。
3. 技能(Skill)在 Hermes 中的工作方式
3.1 一个技能到底有哪些组成部分
在 Hermes 中,一个技能不是一个单纯的提示词,而是一个功能单元。从我实际搭建的目录看,一个标准技能由三个核心文件组成,外加一个可选的执行脚本。
第一个是技能定义文件,通常叫SKILL.md,用 Markdown 或 YAML 格式记录技能的名称、描述、触发场景和参数声明。这个文件是整个技能的大脑。第二个是提示词模板,负责提供执行规则,相当于技能的操作手册。第三个是执行脚本,通常是 Python 文件,负责真正去调用外部工具、处理文件、调用 API。如果这个技能只需要模型按模板输出文本,没有外部动作,执行脚本可以省略。
技能目录结构一般长这样:
skills/ weekly-report/ SKILL.md prompt.md schema.json run.py当你把技能放进 Hermes 的技能目录并重启服务后,Hermes 会把这些技能的描述信息加载进上下文字段。每当用户输入一段话,Agent 会先判断当前请求是否匹配某个技能的描述,如果匹配,就按照该技能定义的文件去生成和调用。
3.2 什么时候触发技能:描述里的描述才决定一切
我在实际操作中发现,决定一个技能能否被正确触发的关键,全在SKILL.md里的 description 字段。这个字段会被嵌入到模型的系统提示词中,模型拿它来判断“用户当前这句话和这个技能是不是同一件事”。
所以写描述的时候要注意两个点。第一,要把触发场景和边界说清楚,比如“当用户提到上周、本周、周报、period summary 相关说法时,优先使用本技能”,这比只写“周报技能”有效得多。第二,要说明技能能够接受的参数有哪些,比如“本周”和“上周”就是不同的时间范围参数。模型会根据你的描述,尝试从用户的话里提取参数值,这个过程叫参数填充。
我通常会做一个对比实验来说明这一点。比如描述写成“生成周报”,模型很多时候不知道该不该调用,尤其是在用户只说半句话的时候。但描述写成“根据用户提供的工作记录,生成四段式周报。当用户提到周报、本周总结、weekly report 时触发,可从对话中提取时间范围”,触发准确率会显著提升。很多人的技能不被调用,问题往往不在代码,而在描述写得不够准确。
3.3 harness 与 agent 的分工
在 Agent 开发里,有两个词经常被混在一起:harness 和 agent。我自己的理解是,agent 是决策者,它负责理解用户的最终目标;harness 是执行框架,它负责把 agent 的想法变成实际动作,比如加载技能、调用工具、管理上下文窗口、处理循环控制。
以 Hermes 的技能调用为例,完整链路是这样的:用户输入文本后,harness 先组装上下文,把系统提示词、可用的技能描述、历史对话记录一起发给大模型;模型返回结果,要么是一段正常回复,要么是一个函数调用指令,比如“调用 weekly-report 技能,参数 date_range=本周”;harness 收到这个指令后,再去加载对应的技能脚本,执行里面定义的动作。所以,技能能不能被调用,取决于 agent 的决策;技能能不能正常运行,取决于 harness 的执行。搞清楚这个分工,排查问题的时候就不会稀里糊涂了。
3.4 技能不是魔法:模型本身要支持函数调用
还有一点必须提醒,如果底层模型不支持函数调用或工具调用,Hermes 的技能机制就很难正常运转。因为模型需要通过结构化的方式告诉 harness“我决定调用某个技能”,这一步依赖模型的能力。我在本地测试时使用过支持工具调用的开源模型,也使用过一些输出能力较弱的模型,体验差异很大。建议你在选择底层模型时,确认它支持 function calling 或 tool use,否则就算技能配置再完善,Agent 也无法稳定地调用它。
4. 手把手创建第一个自定义技能
4.1 从真实场景出发:把“周报生成”做成技能
纸上谈兵没什么意思,我们直接做一个能用的技能。我选择“周报生成”这个场景来演示,因为它高频、边界清晰、省时效果又明显。
需求描述:我是做项目推进的,每周都要整理一份周报,内容包括本周已完成事项、进行中事项、风险和下周计划。原始素材通常是零散的工作记录和会议纪要。我希望 Agent 帮我完成的事情是,把这些原始素材整理成结构清晰的周报,并最终输出一段可以直接提交的版本。
明确边界之后,我开始设计技能。技能名定为weekly-report,输入参数有两个:一个是date_range,表示时间范围;另一个是raw_material,表示原始工作记录。输出格式是四段式结构。在设计阶段,我在笔记本上先手写了一遍提示词,确认这套规则自己是能读懂的,再往下落文件。这个习惯很重要,如果你自己都没法把流程讲清楚,模型也做不到。
4.2 创建技能目录与定义文件
先在 Hermes 的技能目录下新建一个文件夹,名字和技能名称保持一致,方便管理。目录名称我建议都用小写字母和短横线,避免带空格和大写字母,因为一些脚本工具在解析带空格的路径时容易出问题。
mkdir -p skills/weekly-report cd skills/weekly-report然后创建SKILL.md,内容是技能的对外说明。我的写法如下:
--- name: weekly-report description: > 根据用户提供的工作记录或会议纪要,生成格式化周报。 当用户提到“周报”“本周总结”“本周工作”“weekly report”等说法时,优先使用该技能。 可从对话中提取时间范围,默认使用“本周”。 parameters: date_range: type: string required: false description: 周报覆盖的时间范围,例如“本周”“上周” raw_material: type: string required: true description: 原始工作记录,可以是用户直接粘贴的文本 --- # 周报生成技能 将零散的原始记录整理成适合同步给项目组和管理层的周报。这里最关键的是 description 部分,前面花了大篇幅讲的就是这个。它决定了 Agent 什么时候该调用这个技能。参数声明也要写清楚,这将帮助模型在调用时自动填参数。
4.3 编写提示词模板
接下来创建prompt.md,这是技能的实际指令。我把它当成一份“给实习生看的操作手册”来写,要求覆盖输出格式、语言风格、处理规则,并且给一个简单示例。示例很关键,模型在少数情况下需要模仿示例的格式。
你是项目助理,负责把原始工作记录整理成周报。 处理规则: 1. 输入是用户提供的原始记录,可能包含多段内容,先按主题归类。 2. 输出统一为四段: - 本周完成:列出已完成的事项,每条不超过50字。 - 进行中:列出正在进行中的事项,标注当前进展。 - 风险与问题:列出需要协调或可能延期的事项。 - 下周计划:列出下个周期的重点工作。 3. 语言简洁,不要口语化。 4. 如果原始记录包含数字或关键数据,尽量以“事项名称(数据)”的形式保留。 5. 最后增加一段“一句话给管理层的摘要”。 原始记录: {{ raw_material }}编写提示词时有个小技巧:把变量用类似{{ raw_material }}的占位符写出来,这样技能在运行时会把模型提取到的参数填入模板。在 Hermes 的运行逻辑里,你可以在执行脚本中把参数传入 prompt 渲染器,也可以用模板引擎直接替换,具体写法看项目版本。重点是,这个模板应该是一个“稳定规则”,不要每次改来改去。
4.4 注册技能与测试
文件创建完成后,回到 Hermes 根目录,重新加载技能列表。如果你的是有热加载的版本,可以在对话中直接输入“请调用 weekly-report 技能生成周报”来触发;如果你的版本需要重启,就先重启服务,再执行hermes skill list确认技能已经被识别。
第一次测试时,我会先给一个最简单的输入:“这周做完了用户调研,并输出了报告初稿,下周二准备给业务方评审。另外发现一个风险,数据库迁移的进度比预期慢两天。”
然后观察输出是否符合四段式结构。如果输出格式不对,先看 prompt 模板有没有表述清楚;如果触发了技能但没有填参数,检查 SKILL.md 的参数声明;如果根本没触发,检查模型是否支持工具调用、技能描述是否足够明确。这里我想强调一个经验:测试技能时不要用真实的长数据,先用 10 行以内的假数据跑通链路,再逐步加大难度。这样排查成本会低很多。
4.5 参数化:让技能可复用
为了让技能更灵活,我在 SKILL.md 里定义了date_range参数,这样用户说“上周的周报”时,模型会自动把 date_range 填成“上周”,而不是每次都用同一套输出。如果你希望技能输出不同的风格,也可以增加一个style参数,比如“简洁型”“数据型”。但参数不是越多越好,每增加一个参数,模型提取错误的概率就会增加一分。
我个人的建议是,参数数量控制在两到三个以内,并且所有参数都给一个默认值。多一个必填参数,就多一个失败的可能。
5. 进阶玩法:把技能接到工具和记忆上
5.1 在技能里调用工具:走出“纯文本”的限制
很多人把这个阶段叫做“给 Agent 加上手和脚”。如果你只是让模型按模板生成文本,那还只是效率工具,不算真正的新能力。真正让 Agent 学会新能力,是让它可以操作外部系统。
回到周报例子上,假如你每天的工作记录不是粘贴在对话里的,而是存在某个文件夹里的 md 文件,或者是某个项目管理平台导出的 CSV,你可以在技能的执行脚本run.py里写文件读取逻辑:
import glob from pathlib import Path def execute(input_text: str = "", date_range: str = "本周", **context): files = sorted(Path(f"./records/{date_range}").glob("*.md")) combined_lines = [] for file_path in files: combined_lines.append(f"来源文件:{file_path.name}") combined_lines.append(file_path.read_text(encoding="utf-8")) raw_material = "\n".join(combined_lines) # 将 raw_material 传给 prompt 渲染器,这里省略具体调用 return {"raw_material": raw_material, "date_range": date_range}这段代码的思路是:技能执行时先按日期范围去目录里拿文件,把内容拼接成原始素材,再交给提示词模板去生成周报。这样你就能从“手动粘贴记录”变成“Agent 自己去读文件”。
再进一步,你可以把 RPA 技能接进来。比如让 Hermes 启动一个浏览器自动化脚本,从后台系统把当天的销售数据下载下来,再调用数据整理技能自动生成报表。我在做这部分开发时,通常会先为 RPA 脚本单独写一个冒烟测试,确认它能正常跑通,再接到 Hermes 技能里,避免问题混杂在一起不好定位。
5.2 把多步骤工作流保存为技能
自定义技能真正好用的地方,不只是保存一段提示词,而是能把一条完整的工作流保存下来。比如我音频节目组常用的一个流程是:抓取后台订单数据 → 清洗重复项 → 生成日报表 → 发送到指定的通知群。这四个步骤涉及数据读取、字段处理、文本生成、接口调用,但整体是高度固定的,一个月操作三十多次。
我把这套流程封装成了一个叫daily-order-report的技能,执行脚本里依次调用这几个模块:
def execute(input_text: str = "", **context): orders = fetch_orders() cleaned = clean_orders(orders) summary = summarize(cleaned) send_notification(summary) return {"status": "sent", "summary": summary}技能内部可以有清晰的分步执行逻辑,只要每步都能输出结果给下一步使用即可。这么做的好处是,你不用关心 Hermes 内部是怎么调度这些函数的,它把技能脚本当作一个整体来执行,最后把结果交给模型来生成面向用户的回复。对我自己来说,这是“把重复工作流程保存为自定义技能”这句话最实在的落地场景。
5.3 给技能加上记忆:跨会话复用的关键
默认情况下,每次对话结束后,Agent 会清空上下文。这意味着技能上一次执行的结果,下一次不会自动保留。如果想做到“这周的周报能参考上周的周报”,就需要给技能接上记忆模块。
我在实践中用的是最简单的方式:在技能目录里放一个memory.json,执行技能时读取,执行完后写入。周报技能的 memory 文件大概存这样的结构:
{ "last_report": "上周主要完成了用户调研,输出了报告初稿……", "last_updated": "2025-03-16", "next_plan": "下周推进数据库迁移与业务方评审" }在 prompt 模板里增加一段“参考信息”,把本地上次保存的周报摘要放进去,让模型在撰写本周周报时保持连续性。但记忆不是越多越好,我建过一个失败的案例:把一年内的报告全塞进上下文,结果模型被历史信息干扰,写出来的内容啰嗦又跑题。所以记忆只保留最近最相关的几个关键摘要就行。更稳妥的做法是,在技能执行脚本里做摘要,只把之前报告的结论部分写入记忆,而不是完整的历史。
5.4 技能之间的组合:像积木一样搭流程
技能还可以互相调用。我经常跟朋友说,单个技能像是一个零件,组合起来才是一台机器。Hermes 允许在执行脚本里调用其他技能目录里的脚本,或者通过上下文让模型“间接调用”。
举个例子,我有一个fetch-data技能负责取数,又有一个format-report技能负责格式化输出。组合技能时,我在编排脚本里先调用fetch-data拿到原始数据,再调用format-report生成最终文档。这种做法的好处是单个技能保持简单,组合逻辑单独维护,测试时出问题能精准定位到是取数坏了还是格式化坏了。
建议你把可复用的步骤拆成独立小技能,比如“从 CSV 读取数据”“把数据生成图表”“把图表发送到群”,然后在高阶技能里做组合。顺序不要写死,先根据实际场景画一张简单的流程清单,再按清单去编排调用。
6. 常见问题与排查技巧实录
6.1 报错 agent execution terminated due to error
这条报错在 Agent 开发中很常见,字面意思就是“执行被终止”,因为你请求的链路里某个环节已经崩了。根据我的经验,原因通常集中在三种情况。
第一种是工具调用失败。技能脚本里访问了不存在的文件,或者调用的 API 返回了异常状态码。排查方式是看 Hermes 的日志输出,错误堆栈会定位到run.py的某一行。第二种是模型返回了非法的函数调用参数。比如技能定义里要求date_range是字符串,但模型传了一个对象,harness 在解析时就会终止。这种情况可以在技能定义里加强参数校验。第三种是上下文太长导致超时,当原始素材非常大时,脚本可能被模型服务的 token 限制打断。我一般通过减少输入内容或增加摘要步骤来解决。
6.2 Agent 无法生成回复:couldn't generate a response
我还踩过一种很让人头大的情况:Agent 既不报具体错误,也不输出最终结果,而是来回打转,最后提示 “agent couldn't generate a response”。这种多半是循环控制出了问题。
最常见的原因是技能执行结果没有回流给模型,模型看不到工具调用返回了什么,自然无法生成总结。这时你要检查 harness 的配置,确保障 技能执行后的返回值能被重新放入上下文。第二种原因是模型服务端本身报错,比如请求超时、并发限制,查看模型服务那边的日志就能确认。第三种原因是上下文被塞满,导致模型没有足够空间生成最终回复。解决办法是限制历史对话轮数,或者对中间结果做摘要压缩。我的建议是给 Agent 设置最大执行轮数,一般默认 8 轮就够了,超过就主动失败,避免无限循环烧掉时间和成本。
6.3 技能没被触发:先检查 description
技能不被触发是新手最常遇到的问题,但 99% 的情况不是代码有问题,而是 description 写得不够具体。模型需要靠这段描述来判断是否调用技能,如果描述写得太泛,比如“生成周报”,模型在遇到“帮我总结一下这个星期的事情”时,可能就不知道该不该触发。
排查的方法是先看日志,确认 Agent 有没有把技能描述加载进上下文。然后做一次人工测试,在描述里加上明显的触发词,“周报、本周总结、weekly report”都列进去。另外,还可以在提示词模板的开头写一句“当用户提到周报相关需求时,必须使用本技能”,虽然作用有限,但在兼容性不太好的模型上能起到强提示效果。
6.4 RPA 冒烟测试失败怎么定位
如果你像我一样把技能接到了 RPA 流程上,我强烈建议你为 RPA 脚本单独做冒烟测试,不要等到 Hermes 集成后才发现问题。冒烟测试的思路是,用最小可执行样本跑通主链路。比如你写了一个浏览器自动登录脚本,冒烟测试就先只测“打开首页,输入账号密码,点击登录,确认跳转到后台”,不做任何业务操作。
如果这类测试失败,多半是选择器定位不准,网页改版后元素 ID 变了。你需要在 RPA 脚本里把关键元素的定位状态打成日志,这样 Hermes 技能调用失败时,一眼能看出是哪个步骤卡住。我自己的项目里会把 RPA 的输出 JSON 化,每一步执行完都返回当前状态和关键数据,Hermes 侧统一解析。这套做法帮我省掉了大量排查时间。
6.5 技能维护:几条实在经验
技能开发完不是终点,维护才是大头。我那段时间踩出来的几条经验,总结到这里,希望能帮你少走弯路。
技能目录和技能名称要保持稳定,中途改名会导致依赖它的其他技能全部失效,改的时候要检查所有引用。每次修改技能定义后,必须重新跑一遍冒烟测试,不要只改不测,测试成本很低,但线上出问题的成本很高。给技能增加一个“变更记录”字段,哪怕只是几行字,半年后你会感谢自己。最后,定期清理不再使用的技能,技能目录里堆了太多长期不用的技能,会占用上下文窗口,反而干扰 Agent 的判断。我一般按月清理一次,超过两个月没调用的技能先归档再说。
至于排查工具,我一般分三层:第一层看 Hermes 的日志,确认技能加载和调用链路;第二层看模型服务的日志,确认模型返回了什么;第三层在技能脚本的关键位置插入print或者写日志文件,定位具体执行到哪一步。这套三层定位法看起来土,但在实际项目里比任何高级调试工具都管用。