1. Agent Skills 到底是什么从“会聊天”到“会干活”Agent Skills 这个词最近在智能体开发圈子里几乎成了默认术语。我最早被它“逼着”去研究是因为一个特别真实的场景某客户的项目要求对话助手能顺手改工单状态、抓出合同里的关键日期、再按模板生成催款邮件。模型能听懂这些指令但真要它动手它没有“手”。你需要给模型配一套标准化的、它能看懂并且愿意去用的“技能包”。这就是 Agent Skills 的核心价值把外部能力封装成模型可以按需检索和调用的标准单元。这篇文章适合三类人正在用各家智能体平台做应用的开发者、自己在写编排层和工具层的架构师、以及刚开始接触智能体、想知道“技能到底怎么设计”的产品同学。我会从概念讲到结构再给一个完整的实操案例最后把我踩过的坑和排查思路全部摊开来说。1.1 从“嘴上会说”到“手上会做”缺的是什么先做一个思想实验。你直接问一个纯对话模型“帮我查一下上个月华东区的销售数据。”它大概率只回你一段“很遗憾我无法访问实时数据”之类的套话。为什么因为它本质上是一个语言模型参数里装着的是训练时见过的知识不是运行时能访问的外部系统。但如果我在提示里塞给它一个函数定义get_sales_data(region, month, dimension)然后告诉它“你需要数据时就调用这个函数函数返回的是 JSON”。它马上变得“会干活”了。模型还是那个模型变化的只是我们给了它一个“把手”——一个可以被调用、有明确输入输出约定的能力接口。Agent Skills 做的就是把这套逻辑标准化。它不只是一个孤立的函数而是一个“说明文档 可执行脚本 示例数据 运行环境约束”的完整单元。模型看到这个单元的说明文字后能判断“这个需求我该用哪个技能”然后按技能约定去执行最后把结果带回到对话里。1.2 工具、插件、技能到底差在哪这个区别我一开始也搞不清后来对比才明白。工具Tool是最小粒度通常就是单一函数比如“查询天气”“发送邮件”。优势是简单直接缺点是模型很难在大量工具里精准选择——你需要非常精确的描述词否则几百个函数摆在模型面前它很容易选错。插件Plugin是一套集成包比如“把第三方办公套件全部接进来”。它解决的是打通问题但插件内部往往混杂太多动作模型使用时“理解成本”很高而且插件之间可能互相干扰。技能Skill夹在两者之间它把一组相关能力打包更重要的是带上了“给模型看的说明书”什么时候该用、参数怎么填、用了之后会得到什么、甚至附上两三组输入输出示例。能力本身是程序使用说明是给模型的小抄。模型不需要在一堆零散函数里猜它只需要读一段自然语言说明就能做出决定。类型粒度核心目标典型问题工具 Tool单函数暴露一个具体能力工具太多时选择困难插件 Plugin系统级集成打通外部系统理解成本高、易冲突技能 Skill能力包 说明书让模型会选、会用要求说明质量高1.3 为什么“说明书”比“程序”更关键很多人第一次写技能时把绝大部分精力花在脚本上结果模型根本不调用。我后来才意识到脚本是给机器执行的描述是给模型“看”的。模型能不能在正确时机选中这个技能、能不能填对参数完全取决于“说明书”质量。一个合格的技能描述至少要回答四个问题这个技能解决什么问题在什么条件下应该被启用每个参数的含义和取值范围执行后返回什么结构。如果能再附上一两个典型输入输出示例命中率会大幅提升。这就像你不可能让一个新同事在没有任何交接文档的情况下准确使用你的内部系统——模型也需要一份交接文档而且它读文档的能力比读代码强得多。提示模型的上下文窗口有限技能说明越精炼越容易被完整读到。长篇大论的描述会被模型“选择性遗忘”通常只记住开头和结尾所以关键触发条件一定要写在最前面。2. 设计一个技能之前先想清楚这几件事我在给团队搭技能体系的第一周犯过一个很典型的错误一口气写了二十多个技能觉得“覆盖得越全越好”。结果模型面对二十多份说明经常把相似技能搞混比如“生成周报”和“生成日报”频繁互相误触发。后来我把它们合并、重新设计准确率才上来。这件事让我明白在设计阶段多花一小时思考比上线后排查一整天划算得多。2.1 本质是“意图匹配”不是“功能堆叠”模型选择技能的过程本质上是一个意图匹配任务用户这句话表达了什么意图哪个技能的描述与这个意图最接近。所以技能设计的第一原则是每一个技能都要先定义“触发边界”——在什么场景下这个技能必须被用在什么场景下绝不能误用。举个例子一个“删除数据”的技能描述里必须清楚写到“只有在用户明确要求删除或清除记录时使用正常情况下不得调用”。这看起来像废话但实际项目中我就见过模型在用户说“把这些没用的数据清理掉”时直接调了删除接口。原因就是设计者没在描述里给出边界。边界越清晰模型的误判率越低。2.2 粒度怎么定太粗太细都会翻车粒度的拿捏是设计阶段最容易纠结的地方。我的经验是按“用户的自然任务单元”切分而不是按“功能的实现细节”切分。什么叫自然任务单元“把一篇文章翻译成英文”是一个单元。“调用翻译接口”和“对译文做术语替换”可以拆开但正常情况下用户不会单独要求“只调用翻译接口”所以它们应该合并在一个技能里。反过来如果你把“下载附件”“解析附件内容”“按附件内容生成摘要”“把摘要发到群里”全塞进一个“处理附件”技能模型往往搞不清楚用户到底想要哪一步执行结果也容易失控。一个简单的判断标准把这个技能说明给一个不了解系统的人看他能立刻说出“这技能是用来做什么的”。如果说不出说明粒度有问题。技能是给人用的、也是给模型用的理解成本越低越不容易出错。2.3 技能之间会“打架”需要提前规划命名和描述当技能数量超过十个冲突就开始出现。最经典的是“功能相近但细节不同”的技能。我处理这类冲突的办法是在描述开头统一加上“领域前缀 用途短语”比如“销售数据分析按区域、产品线等维度汇总销售数据并生成图表”。所有销售类技能都带“销售数据分析”前缀其他类技能就很少串门。还有一类冲突是“一个需求同时触发多个技能”。比如用户说“帮我把这个月的销售情况整理成邮件发给领导”可能同时命中了“销售数据分析”和“邮件撰写”。这时候光靠描述已经不够需要在编排层做仲裁如果两个技能被同时选中要么按优先级执行要么让用户确认。这部分属于技能编排的范畴我在后面实操里再展开。2.4 结果稳定性让技能输出“可预期”最后一条设计原则一个技能的输出结构必须稳定、可预期。模型拿到返回结果后还要继续对话如果技能今天返回 JSON明天返回对话文本后天又返回一段 Markdown模型后续处理会完全乱套。我一般要求所有技能统一返回 JSON 格式并且附带一个简单的状态字段status、data、error。哪怕是失败也要返回结构化错误信息这样模型才知道下一步该怎么和用户解释。注意技能执行前一定要做输入校验。格式化的 JSON 输入如果缺失字段脚本可能直接崩溃。脚本可以“笨”但绝不能“脆”。3. 技能的标准结构三件套与编写要点概念和设计讲完了下面进入实操层面。一个标准化的 Agent Skill通常由三部分组成技能说明文件、可执行脚本、资源与依赖清单。不同平台叫法会有差异但底层逻辑大差不差。3.1 三件套之一说明文件说明文件是整个技能的大脑。它的作用不是给人读而是给模型读。里面要写清楚技能名称和一句话简介用途与触发条件参数定义与取值范围返回值结构两到三组典型示例写的时候有个技巧尽量使用肯定句和祈使句减少模糊描述。比如“如果用户提供会议转写文本请使用本技能”就比“本技能可被用于会议文本解析”好得多。前者告诉模型“什么时候用”后者只是描述了功能模型读完可能不知道该不该在这里用。说明文件的名字也最好固定且显眼。很多平台约定为SKILL.md因为框架要扫描这个文件来注册技能。如果起一个花哨的名字可能导致技能根本不被加载。3.2 三件套之二执行脚本执行脚本是技能的双手负责真正干活比如请求接口、处理数据、写文件。脚本的设计要点是输入参数校验要充分输出必须和说明文件里承诺的一致。脚本是普通程序不需要“智能化”相反它越机械越好。我见过不少开发者试图在脚本里写一堆“智能逻辑”比如自动判断用户情绪、自动改写请求参数。这种做法非常危险模型本来就有随机性你再在脚本里加一层“智能”出问题的时候根本分不清是模型错了还是脚本错了。脚本的价值是确定性把模型这个不定因素之外的部分全部锁死。3.3 三件套之三资源与依赖如果技能需要额外的数据集、模板文件、第三方库必须在这部分说明清楚并且尽量让技能可移植。一个技能放在 A 机器上能跑搬到 B 机器上就报缺依赖这种技能在真实环境里很难维护。我的习惯是每个技能目录里放一个独立的依赖清单文件比如requirements.txt并且把环境依赖的版本固定住。原因很现实某个库升级一个小版本可能行为就变了。技能在模型调用时是“被动的”它不知道外面环境变了只会老老实实地崩溃。固定版本是成本最低的稳定性保障。依赖的安装时间也要控制。如果技能第一次被调用时需要现场下载几十个包用户等待时长会非常离谱。尽量把重型依赖提前预装到运行环境里或者至少把安装过程放到启动阶段而不是调用阶段。4. 从零实现一个技能会议纪要与待办提取完整实操下面我用一个常见场景把整个过程走一遍从一段会议转写文本里提取主题、结论、待办事项。这个技能我在多个项目里都实现过虽然业务不同但套路基本一致。4.1 需求定义用户场景是这样的业务团队每周开两三次线上会会议记录是语音转写出来的原始文本长且乱。他们希望模型助手能自动整理出讨论了哪些主题、达成了什么结论、谁负责什么事、什么时候完成。这个需求非常适合做成技能因为输入输出都很清晰而且有固定的处理流程。我把它命名为meeting_minutes_extractor会议纪要提取器。4.2 目录结构skills/ └── meeting_minutes_extractor/ ├── SKILL.md ├── script.py └── requirements.txt目录名全部小写加下划线避免大小写问题。SKILL.md给模型看script.py跑实际逻辑requirements.txt声明依赖。这个技能逻辑简单其实没有特殊依赖但文件仍然保留方便以后扩展。4.3 编写 SKILL.md这是整个技能成败的关键。我第一次写的时候只写了“提取会议纪要”五个字模型经常把它和另一个“总结周报”技能搞混。后来我改成下面这种结构误触发率明显下降# 技能会议纪要与待办提取 ## 用途 从一段会议记录的原始文本中提取结构化会议纪要包括讨论主题、关键结论、待办事项、负责人和截止日期。 ## 触发条件 - 输入包含会议记录、语音转写文本、多人讨论内容 - 用户要求“整理会议纪要”“提取待办”“总结会议” - 输入文本的长度超过 50 字且包含多人发言内容 ## 参数 - input_text: string必填要解析的会议转写文本 - need_summary: boolean选填是否生成摘要默认 true ## 返回 返回 JSON 对象 { status: success, data: { topics: [讨论主题列表], conclusions: [结论列表], actions: [ {task: 待办内容, owner: 负责人, deadline: 截止日期} ] } } ## 示例 输入 今天讨论了新版本上线计划。结论是下周初冻结需求。张三负责更新文档李四负责回归测试周五前完成。 返回 {status:success,data:{topics:[新版本上线计划],conclusions:[下周初冻结需求],actions:[{task:更新文档,owner:张三},{task:回归测试,owner:李四,deadline:周五}]}}注意示例的重要性。模型很强但它在决定“要不要调用某个技能”时最依赖的就是示例和自己的记忆是否匹配。示例给得越贴近真实场景命中率越高。4.4 实现核心脚本脚本我是这样写的import json import sys def extract_meeting_info(text: str) - dict: topics [] conclusions [] actions [] sentences [s.strip() for s in text.replace(。, 。\n).split(\n) if s.strip()] for sentence in sentences: if any(k in sentence for k in [讨论, 议题, 主题]): topics.append(sentence) if any(k in sentence for k in [结论, 决定, 确定]): conclusions.append(sentence) if any(k in sentence for k in [负责, 待办, 跟进]): actions.append({task: sentence, owner: , deadline: }) return { topics: topics, conclusions: conclusions, actions: actions, } def main(): input_data json.load(sys.stdin) text input_data.get(input_text, ) if not text: print(json.dumps({status: error, error: input_text 不能为空}, ensure_asciiFalse)) return result extract_meeting_info(text) print(json.dumps({status: success, data: result}, ensure_asciiFalse)) if __name__ __main__: main()这个脚本只能算 Demo它靠关键词扫描来切分句子。真实生产环境中我一般会把这段逻辑改为“先让模型在本地做一次初筛再用正则和实体识别做人名和日期抽取”准确率会高很多。但无论逻辑多复杂输入输出契约必须和SKILL.md里写的一样读 stdin 的 JSON写 stdout 的 JSON。这样模型平台才能无感接驳。脚本必须把“空输入”“格式错误”这些异常情况都照顾好。统一返回{status: error, error: 原因}模型看到错误后才知道怎么补救而不是干巴巴地对用户说“系统出错了”。4.5 测试与效果验证写完之后不要急着接到正式流程里。我在本地先准备了一份模拟转写文本分别测了三种情况一段完整的会议记录、一段只有两句话的简短聊天、一段完全不相关的产品介绍。结果发现对第三种情况脚本也能“强行”提取出所谓主题比如“产品介绍”这个词。这其实暴露了一个问题技能描述里写的触发条件不够严格模型可能在用户没真的开会时误用。我又回到SKILL.md在触发条件里加了一句“只有输入内容包含多人发言或会议场景描述时才使用”。重新测了二十次误用率从之前的三成降到接近零。这里有个经验技能上线前要专门设计一组“负样本”来测试。所谓负样本就是“看起来有点像但绝不该触发该技能”的输入。只测正样本你永远发现不了误触发问题。5. 常见问题与排查技巧实录技能写得多了踩过的坑自然也多。我把最常见的几类问题整理成一张速查表下面再逐个细说。症状可能原因排查方向模型始终不调用技能说明文件里触发条件不清晰或示例缺失检查描述、补示例、缩短描述开头技能被调用但参数错乱参数名和脚本不一致 / 参数类型定义不清统一参数名、明确类型和默认值返回结果不稳定输出结构不统一 / 脚本路径有问题固定 JSON 结构、加日志运行超时脚本依赖库没预装 / 接口响应慢预装依赖、设置超时、换更轻量逻辑误用技能造成数据风险描述中缺少禁止边界增加负面触发条件、加上权限校验5.1 模型总不调用技能怎么办这个问题排名第一。绝大多数情况下问题出在SKILL.md的描述上而不是脚本上。模型要不要用一个技能取决于它读描述时是否觉得“这就是我需要的”。所以排查顺序是先看描述开头 100 字是否说清了“什么时候用”。很多技能描述喜欢先摆背景和原理模型看完还一头雾水。正确的做法是第一句就写“当用户要求 XXX 时你必须使用本技能”。再看示例是否贴近真实场景。示例是模型最强的参考信号没有示例或者示例太抽象模型就会犹豫。最后才检查平台侧的原因比如技能是否真的被加载、名字是否被拼错、环境变量是否缺失。5.2 技能被调用但参数总是错乱参数错乱通常有三种表现字段名对不上、类型对不上、取值范围超出预期。比如脚本读的是input_text模型却传了text返回值自然为空。我的对策是参数名全部小写下划线尽量和自然语言一致。input_text就比text_input_parameter_1好理解得多。同时给每个参数写清“必填还是选填”“默认值是什么”。脚本入口再做一个兜底如果必填参数缺失立刻返回错误 JSON而不是抛一个毫无上下文可言的异常。至少模型看到错误能知道是“参数缺了”而不是“系统崩了”。5.3 安全边界问题误用比不用的危害更大模型一旦接上外部系统就存在误调用的风险。我在某个项目里见过一个惨痛的案例A 同学写了一个“按条件删除记录”的技能描述里只写了“用于清理过期数据”结果用户抱怨一句“这些数据看着就烦删了吧”模型就把半年正常数据删了。从那以后我在所有涉及写入、删除、发送外部消息的技能里强制加三条安全措施描述里明确写“仅在用户明确且直接表达执行意图时才使用”脚本里加一个确认参数且默认值为 false要求模型先向用户确认高风险操作统一走审批接口而不是技能直接执行模型没有“伦理”概念它只会顺着描述执行。边界和安全只能靠设计者提前焊死。5.4 环境依赖与可移植性技能在不同环境之间搬家时最容易出问题的是路径写死。比如脚本里写open(/home/user/data.txt)换一台机器就崩。正确做法是依赖相对路径或者通过环境变量注入路径。还有一个容易被忽略的点脚本输出日志时不要往 stdout 里乱打印。模型平台一般约定 stdout 用来输出结构化结果如果你把调试信息也打印到 stdout返回给模型的内容就会混入乱七八糟的文本导致解析失败。要打日志请走 stderr 或者独立日志文件。5.5 超时与并发技能执行不是瞬间完成的。外部接口如果响应慢整个对话就会卡住。我在所有技能脚本里都要求设置超时时间比如外部调用最多等 5 秒超时就返回error字段说明原因让模型告诉用户“服务暂时不可用请稍后再试”而不是让整个请求挂死。并发场景下还要小心共享资源。如果两个用户同时触发同一个技能而脚本会写同一个临时文件结果就是互相覆盖。解决方案是每次运行时生成带唯一标识的临时文件或者干脆用内存对象处理不落盘。6. 实践中的几条体会与进阶方向6.1 三个值得记住的教训第一技能描述永远比代码重要。模型看得懂自然语言看不懂你的“良苦用心”。把时间花在打磨描述和示例上回报率最高。我见过很多团队把代码写得天花乱坠模型却完全不调用问题就出在“说明书”上。第二一个技能只做一件事。当用户说“帮我整理会议并同步到日历并通知所有人”时这个需求至少拆成“会议纪要提取”“日历创建”“消息发送”三个技能再由编排层串起来。硬塞进一个技能里维护成本会指数级上升。第三测试要加负样本。每次技能改动后至少准备五条正常输入和五条“不该触发”的输入一起跑。只有负样本也通过了这个技能才敢上线。6.2 进阶从单技能到技能编排技能做多了以后你会发现真正的难点不再是单个技能怎么写而是多个技能怎么协作。模型可能同时选中好几个技能或者执行完一个技能后还需要继续完成后续步骤。这些需求需要在编排层处理。我的建议是先给每个技能定义“输入依赖”和“输出产物”。技能 A 的输出恰好是技能 B 的输入就把它俩之间的流转关系写清楚。遇到多技能同时命中时先用优先级仲裁无法判断就让用户确认。宁可让用户多点一次按钮也不要让模型自作主张。从工具到插件从插件到技能再从单个技能到技能编排这个演进路径几乎是每个智能体项目都会走的。现在回看我最早手搓函数调用的那段日子虽然也能跑但每一次改动都要动提示词、动代码、动测试笨重得不行。有了标准化的技能体系之后新增一个能力就像把一块积木插进已有架构简单得多也稳得多。如果你正准备给自己的智能体项目搭技能体系从一个场景单一、边界清晰的技能开始跑通之后再慢慢扩展这条路是最稳的。