
最近几个月“agent-skills”这个词在我待的几个技术讨论群里出现频率明显变高了。一开始我以为又是哪个新框架的宣传话术点进去仔细看了几轮讨论才意识到大家其实在聊一个很实在的问题大模型Agent的能力到底应该怎么封装、怎么沉淀、怎么复用。有人塞了几十个工具进配置文件有人把整套业务流程写进系统提示词还有人已经开始用目录结构管理“技能包”但大多数项目跑到一半就乱成一锅粥。这篇文章我想从工程落地的角度把Agent技能化这件事从头到尾拆一遍聊聊我自己的设计思路、踩过的坑以及一套可以直接照抄的技能包结构。先说清楚这篇文章适合谁。如果你只是用API跑几个Demo那暂时用不上技能化这套东西但如果你在做一个正经的Agent产品需要让模型稳定地完成某一类复杂任务或者在多个项目间复用同一套能力那“agent-skills”这套方法论基本就是绕不开的作业。我会从为什么需要它讲起再给出一套可落地的技能包结构、版本管理方案和完整实战案例最后把我踩过的几个坑一并交代清楚。1. 为什么Agent需要“技能”而不是一串工具1.1 从工具到技能差的不是数量是“会做事”很多人对Agent能力的理解还停留在“给模型一堆函数调用让它自己选”。这在早期确实够用比如让模型查个天气、算个日期一个函数配上参数声明就结束了。但一旦任务复杂起来问题就来了——真实世界的任务往往是多步骤的比如“把这个文件夹里的PDF全部转成图片再按文件名生成缩略图索引”这种活儿拆开看需要文件遍历、PDF解析、图片编码、结果汇总至少四步每一步还可能涉及格式兼容、异常处理、临时文件清理。如果把这四步硬拆成四个独立工具塞给Agent它每次都要在对话里重新组合调用顺序配合不好就翻车。技能化做的事情就是把这四步连带着异常处理、参数校验、结果格式化全部打包成一个黑盒对外只暴露一个清晰的名字和几句描述。Agent看到“create_pdf_thumbnails”这个名字只需要知道“传入PDF目录产出缩略图索引文件”里面怎么实现、有多少中间步骤一概不用关心。这和人类的工作方式很像你不会每次都向同事解释“先打开文件、再逐页渲染、再压缩保存”你只会说“把这份PDF转成缩略图”剩下的是对方的专业技能。所以工具和技能的本质区别一句话就能说清工具是单点能力技能是带完整流程、上下文和验证闭环的解决方案。单点能力再多也只是给Agent提供了积木技能才是把积木预装成功能模块让Agent直接从“搭积木”变成“选模块”。1.2 不技能化会踩的坑三个真实教训我自己早期做过一个内部文档助手最初方案就是把文档解析、文本切片、向量检索、拼接回答全拆成独立工具十几个工具挂在同一个Agent上。结果有四个很头疼的问题。第一个是上下文爆炸系统提示词里要写清楚每个工具什么时候用、怎么用光工具说明就占了两千多字模型决策的准确率反而下降了。第二个是复用困难另一个项目也想用文档解析能力但它的调用参数和返回结构贴着第一个项目的业务写别人拿过去根本跑不通。第三个是验证缺失某个环节调用失败时Agent往往不知道失败在哪一步只能整段流程从头重跑白白浪费大量token。第四是安全问题中间步骤如果涉及删除临时文件或者覆盖目录Agent在没有约束的情况下可能误操作。技能化之后这些问题基本都能缓解。工具说明被压缩成一段短描述上下文占用少了技能包独立成目录按仓库或压缩包分发跨项目复制就能用验证逻辑封装在技能内部失败时直接返回“第2步PDF解析失败”不用模型猜安全边界在技能代码里就写死了不允许Agent传入任意路径去删除。从“工具列表”变成“技能库”这不是概念包装而是实打实的工程范式转换。2. 一个标准Skill长什么样五层结构拆解我见过很多种技能包的组织方式比较下来比较稳妥的结构是五个层次配置说明、参数契约、实现逻辑、验证用例、使用示例。这五层缺一不可少了任何一层技能要么没法被Agent正确调用要么没法被其他开发者信任和复用。2.1 配置与说明Agent选技能的“说明书”技能包的第一层是说明书通常是一个SKILL.md文件作用是告诉大模型这技能是干嘛的、什么时候用、什么时候不用。很多技能包做不好问题就出在这份说明书上。写得太短Agent在多个相似技能之间犹豫写得太长模型注意力被大量细节分散照样选错。我的经验是一份合格的说明书至少要包含四个要素。第一是技能的一句话定义比如“从PDF文件中提取结构化表格数据”这句话要能和其他技能明确区分。第二是适用场景列举三到五个典型触发条件比如“用户提到合同扫描件需要转Excel”。第三是不适用场景这个很多人会忽略但非常重要——明确说“不要用这个技能处理扫描图片请调用OCR技能”能避免模型拿错工具。第四是输入输出的简要说明不用展开细节给个概览即可。说明书还有一个隐性作用它相当于技能的对外宣传文案在Agent加载整个技能库时模型就是靠扫描这些描述来做路线规划的。四要素配齐等于给了Agent一张清晰的选择索引。2.2 参数与依赖把契约定清楚第二层是参数定义我都是直接用JSON Schema来写。为什么强调用标准格式而不是自己发明一套因为Agent原生就理解JSON Schema它可以自动根据Schema生成合法的调用参数不需要你在提示词里额外解释格式。参数定义的关键是该写的必填不该写的别乱加。我见过有人给“发送邮件”技能定义了收件人、抄送、密送、附件路径、邮件优先级、延迟发送时间等十几个参数结果Agent经常因为漏填某个非必填参数而放弃调用。依赖声明也归在这一层。技能代码运行需要哪些Python包、系统命令、外部服务最好在技能目录里放一个requirements文件或Dockerfile。运行时检测依赖、缺失时自动安装或给出明确提示比让用户去翻README再手动装要省事得多。依赖锁定版本也很关键不锁版本上周还能跑的技能这周因为依赖库升级就挂了排查起来相当痛苦。2.3 实现逻辑可验证、可容错、可审计第三层是技能的核心代码。这里最重要的原则是技能内部再复杂对外也要保持稳定和简单。对外接口不能频繁变动内部完全可以按业务复杂度自由拆分。比如“生成周报”这个技能内部可能涉及数十个数据源的拉取、清洗和格式化但只要最终能统一返回一个“周报文案”字符串对Agent来说它就是一次普通调用。在实现层面我建议在代码里重点做三件事。一是结构化错误处理尽量把可能出错的环节都包上异常处理并返回带有步骤前缀的错误信息后面实战部分就是例子。二是日志记录每次调用的参数、耗时、结果摘要最好都有痕迹技能多了之后没有日志根本做不了归因。三是幂等和超时保护能强制设置超时时间的要设置避免某个步骤卡住导致整个Agent流程死掉——这在长任务里尤其致命。2.4 验证案例给Agent的“效果预览”第四层是验证用例也可以理解成一组few-shot示例。代码用单元测试来验证Agent技能则要准备几组典型的输入输出对。比如“批量压缩图片”这个技能验证用例要覆盖传入一张大图期望输出压缩后的文件传入一个已经是压缩格式的小图期望给出“无需压缩”的提示传入一个损坏文件期望返回明确的错误信息。为什么验证用例这么重要因为Agent在真正调用技能前需要通过用例来理解技能的边界。一份描述写得很抽象但看一组输入输出之后模型基本上就知道该怎么用了。此外当技能库升级时验证用例就是回归测试的依据——新版本跑不通旧用例说明兼容性被破坏了不该发布。这层对多人协作尤其关键别人帮你维护技能时没有用例就等于没有兜底。3. 技能库的组织与版本管理从单技能到家族体系你会同时维护很多个技能那就得管好你的技能库。技能库的目录结构、命名方式、版本策略如果一开始不设计好后面冲刺到几十个技能时就会寸步难行。当然技能库和你公司的代码仓库是一个道理该分的分、该聚合的聚合要有一套方便检索的索引体系。3.1 用目录结构当“办公桌”先把技能当作一个个文件夹来管理。我的习惯是每个技能一个独立目录目录名就是技能的唯一标识目录内放SKILL.md、实现脚本、requirements.txt、tests目录、以及若干示例文件。这个约定非常简单但它保证了每个技能包自包含复制到另一个项目就能直接挂载。当技能数量超过二十个之后建议再加一层按业务域分组的目录。比如“document”域放PDF解析、DOCX转换、表格识别“data”域放数据清洗、格式转换、脱敏处理“comm”域放邮件发送、IM通知。域划分能让Agent在加载时做范围过滤不至于每轮对话都把全部技能描述塞进上下文。这就像办公桌的抽屉标签——分类清晰取用才快。3.2 版本发布与兼容性这三件事技能也是代码也要讲版本管理。我见过不少团队用Git管理技能库但完全不打tag结果某一天技能行为悄悄变了所有Agent的表现都跟着变根本查不出是哪次改动导致的。我的建议是每一次行为变更都要跟着版本号走并且用语义化版本规则来约束。具体来说版本号由三段组成主版本号、次版本号、修订号。如果修改了技能的外部描述、增加了新参数或者改变了返回结构属于主版本变更如果只调整了内部实现逻辑输入输出不变属于次版本变更文档修改、注释补充、依赖小版本锁定属于修订变更。版本变更是技能发布流程里最容易被忽视、但对下游影响最大的环节。兼容性测试也不要省。每次升级技能至少跑一遍该技能的验证用例并挑几个典型Agent任务做回归验证。我遇到过技能自身单测全过结果集成到Agent上因为返回格式和模型预期不一致导致无法解析排查了好久才发现问题出在技能里返回了多余的调试日志。增加一个针对Agent调用链路的回归测试能帮你少走很多弯路。4. 手把手实战搭建一个“临时文件清理”技能理论讲多了容易飘还是落一个能直接跑的技能出来。我挑一个安全系数高、需求也典型的任务来练手临时文件清理。Server上跑任务时经常会生成各种临时目录和中间产物Agent经常被要求“把/tmp下的东西清一清”。这个需求看着简单但真要交给Agent做没有技能封装的话非常危险——模型很可能用一条shell命令把整个目录删掉。我写这个技能的时候重点考虑的就是边界清晰、默认安全、失败有明确定位。4.1 为什么选这个技能练手选这个例子的原因有三。第一它足够简单不需要引入外部API任何人都能复现。第二它有一个天然的安全红线不能删任意路径只能删指定目录下符合特定规则的文件。这个红线设置和讲解本身就是技能设计里最核心的要点。第三它覆盖了技能封装的基本完整链路配置、参数、实现、验证、挂载一套流程走完你对agent-skills的套路就熟了。至于更复杂的PDF解析、知识库检索、爬虫类技能套路是类似的只是在实现层加更多业务逻辑而已。4.2 三分钟写出SKILL.md与参数声明先建目录结构temp-cleaner/ ├── SKILL.md ├── clean.py ├── requirements.txt └── tests/ └── test_clean.pySKILL.md核心内容这样写--- name: temp-cleaner description: 清理指定目录中的临时文件和缓存仅支持删除临时目录下的特定类型文件不可用于删除任意路径。 version: 1.0.0 --- # temp-cleaner ## 适用场景 - 用户要求清理 /tmp 或项目运行产生的临时目录 - 磁盘空间不足需要清理 cache、tmp、log 类文件 ## 不适用场景 - 用户要求删除某个用户上传的原始文件请调用 file-delete 技能 - 用户要求按内容搜索文件请调用 file-search 技能 ## 输入 - target_dir: 必填要清理的目录路径 - policy: 可选cleanup策略默认“default”匹配 *.tmp、*.cache、*.log - dry_run: 可选默认为 true只列出待清理文件设为 false 才实际删除 ## 输出 - JSON: { deleted_count, freed_bytes, detailed_files: [...] }写描述的一个小技巧是宁可让它范围窄一点也不要让它看起来能处理所有情况窄描述更有利于Agent选出正确的技能。接下来是参数声明直接用JSON Schema干净又标准{ name: temp-cleaner, parameters: { type: object, properties: { target_dir: { type: string, description: 目标目录的绝对路径 }, policy: { type: string, enum: [default, aggressive], description: 匹配规则aggressive扩展匹配更多文件类型 }, dry_run: { type: boolean, description: 如果true只预览不删除默认true } }, required: [target_dir], additionalProperties: false } }看到这里你可能发现了参数设置里把dry_run默认设为true这是刻意的。技能在默认状态下绝对不能产生破坏性结果Agent必须显式确认“dry_runfalse”才能真正删除。这个设计让技能天然具备一道安全闸门。4.3 核心实现安全边界是第一优先级Python实现里我最花心思的是路径校验因为路径穿越是清理类工具最容易出事故的点。核心逻辑可以先写成这样import os, json, argparse, fnmatch TEMP_MARKERS (/tmp/, /var/tmp/, /var/cache/) def is_safe_path(target: str) - bool: # 必须解析为绝对路径且位于允许的临时目录之下 abs_path os.path.abspath(os.path.expanduser(target)) ok abs_path.startswith(TEMP_MARKERS) # 防止 /tmp/../etc 这类路径穿越 real_path os.path.realpath(abs_path) ok ok and real_path.startswith(TEMP_MARKERS) return ok def scan_files(target_dir: str, aggressive: bool False): patterns [*.tmp, *.cache, *.log, *.swp] if aggressive: patterns [*.bak, *~, *.pid] matched [] for root, dirs, files in os.walk(target_dir): for filename in files: if any(fnmatch.fnmatch(filename, pat) for pat in patterns): full_path os.path.join(root, filename) matched.append(full_path) return matched def main(): parser argparse.ArgumentParser() parser.add_argument(--target_dir, requiredTrue) parser.add_argument(--policy, defaultdefault) parser.add_argument(--dry_run, actionstore_true, defaultTrue) args parser.parse_args() if not is_safe_path(args.target_dir): result { success: False, error: blocked: target_dir outside temp roots or path traversal detected, deleted_count: 0, freed_bytes: 0, detailed_files: [] } print(json.dumps(result)) return matched scan_files(args.target_dir, args.policy aggressive) freed 0 deleted [] for file_path in matched: try: size os.path.getsize(file_path) if not args.dry_run: os.remove(file_path) freed size deleted.append({path: file_path, size: size, deleted: not args.dry_run}) except Exception as e: deleted.append({path: file_path, error: str(e), deleted: False}) result { success: True, dry_run: args.dry_run, deleted_count: len(deleted), freed_bytes: freed, detailed_files: deleted } print(json.dumps(result, ensure_asciiFalse)) if __name__ __main__: main()几个关键点说下。第一is_safe_path做两层校验一层是绝对路径判断一层是realpath解析后的软链接判断防止恶意软链接把路径带到系统目录。第二dry_run不仅打印预览返回的JSON字段里也明确标记了deleted: false这样Agent看到输出就能理解“这次调用没有实际删除”。第三错误处理尽量细化到单个文件——某个文件删不掉时不影响其他文件的清理避免一个异常卡断整批处理。也许有读者会问Agent不一定会按我的命令行参数来调用这个脚本那挂载时怎么对接这里多说一句技能脚本只是一层可执行单元真正的技能调度通常通过运行器runtime统一封装。实现代码和调用方式解耦反而让该技能更容易被测试和维护。4.4 验证用例与回归测试防止Agent误用有了脚本还得有一组能说明“什么情况该用、什么情况不该用”的验证用例。这不是普通的开发测试它同时还是给模型看的“示范”。我准备了三组核心用例用例一目标目录是/tmp下的一个普通文件夹里面有几个.log文件。输入target_dir/tmp/test-runtime期望结果successtruedry_runtrue时deleted_count3。用例二目标目录传入/etc。期望结果successfalseerror提示“blocked: outside temp roots”表示越权拦截生效。用例三目标目录传入/tmp/../tmp2。期望结果被realpath拦截返回安全错误。这三组用例中用例二和用例三尤其关键相当于给Agent划出“绝对不能碰”的红线。Agent在真正执行任务前看到这样的验证用例会更容易遵循安全边界。验证用例代码也不算复杂关键是要在集成Agent前跑一遍确保输出永远是合法JSON并且没有多余日志——这个我非常强调因为模型在解析工具输出时如果输出里混入非JSON内容很可能导致整个决策链断掉。4.5 让Agent跑起来加载与调用的完整链路技能包写完怎么挂到Agent上不同框架的注册方式略有差别核心流程是三步将技能目录放到配置指定的skills目录、在Agent配置里声明启用该技能、通过运行时解析SKILL.md构建说明。以比较通用的JSON配置为例{ agent: { model: qwen-max, skills: [ { source: skills/temp-cleaner, enable: true } ] } }然后Agent实际调用时用户说“帮我看下 /tmp/test-runtime 这个目录下有多少临时文件”我期望它能自动调用temp-cleaner技能并且因为默认dry_runtrue返回的是预览列表而不是直接删除。用户确认“都删掉”之后它再以dry_runfalse调用一次。这样的交互链路才是安全的。我在实操中会把这类交互范式也写进SKILL.md的可选“运营建议”里因为很多Agent框架支持在技能描述中增加调用建议能显著提高最终任务的成功率。5. 我在实战中反复踩的四个坑这一部分按理说应该穿插在技术讲解里但我还是想集中拿出来专门说。技能化方案听着美好真正落地时到处是细节坑有些坑我踩过不止一次。如果你正在搭建自己的技能库这几条建议能帮你省掉好几个晚上的排查时间。5.1 描述写太长Agent反而选不到我第一次写技能描述生怕模型看不懂什么细节都往里塞光适用场景就列了十几条。结果Agent在几个技能之间频繁犹豫甚至把相似技能误调用。后来我把描述精简到两三句话并强调“如果用户提到清理、临时、缓存优先选择本技能”选型准确率立刻上去了。原因也很简单模型读技能库的过程和搜索引擎索引是类似的在上下文窗口里只截到头部内容。长描述反而稀释了关键词密度。5.2 参数校验能省但别真省有一次我写了一个归档技能参数里允许传output_format可选值是“zip”和“tar.gz”。由于我偷懒没有做严格枚举校验Agent传了一个“zip2”进去脚本竟然真的进入zip分支并生成了坏文件。后来我把所有参数的合法值都用枚举类型约束脚本里再做一遍兜底校验双保险才算安心。Agent不像传统程序它的输出天然带不确定性你永远要对传入值保持警惕。在技能入口处做硬防御是保护技能稳定性的第一道闸。5.3 技能命名和既有工具打架技能多了之后命名冲突是必然的。我有两个技能分别叫“file-clean”和“temp-cleaner”结果模型有时候分不清该调哪个。后来我统一了命名规范功能域-动作-对象例如“temp-cleaner-v2”。并且保持同域技能的描述方式一致方便模型做相似性判断。命名这件事越早规划越好临时凑合的名字积累到后来改名的成本会非常高。5.4 只知道封装执行忘了记录过程技能封装让Agent的执行过程变成了黑盒这是优点也是隐患。有一次用户投诉Agent生成的报表数据对不上我检查了Agent自身日志发现它调用了“report-generator”技能但技能文档里只记录了最终结果没有记录内部每一步取数的SQL和中间关键值排查起来完全两眼一抹黑。从那以后我要求每个技能在内部增加日志记录输入参数、关键操作节点、中间产物的来源、耗时和结果摘要。出现问题时至少能顺着日志一步步定位。下面把我在实战中遇到的典型问题整理成一张速查表方便大家直接拿来对照排查症状可能原因排查路径我的建议Agent在两个相似技能间反复切换描述区分度不足对比两个SKILL.md的前100个字符重构描述增加冲突选项的“不适用场景”技能返回结果模型解析失败输出混入非JSON内容手动执行脚本检查stdout禁用print调试统一retrun JSON技能执行速度极慢依赖锁定不严、重装依赖查看启动日志耗时requirement锁版本增加超时跨项目复制技能后运行报错依赖或路径硬编码检查requirements和绝对路径引用全部改为相对项目根的可配置变量技能行为变更但Agent表现不稳定版本未升级或未回归查看技能版本号与调用记录语义化版本升级后跑一遍用例这条表格我建议打印出来贴在显示器边上或者写成团队的内部Wiki置顶帖。很多技能相关的线上问题翻到最后基本都能归到表格里的某一行。排查问题时先对号入座比重新看一遍全部代码要高效得多。6. 从Skills到能力网络下一步怎么扩展技能化Agent这条路走到后面你会发现它不只是“把工具包装一下”那么简单。当技能数量积累到几十个它们之间还会出现互动和组合关系。我现在的做法是把技能分成三层基础原子技能、复合业务技能、领域策略技能。原子技能如“读取文件”“发送HTTP请求”是最底层的轮子复合技能如“生成PDF报表”会组合多个原子技能策略级技能则更复杂像“根据用户身份动态选择报表模板并生成PDF”这种下层会引用多个复合技能。层级划分清晰Agent在规划时更容易构建子任务链路。再往下想技能本身可以做成跨项目复用的资产。我维护一个内部技能仓库每个技能都按“名称域-功能-版本”命名并且写了规范的README其他项目引入时不需要看源码直接按文档挂载即可。这就和公共代码库一样时间越长积累越厚。而且技能一旦稳定下来复用成本会越来越低——一个新项目从零搭建Agent迷茫最大的就是“初始能力从哪来”技能库直接回答了这个问题。我个人在实际操作中的体会是技能化更像是一种工程纪律短期内它让你多写了SKILL.md、参数Schema和验证用例感觉上是慢了。但一旦技能超过十个复用的杠杆效应就非常明显。项目里加新Agent时不需要再把所有能力从零教一遍只需要组合已有技能复杂度能降一个量级。哪怕你只有三个技能我也建议从现在开始按这套结构来组织等到第五十个再说重构成本远高于一开始就定好规矩。最后再分享一个我自己的小习惯每次写完技能我都会用相同的问题问两个不同的模型让它们分别判断该调用哪个技能、参数该怎么填。这一步能很直观地看出描述写得清不清楚值得养成习惯。