说实话,这两年聊 Agent 的文章铺天盖地,但大多数都停在“用 GPT 写个周报”或者“搭个聊天机器人”的层面。真正让 Agent 从“玩具”变成“生产力工具”的,不是模型本身有多聪明,而是你给它装了多少套趁手的“技能”。
我最近一直在折腾的就是这个方向,围绕一个叫agent-skills的开源项目做了不少改造和落地。简单说,它不是模型,不是框架,而是一套标准化的“技能包”机制——让 Agent 能像乐高积木一样,把一项项具体能力(查天气、读文档、操作数据库、发通知)拆成独立模块,按需加载、自由组合、随时替换。
这篇文章我想结合自己实际跑通、踩坑、重构的经历,把 Agent 技能这件事从底层逻辑讲到实战细节。包括技能与工具的本质区别、技能定义文件怎么写才能让模型稳定调用、多个技能同时在线时怎么避免冲突、以及加载和调度上的性能优化思路。不管你是刚接触 Agent 开发,还是已经做了几个 demo 想往工程化方向推进,这篇文章应该都能给你一些参考。
1. 为什么 Agent 的能力上限取决于“技能包”而不是模型本身
先把一个误区说清楚:很多人觉得 GPT 类的模型什么都会,给个 prompt 就能干活。这个方向在单轮问答、写文案上没问题,但一旦进入真实业务场景——比如“帮我把销售周报数据从数据库里拉出来,做图表,再发到钉钉群”——模型的“聪明”就远远不够了。
模型的训练决定了它只能“理解”任务,而“执行”任务要用工具、要操作外部系统。这个鸿沟就是技能包的生存空间。
我在做 agent-skills 的早期版本时,犯过一个大错:把整个业务流程全部塞进 system prompt,让模型靠“理解”去完成。结果就是:它在描述步骤时头头是道,真正调用数据库接口时参数格式五花八门;今天能跑通,明天换了字段名就彻底宕机。后来我才意识到,Agent 要稳定,靠的不是提示工程,而是把“怎么做”固化成可执行的技能,让模型只在“选哪个技能”和“传什么参数”上做判断。
这个项目给我的最大启发,就是它对“技能包”的抽象:一个完整的技能就像一份工作手册,里面明确定义了触发条件、输入参数、执行步骤、输出格式、异常处理。模型拿到用户请求后,首先是“路由”——判断该调用哪份手册,然后严格按手册执行。这样即使底层模型从 GPT 换到 Claude 再换到开源模型,只要技能包接口不变,Agent 的行为就能保持稳定。
1.1 从“会聊天”到“会干活”的三步跃迁
聊到 Agent 的分层演进,我习惯把它拆成三步:
第一步:会聊天。模型基于预训练知识做问答,优点是泛化能力强,缺点是完全不可控——没有确定性的输出,也不能操作外部系统。这阶段的 Agent,说白了就是一个带记忆的聊天机器人。
第二步:会用工具。模型通过 function calling 或者类似机制调用外部 API,能查天气、能查数据库、能发 HTTP 请求。这里模型的角色变成了“决策者”——决定用哪个工具,以及工具的参数怎么填。稳定性依然是个问题,因为工具一多,模型选错的概率就上升。
第三步:会“成套地干活”。技能包把这推到新高度。一个技能是多个工具步骤的编排,甚至包含判断逻辑:先做 A,如果 A 的结果满足某条件再做 B,否则做 C。这种面向“目标”的封装,让 Agent 从“执行单个动作”升级到“完成一个任务”,而且整套逻辑可以复用和共享。
我实际测过同一批任务在“纯 prompt + function calling”和“agent-skills 技能包”两种形态下的稳定性差距。前者大概 30 次对话后错误率开始爬升,特别是参数格式和步骤顺序开始走样;后者跑了一周多,行为一致性高很多,因为模型只做选择题(选哪个技能、填哪些参数),推理负担大幅降低。
1.2 技能包在 Agent 生态里所处的位置
如果拿微服务架构来类比,Agent 本身是“网关层”,负责接收请求、做语义理解、编排调度;skills 就是“业务服务”,每个技能自包含一套逻辑;而底层的 function calling、数据库驱动、API 客户端属于“基础设施”。
这种分层最大的好处是:各层可以独立演进。我这边的实践是,模型可以随便换(从 GPT 到 Claude 再到国产模型),技能包保持兼容;技能包可以单独升级(比如优化某个技能内的执行步骤),不影响网关逻辑;底层工具链出了 BUG,只要接口不变,上面完全无感。
相比传统的 AI 应用开发方式,这种模式的项目管理成本也低很多。以前是“需求变更 → 改代码 → 发版 → 测试”,一套循环走下来大半天;现在是“需求变更 → 调整技能定义/新增技能 → 即时生效”,因为技能包本质上是数据驱动(JSON/Markdown/指令的组合),不需要重新编译部署整个应用。
2. 技能包的核心单元拆解:到底一个“技能”由什么组成
深入研究 agent-skills 之后,我发现它跟传统“插件”本质的不同在于:它明确地把技能的“描述”和“实现”分开了,而且极其重视“模型可读的说明书”部分。
一个标准技能包通常包含这几个文件/模块:
- 技能描述文件(SKILL.md 之类):给模型看的,写清楚这个技能解决什么问题、适合在什么场景触发、有哪些前置条件。
- 参数 Schema(JSON Schema):定义输入参数的格式、类型、必填项和校验规则。
- 执行脚本/指令(Python/JS/Prompt 序列):具体的执行逻辑,可以是代码,也可以是有序的提示词步骤。
- 示例(few-shot examples):至少 3-5 个输入输出对,引导模型正确填写参数、触达正确的技能。
- 校验与后置处理(可选的 validation 和 post-processing):检查技能执行的结果是否符合预期,不符合时如何重试或报错。
这里我得聊一下为什么参数 Schema 和示例如此关键。大模型本质上是一个“概率预测器”,你给它描述越模糊的场景,它的输出越飘;但你给它一套明确的结构和几个标准样例,它就能把意图映射到正确的技能调用上。
举个例子。给 Agent 加一个“查询本周销售业绩”的技能,如果只是写一句“查销售,输出结果”,模型会自己发挥——日期格式可能产生多种,时间范围可能理解错,输出可能是表格可能是文字。但如果你在参数 Schema 里定义了start_date、end_date都是 YYYY-MM-DD 格式,示例里给了一个完整调用 JSON,模型几乎不会出错。
2.1 技能描述文件:模型路由的“指路牌”
技能包架构里最容易忽视、但最影响效果的就是描述文件。它决定了模型能不能在用户提出请求时“一眼”选中正确的技能。
我踩过一个坑:写描述时太口语化,什么“这是一个查询工具,可以查出数据,然后给你看结果”,结果模型在用户说“帮我看看最近单量怎么样”时根本没路由到查询技能,而是用通用能力硬答。后来我把描述改成结构化的维度,情况立刻好转:
- What:这个技能做什么(一句话,动作开头,比如“从销售数据库获取指定日期范围的订单汇总”)。
- When:什么请求才需要调用它,什么场景禁止调用它(负向描述特别重要,能挡掉大量误报)。
- Input:需要的关键信息有哪些,缺什么可以引导用户补充。
- Output:返回结果的形式,是表格、文本还是特定结构。
- Examples:2-3 条典型触发语句和对应的调用参数。
提示:描述文件里的“When”部分一定要写清楚“不要做什么”。比如一个“查天气”的技能,你明确写“仅用于查询未来 7 天天气,不用于历史气候分析”,模型的误调用率会明显下降。这跟人一样,边界清晰才能分工明确。
2.2 参数 Schema 的设计:宁可多校验,不可太宽松
另一个影响稳定性的细节是参数设计。我见过不少 Agent 项目的 function calling 失败,排在第一位的原因不是模型笨,而是参数 Schema 定义得太模糊。
agent-skills 里对 Schema 的做法比较极端,但也确实值得学习:每一个参数都要求写 description、示例值、约束条件(枚举、正则、范围)。特别是required列表,宁可把参数拆细,也不要一个“data”字段全塞进去。模型在填写一个松散的“data”参数时,输出往往毫无章法;但你给它拆成start_date、end_date、group_by、metric这种粒度,它就表现得像老手一样精准。
我测试过一个数据分群技能,最初 Schema 里只有一个query字符串,结果模型生成的 SQL 五花八门,甚至出现冒号、中文括号。重构后我把table_name、conditions、group_by、order_by全拆开了,配合枚举和模板,准确率从 60% 左右直接拉到 95% 以上。
如果你嫌写 JSON Schema 太繁琐,agent-skills 也支持用 YAML 简化配置,底层再自动转成模型需要的格式。这算是个很人性化的设计。
2.3 技能内部逻辑:确定性的代码 + 非确定性的决策,两者分离
再说一个对架构层面的心得。技能的执行逻辑,最好分成两层:
- 确定性层:写死代码逻辑,比如 SQL 拼接、API 调用顺序、数据清洗规则。这块不允许模型“自由发挥”。
- 决策性层:只暴露必要的参数给模型,让它在有限选项中做选择。
举个实际例子。我做过一个“竞品价格监控”技能。输入是:product_name、platforms(只能选固定几个平台)、time_range。技能内部拿到参数后,自己决定调哪几个 API、怎么解析页面、怎么聚合去重。模型完全不需要知道细节,它只需要做决策:分析哪个产品、看哪些平台、覆盖多久。
这种设计的优势,一个是职责清晰:模型做模型擅长的语义理解与路由;代码做代码擅长的逻辑编排,不容易出错。另一个是安全可控:不会出现模型为了“完成目标”而拼接出危险参数的情况(比如让它操作文件路径,结果传了一个../../的目录)。我在安全问题上的处理原则就是:模型永远拿不到超过技能边界的权限,所有敏感操作都靠技能内部的预设配置完成。
3. 实际操作过程:从一个“客服问答 Agent”开始接入 agent-skills
理论说太多了,直接上实战。我在生产环境里跑通的第一套 agent-skills 应用,是一个面向内部员工的客服问答 Agent。需求很简单:员工可以在钉钉上提问,Agent 负责答——包括企业制度流程、IT 报修、请假审批进度查询这些业务。
这块的难点是:制度文档是纯文本,没法直接作为结构知识存入向量库;审批数据在第三方 OA 系统里,没有现成 API;IT 报修又涉及创建工单,是写操作,必须有严格的权限校验。我把它拆成了四个技能:制度文档检索、审批进度查询、IT 报修工单创建、常见问题 FAQ。
3.1 技能目录结构设计与配置文件实例
程序目录结构我参考了 agent-skills 的推荐布局:
skills/ ├── policy_search/ │ ├── SKILL.md # 技能描述文件 │ ├── schema.json # 参数 Schema │ ├── run.py # 执行逻辑:向量检索 + 摘要 │ └── examples.json # 示例输入输出 ├── approval_status/ │ ├── SKILL.md │ ├── schema.json │ ├── run.py # 调用 OA API 查询状态 │ └── examples.json ├── it_ticket/ │ ├── SKILL.md │ ├── schema.json │ ├── run.py # 创建工单,带权限校验 │ └── examples.json └── faq_bot/ ├── SKILL.md ├── schema.json ├── run.py # 基于 FAQ 库关键词匹配 └── examples.json每个技能的 SKILL.md 我都是按统一的模板写的,不搞花活,保证可维护性。以approval_status为例:
# 审批进度查询技能 ## What 根据员工提供的审批单类型、提交日期或审批编号,查询当前审批进度,返回审批节点、处理人、状态和预计完成时间。 ## When - 用户询问“审批到哪了”“报销什么时候批完”“请假流程状态”等场景。 - 禁止用于非审批类查询,如“工资发了没”“考勤异常”。 ## Input - approval_type: enum [leave, reimbursement, purchase, contract] - approval_id: string,可选,员工如能提供审批编号则优先以此查询。 - submitted_date: string (YYYY-MM-DD),可选,用于缩小范围。 ## Output 返回结构化 JSON:{approval_id, current_node, status, history_nodes, estimated_time} ## Examples 用户问:“我上周提的报销审批到哪了?” - approval_type: reimbursement - submitted_date: 上周对应日期 - 预期输出:包含当前节点与审批人这个描述文件写完以后,我自己人工模拟了 20 多条真实员工提问,逐一验证路由是否准确。submitted_date是个典型“需要模型推理”的字段,因为员工不会直接说日期,会说“上周”“前天”这种相对时间,模型需要把它们换算成绝对日期再填入。
我特意在示例里放了两条相对时间的问法,实测中模型对相对时间的解析准确率明显提升。这个在纯 function calling 里往往会被忽略,直接导致查询范围不准。
3.2 技能执行链路:从“用户提问”到“技能返回结果”
接入 agent-skills 之后,整个执行链路变成了这样(这是我自己梳理的流程,项目本身只提供规范参考):
- 用户消息进入 Agent 主控制器。
- 主控制器调用意图识别模块,把用户请求转成一组候选技能。
- 从技能注册表里加载候选技能的 SKILL.md 和 schema 全量信息。
- 把“技能描述 + 用户当前消息 + 历史上下文”拼给大模型,由大模型输出结构化的技能调用指令。
- 格式校验通过后,由执行框架调用对应技能的 run.py。
- run.py 执行期间,可能会调用外部 API、向量检索或本地缓存,期间所有 log 都会记录到链路跟踪表。
- 技能返回结构化的中间结果,最后由主控制器整理成自然语言回复,回传给用户。
第 4 步问答我把“意图识别”和“参数抽取”合成一步了,而不是分开两次调用模型。合并调用省一次模型请求,延迟更低;而 agent-skills 的 schema 定义足够明确,合并后准确率没有明显下降。
在第 6 步的链路跟踪上,我用的是一个简单的 JSON log 文件:记录用户原始消息、选中技能名称、所有输入参数、执行耗时、返回结果摘要、错误信息。这个小习惯在排查问题的时候帮了大忙——很多看似是“模型路由错”的问题,其实是参数被历史上下文污染了,单看最终回复根本看不出来,一查 log 就立刻现形。
3.3 跑通一周后发现的三个“反直觉”调优点
这个客服 Agent 上线跑了一周后,我做了数据统计分析,发现有三个反直觉的现象,值得拿出来聊聊。
第一个:增加技能数量后,路由准确率反而提升了。
一开始我以为技能越多,模型越容易混淆。实际数据是:只有 2 个技能时(policy_search 和 faq_bot),混淆率在 8% 左右;增加到 4 个技能后,混淆率降到了 3%。原因是技能描述之间形成了互斥边界——有了 it_ticket 技能,员工问“电脑坏了”时就不会被误路由到制度检索。界限清晰的一组技能,好过一个大而全的万能技能。
第二个:把输出严格模板化后,用户的“感受变好了”。
早先 run.py 返回的是自然语言片段,再由主控制器二次加工。后来我改成强制所有技能返回严格 JSON schema,再由主控制器统一渲染。看似多了一道序列化和反序列化,但一致性大幅提升,用户不再会遇到“上一句表格、下一句列表”的割裂感。稳定的输出结构,本身就是体验的一部分。
第三个:缓存不是万能的,但针对“参数完全相同”的请求做缓存收益极高。
这个客服场景有很强的重复性——比如月底大家都问“报销报批到哪了”、发工资后很多人问“请假余额”。我对技能执行结果做了天然缓存:相同的参数组合、相同的技能,在特定时间内直接命中缓存,不用再调用大模型解析。准确率和响应速度都上来了。这块把技能执行从平均 3-5 秒压到了 500 毫秒以内。
注意:缓存不适合所有场景。像它工单创建这种写操作绝对不能缓存,审批进度这种强时效数据只能做短时间缓存(比如 30 秒)。我给每个技能加了一个
cache_policy配置字段,根据自己的业务场景控制,非常实用。
4. 技能编排与冲突处理:当多个技能同时命中,听谁的
Agent 进入真实业务后,很快会遇到一个单纯 demo 阶段不会暴露的问题:多个技能同时命中时,怎么编排?
最常见的就是模糊请求。比如“帮我弄一下报销的事”。“弄一下”三个字太宽泛了,它可能对应“查进度”(approval_status),也可能对应“提申请”(reimbursement_submit),甚至可能是“问报销规则”(policy_search)。如果模型自由发挥,大概率会选一个“看起来差不多”的技能,然后一本正经地胡说八道。
我的做法是:技能包方案只在“明确意图”下直接触发;模糊意图一律走“澄清策略”。
澄清不是简单地问一句“你到底要干嘛”,而是要把选项“摆到桌面上”。Agent 会把命中的所有技能查出来,按照预设的优先级排序,然后回复用户:
您是要查询报销审批进度、提交新报销申请,还是查看报销制度?
这种主动澄清方式有三个好处:一是给用户明确的路径选择,比空泛的“请提供更多信息”有用得多;二是通过用户的二次选择反哺 Agent 的意图收敛,间接降低了模型乱猜的概率;三是在企业内部工具场景下,这种“专业客服”的交互方式提升了用户对系统的信任感。
4.1 技能优先级与互斥规则设计
技能数量多了以后,光靠模型做路由决定不保险,我在 agent-skills 之上增加了一层“规则优先”的决策机制:
skill_rules: - trigger_always: true skill: permission_check description: 任何技能执行前,先校验用户身份和数据权限 - if: "message contains 'urgent' or '加急'" priority: 10 skill: escalation_handler - conflict_resolution: - skills: [approval_status, reimbursement_submit] condition: "message includes '提交' or '申请' or '发起'" preferred: reimbursement_submit - skills: [approval_status, policy_search] condition: "message includes '流程' or '需要什么材料'" preferred: policy_search这套规则中的核心是:用简单的关键词/语义规则先挡掉一部分明显冲突,而不是全盘交给模型。规则匹配不上时,模型再上场做二次路由。两层决策的设计在工程上更稳妥——你可以把规则的召回率调得高一点、精确率低些,兜底交给模型。
优先级用了数字机制(10 表示最高),而不是单纯的队列顺序,是为了支持动态调整。比如某些紧急场景(服务器宕机、核心系统异常)可以直接把应急技能提到最高,绕过平时“先问清楚、再执行”的流程。
实际上这套规则上线后,多技能命中率大幅下降、用户澄清次数也降了。很重要的原因是:人会觉得“提交报销”和“查报销进度”是一件事,但在技能分类里它们是两个完全不同的动作,规则在这里把语义精确化了。
4.2 技能组合模式:一个复杂任务拆成多个子技能
说完冲突处理,再说组合模式。单技能只能解决单任务,而真实业务往往是“连招”。
我做过最典型的一个场景是“生成每周数据周报”。拆分后的流程:
data_fetch技能:连数据库拉原始数据。data_summary技能:对原始数据做聚合、环比、异常标注。report_writer技能:基于摘要生成 Markdown 周报。notifier技能:把周报发送到指定钉钉群。
这里为什么需要拆成 4 个技能而不是做成一个大而全的“周报技能”?核心考量是复用性。
比如数据拉取这个技能可以被别的场景复用(销售看板、实时监控、临时查询);notifier 技能也可以被别的场景复用(异常告警、定时通知)。做成大而全技能,每次新增复用场景就得复制一份代码。而拆散后,每组技能可以独立演进、独立测试、独立替换底层实现。
agent-skills 里有个很实用的刻意设计:技能编排层不在“技能包”内部做,而是在外部定义流程模板。每个技能保持独立、无状态、可组合。执行周报任务时,编排层按顺序把四个技能串起来,上一个技能的输出作为下一个技能的输入。这样既保留了技能的复用性,又不污染单个技能的边界。
在跑通这个组合场景后,我发现一个很关键的点:技能之间的“接口约定”需要定义得比技能实现更仔细。比如data_fetch输出的是一个 SQL 查询结果的原始 JSON,data_summary就必须知道 JSON 里的字段结构。我通常会为每个技能的 run.py 输出加一个数据字典级别的注释,并且在联调阶段跑一遍全链路的样例数据,把每一步的实际输出快照存下来,作为版本间的回归基准。
5. 工程化落地:加载、缓存、并发与容错的几个关键设计
从 demo 到生产,中间隔着一条叫“工程化”的河。技能包机制本身思路清晰,但真要在一台服务器上稳定支撑几十个部门、上千号员工的使用,有几个点必须认真设计。
5.1 技能加载策略:全量加载还是按需加载
最开始我图省事,所有技能的定义(SKILL.md + schema + examples)一股脑塞进系统 prompt,大模型每次请求都要处理庞大的上下文。结果就是:token 消耗飙升、响应延迟加大、而且技能多了以后模型对每个技能的关注度被稀释,路由准确率反而掉。
后来改成按需加载:系统进程维护一个注册表,存所有技能的元信息(名称、描述摘要、触发关键词)。用户请求进来时,先用一个轻量级 RAG(简单向量检索 + 关键词过滤)把候选技能筛出来,只把候选技能的完整定义注入上下文。这样每次请求只会有 3-6 个技能完整加载,而不是全部。
对比测试数据很直观:全量加载 20 个技能时,单次请求平均延迟 2.1 秒、token 消耗约 8000;按需加载后,平均延迟 0.8 秒、token 消耗约 2600。路由准确率还从 91% 提升到了 96%(因为冗余干扰信息变少了)。
这块我特别想强调一个经验:技能注册表里的“触发关键词”和“负面关键词”需要持续维护。我每周会从用户实际提问中抽取一批日志,手动标注归属技能,再把这批标注样本转成关键词和示例补充到注册表里。这个“人工反馈闭环”是技能系统持续变聪明的关键,比单纯调 prompt 有效得多。
5.2 技能执行器的容错与降级设计
技能执行过程中必然会遇到外部系统不可用、参数校验不通过、甚至模型返回的调用格式错误等情况。我在 agent-skills 的实践里总结了一套“三级容错”机制:
第一级:参数纠正。模型返回的参数不符合 schema 时,不直接报错,而是通过一个校验器把错误信息返回给模型,让模型重新生成一次。这个“validator 拦截 → 模型重试”的循环最多跑两轮,实测能挽回约 40% 的格式错误。
第二级:技能内降级。主技能失败时,尝试映射到备用技能。比如data_fetch连不上数据库,可以自动降级到data_cache_read(从最近一次缓存快照读取数据),并给主控制器返回一个degraded: true的标记,最终回复里附上“基于缓存数据”的说明。
第三级:人工接管。如果连续多次重试都失败,不再硬扛,直接把错误信息打包推送给人(钉钉工单/企微消息)。对生产系统来说,明确告知“机器搞不定了”远比强行给一个错误答案更好。
三级容错我在线上跑下来的效果是:整体系统可用性从 90% 拉到了约 99.2%,用户几乎感知不到底层故障。因为大部分失败都是瞬时错误(API 超时、网络抖动),第一级和第二级能覆盖掉绝大多数场景。
提示:给每一级容错都加“日志埋点”。我在技能内部做了标准化的
log_step函数,每次调用都会记录:进入哪个技能、执行到哪个步骤、返回了什么、耗时多少、有没有走容错路径。排查“用户说收到错误数据”这类问题时,这些日志就是第一手证据,能快速定位到底是在哪个环节出错的。
5.3 安全边界:技能权限的最小化原则
安全这块我愿称它为“Agent 能不能上生产的生死线”。Agent 的技能如果拥有过大的权限,配合模型可能的误用或恶意诱导,后果可大可小。我在 agent-skills 配置里强制执行了几条安全策略,也是建议每个要上生产的团队参考的:
一、技能沙箱化。每个技能的 run.py 运行在受限的进程环境里,禁止访问环境变量、禁止访问宿主文件系统(除非显式白名单)、网络权限按技能维度单独开放。比如policy_search只允许访问内部文档接口,notifier只允许调用钉钉 webhook,互不越界。这个隔离很关键,就算有某个技能被提示词注入攻击打穿,损失也只会被限制在单一技能边界内。
二、API 密钥永不下发到模型侧。技能内部所有外部系统调用、数据权限校验都封装在代码层,模型中不可感知。大模型只与技能包的描述文件和参数 Schema 交互,没有任何直接的密钥、令牌或敏感配置暴露。从 Agent 的“对话窗口”到“系统操作层”,中间隔着一层代码实现,这层就是安全闸门。
三、写操作要双确认。凡是产生写效果(创建工单、发送消息、修改数据)的技能,一律增加“执行前确认”的流程。Agent 收到模型生成的写指令后,先返回给用户一个确认卡片,用户点“确认”才真正调用外部系统。产品团队起初担心多这一步会增加用户打扰,但我用“误操概率 × 影响面”说服了他们——多数场景下写操作是不可逆的,一刀切的双确认虽然损失了少量便利性,但规避了灾难性的误操作风险。
6. 技能开发工作流:从需求到上线的五个阶段
技能包机制把 Agent 的应用开发变成了一条相对轻盈的流水线。我根据这段时间的实践,总结了一套自己的技能开发工作流。如果你也想从零开始搭一套 Agent 技能库,这套流程可以直接借鉴。
6.1 定义阶段:从业务需求中抽取“技能边界”
不是所有的需求都适合做成技能。做技能之前,先问自己三个问题:
- 频率高不高?如果这个请求一个月才出现几次,不值得做成技能,直接用通用模型回答就够了。
- 确定性高不高?如果这个任务的执行路径五花八门,很难收敛成固定步骤,说明技能边界还太模糊,需要进一步拆解或人工介入。
- 边界清不清晰?一个技能应该只对“一类”任务负责,比如“查测试报告”和“运行测试任务”就应该是两个技能,而不是塞进同一个技能里靠参数区分。
在这个阶段,我会先用一周的时间去记录实际用户提问,把问题分为“高频确定”“低频确定”“模糊长尾”三类。高频确定类进入技能开发池。
6.2 开发阶段:先写描述文件,再写代码
很多开发的直觉是“先把功能写出来再写文档”,但技能包开发恰恰相反:先写 SKILL.md,再写实现代码。
SKILL.md 本质上是在用“模型视角”定义技能,它决定了路由能不能命中、参数能不能填对。如果描述文件写得模棱两可,后面代码写得再完美,模型根本调用不到,等于白做。我一般的开发顺序是:
- 先花 30 分钟把 SKILL.md 的 What、When、Input、Output 写清楚。
- 然后基于 Input 写 schema.json,字段类型、约束、示例一次到位。
- 接着构造 3-5 个 examples.json,覆盖典型场景和边界场景(特别是参数缺省、模糊表述、极端值)。
- 最后才动手写 run.py 实现逻辑。
这个顺序看起来反直觉,但实际操作中能省掉大量返工——因为大部分返工都是发生在“模型调不到技能”或“参数填不对”上,这些和代码质量没关系,完全是定义层的问题。
6.3 测试阶段:建立技能回归基准集
技能开发完,不放点测试基准就上线,迟早出事。我会为每个技能建一个“回归测试集”,包含至少 15-20 条测试用例,横跨:
- 标准场景(用户提问非常明确)。
- 模糊场景(用户提问省略关键参数)。
- 跨技能场景(提问接近另一个技能的边界)。
- 异常场景(用户给了非法参数、超出权限的请求)。
每次我调整 SKILL.md 或 schema,都会跑一遍回归集,对比新版本和旧版本的路由正确率、参数正确率、端到端成功率。没有回归基准集,你根本不知道某次 prompt 改动是变好了还是变坏了。
注意:回归集需要持续补充。我每个月会把当月真实用户的“路由失败”案例中加入测试集。这样一来,每次版本迭代都是在“已知的历史坑”上做回归,系统会越踩越稳。
6.4 发布与灰度:技能包也要有版本管理
技能包本质上是数据/配置,所以天然适合“灰度发布”。我先在内部小流量环境部署新版本,加载少量真实请求,观察路由准确率、平均耗时、错误率三个指标;运行 24 小时没问题再全量放量。
因为技能包是“数据驱动”,所以回滚也极其简单——直接把注册表里的版本指向旧的配置即可,几秒完成。相比传统代码发版,做错的代价小太多了。
6.5 运营阶段:技能使用数据驱动的持续优化
技能上线只是开始,持续运营才是价值所在。我每周定期做一次“技能体检”,主要看三个数据:
- 调用分布:哪些技能调用量高,哪些长时间闲置。开了太多闲置技能会导致注册表冗余,还会稀释路由准确率,该下架就下架。
- 路由失败率:哪些用户提问没有被任何技能命中,或大量触发澄清。这些就是新技能的机会点或既有技能定义盲区。
- 满意度代理指标:用户是否在回复后继续追问,有没有直接切换话题。这类行为往往暗示回答没有命中需求。
这套数据驱动循环跑一两个月,技能库会呈现出明显的“业务拟合”——技能越来越贴近真实用户需求,而不再是你闭门想象出来的功能清单。
7. 围绕 agent-skills 的生态:扩展技能的市场、共享与协作
这个项目之所以值得关注,除了技术设计,还有它的生态意识。技能包机制设计成了一个自包含、易共享的单元,意味着它可以天然地形成“技能市场”的雏形。
我在本地维护的这套技能库里,一部分是从 agent-skills 社区直接拿来的(比如日期计算、数据可视化、会议纪要生成),一部分是我自己开发后打包的。分享技能时,不需要把整个 Agent 代码仓库给对方,只需要给一个目录,包含 SKILL.md、schema.json、run.py 和 examples.json。对方拿过去,放进自己的技能注册表,稍微改改内部 API 地址和权限配置,马上就能用。
这个协作模式将来最大的价值,是“跨组织共享最佳实践”。过了两三年当技能包数量规模化后,组织之间可以互借成熟技能,减少重复开发,把精力放在自己真正的核心业务逻辑上。
不过也提醒一句:开源技能包拿回来不要直接用。至少要做三个检查:
- 依赖检查:技能内部调用的 API 或数据库表,你的环境里有没有?地址、鉴权方式是否一致?
- 敏感信息检查:技能包里可能残留原作者的测试密钥、内部 IP、日志路径。发布前记得全仓库扫一遍密钥和路径。
- 边界测试:把技能丢进你的 Agent,跑一遍你自己的回归测试集,看它和你的现有技能有没有路由冲突。
我现在用技能包里维护了一个CONTRIBUTING.md,记录上传/下载技能的检查流程,算是对团队的一个强制约束。之前有次就是从外部拉了一个技能,里面有测试数据库的地址,没清理就直接上线,导致技能执行时一直连测试库,查了半天才定位到,丢人。
8. 两个小时的“踩坑实录”:我这周刚修复的三个问题
说点实战里最鲜活的部分。就在这周,我修了三个线上问题,每一个都很有代表性。
第八节我这周踩的坑,更直接一些,应该对正在做同类项目的人有参考意义。
8.1 坑一:模型把“创建工单”误解为“查询工单”
用户消息是:“帮我创建一个 IT 工单,我电脑开不了机。”model 却把参数填进了approval_status的 schema,触发了查询逻辑。用户收到的是“您的审批不存在”。这个 bug 很隐蔽,因为是偶发,无法稳定复现。
排查过程:我先查了技能调用日志,发现模型确实选中了approval_status,选中的原因是我在它的描述里写了“查询审批进度”,而用户消息里“开不了机”并没有触发太多关键词匹配。真正的问题是approval_status的“When”部分缺少负向描述,也就是没有明确写“禁止用于创建新审批单”。
修复方式非常简单:在 SKILL.md 的 When 一段里补了一句“仅用于查询已有审批单进度,不能用于创建、提交或撤回审批单”。同时给it_ticket这个创建类技能加了更醒目的触发词示例。修复之后,同样的用户消息几十次测试都没复现。
这个坑给我的教训是:技能的负向定义和正向定义同等重要。你不能只告诉模型“做什么”,还得明确告诉它“不做什么”。
8.2 坑二:上下文污染导致参数携带历史信息
有个用户先问了“昨天我们部门的报销单怎么还没批?”Agent 正确路由到了审批查询。紧接着用户又发了一句“那采购的单子呢?”模型直接把approval_type填成了reimbursement——因为上一次的上下文还在,模型默认延续了之前的“语义习惯”。
这个问题的本质是:模型在决策是否携带历史上下文时,权重太高,导致过度依赖上文。我的做法是在参数抽取前增加一个“上下文清洗层”:当查询参数中缺少明确的关键字段(比如用户没说审批类型),优先以用户当前消息为准,不继承历史上下文中的技能参数,而是把缺失字段标记为“需补充”,启动澄清流程。
处理后用户体验变好了:用户第二句“那采购的单子呢”收到了“请问您是要查询采购申请单的审批进度吗”的追问,而不是直接用历史参数去查报销。虽然多了一次对话,但避免了错误数据的输出。
8.3 坑三:技能内部 API 调用超时未降级
data_fetch技能在后台调用内网数据库接口时,遇到了偶发的连接超时(超过 5 秒)。在无容错设计的版本里,这个超时会直接导致技能执行失败,用户的提问得不到任何回复。
修复方案就是前面提到的三级容错。我在执行器里加了超时重试逻辑,第一次失败后自动重试一次,然后降级到缓存快照。修复后,同样的故障场景下用户依然能拿到基于缓存的数据回复,只是末尾会带一个“(基于缓存,可能存在延时)”。
这里想强调一点:不要让“模型生成回复”感知到降级逻辑里的技术细节。降级是近代码层的事,用户回复只需要告知“数据可能不是最新的”,不需要解释“缓存快照”这种内部术语。面向用户的提示设计也要做一层翻译,保持可读性。
9. 下一步演进方向与个人体会
Agent 技能化这条路,我越走越觉得方向是对的。但目前的 agent-skills 体系还远谈不上完美,我自己的实践里也积累了一些对未来演进的想法。
技能的可观测性还会更重。现在单技能执行链路我靠 JSON log 排查,但多技能编排链路的可观测性还比较弱。我计划下一步给每个技能接入更结构化的 trace——记录每一步的输入输出 schema 和耗时,对接现有监控大盘,做成一个可视化的“技能执行链路看板”。这个做好了,线上问题定位能再快一个量级。
技能的性能开销量级还能继续压缩。目前按需加载已经省了很多 token,但模型每次做“技能路由决策”还是需要消耗几百 token 的推理成本。如果能把高频用户群体的技能选择偏好做成一份局部缓存,甚至对一些完全确定的固定句式(比如“请假余额”“密码重置”)直接走规则引擎绕过模型,延迟还能再压低。
技能市场与共享协作会是下一阶段的主题。单打独斗维护技能库的成本很高,如果能把技能包演进成“类似开源组件”的模式,让不同组织之间共享、评审、共建技能,那么 Agent 能力的增长曲线会陡峭很多。我这边已经在计划把几个不涉密的技能打包开源,反哺社区。
回到我自己的实操体会,最大的一个感受是:别把 Agent 想象成“一个无所不能的大脑”,把它想象成“一个带了一整箱工具的工匠”。工匠能不能把活干漂亮,取决于对工具的理解、摆放和组合调用,而不只是力气大不大。把力气花在磨好每一个技能、理清技能之间的边界与配合关系上,回报会比盲目追求大模型参数值高得多。
我梳理这套实践的时候,其实也等于把最近几个月的踩坑重新过了一遍。如果这篇文章能帮你少走哪怕一两个弯路,省下几个深夜排查 bug 的晚上,那我觉得就很值了。接下来我也会继续迭代自己的技能库,等跑出更有意思的心得,再回来更新这里的内容。