
同样接大模型API有人做出来的是聊天玩具有人做出来的是能独立跑任务的智能体这个差距不在模型本身而在模型之外那一层能力的组织方式。我最近半年一直在折腾agent-skills这套东西核心就一句话与其在对话里反复教模型怎么做不如把“怎么做”固化成技能让模型按技能执行。所谓agent-skills就是给Agent准备一套可复用、可组合、可升级的能力模块让大模型不必每次从零推理而是直接调用沉淀好的动作序列。它解决的是智能体工程里最扎手的三个问题复杂任务容易漂移、能力无法沉淀复用、行为边界难以管控。这篇文章我会从机制原理讲到可落地的设计方法再给一套我实测过的技能模块示例和排查经验适合正在做Agent落地、或者被长流程任务稳定性折磨的工程师和产品经理看完你至少能自己搭出一个像样的技能库。1. 技能不是“把工具列个清单”而是一套运转机制1.1 从智能对话到智能体的关键一跃很多人对Agent有个误解觉得只要接上工具调用Function Calling模型能调API了就是智能体。真跑过复杂任务的都知道差距远不止“能调工具”这么简单。普通对话模型的工作方式是“你想一步我走一步”每一步的推理都在独立的上下文里发生。而智能体一旦进入真实任务比如“从20个数据源抓取竞品信息清洗后生成一份对比报告”模型面对的是几十步的连续决策每步都要选工具、填参数、判断结果、决定下一步。没有约束的情况下哪怕GPT-4这样的模型跑长任务中途也会迷失方向——要么忘了原始目标要么在一个工具调用上反复重试要么跳过关键步骤。技能体系的出现就是为了解决这个问题。它把“工具清单”升级成了“操作剧本”不再告诉模型“你有这些工具可以用”而是告诉模型“这类任务按这个流程走”。模型从“每次边想边做”变成“按剧本执行遇异常再做局部调整”稳定性和可控性完全是两个量级。1.2 技能真正的三个组成部分我踩过不少坑之后对技能的理解可以拆成这样三层第一层是操作步骤也就是把任务拆成有序的动作。比如做一份竞品分析报告技能里写清楚先抓数据源清单然后逐个站点抓取每抓完一个先做字段规整最后汇总生成表格。模型执行时按这个顺序走不该跳步也不该自由发挥。第二层是决策切换就是在什么时候用这个技能、什么时候不用。比如技能描述里要写明适用范围“仅在需要获取网络公开信息时使用离线文档整理不得调用”。模型其实是靠自然语言指令来理解触发条件的这一步写在SKILL.md的规则里越清楚越不会乱触发。第三层是能力封装把外部工具、脚本、数据源都包进技能包里模型不需要知道内部实现。比如“发送飞书告警”这个技能内部可能是一个几十行的Python脚本负责拼消息、鉴权、重试但模型看到的只是“告警标题、告警内容、接收人”三个参数。这三个层次缺一不可只写步骤没有封装模型拿不到执行能力只封装工具没有步骤技能就退化成普通Function Calling没有触发条件模型会乱用技能。理解了这三层再看市面上的Agent技能框架就会清楚很多它们本质上都是在这三个维度上做文章。1.3 一个生活化的类比打个比方你把一个实习生叫来说“帮我做份行业研究”他大概率一脸懵不知道从哪下手。但如果你给他一份SOP上面写着打开三个指定网站按XX格式摘录数据再用Excel生成透视表最后套公司PPT模板输出他就能稳定地完成。Agent技能就是这个SOP大模型就是那个有点聪明但经验不足的实习生。这个类比还能延伸出另一个关键点SOP要写得足够细但也要留判断空间。细到能指导每一步操作但不能细到把路堵死因为真实任务里总有预料之外的状况。好的技能设计就是在这种“给约束”和“给自由”之间找平衡。2. 先拆任务再写技能我常用的自底向上拆解流程2.1 从三个业务场景归纳出来的首版技能清单理论上讲技能可以无限丰富但实际落地时资源有限先建什么后建什么直接决定这套体系的启动速度。我一般会收集团队日常最耗人力的高频场景从这些场景里倒推需要的原子能力。我自己的起步案例是内容运营团队三个高频场景分别是每日行业新闻摘要、竞品动态追踪、周报自动生成。看起来是三个任务但拆到原子层之后共同的基础能力其实是这几项网页抓取、RSS读取、文本切片、标题总结、Markdown表格生成、按模板汇总结论。于是我把技能优先级排成读数据类网页抓取、RSS解析排第一因为没有输入一切都是空的其次是处理类切片、总结然后是输出类Markdown渲染、模板拼装最后才轮到执行类发飞书、写Notion。这个顺序后来验证是对的——基础读数据能力铺好之后三个场景里至少有两个立刻就能跑通而执行类的技能晚点补完全不影响主流程。2.2 技能拆解的两个层次原子技能与复合技能这里要区分两个容易混淆的概念原子技能和复合技能。原子技能定义不可再拆的单一动作。比如“抓取单网页正文”“将一段文本总结成三句话”“把一个JSON转成Markdown表格”。原子技能的特点是输入输出可预期功能单一出错定位容易。复合技能定义由多个原子技能组成的长流程。比如“每日新闻摘要” “读取RSS列表” “逐个抓取正文” “每篇总结摘要” “合并成日报模板” “发送到指定群”。在设计上我坚持一条原则原子技能必须做到“口小肚大”——对外暴露极简的接口和清晰的输入输出复合技能只负责编排和决策不在自己内部重复造轮子。这样拆的好处是新场景来的时候大部分情况下只是重新组合已有原子技能写一个薄薄的复合技能定义文件就够了。2.3 技能粒度的两条判定标准很多新手卡在“技能拆多大合适”这个问题上。拆太细模型调用次数爆炸上下文里塞满了中间结果拆太粗技能内部又是一个大黑盒模型无法做必要的中间决策。我摸索出两条比较有效的判定标准标准一看是否有独立的校验点。如果这个步骤执行完之后需要根据结果决定下一步路径那它就该独立成技能。比如“抓网页”之后要检查“抓取是否成功、正文是否为空”这就是一个完整技能的天然边界。标准二看是否可能被复用。将来有超过一个场景会用到这个能力就该独立如果只为某一个流程服务可以先内嵌在复合技能里等出现复用需求再抽出来。注意判断标准之外还有一条更底层的原则——不要让模型填一个它填不出来的字段。我早期犯的错是让技能接收“目标文章列表”这种需要模型去“找”的参数结果模型只能瞎编。后来规定技能参数必须是执行阶段可以确定值的东西需要探索性获取的信息一律拆成独立的前置技能。3. 手把手搭一个完整技能模块从需求到验证3.1 需求设定和目录结构用一个我跑通的真实案例做示范。需求让Agent自动整理团队散落在多个文档源里的周报素材按“本周进展、风险和阻塞、下周计划”三段式结构汇总成一篇周报草稿。这个任务看起来简单实际上涉及技能读取、理解、分类、重组多个环节。我落地时的技能目录长这样skills/ ├── weekly-report/ │ ├── SKILL.md │ └── scripts/ │ ├── read_sources.py │ ├── classify_items.py │ └── render_markdown.py每个技能都是一个自包含的目录目录里必须有SKILL.md作为技能的说明书模型读这个文件来决定“要不要用技能、怎么用”。脚本目录放可执行代码模型通过工具机制调用脚本并不直接读代码。这套结构学习成本低新人一看目录就明白。3.2 SKILL.md怎么写参数怎么定SKILL.md是整个技能包的核心模型靠它理解技能的触发条件和执行方式。我的模板包含五个段落这五段是长期调优后留下的版本--- name: weekly-report description: 用于将散落文档中的工作记录整理为周报草稿的复合技能。当用户要求生成周报、汇总本周工作、梳理项目进展时使用。 --- # 周报生成技能 ## 适用场景 - 用户要求总结本周或上周的工作内容 - 需要从多个文档或数据库记录中提取进展 - 需要按固定结构输出周报 ## 不适用场景 - 用户只是问某个单一事件的处理结果 - 只有很短的记录不需要跨文档整合 ## 执行步骤 1. 先用 read_sources 列出所有可用的周报素材源 2. 依次读取每个素材源保留原始内容记录来源标记 3. 对每条素材进行分类进展、风险、阻塞、计划 4. 分类完成后按周报模板重组生成 Markdown 草稿 5. 输出草稿不直接修改任何源文件 ## 参数 - sources: 素材源标识列表不传则自动发现 - date_from: 起始日期 - date_to: 结束日期 ## 注意事项 - 分类判断不需要用户确认但遇到无法归类的短句放在“其他”并保留原文 - 报告里每条内容必须带来源标记方便追溯参数设计在这里面最关键。最初我写了sources参数让模型填“素材源标识”后来发现一个尴尬的情况模型经常不知道有哪些素材源可选于是乱填或空着。后来改成“不传则自动发现”把“找源”这件事也变成技能内部逻辑模型只需要做更擅长的语义判断——分类和排序可靠性提高了一大截。3.3 脚本里最值得抄的几段核心逻辑脚本本质上不复杂但要从工程完善度角度考虑。第一段是自动发现素材源我习惯让它读一个配置文件里的源清单同时把环境变量放在外面这样换环境时不用改代码import glob, yaml, os def discover_sources(): cfg_path os.getenv(WR_SOURCES_CONFIG, sources.yaml) with open(cfg_path, r) as f: return yaml.safe_load(f)[sources]第二段是分类逻辑。这里我踩过“用规则尝试精确匹配”的坑最后回归大模型的语义判断通过prompt要求模型按结构输出JSONimport json, subprocess def classify_items(raw_items): prompt 将以下条目分类为进展/风险/阻塞/计划输出JSON数组每项包含{category, text}。\n prompt json.dumps(raw_items, ensure_asciiFalse) # 调用一次轻量模型完成分类 result call_llm(prompt) return json.loads(result)第三段是渲染。有个小技巧每周的模板都可能微调所以把周报模板独立成一个模板文件脚本只负责填充数据改格式的时候不碰代码。3.4 验证清单怎么确认技能是“真的可用”写完了不等于能上线我每次都会跑一遍验证清单再交付第一步用一条简单的、边界清晰的请求测例如“生成上周周报”看它是否触发正确的技能、是否走完所有步骤。第二步故意给模糊请求例如“帮我看看最近有啥要汇报的”看描述里的“适用场景”是否覆盖到这个意图模型是否能通过语义关联触发技能。第三步测异常输入给一个空素材源、给一篇超过上下文长度的文档看技能脚本是否兜得住还是直接报错中断整个流程。第四步测重复调用稳定性同一个请求连跑三次对比输出差异。如果两次结果差异过大说明技能内某个判断不稳定需要追查。提示验证阶段不要用生产数据准备一份脱敏的测试数据集。否则全链路一跑敏感信息跟着进了模型调用容易出合规问题。这个坑我身边不止一个人踩过。4. 三件比写代码更重要的事观测、管控、上下文预算4.1 可观测性Agent黑盒的最后一块遮羞布技能体系跑起来之后最头疼的不是功能写不出来而是出了问题根本不知道在哪一步出的。所以第一件必须做的事就是给所有技能挂上过程日志。我的做法是给每次技能调用发一个trace_id把这个id贯穿到所有日志里。日志分两层技能层日志记“什么时候调用、入了什么参数、返回什么结果”执行层日志记“脚本内部每一步的耗时和状态码”。两层分开是因为它们的查询频率完全不同排查问题时先按trace_id查技能层定位到大致环节再下沉到执行层看细节。举一个我实际遇到过的场景某天早上的日报Agent突然不发了。通过trace_id查到上游抓取技能超时再下沉到执行层看到是源网站升级了页面结构导致正文提取逻辑返回空列表。整个过程定位不超过十分钟如果没有这套日志大概率要手动跑一遍脚本才能发现那又是另外一小时的折腾。另外我强烈建议给关键技能加上结果摘要技能执行完写一条简要日志记录“我做了几件事各是什么状态”格式是流程外额外发到通知群。这样每天不用登录服务器翻日志群里就看到出了什么问题运维成本极低。4.2 安全管控给模型的能力套上缰绳让模型操作文件、执行命令、发消息能力越大责任越大。我在安全管理上采用“三道闸门”策略第一道命令白名单。凡是技能可能触达的shell命令必须显式写进配置白名单之外的命令一律拒绝执行。我早期让模型自己拼命令行结果有一天它为了“查看文件编码”拼了一个带参数的命令直接误删了一个临时目录。白名单看起来保守但长期看是保命符。第二道沙箱。所有涉及文件读写和执行外部命令的技能统一跑在容器里宿主机完全不暴露。配置工作量大一点但换来的是即使技能逻辑有漏洞攻击面也只限于容器内部。第三道数据脱敏。凡是技能要传到外部模型API的参数经过过滤函数处理手机号、身份证、密钥、内部系统路径都会被正则替换。这一步牺牲了一点便利但让你的方案在合规审查时多了一重保障。4.3 上下文预算技能系统跑得久的关键上下文长度是有限资源技能执行过程中最容易踩的坑是把中间数据全塞进上下文。比如抓取50个网页一次抓取的HTML原文全部带回对话模型上下文立刻爆炸后面步骤全在超长上下文里运行又慢又贵还容易出错。我的处理手法是中间结果“外置”技能脚本把每个阶段的输出写到临时文件或内存对象中只回传“成功/失败摘要”的极简状态。只有当模型判断需要看详情时才按需去读文件内容。这个方案在信息密度上很划算——状态摘要一百个token以内就能搞定而全文可能要几万token。同时我还养成了一个习惯每次技能执行完清理临时文件防止磁盘被Agent跑出来的中间产物塞满。时间一长这个习惯从“可选优化”变成了“必需操作”。5. 编排层与开放生态让技能从个人脚本变成团队资产5.1 谁来决定“现在该用哪个技能”技能库一旦变大模型每次决策都要在几十个技能里选准确率会明显下降。这时候需要加一个编排层专门负责技能路由。我常用的方案是标签路由每个技能声明标签比如“read/web”“process/nlp”“output/report”模型决策时先通过标签缩小候选范围再在候选集里读SKILL.md做最终判断。这个方案简单直接标签体系建好之后扩展新技能也很快。候选人少的时候可以直接在系统提示词里加一句“若任务涉及网页内容使用技能库中的read/web类技能”效果也不错。但技能超过十五个之后标签路由是更稳定的方式。我的经验是规模到了这个量级与其反复调提示词不如在架构层面把决策问题简化掉。5.2 MCP开放标准与可移植性的思考当前技能生态里MCPModel Context Protocol这类开放协议是最值得关注的方向。它的价值在于定义了一套标准化的工具调用接口格式让不同平台间迁移技能时不用重写底层连接层。我最近才把之前自研的几个技能接口对齐到MCP规范最大的感受是有了统一协议之后新写一个技能接入现有系统工作量大减而且可以借助社区里大量的现成工具实现。当然对齐规范也要付出成本主要是协议适配层多了一层封装出现协议解析问题时排查链路会更长。我的建议是如果只是自己团队内部用自研标准够用如果技能要跨项目、跨部门甚至对外开源直接站到开放标准一边能省未来大量的迁移成本。5.3 从个人的技能碎片到团队的技能库技能体系真正做到团队层面需要配套一套技能仓库管理机制。我把技能库按Git仓库存放每个技能一个子目录PR必须经过code review才能合并。仓库里有三种角色贡献者负责开发新技能审阅者负责检查SKILL.md描述质量和脚本安全维护者负责版本发布和兼容性验证。管理机制里最重要的细节是技能版本的语义规范主版本号代表行为不兼容变更次版本号代表新增能力且向后兼容补丁号代表内部修复。这样下游服务从v1.1升到v1.2时很放心不用重新测试全链路一旦要跨主版本升级触发完整的回归测试流程。6. 我踩过的几个坑排查链路与修复方案6.1 坑一技能描述写得“太像人话”模型根本触发不了有一次一个“数据对比差异报告”技能做了但怎么调都不触发。查日志发现模型从来没选中过这个技能。回看SKILL.md里的描述写的是“用于在合适的情况下生成有用且高质量的数据对比报告”这句跟没说一样模型在候选列表里根本分不清它和其他通用技能的差别。排查链路先通过日志确认模型没有选中技能再对比其他正常触发技能的description发现正常的都写清了“什么场景”“典型的触发请求长什么样”最后把描述改成“当用户要求对比两个文件、两个版本或两份数据之间的差异并输出差异清单时使用”触发率立刻恢复正常。这件事给我的教训很具体技能描述不是给人看的是给模型看的一定要写“请求样例”式的描述而不是“功能概述”式的描述。模型是靠语言匹配来触发技能的越接近真实用户表述它越容易正确选中。6.2 坑二脚本执行成功但结果全错整个流程“假跑通过”另一次更隐蔽的问题是复合技能里B步骤依赖A步骤的输出但B脚本写的时候直接用了“默认参数”作为兜底。A步骤执行成功但输出是空列表时B步骤以为没数据直接走了默认分支生成了一个“空模板周报”。整个流程日志全绿没有任何报错产出却是一份没内容的假报告。排查链路先从输出反向追踪发现生成报告内容为空再查B脚本入参发现确实为空列表继续查A步骤的日志发现A返回的是一个空列表最后定位到A的源数据发现文档路径写错了。整个链路查下来问题根源是路径配置里有一个环境差异。修复方式不是简单改路径而是在A脚本里增加显式校验读取结果如果为空必须返回非零退出码同时日志里写一条WARN级别记录。从此空数据不再“静默通过”再也不会出现绿日志配空报告的情况。6.3 坑三加了新技能之后旧技能开始误触发这种问题最玄学表现是新增了一个“客户反馈分类”的技能结果原本稳定的“周报生成”技能开始在某些请求上误触发。排查链路很曲折先看日志发现模型在用户问“最近客户有哪些反对意见”时先触发了周报技能再触发了反馈分类技能重叠执行导致输出混乱。继续查两个技能的description发现都含有“客户”“反馈”“总结”等重合词汇模型做路由时出现了语义混淆。解决方式是把两个技能的适用场景重新划边界周报技能里明确写上“非周报周期性的全面汇总不触发”反馈分类技能的description里加上“仅针对单条或批量的客户反馈文本分类不进行跨时间维度的汇总”。边界写清楚之后两个技能各回各位。这个坑揭示了一个深层原则技能体系不是无限堆叠目录而是要有一个技能清单评审动作。每次新增之前快速检查一下已有技能里有没有描述重叠的有的话先调边界再发布。一次评审几分钟但能省掉后面几小时的排查时间。写在最后的一点体会如果你刚起步我的建议是从两三个高频场景的原子技能先跑通闭环别急着追求技能数量。技能体系是个典型的滚雪球工程前两三个技能最痛苦因为要搭流程、定日志规范、配安全策略但一旦基础铺好后面每加一个技能都能立刻产生实际效益速度会越来越快。这套方案的收益在初期可能不明显但当你发现一个新需求从提出来到上线只需要一个下午时就会明白前期沉淀技能库的投入有多值。我目前还在持续扩充技能库最深的体感是Agent能力的上限早就不是模型本身的聪明程度而是你能不能把经验拆成一套模型能稳定执行的操作规范这条路走得通Agent才真正从“好玩”变成“好用”。