如果你最近在折腾大模型应用肯定绕不开一个词skills。我第一次看到这个命名的时候下意识觉得又是包装概念直到在项目里真正用了一轮才发现这东西确实把工具调用的开发体验改变了不少。它不是什么新框架也不是新协议而是一套把模型能力外接这件事标准化的思路——把某个具体任务所需要的指令、脚本、参考信息打包成一个可复用的技能单元模型在需要的时候动态加载、按需调用。这篇文章就围绕 skills 这个主题把我实际做过的技能设计、代码实现、踩坑记录全部梳理一遍。适合已经在用大模型 API 做应用的开发者也适合刚接触 Agent 开发、想搞明白技能和工具到底差在哪的朋友。1. 理解 skills 到底在解决什么问题1.1 从一段让人头疼的提示词说起做过 Agent 开发的人都有过这种体验想让模型执行一个稍微复杂的任务比如把会议录音的转写文本整理成格式化的会议纪要最朴素的写法就是在系统提示词里塞一大段规则。一开始还好等业务规则越来越多提示词动辄几千字每次请求都要把所有规则重新发给模型浪费 token 不说模型还经常把早期规则忘掉尤其是中间穿插了好几轮多轮对话之后。我见过一个团队的做法更粗暴把所有工具说明、业务规范、示例模板全部拼进 system prompt结果上下文窗口被占掉一大半模型回答质量肉眼可见地下降。这根本不是模型能力问题而是工程结构问题。skills 的思路正好对症——把不同任务所需的知识和脚本拆成独立模块模型先读技能清单确认需要哪个技能再加载对应内容用多少加载多少。1.2 Skills、工具调用、模型上下文协议之间的关系很多朋友第一次接触这三个概念容易混我用自己的理解说清楚一点。工具调用Function Calling是模型输出结构化调用指令的能力底座它解决的是模型决定调用哪个函数、传入什么参数的问题。模型上下文协议MCP是工具和数据的传输标准解决的是不同客户端怎么统一连上外部工具的问题。而 skills 更侧重任务级的封装——一个技能里可以包含多个工具调用、一段专有的指令模板、一批参考数据甚至还可以内嵌一套自己的工作流。打个比方工具调用相当于给你一把螺丝刀MCP 相当于统一了螺丝刀的接口标准而 skills 是一整套怎么用螺丝刀组装一台柜子的作业指导书加配套工具箱。螺丝刀你有很多把真正让你高效干活的是那个装了指导书和专用工具的盒子。1.3 一个技能包的典型构成我实际用下来的经验一个完整的技能包通常包含三部分。第一是描述文件用 YAML 或 Markdown 开头写成里面写名字、用途描述、适用场景、参数定义这段是给模型看的决定了模型要不要用、什么时候用、怎么用这个技能。第二是执行逻辑可以是一段脚本、一个命令行程序或者一个 API 调用封装技能被选中后由它完成实际计算。第三是参考资源比如模板、示例、分类规则、历史案例模型在执行时可以读取这些内容来提升输出质量。这里有一个关键设计技能不是只给模型看文档也不是只给程序跑脚本它是文档 代码 数据三者的组合。模型理解任务、拆解步骤脚本负责精确计算数据提供领域知识各干各的活。后面我会用一个具体例子把这三部分串起来。2. 技能设计先想清楚边界再写代码2.1 技能粒度怎么定技能粒度是设计阶段最需要想清楚的问题。定得太粗一个技能里塞了太多任务类型模型不知道该调用描述也不好写。定得太细技能数量爆炸模型在清单里选择困难管理成本也上来了。我常用的判断标准很简单一个技能必须能回答三个问题——输入是什么输出是什么在什么条件下触发。如果这三个问题里有一个回答得含糊说明粒度不对。比如整理文档这个技能就不合格因为输入可能是音频、图片、PDF、网页输出可能是摘要、思维导图、翻译稿触发条件也不明确。拆成会议转写整理PDF合同风险提取网页文章摘要三个技能就清楚多了。这里还牵扯到一个取舍技能数量不是越少越好也不是越多越好。以我目前带过的项目来看二三十个技能以内模型选择基本不会出乱子超过五十个就要考虑给技能加元标签做分组筛选了。2.2 描述文件是给模型看的说明书整个技能包里面描述文件的优先级最高但很多人恰恰最不重视。描述写得好不好直接决定模型调用率。如果你发现模型总是忘记用某个技能九成是描述文件写得不够清楚。我总结出一套描述文件的写法开头一句话说清楚技能是干什么的使用主动语态动词开头。接下来写适用场景和不适用的场景尤其要写不适用这能帮模型排除干扰项。然后写参数定义参数名、类型、取值范围、是否必填越精确越好。最后写一个使用示例用一两句话展示什么情况下该调这个技能。我有个项目里的技能描述初稿写的是帮助用户整理文件模型调用率不到四成。改成当用户提供多个本地文档或扫描件并要求批量生成摘要时调用本技能。每个文档单独输出一段 200 字以内的中文摘要不支持图片识别不支持跨文档对比之后调用率直接升到九成以上。不适用那几句话功不可没。2.3 输入输出协议设计要点输入输出设计上我踩过几次坑最大的一个是参数过度设计。早期做技能的时候总想把所有可能的参数都暴露给模型结果模型在传参时经常互相矛盾比如既传了日期范围又传了具体日期。后来我把参数精简到最少必要集合能用默认值解决的就不让模型传参数之间的依赖关系用描述写清楚问题基本消失。输出方面要特别注意结构稳定。如果是给下游程序消费必须规定输出格式为结构化数据并给出字段说明。如果是给人读的文本最好提供输出模板让模型按模板填充而不是自由发挥。技能包里的模板文件这时候就派上用场了。3. 从零实现一个可用的技能3.1 搭建目录与元信息拿一个我实际做过的会议纪要整理技能举例。它的任务是把一段口语化的会议转写文本整理成包含决议、待办、风险三个模块的结构化纪要。我在项目里把目录建成了这样team-skills/ └── meeting-notes/ ├── SKILL.md ├── scripts/ │ └── format_notes.py └── templates/ └── meeting_minutes_template.md描述文件我通常命名为 SKILL.md放在技能根目录。开头用 YAML frontmatter 写元信息下面是正文指令。这是我最常用的一种写法--- name: meeting-notes description: 当用户提供会议或讨论的原始转写文本要求整理成会议纪要时使用。输出包含决议、待办、风险三部分的结构化中文纪要。不适用于录音文件直接转写不适用于生成翻译稿。 --- 你需要将用户提供的原始转写文本整理为会议纪要。 步骤 1. 阅读全部转写文本识别讨论主题和关键人物。 2. 区分内容已达成一致的结论写入决议明确分配给某人的动作写入待办并标注负责人可能阻碍进度的内容写入风险。 3. 按模板格式输出不得遗漏任何模块。 注意 - 待办事项必须包含负责人和截止时间如原文未提及则标记为未明确。 - 不得添加原文不存在的业务决策。这里 YAML 里的 name 和 description 会被系统读取生成技能清单而正文部分只有在模型选中这个技能后才会被完整加载。这是 skills 和传统提示词工程的一个显著区别不是所有内容一开始就占用上下文而是按需加载。3.2 编写执行逻辑脚本描述文件负责让模型知道怎么用脚本负责让结果更可靠。不是每个技能都需要脚本但如果你的技能里有计算、格式化、数据转换这类确定性工作别指望模型自己完成——模型擅长理解和生成不擅长精确计算和格式控制。我在会议纪要整理技能里加了一个 Python 脚本专门做待办事项的格式校验和时间归一化。模型生成纪要后脚本自动检查每个待办是否有负责人和截止时间顺手把明天下周一这类相对时间转成具体日期#!/usr/bin/env python3 import re import sys from datetime import datetime, timedelta def normalize_deadline(text: str) - str: today datetime.now().date() if 明天 in text: return (today timedelta(days1)).isoformat() if 下周 in text: return (today timedelta(days7 - today.weekday() 7)).isoformat() if 这周 in text: return (today timedelta(days5 - today.weekday())).isoformat() return text def check_todo_item(item: dict) - dict: issues [] if not item.get(owner): issues.append(缺少负责人) if not item.get(deadline) or item[deadline] 未明确: issues.append(缺少截止时间) if item.get(deadline): item[deadline] normalize_deadline(item[deadline]) item[issues] issues return item if __name__ __main__: # 实际项目中这里会读取模型输出的JSON这里只展示核心逻辑 sample {task: 完成架构方案评审, owner: , deadline: 下周五} print(check_todo_item(sample))脚本的输出可以作为校验结果回传给模型模型根据校验结果自行修正。这种做法把类型化工具调用和模型自主修正结合了起来比单纯让模型一次性生成终稿稳健得多。3.3 接入测试从单测到端到端技能开发完必须测而且不是测一次是持续测。我习惯先做单元测试——用固定的转写文本跑技能检查输出结构是否完整再做多样本测试——准备几十条覆盖不同场景的输入看模型的调用表现和输出质量最后做回归测试每次更新描述文件或脚本之后把历史测试用例全部重跑一遍防止改一个技能的某个细节导致另一个场景失效。我在这个环节踩过最大的坑是测试样本太干净。真实用户的输入往往夹杂口语、口误、打断、闲聊和测试时给的条理清晰的转写稿完全两回事。后来我专门收集了一批真实场景的转写片段加入测试集问题才逐渐暴露并被修复。记住一个原则测试输入要脏模型才能变强。4. 进阶优化与工程化落地4.1 上下文瘦身渐进式披露上文提到技能的核心优势是按需加载实际操作中还能再进一步。一个技能包里的参考文档可能很大如果模型一选中技能就把所有参考文档全部塞进上下文性能优势就被抵消了。我的做法是分级披露技能被选中时只加载描述文件里的简短指令和索引模型判断需要更细的规则时再通过特定格式的指令去读取详细参考文档。比如在合同风险提取技能里参考文档是一个包含几十类风险条款定义的文件。如果一次性加载占了大量上下文但可能只用到其中几类。我在描述文件里写了如遇合同包含知识产权条款请查阅 references/ip_clauses.md 获取判定规则让模型按需去读。实测上下文占用能减少一半以上响应速度也明显提升。4.2 权限与沙箱给你的技能上锁技能本质上是可执行代码一旦封装不严就是安全隐患。我见过有人把技能脚本写成直接调用系统命令如果模型被提示词注入诱导后果不堪设想。我建议所有技能脚本运行在受限环境里只开放必要的文件路径禁止网络访问除非明确配置限制执行时间和资源占用。权限控制也要细化到用户维度。同一个文件搜索技能普通用户版只能搜自己目录下的文件管理员版才允许搜共享目录。这个逻辑不能在模型层做必须在技能脚本的入口做校验因为模型不可控代码才可控。4.3 版本管理与团队协作技能是会持续演进的没有版本管理必然出事。我的做法是用 Git 管理技能仓库每个技能独立目录改动走分支合并。关键的一点是技能包的 SKILL.md 头部加一行 version 字段技能清单里也展示版本号这样当模型输出和预期不符时能快速定位是不是技能版本变更导致的。团队协作时最忌讳的是某人在一个技能里顺手改了另一个技能的东西。我要求所有修改必须附带变更说明描述文件里的任何改动都要在 CHANGELOG 里登记。刚开始觉得繁琐后来一个线上事故全靠变更记录排查出来才觉得完全值得。5. 常见问题与排查技巧实录5.1 技能始终不被调用怎么办这是新手最常遇到的问题。排查顺序我建议从三个层面走先看技能清单里描述是否正常展示再看描述与用户问题是否真的匹配最后看技能是否被隐藏或禁用。前两个层面占到九成以上原因。我处理过一个案例某同学做的代码审查技能描述里写的是当用户提交代码并希望检查质量时使用结果用户说的是帮我看看这段代码有什么问题模型没调用。原因就是希望检查质量和看看有什么问题在语义上模型判定不匹配。把描述改成当用户提供代码片段并询问其缺陷、优化空间或质量问题时使用后调用立刻正常。描述文件要多写触发信号少写抽象概念。5.2 参数被模型编造了模型在调用技能时编造参数是另一个高发问题。典型场景是技能要求传用户 ID模型没从上下文找到真实 ID直接捏了一个。解决这个问题不能只靠提示词不要编造要双管齐下参数校验脚本必须检查每项数据是否有可靠来源无法确认来源的参数直接拒绝执行描述文件里明确写用户 ID 必须从对话上下文中获取不得自行假设。更稳妥的做法是把可从上下文自动推导的参数做成可选参数偏向缺省值把必须由用户明确提供的参数设计为强校验字段。那些编造的参数通常都是模型自以为能从上下文里推断出来的我们把参数设计得越难猜编造概率就越低。5.3 输出格式不稳定下游解析报错技能输出格式不稳定往往发生在输出是自然语言夹杂结构化数据的时候。模型可能一会儿输出 JSON 一会儿输出 Markdown一会儿字段名带引号一会儿不带。我的解决方案是两层描述文件里给出严格的输出格式定义和示例脚本做后处理时用容错解析比如提取 Markdown 代码块里的 JSON或者用正则抓取关键字段。还有一个偏方挺好用让模型先把内容生成到中间格式脚本负责转换成最终格式转换后再校验一次完整性。格式问题在脚本层解决永远比在模型层解决省力。5.4 注意技能的边界与安全最后必须强调安全。技能描述文件里如果包含了敏感业务信息调用模型读取时也可能泄露风险。技能脚本对外发请求时要审查目标地址技能日志不要记录敏感字段。我把技能日志分为两级调试期记录完整入参和出参上线后只记录技能名、耗时和结果状态。还有一点很多人忽略技能清单本身也会暴露业务信息。在面向外部用户的场景技能清单应该做权限过滤别让用户看到系统一共注册了哪些技能。这不是多虑是基本的安全卫生。最后分享一点个人体会我在实际项目里用下来的最大感受是skills 改变了我和模型协作的方式。以前写提示词总觉得是在教育模型把每一条规则都塞进上下文里现在更像是在给模型配置能力它自己决定什么时候调用什么。这种转变带来的直接收益是长对话场景下的稳定性明显提升上下文占用下降维护成本也低了。如果你正打算把东西技能化我给一个建议从一个小而明确的场景开始拆好描述文件做好测试集比一上来做一堆技能有用得多。技能化的本质不是写代码而是把一件事的边界、输入、输出和判断标准想清楚——想清楚了代码只是顺水推舟的事。