
大模型浪潮走到今天一个明显的趋势是各种助手应用不再只是把模型接口包一层对话壳子而是开始沉淀一个个可复用的“技能”。我见过不少团队最初把能力全写死在Agent主循环里每次想加一个新功能就得动一遍核心代码参数越堆越多维护成本高到怀疑人生。后来转向把能力拆成独立技能模块才体会到这套结构的价值。这篇就聊聊大模型Skills应用里的标准目录结构和开发Skill的完整步骤分享我实测下来最顺手的做法。我能说的再直白一点Skill本质上就是一个自带说明书和入口的“插件”让大模型知道这个模块能干什么、怎么调用、需要什么参数。标准目录结构则是让这个插件在任意项目中都能被一眼看懂、一键接入的约定。这篇内容适合两类人一类是刚接触Agent开发想了解技能系统长什么样另一类是已经在写技能但总感觉组织混乱、难维护想看看一套通用做法的人。1. Skills这套“小插件机制”到底在解决什么问题先说一个特别容易踩的误区很多人把Skills理解成“给模型写提示词”。实际上一个真正可复用的技能模块解决的是三个层面的问题远不止提示词那么简单。第一层是能力的模块化。假设你的助手应用需要生成文件目录树、解析会议纪要、批量重命名文件、抓取网页正文这四个能力如果全写在同一个Agent的循环逻辑里代码会迅速膨胀。每次修改其中一个都得小心翼翼生怕影响另外三个。把每个能力拆成一个独立Skill之后主逻辑只需要负责“根据用户请求选择合适的技能并传入参数”每个技能内部怎么实现和外界完全解耦。第二层是调用契约的清晰化。这也是我后来才深刻理解的点。一个技能能被大模型正确调用前提是模型能看懂它的输入和输出。输入长什么样、输出长什么样、有什么副作用比如是否写文件、是否需要网络这些信息必须以一种模型容易理解的方式描述出来。目录结构里放一个配置文件本质上就是在做这件事。第三层是资源的自包含。一个技能如果需要依赖外部文件、模板、字典数据甚至子脚本这些资源最好都放在技能自己的目录下而不是散落到项目各处。我自己之前吃过亏一个技能依赖一张映射表表放在工具目录代码里写死相对路径结果换了机器或者打包发布直接找不到文件。后来改成自包含的目录结构再也没出过这种问题。可以打个比方技能系统就像是给模型配了一套有标准接口的插座面板每个技能是一台独立的电器。你不需要知道冰箱里面怎么制冷你只需要知道把插头插进对应的插座。目录结构就是这些插座的规格说明书让“插头”和“插座”永远能匹配。再说说适合什么场景。如果你只写一个临时脚本跑一次确实不需要这套结构。但只要你打算长期维护一个有多个能力面的项目或者想把自己的能力模块分享给其他开发者使用标准目录结构就值回票价了。它最大的好处是——新成员接手项目时可以不用看任何文档就大致猜出某个技能是干嘛的这种心智负担的降低在项目变大之后非常明显。2. 一个Skill的标准目录结构长什么样为什么每一层都这么放网上不少项目都有自己约定俗成的技能目录格式虽然细节有差异但主干逻辑基本一致。我常用的标准结构是这样skills/ ├── my_awesome_skill/ # 技能包名统一用下划线小写 │ ├── skill.yaml # 技能元信息名称、描述、参数说明 │ ├── SKILL.md # 给模型看的说明文档定义何时调用、怎么调用 │ ├── __init__.py # 核心逻辑的入口如果按Python包组织 │ ├── src/ # 主代码目录也可叫 handlers 或 core │ │ └── main.py # 技能核心实现 │ ├── assets/ # 静态资源模板文件、数据字典等 │ │ └── templates/ │ ├── tests/ # 单元测试与集成测试 │ │ └── test_main.py │ └── examples/ │ └── example_input.md # 输入示例我来逐个解释每个文件为什么存在以及不这样放你会遇到什么麻烦。skill.yaml是技能的脸面。它是一切自动化和模型调度的起点。里面一般存技能的名字、版本、一句给模型看的描述、参数定义表。为什么是这个格式因为YAML可读性好、嵌套能力比纯文本强、被各种配置系统天然支持。我见过有人把参数定义放在Markdown里而不是YAML里这在某些框架下也能跑但一旦框架要做参数自动注入、类型校验YAML就比自然语言更可靠。给你看看我常用的字段长啥样name: repo_tree_builder description: 生成项目的目录树结构可自定义深度与是否包含文件 version: 1.0.0 parameters: - name: root_path type: string description: 要生成目录树的根路径 required: true - name: max_depth type: integer description: 最大递归深度不传则默认为2 default: 2 required: false - name: include_files type: boolean description: 是否在树中显示文件名 default: true required: false这里最容易被忽略的是type和default字段。很多新手只写name和description结果模型经常把数字参数传成字符串代码里还得再做一遍强制转换。把类型写清楚之后调度框架可以帮你做一层预校验和转换少很多麻烦。SKILL.md是给模型读的说明书说白了是提示词工程格式化的产物。它和skill.yaml的分工是yaml是给调度器和代码看的SKILL.md是给大模型理解“什么时候该用它、怎么调”看的。很多框架在推理时会自动把SKILL.md的内容注入到系统提示词里让模型学会主动调用这个技能。所以这段说明要怎么写非常讲究。我的经验是开头先给一段“这个技能解决什么问题”然后是一段“请你在遇到下列情况时调用本技能”最后是一个可直接套用的调用示例。必须避免写得像代码注释一样干巴巴要有场景感的描述。init.py和src/main.py是技能的躯干。前者负责暴露入口函数后者是真正干活的地方。这里有一个原则我坚持了很久入口函数的设计一定要轻。入口函数只需要接收参数、调用核心逻辑、返回结果不要把业务逻辑写在这里。否则你会发现写着写着这个入口函数越来越胖最后根本没法测试。assets/目录帮你管理“附带资源”。一个技能依赖的资源可能有模板文件比如生成报告时的HTML模板、术语映射表比如把口语说法转成书面语、甚至一个小的词库。所有这类资源统一放assets/并在代码中用相对当前包路径的方式读取别用绝对路径。这一点在容器化和打包部署时尤其关键。tests/和examples/是加分项但我建议当成必选。有很多技能出错其实不是逻辑问题而是你对“模型怎么理解这个技能”的高估。你不写几个典型输入用例就很难发现“啊原来模型会传这种格式的参数”。tests/里至少要有正常参数调用、缺失参数调用、极端值调用三个用例。examples/则给使用者看存放真实可跑的输入案例。如果对比一下“把所有逻辑都塞进一个脚本”的做法标准目录结构看起来像是多花了不少时间在“搭架子”但架子搭好后每个技能都能独立演进、独立测试、甚至独立发布这个收益在中后期会指数级放大。3. 从零到一开发一个Skill我总结的六个关键步骤光说结构有点空洞我直接用一个真实技能来边做边讲。这次我想开发一个技能名字叫“一键生成项目目录树”repo_tree_builder。这个技能做的事情很简单接收一个根路径递归扫描目录结构输出一棵带有缩进关系的目录树。别看简单它能讲清楚技能开发中几乎所有关键步骤。第一步起个好名字并写清边界。技能名不能含糊。我见过有人给技能起名叫“文件操作”结果模型根本不知道什么时候该用——因为“文件操作”包含创建、删除、读取、写入等一堆子能力边界不清等于没有边界。我的做法是名字里尽量包含动词和对象比如repo_tree_builder、meeting_minutes_parser、pdf_table_extractor。一眼就能看出“做什么动作、处理什么东西”。确定名字后还要用一句话写清楚边界这个技能负责什么不负责什么。比如我的技能边界是“负责扫描本地文件系统目录生成缩进格式的目录树不负责打开文件夹、修改目录结构、统计文件大小”。模型看到这句话就知道什么时候该调你什么时候不该。第二步把输入输出定到“机器较真也不怕”的程度。这一步决定了你后面要少掉多少头发。开发时你自己心里清楚要传什么参数但大模型不知道它可能会按照你的描述发挥创造力乱传参数。所以输入定义里要写清楚每个参数的类型、是否必填、默认值最好再加一个取值范围约束。我这棵目录树技能参数就是root_path、max_depth、include_files类型和默认值必须一开始就想清楚。输出也是一样我是直接返回一棵带缩进的文本树还是返回一个JSON结构让外面的界面去渲染越早定越好因为输出结构一变所有调用方都要跟着变。我建议输出统一用结构化数据比如JSON、列表而不是纯展示文本这样后续接别的界面工具会方便很多。第三步写核心逻辑时要特别处理“外部资源边界”。这一步是代码层面最容易出问题的环节。目录树扫描本身不难难的是各种边角情况权限不够导致某个目录读取失败、目录嵌套太深触发递归溢出、符号链接形成循环引用。我的处理方案是遇到无权限目录就标记一个[PERMISSION_DENIED]遇到符号链接就跳过不追踪最大深度超过10就做个截断提示。凡是这类边界情况都要在代码里显式写出来绝不能偷偷吞掉错误否则模型看到的结果和真实情况不一致会误导用户。第四步编写元信息与调用说明。这一步是最接近“传统提示词工程”的部分但比写提示词复杂一些。你要在SKILL.md里让模型明确理解这个技能解决什么问题在什么场景下必须调用它怎么把用户的话转成参数。我写SKILL.md的习惯是弄一个“触发信号区”把可能的触发场景都列出来。比如用户说“帮我看看这个项目的结构”“这个文件夹下面有哪些内容”“想生成一个目录树”都可能会触发repo_tree_builder。写完“触发信号”后一定要加一段“调用示例”最好直接告诉模型当用户说“帮我看看当前目录下两层深度的内容”时你应该调用repo_tree_builderroot_path传当前目录max_depth传2。有个小技巧很多人不知道你可以在SKILL.md里写一个“反面示例”——什么情况下不要调用。对降低误调用率有奇效。第五步提供示例与最小测试。很多人在这一步偷懒了觉得代码能跑就行了。但技能类的代码坑往往不在语法错误而在交互契约上。写examples的语义是给使用这个技能的模型也好、开发者也罢一条“输入输出对齐”的锚。比如我写了一个example输入是root_path./my_projectmax_depth2然后贴出预期的输出树。这个例子有两个作用一个是读者秒懂这个技能的效果另一个是你可以拿它当回归测试基线——以后改了代码跑一下例子发现输出和预期对不上说明改坏了。tests目录里再放三个测试正常用例、边界用例深度为0、异常用例路径不存在这就够了。第六步在真实场景里验证而不是只跑单测。单测通过不算完。技能的真正考验是你把SKILL.md注入到对话系统后用自然语言去“忽悠”它。比如你说“我的项目有多大体积”模型不应该调用我这个目录树技能因为我的技能根本不看体积。你再问“我的代码除了源码还有什么东西”这时候模型就应该调用技能去扫一遍实际目录。我验证时会准备一张表列各种意图看模型的调用率正确与否。这一步发现的问题往往才是SKILL.md描述写得不够好的真凶。开发技能本质上是在搭一座桥一头是模型的理解能力另一头是代码的稳定输出。桥搭得稳不稳完全看中间这些契约有没有对齐。每一步都别急着走节奏反而是最快的。4. 从我踩过的坑里整理一份“技能开发避坑手册”说了这么多正确做法再聊聊我实际踩过的坑而且是反复踩过才长记性的那种。避开这些坑你至少能少折腾两周。坑一相对路径的定时炸弹。如果你在处理assets下的资源、或者读取模板文件千万不要写死程序的相对路径比如open(assets/template.txt)。因为你的技能不一定被放在哪个工作目录下运行一旦挂载到别的路径这个文件铁定找不到。我的解决办法是在入口文件里用Path(__file__).parent.parent / assets / template.txt这种方式基于当前代码文件位置推导出资源路径。这样技能包不管放在哪里都能正确定位自己的资源目录。这不是高级技巧但真的救了我很多次。坑二一个技能想干什么都管。我早期做技能容易把能力堆在一起比如“目录树生成文件大小统计最近修改时间展示”本来是为了图省事以为多给模型点能力是好事。结果模型经常误判用户明明只要目录结构模型却额外去计算文件大小输出又长又误导。后来我狠心拆成三个独立技能某个功能不用就整个不挂载。每次只给模型一个清晰的能力边界误用率立刻下降。技能的“单一职责原则”比代码里的单一职责更严格因为模型对边界的判断力远没有你想象的强。坑三参数校验太死把模型“逼疯”。我之前在参数校验上做得特别严格模型传错一个类型就返回一个硬错误结果模型死循环式地重试用户体验极差。后来我改成“宽容输入、严格输出”入参尽量让框架做一次宽松转换——字符串3转成整数3缺省参数就用默认值实在转换不了的才返回结构化错误信息并且把错误原因写清楚让模型看到之后能自己修正调用方式。你可能会担心宽松转换会让错误参数悄悄溜进来但实际观察下来模型的调用可靠性反而提高了——因为减少了无谓的重试摩擦。坑四技能里的状态残留。有时候我在技能里做了一个全局变量做缓存比如上次扫描的目录结果。看起来能提速实际上一旦被多线程复用或者一次对话里多次调用就可能返回旧数据。模型只要说出“我刚刚问的不是这个目录”你就会陷入尴尬。技能应当设计成无状态的每次调用只根据传入参数计算除了写明缓存策略的白名单场景不然不保存任何状态。我把这条当成铁律。坑五不写example就上线。有一次我把一个PDF表格提取技能发布给团队用结果大家反馈”模型经常乱猜参数输出格式不稳定“。我拿过代码一看SKILL.md写得还行但没有一个example模型根本不知道输出到底长什么样于是自由发挥。补上几个典型输入输出示例之后格式漂移问题立刻好了大半。例子就是给模型照着的“模板”没有模板它只能靠想象有了模板它只需要匹配。坑六以为测过就跑不坏。技能的调用方是大模型模型版本一升级可能对SKILL.md的理解能力就变了。同一个技能此前你用的模型能理解你的描述换个新模型未必。所以我养成了一个习惯每次切换模型版本都把技能回归跑一遍尤其是“触发时机”和“参数传递准确性”这两个模块。别怕麻烦模型理解能力的波动是常态定期回归才是唯一可靠的保障。这六个坑没有一个是特别高深的但每一项都是真实项目中高频发生的。技能开发之所以有意思就是因为它是个“人机协同意愿对齐”的过程你要同时照顾代码的严谨和模型的模糊理解两者缺一不可。5. 给想要上手的朋友一段基于我亲测的实操建议如果你现在准备在自己的项目里引入技能化改造我给两条非常具体的路线。路线A如果你用的是支持技能机制的Agent开发框架直接按框架提供的脚手架生成目录然后对着本文讲的步骤走一遍就好。注意框架生成的目录可能和我上面列的不完全一样比如有的把技能描述放在一个叫instructions.md的文件里有的把参数定义放在JSON里。别慌本质不变元信息在一个机器易读的地方说明在一个模型易读的地方核心逻辑在独立代码目录里。你把这两条记住就能快速适配任何框架。路线B如果你暂时不想引入框架打算自己维护一个最小的技能体系也可以这么做建一个skills目录每个技能下面放一个SKILL.md和main.py再用一个全局的注册表一个YAML或JSON文件把名字、描述、参数清单汇总起来。你的Agent主逻辑读这个注册表一旦判断要执行某技能就用subprocess或importlib动态加载对应目录的代码。这种“轻量自制版技能系统”在改造成本和可维护性之间能取得很好的平衡。再分享一个习惯每开发完一个技能我会做一个小型的“技能验收表”逐项检查名字是否清晰、边界是否明确、参数是否带类型和默认值、SKILL.md有没有触发信号和反例、examples能不能跑通、tests里有没有边界用例、资源路径是不是基于包路径的、有没有意外状态残留。整个过程大概几分钟但能让你的技能质量稳定在线。这套验收表对于不愿意写文档的人尤其有用它把你该做的事变成了勾选项效率反而更高。我自己的感受是技能开发的繁琐程度低心智负担却高。难的不是写代码而是你要时刻站在模型的角度想一想它看到这个目录和说明能不能准确理解我打算让它干什么一旦你习惯了这种换位思考再去写各种Agent应用基本就是降维打击——你不只是在写功能而是在给模型构造一个又一个可以精确驾驭的能力入口。