资讯动态

AI Agent技能系统拆解:SKILL.md设计、加载与调试实战

发布时间:2026/10/8 9:17:28 来源:尧图企业网站定制
这几年在搭 AI Agent 的时候我几乎每个项目都会在某个阶段被“skills”这个词搞得又爱又恨。爱的是只要技能抽得足够干净Agent 的能力边界立刻清晰维护成本能跌一半恨的是刚上手时完全没有头绪技能目录、描述文本、脚本封装、加载时序这些细节全靠一次次踩坑试出来。今天就把这套东西彻底拆开讲一遍从设计思路到文件结构再到完整实操和排障记录一次性说透。这个内容适用于三类人正在做 Agent 应用开发的工程师、想把大模型接入具体业务场景的产品/技术负责人还有刚接触 Agent 框架但对“技能系统”这个抽象概念有点懵的学习者。阅读前你不需要懂太多只需要知道 Agent 的本质是“模型 工具 记忆 技能”而 skills 就是那把让模型真正能干活的螺丝刀。1. 先把“技能”这件事想清楚1.1 技能、工具与插件边界到底在哪围绕 Agent市面上有太多相似词tool、function call、plugin、action、skill……每个框架的叫法还不一样。我第一次接触 skills 时就栽在这上面以为技能就是函数的升级版结果设计出来的东西四不像。后来踩过一轮坑我才总结出自己的一套划分标准。工具Tool是最底层的原子能力比如“发送 HTTP 请求”“读取文件”“执行一条 SQL”。它通常只干一件事输入输出都是明确的参数模型通过函数调用function calling直接使用它。技能Skill是把工具、提示词、校验逻辑、处理流程组装成的一个“半成品解决方案”。它的核心特征是“打包后具有上下文感知能力”。什么意思工具回答的是“我能做什么”技能回答的是“在当前这个任务里你应该怎么做、按什么步骤做、做完怎么校验”。比如“发送 HTTP 请求”是工具“调用 GitHub API 获取仓库概览并按指定格式输出摘要”就是技能。插件Plugin通常指可独立分发、可组合的软件实体往往包含多个技能甚至多个工具。一个“GitHub 插件”可能同时包含“仓库概览技能”“Issue 管理技能”“PR 检查技能”。三者的关系可以用一个生活化类比理解工具是菜刀、砧板、灶台技能是一道完整的菜谱告诉你先切什么、炒多久、什么时候放盐插件则是一本菜谱合集甚至自带食材采购清单。这套边界定下来之后我再也没出现过重复造轮子的问题因为做技能层之前我会先问一句底层工具是否存在不存在就先补工具而不是在技能层里硬塞实现。1.2 我为什么单独抽一层技能系统有些项目一开始没有技能层所有能力都揉在 Agent 的 system prompt 和工具列表里。规模小的时候完全没问题但随着需求增多几件事开始失控提示词膨胀为了让模型每次都按正确步骤执行我在 system prompt 里反复写了大量规则结果上下文越来越长模型反而抓不住重点。工具列表混乱几十个工具全部平铺给模型模型在每次调用时都要从一大堆选项里挑选择准确率肉眼可见地下降。复用困难业务 A 用过的“查询订单并催付”逻辑业务 B 需要时只能复制粘贴改一个字段要同步改好几处。评估缺失没有一个统一机制能验证某个功能是否真的表现良好只能靠人工聊天试。技能层把这些问题串起来解决了。每个技能自带运行所需的一切——描述、参数说明、执行脚本、校验逻辑Agent 在运行时按需加载而不是把所有东西一次性塞给模型。这就像把工具箱里的每把螺丝刀都贴上“适用场景”标签要用时精准取出而不是把整个箱子倒在地上让模型自己翻。1.3 一套技能系统要解决的核心问题在设计技能系统时我给自己列了一张需求清单后续所有实现都围绕这张清单展开需求项说明技能发现模型能根据任务自动选择合适的技能技能执行技能能接收参数并稳定执行结果可解析技能管理技能可独立新增、修改、下线不影响其他部分技能评估能用一组测试用例量化技能的表现技能扩展新技能接入成本低最好只需新增一个目录这五项缺一不可。如果只做技能文件的编写而不解决发现机制模型不会主动调用技能形同虚设如果只做调用而不管评估技能质量越堆越烂。后面我会逐个展开讲怎么落地。2. 技能文件结构与描述词写法2.1 一个技能最小的目录长什么样先给出一份最简技能目录这是在通用 Agent 框架中比较常用的结构skills/ └── github-overview/ ├── SKILL.md ├── scripts/ │ ├── summary.py │ └── requirements.txt └── resources/ └── template.mdSKILL.md是这个技能的心脏里面包含元信息技能名、描述、参数定义和指令正文告诉模型什么时候用、怎么用、有哪些坑。scripts/存放实际执行逻辑的脚本resources/放模板、示例输出等辅助文件。这个结构看似简单实际是有讲究的。把脚本和资源独立在 SKILL.md 之外核心目的有两个第一避免在一次上下文里塞进太多非文本信息。SKILL.md 是要被模型完整阅读的而脚本只有真正执行时才需要加载两样东西混在一起会白白占用 token。第二方便独立测试。脚本可以脱离 Agent 直接跑单元测试只需要针对 scripts 目录而不必启动整个 Agent。2.2 描述字段的“路由价值”SKILL.md 里的 description 字段是技能系统里最重要的一个字符串它的质量直接决定技能的发现成功率。我在早期试过把 description 写得很简陋比如“GitHub 概览”结果模型经常在需要调用时选择其他技能或者根本意识不到有这个东西存在。一个好用的 description 应该是“路由友好型”的它需要回答三个问题什么情况下使用写明触发场景比如“当用户需要了解某个 GitHub 仓库的基本信息时”。做什么说明技能产出什么比如“获取 star 数、语言、最近更新时间和 README 摘要”。有哪些注意事项比如“只能处理公开仓库私有仓库会返回错误”。我还倾向在 description 里加上几个典型问句示例比如“这个仓库是什么”、“它最近活跃吗”因为模型的语义匹配对自然语言问句比对单纯名词更敏感。实测下来加了示例之后技能触发准确率提升了差不多 25%。2.3 脚本与依赖封装到一步到位技能内部脚本的编写理念和普通业务代码不同核心原则是“一次执行完成一个完整目标”。脚本不需要追求内部结构的优雅更看重傻瓜化、可复用和输出稳定。还是拿 GitHub 概览技能举例summary.py的核心逻辑大致如下import os import sys import json import requests def get_repo_overview(repo_full_name: str) - dict: token os.environ.get(GITHUB_TOKEN, ) headers {Accept: application/vnd.githubjson} if token: headers[Authorization] fBearer {token} url fhttps://api.github.com/repos/{repo_full_name} resp requests.get(url, headersheaders, timeout10) if resp.status_code ! 200: raise RuntimeError(fGitHub API returned {resp.status_code}: {resp.text}) data resp.json() return { full_name: data.get(full_name), description: data.get(description), stargazers_count: data.get(stargazers_count), language: data.get(language), pushed_at: data.get(pushed_at), topics: data.get(topics, []), } if __name__ __main__: repo sys.argv[1] result get_repo_overview(repo) print(json.dumps(result, ensure_asciiFalse, indent2))这段脚本有几个细节值得留意直接参数化输入仓库名通过命令行参数传入不写死在脚本里便于技能调度器统一调用。显式超时设置 10 秒的请求超时避免某个技能卡死导致整个 Agent 无法响应。标准化输出统一用 JSON 打印结果这样上层 Agent 可以稳定解析而不是靠正则去抠文本。异常上抛遇到 4xx/5xx 时直接抛异常方便上层捕获并让模型决定下一步是重试还是换一种方式。依赖管理同样重要。requirements.txt里列清楚依赖及版本并且在加载技能时装进独立的虚拟环境或者容器里避免技能之间相互污染。曾经有个项目里两个技能分别依赖不同版本的 requests 库结果后加载的技能把前面的环境搞坏了排查了很久才发现是依赖冲突。2.4 技能的数字资产示例、评估、版本成熟技能的目录里除了 SKILL.md 和脚本我通常还会放三类数字资产examples/存放输入输出示例比如一组“问题 - 技能参数 - 预期输出”的样本。这些样本既可以在模型不确定时作为 few-shot 提示也可以作为评估集。tests/针对脚本和调用链路的自动化测试至少覆盖正常路径和一个边界路径。技能升级或模型框架升级时有没有这层测试差别特别大。CHANGELOG.md记录技能版本变更。Agent 系统的技能往往是被多个业务共用的没有版本记录回滚时根本不知道之前长什么样。这些资产看起来“麻烦”但能避免一个非常现实的问题技能在 A 场景表现优秀换到 B 场景就翻车而下一次改技能时你根本不记得是谁、在什么情况下改了什么。3. 完整实操从零添加一个可用技能3.1 实例场景GitHub 仓库概览技能接下来用一个完整示例把“从零添加技能”全流程走一遍。场景很简单用户问“介绍一下某仓库”Agent 需要调用 GitHub API 获取仓库的基本信息并整理成易于阅读的摘要。按上一节的目录结构先创建基础目录mkdir -p skills/github-overview/scripts mkdir -p skills/github-overview/examples mkdir -p skills/github-overview/tests然后写 SKILL.md 的核心内容--- name: github-overview description: 当用户需要了解某个 GitHub 仓库的基本信息包括 star 数、主要语言、最近推送时间、主题标签、仓库描述时使用。适用于类似“这个仓库是做什么的”、“它最近活跃吗”、“有多少人 star”等提问。只能用于公开仓库。 --- # GitHub 仓库概览 用户想了解一个 GitHub 仓库时使用本技能获取基础信息。 调用参数为仓库的全名格式必须是 owner/repo例如 octocat/Hello-World。 不要尝试获取私有仓库信息API 返回 404 时告知用户该仓库不存在或不可访问。 获取后按以下模板输出 仓库名称 仓库描述 Star 数 主要语言 最近推送时间 主题标签注意这个 SKILL.md 的写法description 是给模型“路由”用的所以写满了触发条件和示例问句正文是给模型“执行”用的所以写清了参数格式、失败处理方式和输出模板。两部分的关注点完全不同不能混。3.2 Skill 注册与加载框架在启动时通常会扫描特定的 skills 根目录按子目录逐个加载 SKILL.md。加载过程中会做几件事解析 frontmatter提取 name 和 description。将 description 注入 Agent 的工具路由列表相当于给模型一张“技能菜单”。将正文指令按需注入 system prompt 或者上下文这里各框架做法不同有些是在模型选择了该技能后才注入正文有些是直接全部塞进上下文。校验 scripts 目录里是否有可执行入口并检查依赖是否已安装。我第一次用这个流程时踩了一个隐蔽的坑技能目录里有多余的非技能子目录比如skills/archive/github-overview/这个 archive 也会被框架当作一个技能加载导致系统里出现两份同名技能。后来我养成了一个习惯凡是下线的技能都在技能目录外单独建一个_disabled/文件夹而不是直接留在 skills 目录里改名。3.3 调用链路与运行时调试技能在运行时的完整调用链路通常是这样的用户提问 - 模型感知到技能存在 - 模型生成技能调用意图与参数 - 调度器校验参数 - 执行 scripts 脚本 - 解析结果 - 返回给模型 - 模型组织最终回复这里最值得调试的点是“模型是否选择了正确技能、生成了正确参数”。建议在开发时开启框架的 trace 日志把每一步决策都打印出来。我曾遇到过模型生成了github-overview但参数传成https://github.com/octocat/Hello-World这种完整 URL 的形式脚本没法处理报错后模型又尝试自己修浪费了好几个来回。解决办法是在 SKILL.md 里加一行显式的参数格式说明并且在脚本里做一次预处理from urllib.parse import urlparse def normalize_repo(raw: str) - str: raw raw.strip() if raw.startswith(https://github.com/): parsed urlparse(raw) parts parsed.path.strip(/).split(/) if len(parts) 2: return f{parts[0]}/{parts[1]} return raw这一步非常实用模型生成参数时经常会带多余信息与其靠提示词约束不如在脚本入口做兼容。消耗的代码量极低却能把成功率拉高一大截。3.4 测试与回归技能写完不是终点我建议至少补齐下面三类测试脚本层单测直接对normalize_repo和get_repo_overview做断言覆盖正常仓库名、完整 URL、带尾部斜杠、不存在仓库等输入。调度层集成测试模拟模型发出一条调用指令验证从意图识别到脚本执行再到结果反馈的完整链路。端到端评估准备一组真实的用户问题让 Agent 跑一遍人工或自动判断最终回复是否符合预期。端到端评估尤其容易被人忽略。脚本层测试通过不代表 Agent 表现正常因为模型的意图识别随时可能抽风。我在自己项目里做了一个极简自动评估脚本循环跑一组问题把每次的技能调用记录和回复结果保存下来定期对比哪个技能被调用频率下降或者失败率升高就可以立刻定位。4. 常见问题与排查技巧实录4.1 技能为什么一直不被调用这是所有技能系统新手的第一个坎技能写好了、目录结构没问题、脚本能跑但 Agent 就是不用它。我的排查顺序是确认技能是否成功加载看启动日志里有没有 “Loaded skill github-overview” 这类输出。没有加载就是目录或命名问题。确认 description 是否进入了模型可见列表有些框架只暴露部分技能给模型需要检查技能路由配置比如最大技能数量限制、启用的技能白名单。确认 description 是否具备触发条件如果描述写得太泛比如“提供 GitHub 相关信息”模型很难把它和“介绍某个仓库”这个问题关联起来。改成带触发场景和示例问句的描述问题立刻缓解。确认是不是被其他技能压制如果同时存在多个 GitHub 相关技能模型的选择概率会被分散。这时可以给关键技能提高“优先级权重”或者在 description 中注明“优先使用本技能处理公开仓库信息”。4.2 描述写太细或写太泛都不行描述文本的平衡度很难把握我见过两个极端派别。“写太泛派”的典型描述是“处理 GitHub 相关内容。适用于需要 GitHub 信息的场景。”模型完全不知道该在什么时候用结果就是技能几乎不被调用。“写太细派”的典型描述是“当用户问及某个仓库的 star 数、语言、最近推送时间、主题标签、仓库描述且仓库为公开仓库且平台为 github.com 且用户未指定其他需求时使用……”这种描述把执行细节全部塞进路由信息里不仅浪费 token还会让模型在条件不完全匹配时放弃选择宁可自己瞎编。我的经验是描述字段只写“什么场景用 做什么 关键限制”执行的具体流程和输出格式放到正文里。用一个判断标准自测如果描述能被一句完整的话概括那就是好描述如果需要分三点以上才能说清那说明你在往描述里塞执行细节。4.3 多个技能互相打架当技能数量超过一定规模技能间冲突会变成一个非常普遍的问题。形式有几种触发条件重叠两个技能都说自己适合“获取仓库信息”模型每次都随机选一个结果不稳定。命名冲突两个技能都叫summary但一个总结 GitHub 仓库一个总结新闻文章加载器报错或后者覆盖前者。参数冲突同一套参数在不同技能被定义为不同类型调度器在切换执行器时类型校验报错。处理办法分三层。第一层在架构层面给技能加scope标签比如scope: github描述里明确“仅用于 GitHub 场景”第二层在注册层面加载时做重名校验拒绝重复注册第三层在运行层面如果模型长时间在重叠技能间摇摆就合并技能——把两个技能合并成一个“多模式技能”在正文里根据参数或关键词分流。4.4 依赖与环境的坑技能脚本的环境问题比普通服务更隐蔽因为技能往往是被在线动态加载的你无法保证运行环境始终一致。常见的坑包括依赖缺失新机器上跑 Agent 时某个技能 import 了不存在的包直到被调用才报错。版本冲突两个技能依赖同一个包的不同版本某次更新后相互覆盖。环境变量缺失脚本里读GITHUB_TOKEN但环境变量没注入技能一调用就 401。我的建议是每个技能在目录下手写一个requirements.txt并锁定版本不要用裸包名。启动 Agent 前统一做一次依赖检查不要等到调用时才暴露。敏感信息token、key一律通过环境变量注入严禁写入 SKILL.md 或脚本源码。在脚本入口加一段启动自检比如检查关键环境变量是否存在不存在时直接返回可读错误信息而不是在调用中途抛一个晦涩的 traceback。依赖这个问题真的是越早规范越省事。技能少的时候看不出问题技能上了两位数之后今天坏一个环境、明天坏一个版本的事会频繁发生提前铺好检查机制后面会少掉非常多半夜救火的时刻。4.5 超时、重试与结果解析的隐形问题技能脚本的执行时长是另一个常被低估的问题。模型调用技能的等待时间如果太长会直接影响用户体验。以 GitHub API 为例正常响应在 200-500ms如果网络环境不好可能拖到几秒此时用户已经在等回复了。建议给技能执行设三档超时档位时长用途脚本内部请求超时10 秒防止单个外部请求拖死脚本调度器整体超时30 秒防止脚本死循环或依赖卡顿Agent 总响应超时60 秒保证用户等待有上限重试逻辑不要堆在脚本外层尽量在 SDK 或请求层做一次动态重试比如 5xx 错误时退避 1 秒重试一次4xx 错误直接放弃并返回详细信息因为那是参数或权限问题重试没有意义。最后是结果解析。很多脚本输出的文本格式是给模型看的自然语言但 Agent 框架可能要解析其中的字段做后续逻辑。如果输出是纯文本解析只能靠正则或不可靠的语义理解如果输出是 JSON一切就变得可编程化。所以我在技能设计里定了一条规矩一律输出 JSON由模型负责把 JSON 翻译成用户友好的自然语言。这样既保住了机器可读性又没牺牲对话体验。5. 最后分享一点个人经验技能系统的设计没有银弹它更像是一门“做减法的艺术”。每次往技能库里新增技能前我都会先问自己这个能力能不能复用已有技能能不能合并到现有技能里能不能用一条更精准的描述解决而不是再造一个新的目录技能库越简洁模型的决策越稳定。另外别把技能系统当成一次性工程它需要持续观察调用日志、持续评估效果、持续修剪描述文本。我自己的习惯是每周花一点时间看一遍技能调用失败记录把失败率高的技能挑出来重新打磨。坚持几次之后整体的调用成功率会有非常明显的提升——这种迭代的感觉是搭 Agent 最愉悦的部分。

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

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

免费获取报价 →
↑