
1. 为什么需要给 Agent 做“技能模块化”1.1 裸 Prompt 的失控现场先说说我看到的实际现象。很多人第一次接触 Agent 开发时第一反应是把所有指令写进一个超大 Prompt 里——“你是一个全能助手能帮我写周报、也能管理日程、还能分析数据当用户提到周报时你要先……提到日程时你要……”这种写法在小规模演示里确实跑得通模型也能勉强应付两三个任务但一旦任务超过五个或者你开始对接真实业务数据整段 Prompt 就会变成一个谁也管不住的黑箱。最典型的问题有三个一是任务描述之间互相覆盖比如“整理会议纪要”和“提取待办事项”这两条指令经常被模型混着执行输出结构时好时坏二是一次对话里塞入太多上下文模型容易“忘记”早期指令尤其是在长对话里后面的输入一多前面的约束就被稀释了三是任何一处小改动的副作用不可控你以为只是加了一句话结果模型在另一个任务上的表现莫名其妙变差了而且你还不知道是哪句话导致的。这些都是我在项目里实际踩过的坑不是理论推演。那时候我就在想能不能把“让 Agent 做某件事的能力”本身当成一个独立的、可插拔的模块来管理。就像后端开发里把数据库访问封装成 Repository把外部接口封装成 Client 一样——每个模块有明确的输入输出、有独立的版本、能单独测试、也能组合复用。这就是我从 2024 年年底开始在我的项目里落地 agent-skills 的初衷。所谓 agent-skills就是在 Agent 应用里以标准化结构封装的一组“可复用技能单元”每个单元负责一类具体的任务能力比如“提取待办”、“生成周报”、“分析报表”并且通过统一的接口协议被 Agent 调度和执行。1.2 Skill 到底解决了什么问题把技能从 Prompt 里拆出来之后最直观的感受是开发方式从“写提示词”变成了“定义能力”。这两者有本质区别。写提示词是在跟模型沟通希望它“理解”你的意图而定义能力是在给模型提供一套清晰的执行框架它只需要学会“什么场景调用哪个能力”就行。前者依赖模型的临场发挥后者把不确定性收缩到了一个可控的范围内。具体来说一个设计良好的 Skill 能带来四件实打实的好处。第一行为稳定。同一个 Skill 每次执行的输出结构是固定的你可以用 JSON Schema 约束它下游代码不需要写一堆防御逻辑去猜测返回格式。第二可测试。每个 Skill 是独立单元你可以单独喂给它一组输入验证它返回的结果是否符合预期回归测试也只需要跑这个 Skill 的用例集不用把所有场景重新过一遍。第三可复用。同样一个“网页内容清洗” Skill既可以用在舆情采集流程里也可以用在文档归档流程里只要输入输出协议一致就能无缝接入。第四可迭代。你对某个技能有优化想法时只改这个 Skill 的版本就够了不用动整个 Agent 的业务逻辑升级和回滚都很干净。1.3 Skill 和 Function、Tool、Plugin 到底是什么关系这里有必要澄清几个概念因为我在社区里经常看到有人把 Skill、Function Calling、Tool、Plugin 混着说。我的理解是Function Calling 是模型调用外部函数的一种协议能力它是底层机制Tool 是暴露给模型的一个可调用函数的封装形态通常包含函数名、描述和参数 SchemaPlugin 更偏向产品层是一组功能和 UI 的整体打包。而 Skill 是一个介于 Tool 和业务逻辑之间的概念它是一个“完成某类任务”的能力单元内部可能调用多个 Tool也可能只有自身的 Prompt 和推理逻辑最终向 Agent 暴露一个统一的入口。打个比方如果 Tool 是一把螺丝刀Skill 就是“用螺丝刀把面板拆开再换掉故障零件”的整套标准作业流程。它不只是知道怎么拧螺丝还知道先拆哪颗、后拆哪颗、拆完怎么装回去、装完怎么验证。理解了这层关系你就知道为什么说“给 Agent 多加点 Skill”不等于“多注册几个 Function”了。Function 解决的是“能做什么动作”的问题Skill 解决的是“怎么把一件事做完整”的问题。2. Skill 的结构设计与定义规范2.1 一个 Skill 的标准组成部分要落地 Skill 体系第一步是把结构定下来。参考我在多个项目里的实践一个设计完整的 Skill 应该包含六个部分元信息Meta、输入协议Input Schema、输出协议Output Schema、执行逻辑Execution Logic、依赖声明Dependencies、测试用例集Test Cases。这六个部分不是可选项而是我在项目里验证过的最低配置缺任何一环都会在后续迭代时付出代价。元信息是 Skill 的身份证包括唯一标识符、版本号、作者、描述、标签。其中描述字段非常重要因为它直接决定了 Agent 的调度器能不能在合适的时机把合适的 Skill 选出来。我见过很多团队在写描述时非常随意就写一句“处理文档”结果模型根本不知道这个 Skill 到底能处理什么文档、在什么场景下用、输出长什么样、跟其他 Skill 有什么区别。这样的描述等于没写。一个合格的描述应该包含触发场景、适用对象、可执行的典型任务、以及不适用的情况尽量用两到三句话把边界描述清楚。输入协议和输出协议决定了这个 Skill 对外部的接口契约。输入通常用 JSON Schema 定义输出建议也尽量用结构化数据即使你的业务最终需要自然语言报告也应该拆成“结构化中间结果 自然语言渲染层”两部分。这样做的原因是结构化输出方便后续程序继续处理自然语言只是给人看的。如果整个链路都是自然语言那下游任何一步想做程序化判断都会变得非常痛苦。2.2 输入输出协议把接口定清楚输入协议的设计是 Skill 成败的分水岭。我强烈建议在设计 Skill 之前先把所有输入字段的意义和边界列出来宁可多花半天时间做字段梳理也不要边写边加。因为协议一旦被多个调用方使用改字段名、改类型、改必填项都是破坏性变更牵一发而动全身。举个例子我在设计“会议纪要整理”这个 Skill 时最初只定义了 transcript访谈原始文本这一个输入字段。后来发现实际使用中有不同来源的纪要需要处理有人直接粘贴录音转写文本有人上传 PDF 会议记录有人只给了一份待整理的事项列表。这三个场景对字段的要求完全不同。如果不区分场景统一走一个输入字段Skill 内部就要写大量判断逻辑而且很难兼顾。后来我把输入协议改成了这样{ $schema: http://json-schema.org/draft-07/schema#, type: object, properties: { source_type: { type: string, enum: [transcript, pdf_document, structured_notes] }, raw_content: { type: string }, options: { type: object, properties: { language: { type: string, description: 输出语言, default: zh-CN }, include_decision: { type: boolean, description: 是否提炼决策项, default: true } } } }, required: [source_type, raw_content] }这样设计的好处是调用方很清楚自己该传什么Skill 内部也可以根据 source_type 走不同的预处理逻辑。输出协议也一样我会固定一个顶层结构比如包含 summary、decisions、action_items、risks 这几个字段每个字段定义好类型和描述。这样下游无论是存数据库还是渲染成报告都有稳定的数据抓手。2.3 依赖声明与资源管理Skill 不只是 Prompt 和代码它在执行时可能依赖外部资源。常见的依赖有几类模型依赖比如某些 Skill 需要支持长上下文的模型才能跑、工具依赖比如需要调用浏览器工具、代码解释器、数据依赖比如需要读取某个内部 API 或者本地数据库、第三方服务依赖比如需要访问某个外部服务。这些依赖如果不显式声明在 Skill 迁移或者多人协作时就会出大问题。我建议在每个 Skill 的 meta 里增加 dependencies 字段用数组列出所有需要的资源标识。同时在执行层做依赖检查一个 Skill 在被调度之前先校验它依赖的资源是否就绪如果没就绪就直接返回一个明确的错误码而不是执行到一半才报错。这个设计我在早期没做结果有一次某个 Skill 的模型依赖被悄悄换成了一个更小的模型部分功能因为上下文不够直接静默降级了排查了很久才定位到问题。后来我加了依赖声明和启动检查这类问题基本绝迹。另外要提醒一点Skill 内部尽量不要硬编码环境相关的信息比如 API Key、数据库连接串、文件路径这些。正确做法是全部通过统一的配置注入。原因有两个一是安全硬编码等于把密钥写进代码库泄露风险极高二是可移植性同一个 Skill 要在测试环境、预发布环境、生产环境间迁移硬编码路径会让迁移变成噩梦。我自己的实践中所有外部依赖都通过一个 environment context 对象传入Skill 内部只声明“我需要什么”不关心“这个依赖从哪里来”。2.4 Skill 的命名与描述规范命名和描述看起来是小事实际上是整个 Skill 体系最容易翻车的隐形工程问题。名字起不好描述写不清楚直接后果就是 Agent 在技能路由阶段选错技能、或者压根不选中任何技能。这个问题在 Skill 数量超过 20 个之后会变得尤其明显我见过有团队做了 30 多个 Skill结果模型频繁把一个叫“generate_report”的 Skill 当成“analyze_data”来用就是因为两者的描述里都出现了“数据分析”“报表输出”这些词边界没有划清。我自己的约定是Skill 名称统一用动词开头比如“extract_action_items”“summarize_meeting”“classify_ticket”这样模型一看名字就大概知道这个技能是干嘛的。描述部分采用三段式第一段说清楚适用场景第二段给出典型任务示例第三段说明不适用的情况。最后一段非常关键它能帮模型做负向排除。比如“extract_action_items”的描述可以写“适用于从会议记录、聊天记录或需求文档中提取明确的任务事项。典型任务包括从会议纪要中提取待办、从邮件中提取责任人、从需求文档中提取交付物。不适用于回答一般性问题、生成总结报告。”你别说这段“不适用”的描述在减少技能误调上的效果比其他任何参数都明显。3. 从零落地一个 Agent-Skills 套件3.1 场景选择与技能拆解理论讲再多不如亲手跑通一个例子。我拿一个实际做过的场景来说明搭建一个“销售周报助手”Agent它接收销售团队一周的零散工作记录自动整理成一份结构化的周报。在这个场景里我先带着团队做了一次技能拆解会议最终把整个流程拆成了四个 Skill。第一是“extract_work_items”负责从原始文本里提取一条条的工作事项。输入是一大段杂乱的记录输出是结构化的条目列表每条包含时间、工作内容、涉及客户、成果描述。第二是“classify_work_type”把提取出来的条目按预定义类型打标签比如“客户沟通”“方案制作”“内部协调”“培训学习”。第三是“score_priority”根据紧急程度和重要程度给每条工作打一个优先级分规则是团队内部商量好的。第四是“compose_weekly_report”把前三个 Skill 的输出汇总起来生成一个符合周报模板的自然语言报告。这次拆解让我印象最深的点是不要试图让一个 Skill 一口气完成从“原始输入”到“最终报告”的整个流程。很多人会觉得“我直接把所有逻辑写在一个 Skill 里不就行了”短期看确实行但一旦你的报告模板变了、或者你想在中间环节插入一个人工审核步骤你会发现一个大而全的 Skill 几乎没法改。拆成四个小 Skill 之后每一步都能独立优化、独立测试中间环节也随时可以替换。3.2 定义数据模型和 Schema技能拆解完之后紧接着要做的是把每个 Skill 的输入输出数据模型定下来。这一步我建议先画一张“数据流转图”不用画得很复杂就在白板上列出每个 Skill 的输入字段和输出字段然后用箭头连起来看看上游的输出是不是能天然成为下游的输入。如果两个 Skill 之间的字段对不上趁现在调整一定是最省成本的。以我的周报助手为例四个 Skill 的数据流是这样的extract_work_items 输出一个 work_items 列表列表里每项包含 task_time、description、related_customer、result 四个字段classify_work_type 的输入是 work_items输出是加了一个 work_type 字段的增强列表score_priority 的输入是带类型的工作列表输出是加了 priority_level 和 priority_reason 字段的列表最后 compose_weekly_report 接收完整的工作列表输出的是 { title, summary, sections, suggestions } 四个字段的最终报告对象。这里有一个非常重要的实践细节每个 Skill 的输出字段必须在 Schema 里写明每个字段的枚举值或取值范围。比如 priority_level 的取值只有三个high、medium、low不允许模型自由发挥写“非常紧急”之类的文字。这样下游做排序、过滤、统计的时候才有确定性。我在做这个项目的时候团队成员第一次把 priority_level 设成了自由文本格式结果模型输出了“重要且紧急”“一般”“先放一放”等十几种漫无边际的说法后面做统计匹配极其痛苦后来改成枚举类型一次解决。3.3 实现 Skill 执行层数据协议定好之后下一个问题是怎么“执行”一个 Skill。在我的实践里执行层分为两种形态一种是不需要写代码、完全靠模型推理的 Prompt-based Skill另一种是需要调用工具、代码、外部 API 的 Tool-based Skill。刚开始做的时候我建议优先把 Skills 实现成前者也就是“一个定义良好的 System Prompt 一组输入输出 Schema 少量校验逻辑”等跑通之后再逐步把涉及外部资源的环节替换成 Tool-based。这里我分享一个实现 Prompt-based Skill 的内部模板经过多次迭代后已经很稳定了。整体结构分四段角色定位、执行步骤、输出约束、反例提示。角色定位告诉模型“你在执行一个什么任务”执行步骤是给模型提供可遵循的操作流程降低发挥空间输出约束把输出格式卡死反例提示告诉模型“这些情况不要做”。举一个 compose_weekly_report 的例子你是一名销售运营助理负责把团队一周的工作记录整理成周报。请严格按以下步骤执行1. 通读输入的 work_items 列表2. 按 work_type 字段分组3. 在每组内按 priority_level 从高到低排序4. 生成总结段落说明本周主要进展和关键指标5. 输出 JSON格式必须符合 Output Schema。注意不要在报告中添加输入数据中不存在的信息不要臆造客户名称和数字如果输入列表为空请在 summary 字段直接返回本周暂无工作记录。注意这里每一步都是显式指令模型没有太多自由发挥空间。有人担心“这样写是不是太死板了”我的经验是Agent 任务的稳定性永远优先于创造性。你需要创造性的是 Skill 的设计过程而不是 Skill 的执行过程。3.4 技能路由与选择策略有了多个 Skill 之后Agent 怎么知道当前这个用户请求该用哪个 Skill这就是技能路由要解决的核心问题。在实践中我见过三种主流方案各自适用不同阶段。第一种是规则路由维护一张关键词和规则表命中后直接绑定 Skill。优点是简单可控、零模型开销缺点是泛化能力极差换个说法就失灵。第二种是语义路由把用户输入和每个 Skill 的 description 一起丢给模型让模型判断该调用哪个。这种方式是最推荐的成本低而且效果不错元信息里的描述是否写得清楚就直接决定这条路的效果。第三种是向量检索路由把用户输入和 Skill 描述都向量化在向量数据库里做相似度匹配返回 Top-K 候选再交给模型或规则做最终决策。这种方式适合 Skill 数量很大、超过 100 个的场景但实现和维护成本也高。我的实践建议是如果只有 10 个以内的 Skill直接用语义路由就够了用一个模型调用把所有 Skill 的描述传进去让它输出一个选择决策。注意这里的决策信息一定要结构化输出包含 selected_skill_id、confidence、reason 三个字段。confidence 低于阈值时Agent 应该采用兜底策略而不是硬着头皮执行。我在这上面吃过亏当时模型以 0.5 的置信度选了一个技能我以为它能行结果输出完全跑偏后来加了置信度阈值校验0.7 以下直接走“澄清问题”分支明显稳了很多。3.5 测试与回归机制Skill 体系最大的优点之一是可独立测试但前提是你真的把测试建起来了。我推荐每个 Skill 维护一个 cases 目录里面至少包含三组用例正向用例happy path正常的输入输出对、边界用例空输入、超长输入、缺失字段、枚举值之外的输入、负向用例明确应该拒绝的场景。正向用例的意义是确信“这个功能没退化”。我会把每个 Skill 在迭代关键版本时的输入和输出快照存下来之后的改动都拿这批数据回归。边界用例的价值在于防止系统偶发崩溃。我印象最深的一个案例是某个 Skill 接到的输入字段是空字符串模型当时的处理方式是把空字符串当成了“没有输入”然后生成了完全编造的答案。后来我在执行层加了校验逻辑——如果关键输入字段为空直接返回错误码 ERR_EMPTY_INPUT不允许模型临场发挥。负向用例则是你把控安全性的最后一道防线。比如“销售周报助手”里的 extract_work_items如果输入内容是一段与销售完全无关的闲聊我希望它返回“无法识别到有效工作事项”而不是硬凑几条看起来像样实则编造的内容。这个能力必须靠负向用例反馈给模型让它在设计中就知道什么情况下要“拒答”。没有这一步你会在大规模应用时被模型幻觉问题打一个措手不及。4. 常见问题与排查技巧实录4.1 Agent 不会正确调用 Skill 怎么办我收到过最多的咨询就是“我的 Agent 根本不会调用 Skill或者总是调错”。面对这个问题我先不让你去检查代码而是让你先检查 Skill 的描述和名称。我在 2.4 节里强调过的内容在线上环境里会成倍地放大。排查路径是这样先看模型的输出日志里选中的 skill_id 是什么再看这个 Skill 的名称和描述是什么最后想一下在模型眼里当前这个用户问题和这个 Skill 描述之间是否真的存在强关联。如果描述写得含糊不清模型调错不怪模型怪你自己。另外一个高频坑是同时注册的 Skill 过多。早期的 Agent 框架里所有工具都一次性传给模型模型面对的候选越多选择错误率越高同时 prompt 变长还会挤压真正的上下文空间。我的处理办法是引入“预筛”流程先用向量检索把几百个 Skill 粗筛到 3 到 5 个候选再把候选描述丢给模型做最终选择这样错误率和上下文开销都显著下降。这套方案在几十个 Skill 的中型项目中已经足够不必一开始就上复杂的企业级路由框架。4.2 Skill 之间职责重叠怎么处理Skill 一多边界一定会模糊。比如“analyze_data”分析数据和“generate_report”生成报告在很多人的定义里几乎就是一个东西但在我这套体系里我应该让“analyze_data”侧重统计计算和结论提炼“generate_report”侧重把分析结论按模板展开成完整文档。边界切的越清晰模型选错的概率越小。遇到重叠问题时我的做法是先从描述上做负向排除——在各自的 description 里明确写“这个任务不在本技能范围”。如果负向排除之后还是频繁选错就要考虑是不是该合并 Skill或者把公共部分抽成子模块。比如多个 Skill 里都需要做“文本清洗”我会把清洗逻辑抽成一个共享子工具而不是在每个 Skill 里各写一份这样既减少了重复代码也避免了各版本行为不一致的问题。4.3 技能执行失败如何降级线上环境里Skill 不可能永远执行成功。模型超时、返回格式不合法、校验不通过、外部依赖挂了这些都是家常便饭。我的原则是每个 Skill 在失败时都必须返回结构化错误码而不是抛一个异常或者返回一段自然语言的“我失败了”。错误码要尽量具体比如 ERR_MODEL_TIMEOUT、ERR_INVALID_OUTPUT、ERR_DEPENDENCY_UNAVAILABLE这样上层调度才能根据错误码决定是否重试、是否换一个 Skill、还是直接告知用户稍后再试。另外一个经验是重试时不要原样重试而是做输入扰动。比如模型超时可能是并发太高重试时可以降低模型温度校验失败可能是模型输出格式漂移重试时可以在 Prompt 里追加一句“请严格注意输出格式上一次格式不合法”。我观察过第二次带反馈的重试成功率比盲试高很多。这是很多人的盲区——失败后只顾着重发请求却没有给模型任何额外的帮助信息。4.4 版本更新踩过的坑Skill 版本管理看起来很基础但做起来容易掉进两个隐坑。第一个是没有“缓存失效”机制。模型输出的结果会被缓存但 Skill 更新了之后旧缓存还在用户拿到的还是旧逻辑结果。你要确保 Skill 内容更新时相关缓存的 key 也要带上 skill version 字段不匹配就自动失效。第二个是跨 Skill 的兼容性。上游 Skill 输出协议改了下游 Skill 的输入协议如果没有同步更新整条链路会在运行时才报错。强烈建议在 CI 里加一道协议兼容性检查——每次更新 Skill 时自动比对所有相邻 Skill 之间的输入输出 Schema 是否有破坏性变更。我自己的习惯是每个 Skill 的版本号就写在它的 meta 文件里每次改动必须更新版本号并附上 changelog。虽然这套流程在项目早期看起来有点重但等你的 Skill 积累到几十个、团队也开始有几个人协作的时候没人能在没有版本记录的情况下说清楚“这个行为是哪个版本引入的”。那时候你再回头看会觉得这套版本管理机制帮你省下了大把排查问题的时间。5. 经验总结与工作流沉淀如果说这段实践之旅教会了我最重要的一件事那就是Agent 应用开发的核心瓶颈不完全是模型能力而是工程化水平。你给 Agent 配了多强的模型都不如把它需要做的每个动作封装得清晰、稳定、可维护、可测试。agent-skills 本质上是一种工程手段它把“让模型做好一件事”拆成了“定义清楚一件事”和“让模型按约定完成一件事”两个更可控的问题。我给正在尝试落地的朋友一个建议不要贪多求全先挑一个你最常用的业务场景拆出三到五个 Skill把每个 Skill 的输入输出协议和描述规范打磨到位再把测试用例建起来。你自然会发现Agent 的整体表现会上一个台阶而且这个体系会越长越顺。等你积累足够多的技能之后你会发现原来零散的工具函数正在聚合成一张越用越懂得如何协作的任务网络这才是 agent-skills 真正有趣的地方。