ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent Skills技能包从零开发实战:目录结构、SKILL.md与参数设计

Agent Skills技能包从零开发实战:目录结构、SKILL.md与参数设计 先说明一下“skills”这个标题我在不同阶段看过三遍第一遍是刚入行时觉得它就是简历上的关键词第二遍是做自动化脚本时觉得它代表一套可复用的经验第三遍是最近两个多月在落地Agent项目时我突然意识到它已经变成了一个非常具体的工程产物——把某项能力打包成一个带说明文档、带参数契约、带可执行脚本的目录让大模型在合适的场景自动调用。这篇博文就围绕第三层含义来拆讲讲我是怎么从零设计、开发并调试了一套内部用的Agent Skills技能包包含完整目录结构、SKILL.md写法、参数设计原则、踩坑实录和团队复用方案适合正在做Agent应用、想把重复性工作沉淀成“即插即用技能”的人参考也适合刚接触这个概念、想快速上手的新手照着做。1. 先把“skills”这个标题拆明白1.1 从简历词汇到Agent工程产物以前聊到“skills”大家默认是指人的能力比如编程、沟通、项目管理。但Agent开发这个场景把词义彻底改了现在说“这个项目里有几个skills”指的是模型可以调用的能力模块。为什么会有这个转变核心原因是LLM本身不擅长稳定执行复杂操作。你让模型用自然语言描述“帮我批量压缩图片”它能说得头头是道但真让它一张张处理它就蒙了——不该压缩的它压了该保留参数的它丢了中途还容易编造不存在的文件路径。这不是模型笨而是因为它没长“手”。Skill就是给模型配的那只手把图片压缩这套操作固化成脚本再配上说明文档告诉模型什么时候用、参数怎么传、结果怎么解析。我之前用纯Prompt方式做过类似的事每次都得在对话里重新描述一遍“任务背景、输入输出格式、约束条件”模型理解得时好时坏。换成Skill之后描述只需要写一次模型每次像拿工具一样拿它稳定性和复用性都上了一个台阶。1.2 Skill的本质一份带说明的“经验包”很多人第一次打开Skill目录时会懵里面不就一个脚本加一个Markdown吗有什么特别的特别就特别在那个Markdown上。一个完整的Skill核心三要素缺一不可元数据描述名字、适用场景、边界条件、执行体脚本、命令或API调用入口、示例few-shot让模型知道正确调用长什么样。其中元数据描述是最容易被忽略、也最影响成败的部分。因为大模型不会去读你的Python代码它理解Skill的唯一途径就是那几百字的文本描述。描述写得好模型在正确时机调用、传正确的参数描述写得差模型要么永远不碰这个Skill要么乱传参数把脚本搞崩。可以把它想象成厨房里的预制料包厨师不需要知道里面有多少种香料、每种的比例只需要知道“做红烧肉时放一包、加水500毫升、小火炖40分钟”。Skill就是AI的预制料包SKILL.md就是料包外包装上的说明。所以写Skill的核心工作不是写代码而是写说明书。1.3 现在动手做Skill收益在哪里我一开始也觉得多写一个Skill不如多写几行Prompt来得快。但用了几个星期后收益很清楚一是调用稳定。同样的任务用Prompt描述每次效果都有波动用Skill调用基本是固定输出因为执行路径是代码在走不是模型在猜。二是可测试。脚本可以单测参数可以断言模型不参与核心逻辑问题定位简单得多。三是可沉淀。团队里任何人在任何项目里都能复用这份Skill不需要重新讲一遍业务背景。对做内部工具或交付项目的人来说这是实打实减少重复劳动的手段。2. 一个最小可用Skill的解剖2.1 目录结构与三个核心文件先看一个我实际在用的最小目录结构它是我所有Skill的统一骨架my-image-tool/ ├── SKILL.md ├── scripts/ │ ├── batch_compress.py │ └── requirements.txt └── examples/ └── demo.jsonSKILL.md放在根目录这是硬性约定。不管是Claude的Agent Skills还是其他兼容体系解析器都会优先查找这个文件名。它负责回答模型最关心的三个问题我什么时候该用你我需要提供什么参数你会返回什么结果scripts目录放真正的执行逻辑。这里有一个容易被误解的点scripts里不一定要放Python可以是Shell脚本、Node脚本甚至是一段curl命令只要能通过命令行调用即可。关键是可执行、有退出码、输出能被模型直接读取。examples目录放调用示例。很多Agent框架支持在提示词中注入示例模型看到“别人是这么调用的”比干巴巴的字段描述直观得多。我见过有人偷懒不建这个目录结果模型总是把参数名猜错加上的当天调用成功率就高了不少。2.2 SKILL.md的“三段式”写法SKILL.md本质上是一份给模型看的说明书我的经验是固定用三段式结构避免自由发挥导致模型抓不住重点。第一段是when_to_use写清适用场景和边界条件。我习惯用“当用户需要……时使用”开头紧接着写“在……情况下不要使用”。很多人只写前者不写后者结果模型遇到不相关的需求也硬调用。举个例子我这个图片压缩Skill的描述会明确写“仅适用于位图图片JPEG/PNG/WebP遇到视频、动图请勿调用”。第二段是parameters用表格逐行定义每个参数。后面会详细展开这里只说一个原则所有参数都要有默认值都要给取值范围。模型天生不喜欢填必填项如果你告诉它“image_dir必填没有默认值”它可能干脆放弃调用。第三段是workflow说明执行的步骤和返回格式。我要求每个Skill的输出必须是结构化JSON形如{success: true, count: 12, compressed: [...], saved_size: 3.2MB}。模型拿到JSON之后才能继续给用户做解释如果输出是纯文本模型还得二次猜容易猜错。2.3 参数设计的三个铁律参数设计是Skill里最容易被新手搞砸的地方。我踩过几轮之后总结出三个铁律。铁律一参数名用全称不要用缩写。你写input_dir模型大概率会传input_dir你写in模型可能传input也可能传in_dir五花八门。虽然描述里写了字段名但模型自动补全的毛病很难治全称能减少猜测空间。铁律二每个参数都给默认值和边界。模型不会读你的代码它不知道quality传200会不会爆不知道path不存在会不会崩。你要在参数表里写清楚“quality: 整数范围0-100默认85”。这样即使模型漏传、乱传脚本也能按保守策略兜底。铁律三输出格式必须稳定。如果脚本有时候输出JSON、有时候直接print错误堆栈模型读起来就非常困难。我要求脚本必须try-except包裹任何异常都转换成JSON错误结构并附带人类可读的hint字段。这三个铁律有一个共同的出发点模型是个“经验不足但很听话的实习生”你给他设置的参数契约越精细他发挥就越稳定。3. 手把手从零写一个“批量压缩图片”Skill3.1 先写裸脚本把功能跑通我建议不要一上来就写SKILL.md先写一个不带任何Agent色彩的“裸脚本”用命令行把功能跑通。这一步的目的是先把技术风险消灭掉避免把调试范围扩大。我选的场景是批量压缩图片是因为团队每周都要处理几十张活动海报原图好几MB发群里卡得要命。之前靠人工用在线工具一张张拖效率太低我决定做成Skill。核心脚本用Python Pillow实现代码不长核心逻辑是这样的#!/usr/bin/env python3 Batch compress images in a directory. import argparse import json import os from pathlib import Path try: from PIL import Image except ImportError: print(json.dumps({success: False, error: PIL not installed, run: pip install pillow})) raise SystemExit(2) def compress_image(src: Path, dst: Path, quality: int) - dict: with Image.open(src) as img: original_size src.stat().st_size img.save(dst, qualityquality, optimizeTrue) compressed_size dst.stat().st_size return { name: src.name, original_size: original_size, compressed_size: compressed_size, ratio: round((1 - compressed_size / original_size) * 100, 1), } def main(): parser argparse.ArgumentParser() parser.add_argument(image_dir, helpDirectory containing images) parser.add_argument(--quality, typeint, default85, helpJPEG quality 1-100) parser.add_argument(--output_dir, defaultNone, helpOutput directory) args parser.parse_args() src_dir Path(args.image_dir) if not src_dir.exists(): print(json.dumps({success: False, error: finput dir not found: {src_dir}})) raise SystemExit(1) out_dir Path(args.output_dir) if args.output_dir else src_dir / compressed out_dir.mkdir(parentsTrue, exist_okTrue) results [] for img_path in sorted(src_dir.iterdir()): if img_path.suffix.lower() not in {.jpg, .jpeg, .png, .webp}: continue try: out_path out_dir / f{img_path.stem}_compressed.jpg results.append(compress_image(img_path, out_path, quality)) except Exception as e: results.append({name: img_path.name, error: str(e)}) print(json.dumps({success: True, count: len(results), results: results})) if __name__ __main__: main()这里有几个刻意为之的设计输入目录不存在时输出结构化的JSON错误而不是直接抛堆栈每个文件单独try-except单个文件损坏不影响整批任务输出目录默认是输入目录下的compressed子目录避免覆盖原图。这些设计都是为后面接Agent做准备的裸脚本阶段就要把防御性做好。3.2 再写SKILL.md把经验装进说明书脚本跑通之后就要把它“翻译”成模型能读懂的语言。我按之前说的三段式写SKILL.md完整内容如下# Batch Image Compression ## When to use - Use when the user needs to compress images (JPEG/PNG/WebP) in a directory to reduce file size. - Use when the user wants to batch optimize images for sharing, upload, or archiving. - Do NOT use for videos, GIFs, or vector graphics (SVG). - Do NOT use unless the user explicitly asks for image compression or optimization. ## Parameters | name | type | required | default | description | |------|------|----------|---------|-------------| | image_dir | string | yes | - | Path to the directory containing the images. | | quality | integer | no | 85 | Compression quality, range 1-100. Lower means smaller size. | | output_dir | string | no | {image_dir}/compressed | Directory where compressed images will be written. | ## Workflow 1. Read the image_dir parameter and verify the directory exists. 2. Iterate over files with extensions .jpg, .jpeg, .png, .webp. 3. Compress each file using the quality value. Output file name: {original_name}_compressed.jpg. 4. Print a JSON result with success, count, and a list of per-file results. ## Example json { success: true, count: 12, results: [ {name: hero.jpg, original_size: 2048000, compressed_size: 512000, ratio: 75.0} ] }这里有个关键细节Workflow里故意写明了“Verify the directory exists”这句话是在提醒模型如果目录不存在直接用脚本的报错信息回复用户即可不要自己编一个目录。模型看多了这种表达就会养成“先检查再执行”的习惯。 ### 3.3 本地调试验证模型真的会调用 写完SKILL.md不等于Skill开发完成还要验证模型在真实对话里会不会正确调用它。我的调试方法是“三连测”。 第一轮是dry-run测试。我给Skill加一个隐藏参数--dry_run脚本接住之后只打印收到的参数不真正执行压缩。然后在对话里对模型说一句“帮我压缩一下 /tmp/test_images 这个目录质量调到70”再看日志里模型传的参数是不是image_dir/tmp/test_images、quality70。如果传参正确说明SKILL.md描述没问题如果传成别的就得回头改描述。 第二轮是单文件测试。只放一张小图片在目录里让模型调用看返回的JSON是否被模型正确转述给用户。这一步主要验证输出解析。 第三轮才是全量测试。放几十张各种格式的图片包括一个损坏的文件故意测试脚本的容错能力同时也看看模型在遇到部分失败时会怎么给用户汇报。这里有一个我特别在意的点模型必须引用“脚本返回的JSON里的error字段”而不是自己脑补错误原因。 三轮下来没有问题这个Skill才敢真正交给团队使用。 ## 4. 上线之后的常见问题与排查实录 ### 4.1 模型不调用Skill问题出在描述 最常收到的反馈是“我把SKILL.md都写好了模型就是不调用它。”第一次遇到时我也以为是框架的bug后来打开调用日志才明白问题几乎都在描述文本上。 调用日志能看到模型对每个候选Skill的“评分”。评分高但不调用通常是触发条件太窄比如我把when_to_use写成了“当用户需要批量处理活动海报时”模型在其他场景下就傻眼了。改成“当用户需要压缩、优化或批量处理图片文件时”之后调用率立刻上去了。所以第一条经验适用场景描述要覆盖所有合理变体宁可宽一点也不要窄。 第二类是描述里出现了和别的Skill重叠的关键词。比如同时有“图片压缩”和“图片格式转换”两个Skill模型容易混淆。这时候要在when_to_use里明确区分“格式转换指改变编码格式如PNG转JPEG压缩指不改变格式、只降低体积”。边界写不清楚模型就只能靠猜。 ### 4.2 脚本报错但Agent“视而不见” 另一个典型问题是脚本明明崩了模型却像没事人一样回复“处理完成”。最初我觉得是模型笨后来一看输出就明白了脚本崩溃时打印的是Python默认的Traceback一大段堆栈里面全都是模型的陌生符号和英文路径。模型根本读不懂就默认是成功或者干脆忽略了。 解决办法就是我前面说的——所有异常必须转换成结构化的JSON并且带一个hint字段。比如目录不存在时脚本输出 json {success: false, error: input dir not found, hint: 请检查传入的路径是否存在再重新调用}模型一读到successfalse就知道任务失败了读到hint就有现成的话术转述给用户。脚本的输出格式决定了模型对执行结果的“理解下限”。4.3 参数传错的高频坑与防御参数传错有两类常见情况。第一类是模型把你的参数描述里的默认值当真了明明用户没提quality它却在调用时自作主张传了100。这类问题我一般通过把默认值写进参数表来缓解并且在Workflow里加一句“Receive quality from the parameter, do NOT infer it from context”。代价是脚本内部还是要按默认85处理防止模型真漏传。第二类是模型把路径参数拼接错了比如用户说“压缩桌面的图片”模型直接传了“桌面”这种模糊词而不是完整路径。这是模型没有权限访问文件系统的表现。我的做法是在SKILL.md里加一条Pre-condition“If the path is not absolute, ask the user for an absolute path before invoking the script.”有了这条说明模型就会先向用户索要准确路径而不是拿着假路径硬跑。可以用一个表格把这三类典型问题串起来方便排查现象常见原因排查步骤修复方案模型从不调用Skill触发条件描述过窄或重叠查看调用日志中Skill评分扩展when_to_use明确反例边界脚本报错但Agent说成功错误输出格式模型无法解析看脚本的stdout/stderr内容全部异常转为JSON完整错误消息参数传错或漏传参数契约不清晰开启dry-run回显参数参数表用全称、给默认值与边界增加绝对路径要求5. 把Skill变成团队资产的进阶玩法5.1 命名、版本与仓库管理单个Skill很容易写但当一个仓库里积累了几十个Skill之后管理就成了新问题。我在团队内部推行了几条命名规范一是目录名用kebab-case如batch-compress、json-sanitizer、pdf-merge不要用带空格的目录名也不要混大小写。原因是命令行调用和路径解析时连字符最稳妥。二是每个Skill根目录下加一个CHANGELOG.md或至少是版本注释。Skill的迭代经常是微调描述、修改脚本如果其他人复用看到的是新版本却不知道改了什么会很痛苦。我习惯在SKILL.md头部加一行version: 1.2.0每次改动都升号。三是建议把所有Skill放在一个Git仓库里用目录分组。比如agents/image-tools/、agents/text-tools/。这个仓库独立于具体项目任何项目需要时通过git submodule或路径引用的方式接入修一个Skill所有项目同时受益。5.2 团队评审清单与自检十条Skill是要被大模型反复“阅读”的文档所以比普通代码更值得评审。我设计了一个十项自检清单所有Skill必须过一遍才算完成这里挑几个容易被忽视的说。第一个是“反向描述检查”。让写作者只读SKILL.md、不看代码试图用这段描述拼接出一个调用请求如果拼不出来说明描述有歧义。第二个是“默认值安全性检查”。所有参数是否有默认值默认值是否安全到“即使模型瞎传也不会删库、不会覆盖原文件”。我的图片压缩默认不覆盖原图就是出于这个考量。第三个是“错误信息可读性检查”。让脚本在错误路径下跑一遍确认输出的JSON里success字段永远存在且error字段是人话而不是“IndexError: list index out of range”这种代码堆栈。第四个是“边界测试检查”。故意传空目录、不存在的路径、超大的quality值确认脚本不会崩溃并且每个错误都被正确捕获。这十条执行久了团队会形成共识Skill不是“给模型加个插件”而是“给模型建立一套可靠的工具契约”。评审的越严格上线后要填的坑就越少。我现在自己写Skill时还有一个习惯所有新Skill上线前先把它挂成一个“影子模式”只有设置开关才启用先在几个特定任务里观察模型的调用行为跑一周没问题再全量开放。我自己写过不少Skill也删过不少最终留下来的都有一个共同特点边界讲得极其清楚并且出错时能给模型留好台阶下。如果你准备开始做自己的第一个Skill不要纠结一开始写得不够完美先拿一个几天前还需要手工重复操作的流程开刀做一个能用的版本再慢慢打磨描述和容错这条路线是最扎实的。
返回列表