资讯动态

Agent Skills 实战:把 AI 能力从提示词变成可复用技能包

发布时间:2026/10/13 21:14:07 来源:尧图企业网站定制
如果把 agent-skills 这个词拆开看它其实指向一个很实际的问题怎么把“让 AI 干活”这件事从一次性提示词变成一份可以维护、可以复用、可以测试的能力资产。以前我们想让大模型完成一个任务最常见的手段就是写一段超长提示词把步骤、例子、要求全部塞进上下文里效果好不好全看措辞和运气。现在越来越多的人在尝试另一种思路把某个能力打包成一个独立单元比如“批量压缩图片”或“整理会议纪要”单元里带说明、带脚本、带测试样例AI 助手在需要的时候自动把这个单元加载出来执行。这个单元就是 skill也就是 agent-skills 这套实践的核心。我写这篇文章会用我在实际工程里的视角把这套能力包机制拆开。从它要解决什么问题、内部结构长什么样到手写一个可用的 skill、挂载到 Agent 调度层再到常见的翻车点和排查技巧全部过一遍。适合正在折腾 Agent 应用的开发者也适合刚听说这个概念、想知道它和普通工具函数到底差在哪里的朋友。你不需要有很深的背景只要写过一点 Python、会跑几行命令基本就能跟上。1. agent-skills 出现前Agent 的能力是怎么长出来的1.1 从“会聊天”到“会用工具”如果你接触过早期的对话式 AI应该还记得那种无力感模型什么都能聊但一让它做实事就露馅。让它在文本里编一段代码可以真要它读取本地文件、调用某个服务、把结果写回磁盘它就无能为力了。后来大规模语言模型开始支持工具调用也就是业界常说的 function calling情况才慢慢好转。工具调用的思路其实很直接开发者预先把能力暴露成一个个函数比如send_email(to, subject, body)、search_files(path, key)每个函数有明确的名字、参数和返回值。模型在推理时看到用户请求会从函数列表里挑一个最匹配的生成结构化参数由外部系统真正执行。在这个过程中模型扮演的是一个调度官的角色它知道“此时应该调哪个函数”但不需要知道“这个函数内部到底怎么运作”。这套机制能解决很多确定性任务但它有一个隐性天花板。工具描述里通常只写“这个函数干什么、参数是什么”而完成一个真实任务往往需要一系列步骤和判断。举个例子“把图片批量压缩并输出到一个独立文件夹”这件事如果只暴露一个compress_image()函数模型也许能填对路径但过程中涉及到的质量参数怎么定、输出目录不存在了要不要自动创建、原图比压缩后还小的情况怎么处理这些都没法通过简单的函数签名传递。要么你在函数内部把逻辑写死要么靠系统提示词反复声明两条路都不舒服。1.2 纯提示词方案的三个硬伤在没有 agent-skills 这类机制之前我和周围不少开发者走的都是“提示词工程”路线。也就是把任务流程、边界条件、注意事项全写进 system prompt 里然后在对话里反复强调。刚开始做一两个任务还行一旦任务多起来问题就很明显了。第一个问题是上下文膨胀。你每加一个技能说明system prompt 就要多一大段。比如你写了“图片压缩流程”又把“会议纪要整理流程”也塞进去用户每发起一次新对话模型都要重新读一遍所有内容token 消耗越来越大响应越来越慢。更麻烦的是很多描述并不是每轮对话都相关它们只是白白占着上下文窗口。第二个问题是能力隔离差。你做“批量压缩图片”的时候写了一段流程说明后来做“图片格式转换”时又想把其中一部分逻辑拿过来复用。复制粘贴很容易但后续一旦要改压缩参数你必须在多个地方同步修改漏掉一处就会出现行为不一致。提示词之间没有边界没有版本没有依赖管理。第三个问题也是我最在意的不可测试。提示词写错了没有编译器给你报错最多是运行起来发现行为不对。你想要为某个能力建立单元测试都无从下手因为提示词不是结构化的没法稳定地跑断言。而 agent-skills 的出现恰好把“能力”变成了目录里的一组文件有了文件就有了测试、版本和审查的可能。1.3 Skills 与 Tools 的核心边界很多人第一次接触 agent-skills 都会问这不就是工具函数换个说法吗在回答这个问题之前建议先看这么一组对比对比维度普通 ToolAgent Skill粒度单个 API/函数一个完整“任务能力包”描述内容参数、返回类型目标、步骤、失败处理、环境要求是否含流程通常不含通常包含明确流程和决策规则依赖管理由宿主代码统一管理自带依赖声明维护单位一个函数一个目录可进行版本控制触发方式模型从函数清单里选模型通过描述检索后加载整个技能包从这张表能看出Tool 更像一个“孤立的 API 开关”你给它参数它给你结果。而 Skill 是一个“可执行的岗位说明书”不只是告诉模型可以调用什么还告诉它这项工作具体应该怎么做、什么情况下算成功、遇到异常时该怎么办。举个例子一个 Tool 可以这样描述“输入图片路径返回压缩后的图片路径。”一个 Skill 则会更像这样“这个技能负责批量压缩目录下的图片目标是输出一个独立文件夹。执行前先检查输入目录是否存在压缩时默认最长边不超过 1600 像素质量参数 82如果压缩后的文件比原图还大则保留原图并记录。不能处理动图不能覆盖原始文件。”同样一件事后者给了模型足够的上下文去做判断和变通这才是 skill 存在的意义。2. 拆开一个 skill一个能力单元到底装了什么2.1 一份合格的描述文件长什么样一个标准化的 skill本质上是一个目录。目录里最重要的文件是一份描述文件比如SKILL.md它相当于这个技能的“身份证”和“说明书”二合一。还有一些伴生脚本、依赖清单和测试样例它们共同组成了完整的技能包。我先给一份结构比较标准的描述文件示例name: batch-image-compress description: 批量压缩指定目录下的图片输出到独立目录支持 jpg/png/webp不覆盖原图。 version: 1.2.0 author: ops-dev license: MIT environment: python: 3.10 dependencies: - pillow10.0 attachment: - compress.py - fixtures/有些实现会把描述文件写成 Markdown 风格有些会采用 YAML 头部加正文的方式但核心字段都是相通的。name是这个技能的唯一标识用于索引和引用description用于模型检索时匹配用户请求version用于版本追踪dependencies用于声明运行时依赖attachment列出这个技能包自带哪些文件。很多人写 skill 时最容易敷衍的字段就是description。我见过不少描述就写“处理图片”“备份文件”这种几个字完事的结果就是模型在需要它的时候根本想不起来有这个技能存在。描述文件是给模型看的检索入口不是给人类看的注释它需要包含触发场景、输入输出格式、关键约束。一句话说清这个技能解决什么问题比写十句恭维话都有用。2.2 指令文本把“怎么做”写清楚描述文件负责告诉 Agent“这个技能是什么”而技能内部的指令文本比如SKILL.md的正文部分负责告诉模型“这件事具体怎么做”。这部分内容的受众不是开发者而是模型本身。你可以把它理解成写给一个能力很强但容易自作主张的临时实习生看的工作手册。我建议指令文本至少包含四块内容。第一块是任务目标这个技能最终要交付什么结果成功标准是什么。第二块是输入输出约定输入从哪来输出写到哪里路径如何解析。第三块是流程步骤按顺序列出主干步骤关键决策点给出明确分支规则。第四块是异常处理遇到依赖缺失、文件不存在、格式不支持时应该怎么响应。举个例子一份给模型看的压缩指令可以长这样# 批量图片压缩 ## 目标 把 source_dir 下的所有图片压缩到 target_dir单张最长边不超过 1600px质量参数 82。 ## 流程 1. 先确认 source_dir 存在不存在则直接向用户说明错误。 2. 调用 compress.py 执行压缩禁止绕过脚本直接调用图像库。 3. 压缩完成后比较每个文件的大小如果压缩后大于原图保留原图到原目录并输出提示。 ## 异常处理 - 依赖缺失时先说明需要安装 pillow征询用户后再执行安装。 - 遇到非图片文件跳过并汇总报告。你可能注意到我没有写“你要聪明地处理所有情况”这种废话。原因是模型在面对模糊指令时倾向于自己脑补一套流程而这个流程未必符合你的预期。把步骤和分支条件写死反而能保证行为稳定。如果后面要调整策略直接改这份指令文本就行不需要动代码。2.3 伴生脚本与依赖锁定描述文件和指令文本只是“怎么说”的部分真正干活的还是伴生脚本。Skill 的脚本设计和普通工具函数有一点不同它要尽量简单、纯粹最好只做好一件事把复杂的调度和判断逻辑留给模型去完成。脚本的依赖管理是很多人忽略的重点。你写一个压缩图片的脚本本地跑得好好的换到另一台机器上图像库版本不一样接口行为有差异技能立刻失灵。所以技能包里最好放一份锁定的依赖清单比如把版本号写死pillow10.4.0如果条件允许给每个技能建立独立的运行环境是最好的隔离方案。这样多个技能之间就不会因为共享依赖而互相踩踏。比如你同时挂了“图片压缩”和“OCR 识别”两个技能一个依赖旧版图像库一个依赖新版图像库共用环境很容易出现版本冲突。各自独立的环境虽然多占用一点磁盘但换来的是互不干扰这个取舍是很值的。我给个人项目的建议是依赖锁文件必须有独立环境尽最大努力做。如果只是本机实验用虚拟环境就能解决大部分问题如果技能要分发给别人尽量做到“拉到目录、声明依赖、一键安装”就能跑起来这才是 agent-skills 该有的体验。2.4 测试样例是你的安全网技能包里的另一项关键资产是测试样例。之所以把它单独拿出来说是因为我在把技能挂到 Agent 上之后很快就发现一个规律模型在调试状态下会自己尝试执行脚本但如果脚本的输入输出边界没有测试用例兜底它很容易在“看似合理的错误路径”上打转。每个技能最好都带一个fixtures目录里面放一份最小化的输入样例和期望输出附带一个可执行的校验脚本。比如压缩图片技能就可以放两张不超过几百 KB 的测试图一张处理后期望边长 1600 像素一张原图就小于目标尺寸、期望保持不变。校验脚本跑一遍如果断言全部通过这个技能的基本盘就是稳的。我在实际使用中会把这种校验脚本当成技能包的“健康检查”。每改一次描述或脚本先跑一遍测试再把它推给 Agent 使用。别看只是几个小文件它节省的排查时间远远超过编写时花费的半小时。这就是人和模型之间的一份契约脚本保证“做完”测试保证“做对”。3. 手搓一个可用 Skill以“批量压缩图片”为例3.1 先定边界什么能做什么不做现在我来实际操作一次做一个“批量压缩图片”的 skill。动手前先花两分钟把边界画清楚这比写代码重要得多。边界分成两类能力边界和信任边界。能力边界解决的问题是“这个技能到底管到哪”。压缩图片就是压缩图片它不负责下载图片、不负责上传 CDN、不负责生成缩略图。如果描述里把太多能力混在一起模型在调度时会给它派各种奇怪的活比如“把这几张图转成视频”也会被拉过来尝试。信任边界则关系到安全这个技能默认只能操作哪些目录能不能写文件能不能执行任意命令。在这个例子里我限制它只能处理用户指定的输入目录输出到指定的目标目录不覆盖原目录中的任何文件。边界定下来之后整个技能目录就可以规划了batch-image-compress/ ├── SKILL.md ├── compress.py ├── requirements.txt └── fixtures/ ├── sample_1.jpg ├── sample_1_expected.jpg └── check.sh这个目录结构不复杂但足够支撑后面所有的流程和测试。实际写的时候你可以把技能命名空间做得更细比如tool-image-v2、skill-video-cover关键是要让名字本身具有辨识度能被检索到。3.2 逐字段填写描述文件先写SKILL.md。缩略版如下--- name: batch-image-compress description: 批量压缩本地目录中的 jpg/png/webp 图片支持自定义最长边与质量输出到独立目录不覆盖原图。适合处理“把文件夹里的图片变小一点”“批量压缩照片”等请求。 version: 1.2.0 dependencies: - pillow10.4.0 --- # 批量图片压缩 ## 目标 将 source_dir 下的图片压缩后输出到 target_dir压缩后的最长边不超过 max_edge默认 1600像素质量为 quality默认 82。 ## 流程 1. 校验 source_dir 存在且非空否则向用户说明错误。 2. 调用 compress.py --src source_dir --dst target_dir --max-edge 1600 --quality 82。 3. 等待脚本结束读取结果索引文件向用户展示压缩前后的文件数量与体积变化。 ## 异常处理 - 依赖缺失提示运行 pip install -r requirements.txt。 - 输出目录不存在脚本会自动创建无需模型手动 mkdir。这个文件的前半段是元信息后半段是给模型的执行指令。注意description字段里我专门加了“把文件夹里的图片变小一点”之类的口语化描述原因是用户在自然对话里很少会说“批量压缩图片”这么标准的词。模型做语义匹配时这些口语化的触发词往往比正式名称更有用。3.3 准备校验脚本和最小样例接下来是compress.py。我不追求把所有功能写进一个文件而是保持脚本简单直接主要逻辑就是遍历目录、读取图片、按参数压缩、输出结果。中间有一点值得强调脚本一定要自己创建输出目录不要依赖模型或用户先建好目录。模型经常忘记“目标目录还不存在”这个细节脚本里自动处理掉能减少很多无效报错。写完脚本后我顺手准备一个check.sh#!/usr/bin/env bash set -euo pipefail cd $(dirname $0)/.. rm -rf /tmp/skill_test_out python compress.py --src fixtures/sample_1.jpg --dst /tmp/skill_test_out test -f /tmp/skill_test_out/sample_1.jpg echo PASS || echo FAIL这个校验脚本看起来很朴素但它满足了一个我很看重的标准结果机器可读。要么输出PASS要么输出FAIL这样一来后面接 CI、接自动化回归都非常方便。有些朋友喜欢在校验脚本里打印一大段日志可读性虽好但自动化处理时反而麻烦。建议分两个接口一个给人看的详细日志一个给机器看的结果标记。3.4 挂载到 Agent 进程里技能包写好后最后一步是让 Agent 能发现它。我常用的方式是基于目录扫描把技能目录放进配置的根目录Agent 启动时自动遍历所有SKILL.md建立技能索引。我一般建议的启动配置是这样的agent-skills --root ./skills --auto-index不同产品有自己的挂载方式有些支持直接从配置目录读取有些需要手动刷新索引但底层机制大同小异。这里有一条实战经验先只挂一个技能跑通链路再挂第二个不要一次性把几十个技能全塞进去。原因很简单技能一多一旦调度出了问题你很难判断到底是谁抢了谁的活。小步挂载、逐个验证是排查这类问题的最高效路径。挂载成功以后你可以直接在对话里发一个接近真实场景的指令比如“帮我把桌面上的图片都压缩一下输出到新文件夹”。看模型能不能自己找到这个技能、填对参数、正常执行。如果这一步通了再去扩展更多的技能包会顺手很多。4. 调度层怎么决定用哪个 Skill4.1 描述匹配模型靠什么“想起”技能技能挂载之后摆在面前的第一个问题就是模型怎么知道什么时候该用哪个技能这背后一般是检索加排序的机制。Agent 会把每一个技能的name和description向量化存成一个索引。用户发起请求时系统计算请求文本和技能描述之间的相似度分数高的技能会被塞进模型的上下文窗口。这个机制决定了描述文件写作的重要性。描述太短检索召回率会很低描述全是术语口语化请求又匹配不上描述写成长篇大论匹配噪音又会变大。我个人的经验是把最重要的触发场景和高频口语表达放在描述的前一两句里。尤其是在一次请求对应几十个技能的场景下前 50 个 token 基本决定了模型能否精准地“想起”这个技能。举个例子同一个技能有两种描述差compress images好批量压缩目录中的图片支持 jpg/png/webp输出独立目录不覆盖原图适合“把图片变小”“缩小照片体积”等请求后者不仅描述更完整还顺便覆盖了用户在对话里的常见说法检索匹配的成功率会明显更高。4.2 参数填写的两种风格模型选中技能之后接下来就要生成参数。参数填写通常有两种风格一种是模型从用户话术里提取结构化参数另一种是技能内部自己带一层参数归一化逻辑。拿“帮我把桌面那些图弄小点”这句话来说模型需要把“桌面”解析成~/Desktop这个路径把“弄小点”解析成max_edge1280或者quality70。第一跳往往不准所以技能指令里最好给出默认映射规则没有明确尺寸时最长边默认 1600用户说“小一点”时默认 1280用户说“保持画质”时质量不低于 85。这些规则放在技能描述里而不是放在系统提示词里是 agent-skills 非常有价值的一个特性。它让“提示词工程”下沉到了每个技能包里技能的维护者可以独立优化这个技能的参数映射不用去和系统提示词打架。以后哪怕这个技能被复制到另一个项目里它的参数映射习惯也跟着走。4.3 本地目录、远端仓库与协议对接技能不一定是零散文件也可以放进版本控制系统作为团队可共享的资产。个人使用阶段本地目录足够几个人协作技能放到一个远程仓库里每个人拉取同一份技能集合行为就一致了再往上走还可以通过开放的上下文协议让不同产品之间互相调用技能包。我对技能库管理有一个稳步推进的建议先做本地目录跑熟为止再考虑 Git 仓库做版本管理和团队分发最后才是协议对接。别在一开始就想做一个完美的大一统技能中心越重的架构越容易拖慢迭代速度。我见过很多项目在最开始设计了复杂的插件体系和协议层结果技能包一共只有三五个架构复杂度反而成了负担。技能数量到了一定规模再上重型管理工具也不迟。5. 实测中的高频翻车点和排查清单5.1 “它明明有这技能却不用”这是所有 Agent 使用者都遇到过的问题技能已经挂上去了模型就是视而不见宁可自己瞎编流程也不用现成的能力。遇到这种情况我的排查路径是固定的。第一步确认索引里真的有这个技能。很多产品有自动索引但缓存没刷新新技能不会被加载。第二步检查描述的召回质量。你可以把用户的话术原封不动地拿去和技能描述做一次相似度计算看看分数低不低。第三步在对话中主动提及技能名看模型能不能直接调用。如果主动点名能用、不点名不能用那基本就是描述匹配的问题不是技能本身的问题。我还发现一个很实用的小技巧给技能加一个硬别名。比如压缩图片技能的全名是batch-image-compress我在描述里专门写一句“这个技能也叫 png-shrink / 图片瘦身”然后把这些别名作为检索关键词。实际操作中带别名的技能被模型正确调用的比例会高不少因为用户表达习惯差异太大多一个别名就多一条路。5.2 参数幻觉和路径错误模型填参数时偶尔会“脑补”出一些不存在的路径。用户说“把那批图处理一下”模型可能直接生成/users/xxx/desktop/images这样的路径但真实环境里这个目录根本不存在。脚本拿去跑报错然后模型开始幻想自己创建了目录实际上什么都没有。对付这类问题脚本端要做足防御。每个输入路径先做解析转成绝对路径文件不存在时直接给出明确报错而不是继续往后执行输出目录交给脚本自动创建。我自己的原则是永远不要把“模型先建好目录”当成前提。你已经知道模型可能偷懒、可能记错那脚本就应该在第一时间把错误暴露出来而不是让错误继续传递下去。还有一个比较隐蔽的问题是路径里的空格和特殊字符。用户文件夹叫My Photos模型生成参数时如果不做转义命令可能直接被截断。脚本里统一用参数传递而不是拼字符串可以规避掉很大一部分这类问题。5.3 依赖污染、版本乱套当技能数量多起来依赖冲突会变成最恶心的坑。每个技能都声明自己的依赖看起来没什么问题但如果它们在同一个环境里运行某个库的版本就会被一通乱搞。我有一次同时跑了图片压缩和 OCR 相关技能后者装了一个新版本图像库直接把前者的接口行为带崩了查了很久才发现是共享环境下的版本冲突。现在的习惯是凡是准备长期使用的技能一律给它配独立环境。技术上不一定非得用重型容器虚拟环境就够用。关键在于让每个技能在它的环境里声明和安装依赖时“锁死版本”。依赖文件里写pillow10.4.0和写pillow10是完全不一样的效果前者保证可复现后者在三个月后可能就变成了另一个陌生的行为。如果已经出现依赖混乱排查时有一个很直接的动作跑一遍该技能的校验脚本。如果校验通过说明依赖环境没大问题如果校验失败再根据报错回溯谁改了依赖。这个动作很简单但真的能节省大量时间。5.4 安全边界别把大招裸奔Agent 技能本质上是可以执行任意代码的这既是它强大的原因也是它危险的地方。技能包里如果混入恶意代码模型会忠实地替你运行它。对于安全边界我有几条必须守住的原则技能默认只读只有显式声明需要写入的目录才允许写。禁止技能直接访问系统敏感目录比如凭证目录、配置文件目录。任何命令执行前打印出将要执行的完整命令让用户有机会拦截。从外部下载的技能包先人工审查一遍脚本再挂载不要直接信任。风险点风险示例防护手段路径穿越输入路径包含../跳出目标目录解析后校验前缀是否在允许范围内任意命令执行描述诱导模型调用rm -rf脚本层白名单禁止执行 shell 命令依赖投毒技能安装恶意版本的依赖库锁定版本、审查第三方包数据外传技能读取文件并发到外部接口默认禁止网络请求按需开放我见过不少人觉得这些防范“过度设计”直到某次技能误删了一个文件夹或者把内部文件内容发给第三方才意识到边界控制有多重要。安全不该是事后补救它应该写进技能模板里每个新技能默认遵守。6. 我个人的几条实战习惯6.1 把描述文件当成交接文档来写写技能的时候我会想象一个月后接手的人不是某个同事而是未来的我自己。所以我描述文件里一定会写清楚“为什么这样做”而不是只写“做什么”。比如压缩参数为什么默认 82为什么不处理动图这些都是将来最容易被人改坏的地方。很多技能写出来用几天被废弃不是能力不行而是后来没人敢碰它。一份好的描述文件就是这个技能的续命符。6.2 小步挂载持续删减我有段时间热衷收集各种技能什么代码审查、日志分析、报告生成装了一大堆。结果反而导致模型在选择时频繁犹豫或者用错技能。后来我改成强迫自己每隔一段时间清理一次凡是一个月内没被调用的技能一律下架。技能库更像衣柜不在于多而在于每一件都能在需要的时候被一眼找到。删掉的就进版本库未来需要随时找回没什么可惜的。6.3 最后再分享一个小技巧在技能包的文件结构里我喜欢多加一个可选的debug参数。模型执行技能时如果遇到问题可以让它带上--debug跑一遍输出当前环境里的路径、依赖版本和执行堆栈。这个参数平时没什么用但一旦技能运行环境出了问题它能帮你和模型快速定位故障位置。我靠着这个小小的参数很多难啃的问题十分钟就查清了省下了反复对话追问的时间。agent-skills 这套理念真正吸引我的地方在于它把“让 AI 干活”这件事从玄学变成了工程。你可以给一个能力定义边界、写清楚流程、配上测试、控制住权限然后让模型在合适的时候把它用起来。它不复杂但每一点做扎实了整个 Agent 的行为就会稳定一大截。如果你正准备开始搭自己的技能库我建议就从一个小而清晰的技能开始先把一个流程跑通再往里面加东西。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑