
过去一年我一直在跟 Agent 打交道反复被同一个问题折磨同一个模型有些人调出来的智能体特别“听话”换个人来做就完全不是一回事。后来我意识到差的不是模型而是你有没有把“技能”当作一个正经的工程对象来管理。“agent-skills”想解决的问题说到底就是这一件让 Agent 的能力不再是零散的提示词片段而是一套可以被设计、被复用、被评测、被组合的结构化技能库。这篇文章我会从技能体系的价值讲起把手上的拆解方法、定义规范、落地实现和踩坑实录全部摊开来讲内容偏工程实践适合正在做 Agent 应用、或者准备把 Agent 产品化的朋友参考。1. 为什么 Agent 需要一套“技能”体系1.1 技能和提示词本质上不是一回事我见过很多团队的代码仓库里躺着一堆.md文件里面写满了“你是一个优秀的文案专家”“请帮我润色这段文字”这种话。它们通常被叫做提示词模板偶尔也被叫做“技能”。但严格说这不是技能这只是技能的一小部分。技能是一个包含输入输出协议、执行逻辑、工具调用、容错处理和评测标准的完整工程单元。提示词只是其中“和模型沟通”的那一层壳。举个例子一个“查询天气”的技能如果只是提示词大概长这样请查询一下北京的天气。但一个真正能跑起来的技能至少要包含城市参数怎么传、日期格式是什么、调用哪个天气接口、接口超时了怎么办、找不到城市时是否给出模糊匹配候选、返回结果用 JSON 还是自然语言。这些内容提示词管不了必须在技能定义层解决。这一点想清楚之后很多 Agent 效果不稳定的问题就找到了根因——你以为缺的是提示词技巧其实缺的是技能封装。提示词能决定模型在某一次对话里表现得好不好技能体系能决定 Agent 在长期运行中能不能稳定、可控、可持续迭代。1.2 技能体系重点解决三类核心痛点第一类痛点是可复用性极差。我早期做 Agent 项目时“总结文档”这个能力在三个不同场景里写了三份提示词每次改动都要同步改三处漏改一处就会出现两个 Agent 行为不一致的问题。技能化之后只需要一个“文档总结”技能不同场景通过参数控制风格和长度改一处全局生效。第二类痛点是无法评测。提示词写得好不好全凭感觉线上效果只能通过用户反馈间接判断。但技能不一样技能有固定的输入输出 schema我可以为每个技能准备一组评测用例每次改动都能跑回归测试。比如“信息抽取”这个技能我准备了 100 条测试样本精确率、召回率全部量化改模型版本或者改技能描述时分数一对比就知道改动到底值不值。第三类痛点是不可组合。复杂任务很少由一个技能完成往往是“理解用户意图 → 检索知识库 → 生成回答”这样多个步骤。没有技能化的时候这些步骤都混在一个大逻辑里根本无法独立演进。技能化之后检索归检索、生成归生成我可以单独优化某一步再用编排层把它们组装起来。这让整个系统具备了一种“搭积木”的能力越到后期优势越明显。1.3 什么时候你就该认真建技能库了如果你的项目满足下面任意一条我就建议你别再靠堆提示词了一是同一个能力要在两个以上场景使用二是你开始记录“这段提示词在某某场景下表现好”三是你发现自己反复调整提示词却总是按下葫芦浮起瓢四是你的 Agent 要面向外部用户行为稳定性有硬要求。技能体系本质上是用结构换确定性。它确实比随手写提示词多了一些定义成本但这个成本是值得的。尤其是团队协作时技能库就是团队共同维护的“能力资产”新人来了看技能库里有什么就知道这个 Agent 能做什么不需要再翻聊天记录找提示词。2. 技能拆解与设计从一个需求到一组技能2.1 技能拆解的三层漏斗法把业务需求拆成技能我通常用三层漏斗需求层 → 任务层 → 技能层。假设需求是“做一个能帮用户写周报的助手”。需求层很清楚用户给一堆零散工作记录Agent 输出结构化周报。到了任务层需要拆解成四个任务第一把原始材料按项目归类第二提取每类任务的关键进展第三结合上期周报找出进度差异第四按照周报模板生成最终文本。每个任务再往下一层映射就得到了技能你要一个“文本分类”技能一个“信息抽取”技能一个“差异对比”技能一个“模板渲染”技能。这层拆解很多人容易犯急性子一上来就想找一个“写周报”的大技能。这样做不是不行但复用性会很差。“写周报”只能写给周报换个场景变成“写项目总结”又得重新写一个。拆成基础技能之后“信息抽取”既能用在周报场景也能用在简历解析、订单信息提取等完全不一样的场景。拆解时还有一条判断标准如果一个子任务在其他场景中有可能以同样的方式被使用那它就应该被独立成一个技能如果一个子任务只属于当前业务场景那它可以保留在场景内部的私有技能列表里。按这个标准拆出来的技能库基础技能占比会越来越高业务技能越收越窄整个体系的可迁移性就会很强。2.2 技能定义的五大核心要素一个可落地的技能定义在我这里必须包含五个要素。要素一技能名称。名称要短、要精确、要有区分度。比如“web_search”和“knowledge_base_retrieval”一看就知道是外部搜索还是内部知识库检索。不要去起“helper”“processor”这种含糊的名字技能多了之后路由全靠名称和描述含糊等于没法用。要素二技能描述。描述是给模型看的路由提示。它决定了模型在什么情况下会调用这个技能。描述不能只写功能还要写清楚适用范围和边界。比如“文本分类”技能的描述我会写成对给定文本按指定类别体系进行分类返回各分类标签及置信度分数适用于意图识别、内容归类、主题标注等场景不适合需要主观判断的评价任务。要素三输入参数 Schema。这是技能的外部契约。参数名、类型、是否必填、默认值、取值范围全部要明确定义。参数 Schema 是 Agent 与技能之间的界面界面不稳定上层编排和下层实现都会跟着动荡。要素四执行逻辑。这是技能内部真正干活的部分可能是一段 Python 代码调用一个第三方 API也可能是一段模板提示词加模型调用还可能是两者的组合。执行逻辑需要把参数映射到真实操作并处理各种异常情况。要素五输出格式。输出必须结构化至少是统一封装的 JSON。这样上层编排层才能拿到结果继续做后续处理。输出格式还包括对输出内容的约定比如字段含义、单位、精度尽量避免让上一层去猜测。这五个要素缺一个技能在长期运行中都会出问题。尤其是输出格式有很多人图省事让技能直接返回一段话当时很爽后面编排复杂任务时就会吃苦头因为你想从一段自然语言里再提取结构化信息等于重复造了一次轮子而且更容易出错。2.3 技能描述怎么写模型才“认”技能描述是生产环境里最容易被忽视但影响最大的部分。模型在大段技能清单里做路由选择本质上是在做语义匹配描述写得好不好直接决定路由准确率。我总结了几条经验。第一描述要以动词开头准确表达动作。“搜索、检索、提取、总结、生成、对比、分类”每个动词背后代表一类操作模型对动词的语义是敏感的含糊的描述会让模型难以判断。第二要写清楚触发场景用“当用户需要……时调用”给模型一个明显的“信号”。比如“当用户需要查询最新资讯或网页内容时调用”模型一眼就能对上号。第三要写反例边界。描述里明确“不适用于什么”非常有用。比如“本技能只用于事实性信息检索不适用于情感分析或主观评价”。这能显著降低模型乱调技能的概率。第四同一个技能的描述要为不同时机的路由分别维护。如果技能列表很长模型第一轮只需要判断大类详情描述可以在选定大类后再暴露给模型。我做过一次实验把 40 个技能的一次性列表改成先分 6 个大类再二次路由路由准确率从 82% 提升到 95%。这个提升不是模型变强了而是决策难度降低了。2.4 技能粒度拆多细才合适技能粒度是最难平衡的问题。拆得太粗复用性差拆得太细技能数量爆炸路由困难维护成本也高。我个人的经验是一个技能的输入输出如果超过 7 个参数说明它太粗了两个技能如果总是成对出现说明它们可能该合并成一个。举个例子“生成销售周报”本质上包含“数据汇总”和“报告生成”两个技能如果它们每次都在一起调用不如合并成一个“销售周报生成”技能把内部流程封装起来。反过来“获取客户信息”这个技能如果既要查客户基本资料、又要查客户订单、又要查客户售后记录那它其实该拆成三个。判断标准就是看“变化概率”和“复用面”如果一部分逻辑变化频繁、另一部分趋于稳定拆开更明智如果某一段逻辑可能在多个场景中复用也应该独立出来。3. 技能落地实现构建一个可直接调用的技能库3.1 技能库的目录结构与元信息设计我在项目里通常用一个skills目录来组织所有技能每个技能占一个子目录目录名就是技能名。一个典型的结构长这样skills/ web_search/ SKILL.md schema.json run.py tests/ cases.json info_extract/ SKILL.md schema.json run.py tests/ cases.json doc_summarize/ SKILL.md schema.json run.py tests/ cases.jsonSKILL.md 是技能的“人读”文档描述技能的用途、适用场景、使用注意事项。schema.json 是技能的“机读”说明定义输入输出参数。run.py 是技能的“执行”入口。tests 目录存放技能级评测用例。这套结构的好处是每个技能都有独立生命周期可以被 Git 单独追踪。我可以用代码评审的方式审技能改动也可以在技能出现回归时单独回滚它而不会影响其他技能。对于一个几十技能规模的项目来说这种结构上的独立性会让迭代效率有很大提升。3.2 技能描述的元信息不只写给人看很多人在 SKILL.md 里写作用、写注意事项但很少写成结构化的元信息。我的做法是给 SKILL.md 增加一个 YAML front-matter里面写好路由信息。这样技能既能被人类阅读也能被程序解析。一个典型的示例--- name: doc_summarize description: 对输入文档进行要点提炼生成结构化摘要适用于会议纪要、报告、论文等长文本不适用于代码审查或数据统计。 input: document: string, 必填, 原始文档内容 max_points: integer, 选填, 摘要要点数上限, 默认5 language: string, 选填, 输出语言, 默认zh output: summary: string, 摘要正文 points: array, 要点列表 word_count: integer, 摘要字数 ---这个 front-matter 可以用程序直接读出来拼装成模型路由时看到的技能列表。我在实际项目中技能列表不是手写死在某段代码里的而是启动时扫目录自动生成。新增技能只需要在skills目录下加一个子文件夹Agent 能力就能扩一份这套机制让技能库扩展变得非常轻。3.3 技能的执行封装把工具调用和模型调用统一起来技能的执行逻辑不能五花八门要有一个统一的执行接口。我的实现思路是让run.py暴露一个run(params: dict) - dict函数所有技能都遵守这个签名。好处是上层编排层可以无差别调用任何技能传入一个参数字典拿到一个结果字典。在 run.py 内部我建议把工具调用和模型调用分开。比如 doc_summarize 这个技能本质上需要先对文本做分段再逐段调用模型提炼要点最后合并生成摘要。工具调用的部分可以用普通 Python 代码实现模型调用的部分单独封装一个call_llm的公共模块方便统一管理模型版本、温度参数和 token 上限。还有一个容易被忽略的细节给每个技能加上 execute 的超时控制。Agent 跑起来之后很多技能会因为外部接口变慢或者模型推理超时而拖住整个链路没有超时控制会让用户感受到“卡死”。我习惯把超时拆成两层工具调用超时和模型调用超时默认分别 15 秒和 60 秒超时后技能返回一个统一的timeout_error结果由上层决定重试还是降级。3.4 技能编排把原子技能串成工作流有了基础技能下一步就是编排。编排层的目标是根据用户请求决定调用哪些技能、按什么顺序调用、如何处理中间结果。我试过两种主流方案。第一种是代码编排用 Python 写死工作流。比如“处理用户周报请求”这个流程在代码里就是先调用classify_task再调用info_extract最后调用doc_summarize。这种方案优点是完全可控、可调试缺点是不灵活流程变化需要改代码。第二种是模型编排让模型基于技能元信息动态决定调用顺序。这种方案灵活但可控性差容易出现模型用错技能、跳步、死循环等问题。我的实践是尽量用代码编排固定主流程在固定的主流程之间允许模型做局部选择。混合编排既保证了关键路径的稳定性又保留了模型选择的弹性。我还给编排层设计了一个简单的任务状态对象记录每个技能调用的输入输出、耗时、成功/失败状态。这相当于给 Agent 加了“操作日志”排障时能清晰看到是哪个技能出了问题、哪一步耗时超标、哪一步产生了错误输出。这套日志极大地缩短了我的排障时间强烈建议大家在早期就把埋点做好不要等出了问题再补。3.5 技能评测与回归技能库质量的生命线技能库建起来之后最怕的就是“改坏了”。模型升级、技能描述修改、参数调整都可能让某个技能表现退化。没有评测这些问题只能靠线上用户抱怨来发现代价太大。所以我坚持为每个技能建立评测集技能越基础评测集要越完善。评测集至少包括三类样本常规样本、边界样本、错误样本。常规样本覆盖典型输入边界样本覆盖空输入、超长输入、异常格式错误样本覆盖那些必须拒绝或给出明确错误信息的场景。以“信息抽取”为例常规样本是正常的订单信息文本边界样本是一个超长合同文本错误样本是只有一句“你好”却没有可抽取信息的情况。我每两周跑一次全量技能评测回归模型或技能库有改动时立即触发一次。跑完自动生成一份报告对比每个技能的精确率、召回率和耗时。有了这份报告“最近 Agent 变笨了”这类模糊反馈就能迅速定位到具体技能。评测是 Agent 工程质量的地基这门课偷懒不了。4. 常见问题与排查技巧实录4.1 技能调用失败的典型根因我踩得最多的坑是输入参数类型不匹配。技能的 schema 写了max_points是 integer但模型传了一个字符串 5。小项目里这种情况能侥幸跑通技能多了以后必然炸。解决方法是所有参数进入技能前先做一层严格校验类型不对就抛错而不是在技能内部进行隐式转换。隐式转换的毛病在于它会把问题掩盖住等数据流到深层逻辑里才爆炸排障成本成倍增加。第二类高频问题是技能内部调外部 API 的鉴权失败。这个问题比较隐蔽因为不是每次都会出问题。我后来做了一个统一的credentials管理模块所有技能都不能自己存密钥必须从统一模块里取并且定一个规则凡是涉及到密钥变更必须立刻跑一次相关技能的评测集避免“改完配置后某个功能悄悄失灵”的尴尬局面。第三类问题是技能输出不符合 schema 约定。尤其是模型直接输出结果的技能经常多给字段、少给字段、嵌套结构不对。解决方法是模型输出后加一个协议校验层校验不通过就自动重试一次重试还不通过就返回错误不要硬着头皮往下走。协议校验的成本很低但能挡住大量脏数据。4.2 技能路由混乱与描述冲突技能多了之后路由冲突是必然的。最典型的情况是两个技能都能处理用户的同一类请求模型一会儿选这个一会儿选那个用户就感觉 Agent 行为不稳定。我给技能列表做了一次“互斥性审查”专门找那些描述存在重叠的技能。比如早期我的技能库里有“文档总结”和“会议纪要生成”两个技能描述都出现了“对会议内容进行整理”。模型经常混用。后来我把边界重新划清会议纪要生成只负责“把原始会议记录转成格式化纪要”文档总结负责“对任意文档提取要点”。描述里互相加了排除语“会议纪要生成”写明“不适用于非会议类文档”“文档总结”写明“不适用于需要保留原始结构的格式转换”。改完之后路由准确率明显上升。冲突排查还有一个技巧在技能评测集里加上“路由测试”专门测试一段请求会被模型路由到哪个技能。如果两个技能都频繁出现在结果里就要及时调整描述或技能边界。路由测试应该成为技能库的常态化检查项等上线后再发现路由混乱代价就大了。4.3 技能膨胀什么时候该合并或拆分用技能库的时间越久技能数量越容易膨胀。我见过一个项目从 20 个技能膨胀到 80 多个看上去很丰富实际上有一半技能调用率极低还有一部分功能重叠。技能数量多未必是好事因为路由空间越大会决策越困难维护成本也越高。我每季度会做一次技能盘点看两个数据调用次数和成功次数。调用次数低于阈值的技能要么下掉要么合并到通用技能里。功能重叠的技能直接合并。比如“PDF 内容提取”和“Word 内容提取”两个技能本质上都是在做“文档解析”我合并成一个单参数file_type控制的“文档解析”技能减少一个技能就少一份维护和路由负担。还有一个值得警惕的信号如果某个技能的描述里出现了大量条件分支句式说明这个技能已经在承担超出它职责的能力了。比如描述里出现“当输入是 A 类型时执行 X当输入是 B 类型时执行 Y”这通常是要拆成两个技能的信号。技能职责单一才能保证描述简单、路由准确、行为稳定。4.4 三个最值得记住的实操坑第一个坑不要完全相信模型编的技能调用参数。早期我让模型自己决定技能入参结果经常出现模型编造城市名、编造日期的情况。后来我在编排层强制要求凡是技能入参必须来自上游可信数据优先从前一个技能的结构化输出里提取不直接采信模型从用户话里猜测的值。实在需要猜测那就额外加一个“参数确认”环节让用户确认后再调用技能。第二个坑别在技能描述里写“如果...就...”这类规则。描述是给模型做语义匹配用的不是给模型做逻辑推理用的。你把复杂规则写进描述模型大概率不会严格执行反而会影响路由。复杂的判断逻辑应该放到编排层代码里让代码来做决策模型只负责做它擅长的事情。第三个坑技能评测集不是一次性资产。评测集要跟着业务演进而不断扩充。我每次在线上发现问题都会把出问题的输入样本追加到评测集里形成“线上回流”机制。这样每个被修复的 bug 都会变成一次永久回归保护技能库的质量就会像滚雪球一样越滚越好。不回流评测集就会慢慢陈旧最后沦为一堆没有意义的数字。我在实际项目中还有一个小习惯每当技能系统做了较大改动我会重新读一遍全部技能的描述站在一个完全不了解技能库的模型视角去检查如果只给我这些描述我能准确选出合适的技能吗这种做法虽然耗时却总能发现很多平时注意不到的歧义和边界问题。技能库维护是一场持续投入的工程活但它的每一分投入最终都会反映在 Agent 的稳定性和用户体验上。