
先说结论如果你已经在做 Agent 相关的东西不管是基于 Claude、OpenAI 还是各类国产大模型大概率你已经撞到过同一堵墙——Prompt 越来越长模型总是抓不住重点同一个功能换个场景就要重新调半天。我一开始也以为是模型能力问题直到我把“怎么做事”从“告诉模型做什么”里彻底拆出来用一个独立的“技能层”去承载整套 Agent 的稳定性才真正上来。这个东西业界现在叫它 Agent Skills也就是项目标题里那个 agent-skills。它解决的不是某个单点问题而是 Agent 工程化里最要命的一环把不可控的“模型自由发挥”尽量变成可控的“技能调用”。你不再祈祷模型听懂你的一大段话而是给它一套写好的、结构化的、可复用的能力模块它需要的时候就拿起来用。这篇文章我不讲空概念直接从我实操的角度把 Agent Skills 的设计思路、文件结构、开发流程、调用机制、坑和排查方法全部摊开讲。1. 内容整体设计与思路拆解1.1 为什么 Agent 需要 Skills而不是更长的 Prompt先想一个问题你给模型一条 Prompt本质是在干什么是在一次性传递“背景信息 任务目标 操作细节 输出格式 边界约束”。当任务简单的时候这套东西没问题。但 Agent 类应用一旦复杂起来你会遇到三个连锁反应。第一Prompt 会膨胀到失控。我见过一个真实项目为了把一个多步骤的客户工单处理流程塞进系统提示最后 Prompt 写了两万多字。效果并没有变好反而模型开始丢三落四前面的规则记得住后面的规则就开始“自由发挥”。原因不复杂你让模型在每一轮对话里都重新理解一遍全部上下文它的注意力是有限的长尾细节必然会被稀释。第二能力无法复用。你今天写了一个“从 PDF 里抽取发票信息”的 Prompt明天要在一个新的 Agent 里用同样的能力只能把那段 Prompt 复制过去再改改措辞。后天要升级抽取逻辑你得满项目地找那些复制粘贴过的 Prompt一个一个改。这哪是工程这是手工作坊。第三也是最容易被忽略的——模型不知道自己“有什么工具可用”。Agent 的核心是模型做行动规划如果行动能力只是藏在 Prompt 文字里模型经常会“忘记”用它。你明明把步骤写得清清楚楚它还是跳过了关键环节因为它没有把“这个能力”当成一个可调用的整体而只是当成背景文字。Agent Skills 的思路就是把这团乱麻重新理顺。一个 Skill 就是一个“能力包”它有名字、有清晰的触发条件、有执行步骤、有参考代码模型在规划的时候能明确感知到这个能力的存在需要用的时候直接调用。这相当于给 Agent 配了一个工具箱而不是给它一本使用说明书。1.2 技能在你的 Agent 架构里处于什么位置你要理解 Skills最直观的方式是看它在完整 Agent 架构里的位置。一般一个 Agent 应用分成这么几层模型层负责理解和生成也就是那个大模型本身。编排层负责拆解任务、决定步骤顺序规划怎么用能力完成任务。工具层负责和外部世界交互比如调 API、查数据库、发请求。这一层常见的形式是 function calling 里的那些 tools。记忆层负责保存对话历史、用户偏好、长期事实。技能层也就是 Agent Skills 所在的位置。它本质上是“模型完成某类任务时可以参考执行的标准作业流程”。一个常见的误解是Skills 就是工具Tools。其实不一样。Tools 更像“手脚”是执行动作的接口比如”调用天气 API“、”发送邮件“Skills 更像“操作规程”包含了触发条件、执行步骤、注意事项、甚至配套的脚本。一个技能内部往往会调用多个工具也可以完全不调用工具纯靠模型推理完成。举个例子一个叫“简历筛选”的 Skill它内部可能调用了“读取 PDF”这个工具也可能调用了“写结构化摘要”的能力最后产出一份标准格式的评价表。对编排层来说它只需要知道“有个叫 resume_screening 的技能可以完成这个任务”不需要关心这个技能内部调了什么工具。这个分层带来的直接好处就是技能可以独立开发、独立测试、独立升级。你换模型的时候不需要改技能你加新能力的时候也不需要动编排逻辑只要把新技能注册进技能库。1.3 方案选型把技能写成文档而不是写进代码我做 Skills 的时候其实纠结过一个问题是把技能定义成一段 JSON 配置存进数据库然后由代码控制调用逻辑还是把技能定义成人类可读、模型可读的 Markdown 文档JSON 配置的诱惑在于结构化调用的时候程序好解析校验也方便。但实际跑下来会发现一个问题技能的核心内容是给模型看的不是给程序看的。你用 JSON 去描述一段 SOP标准作业流程既难写又难维护而且模型阅读这种高度结构化的文本时反而不如读自然语言文档来得“理解深刻”。所以我最终选择的是 Markdown 文件的方式一个技能就是一个文件夹里面一个 SKILL.md 作为主文档再加上若干辅助文件。这套方案现在也是业界主流做法它有三个好处人和模型都能读。写技能的人可以直接打开文件审阅逻辑模型也能高效理解里面的步骤描述。版本管理友好。整个技能文件夹就是一个 Git 仓库里的子目录改动历史清清楚楚。组合灵活。一个技能可以引用同目录下的其他文件把大技能拆成多个小模块避免一个文档写得过长。你可能会问那纯代码实现的能力怎么办很简单把实现细节放在配套的脚本文件里SKILL.md 里只说明什么时候执行哪个脚本、预期得到什么结果。2. 核心细节解析与实操要点2.1 一个 Skill 的典型目录结构与每部分职责我先给你看一个我常用的标准目录结构然后再逐个解释每个文件的用意skills/ └── pdf-invoice-extractor/ ├── SKILL.md ├── reference.md ├── scripts/ │ ├── extract_tables.py │ └── validate_fields.py └── examples/ ├── input_sample.pdf └── output_sample.jsonSKILL.md 是这个技能的主控文档模型决策时会优先读它。它的结构一般包含技能名称和一句话简介、这个技能适用于什么场景、不适用于什么场景、详细的执行步骤、输出格式要求、需要参考哪些辅助文件、有哪些注意事项。举例来说SKILL.md 里的执行步骤部分我不会写“抽取发票信息”这种一句带过的话而是会写## 执行步骤 1. 首先读取 PDF 文件内容识别所有文本块和表格结构。 2. 从文本中寻找”发票号码“、”开户行“、”税额“等关键字段。 3. 如果表格结构不清晰调用 scripts/extract_tables.py 进行表格解析。 4. 对抽取结果执行 scripts/validate_fields.py 的校验逻辑。 5. 按输出模板生成 JSON 结果字段缺失时输出 null 而不是猜测。你看到这里的核心逻辑了吗我把“怎么做”的决策权收回来了一大部分模型虽然还是自由发挥但它的发挥空间被限制在了步骤框架里每一步都是明确的、可验证的。reference.md 是给模型按需查阅的知识补充不用一次性带进上下文。比如你那个技能涉及各种票据版式差异、常见字段的歧义说明这些内容写进去SKILL.md 里用一句“如果遇到板式差异参考 reference.md 中的处理规则”来建立关联。这样做的好处是避免主文档过长同时保留了按需加载的可能。scripts/ 目录存放可执行的辅助代码。注意这里并不是说所有能力都要写成脚本而是说那些模型做不稳的事情精确计算、格式校验、文件解析应该用代码做让模型只负责判断和规划。examples/ 目录放输入输出样例。这个目录的价值在于“少样本”效果。模型看到真实样例之后对输出格式的理解会立刻具体化。我见过不少技能效果不稳的情况加点例子之后效果立刻改观因为模型终于知道你想要的 JSON 长什么样了。2.2 技能的最小区分粒度拆到什么程度才算合适这是做 Agent Skills 最核心的决策问题。你拆得细技能数量爆炸模型在几十个技能里做选择难度大增你拆得粗一个技能内部塞了大量逻辑又回到了 Prompt 膨胀的老路。我自己常用的一个判定标准叫作“一个技能只负责一个完整任务”。什么是完整任务就是它有一个明确的输入、明确的输出、清晰的完成边界而且这个任务通常独立于其他任务存在。举个例子“处理客户投诉”不是一个技能它是一个复杂流程里面包含“识别投诉类别”“查询历史订单”“生成回复草稿”等多个任务。而“查询历史订单”可以是一个技能“生成回复草稿”也可以是另一个技能。你可能会说那流程怎么串起来串流程是编排层的工作也就是 Agent 自己规划步骤的时候按先后顺序调用多个技能。你把技能做得足够独立编排的灵活性就越高反过来如果你把一个流程整个做成一个技能那其他场景想复用其中某个环节就彻底没戏了。另外一个实用的判断标准是看任务是否经常被重复使用。如果一个操作在多个业务流程里都会出现比如“提取日期信息”“判断文本情感倾向”那它就值得单独成为一个技能即使它很小。2.3 技能文档的语言风格写得越像给新人看的操作手册越好这个体会是我踩了很多坑之后得出来的。一开始我写技能描述喜欢写得高度抽象比如“本技能用于高效抽取文档语义信息”结果模型经常不太明白应该什么时候触发。后来我把描述改成大白话直接写“这个技能用于从上传的 PDF 或 Word 文件中提取发票、合同、简历等指定类型的结构化字段”效果立刻好了很多。原因很简单模型的语义理解是数据驱动的它对“直白、具体、口语化”的描述敏感度远比“抽象、书面化”的描述更高。你想象一下你给一个新人同事写交接文档你会怎么写你肯定会写“当客户说想退款的时候先查订单状态如果已发货就走退货流程如果没发货就走取消流程”。Agent Skills 的文档就应该是这个风格。还有一个小技巧在 SKILL.md 里明确写“什么时候绝对不要用这个技能”。这个负向约束常常比正向描述更有效它能帮模型快速排除错误选项避免幻觉性调用。2.4 技能触发条件的写法让模型一眼就能判断用不用技能触发条件写在 SKILL.md 的头部区域。我的标准写法是这样--- name: pdf-invoice-extractor description: 从PDF发票文件中提取结构化字段。当用户提供或要求解析发票PDF时使用也适用于包含发票信息的扫描件或照片。图片发票请转换为PDF后再使用本技能。 ---这里有两个细节值得注意。第一description 里尽可能枚举触发场景但注意不要过度堆砌同义词而是写清楚“任务核心动作是什么”。第二要写明前置条件比如“图片发票需要先转 PDF”这能防止模型在不合适的输入上强行触发技能导致输出乱七八糟。还有一种情况是多个技能之间的边界容易模糊。比如你有“合同信息抽取”和“发票信息抽取”两个技能模型可能面对一个既包含合同又包含发票的文件时不知道该用哪个。我的解决方法是在各自的 description 里写明分工边界比如一个写“仅处理合同相关字段”另一个写“仅处理发票相关字段合同信息请使用 contract-extractor”。这种指引看着啰嗦但实测对模型的选择准确率提升非常明显。3. 实操过程与核心环节实现3.1 从零做一个“简历筛选”技能完整开发流程我用一个具体案例带你走一遍完整开发流程。假设我们想做一个 resume-screening 技能用于自动从简历 PDF 中提取候选人关键信息并输出结构化评价。第一步不是写代码而是列需求清单。需求清单包括输入形式PDF 或图片、输出结构化字段姓名、工作年限、技能标签、最近公司、教育背景等、需要做什么分析比如匹配 JD 的关键词覆盖率、最终交付格式JSON 或固定格式的 Markdown 报告。第二步是确定哪些步骤交给模型哪些步骤交给代码。提取 PDF 文本属于精确操作模型直接读 PDF 二进制是不现实的需要代码先把 PDF 转成文本而判断“这个人的经历和 Java 开发岗位匹配吗”属于综合判断必须靠模型。所以我们的技能里会有一个辅助脚本做 PDF 文本抽取模型则专注于信息整理和评估。第三步写 SKILL.md。我会把执行步骤拆成这样1. 使用 scripts/pdf_to_text.py 将简历 PDF 转换为纯文本。 2. 根据文本内容抽取候选人的姓名、联系方式、工作经历、教育背景、技能列表。 3. 如果文本中存在大量乱码或表格结构丢失尝试使用 scripts/pdf_tables.py 二次提取。 4. 对照岗位描述用户输入或 reference.md 中的 JD 要求逐项评估候选人的技能覆盖情况。 5. 输出 JSON包含字段basic_info、experience_summary、skills、jd_match_score、suggestion。第四步写例子。我会找几份典型的简历一份应届生、一份资深开发、一份转行者手动标注好理想的 JSON 输出放进 examples/ 目录。这一步我强烈建议不要偷懒你花在这里的每一分钟都在为模型少犯一次错误做铺垫。3.2 技能装载与调用上下文注入和工具调用两种方式技能写好之后怎么让模型“看到”技能目前主流是两种机制各有适用场景。第一种是把技能文档内容直接拼进系统提示或上下文。这种做法简单直接适合技能数量少、单个技能文档体积可控的场景。比如你只有五六个技能每个技能文档三五千字一次性把全部技能描述注入上下文是可行的模型能看到全部能力清单规划时信息完整。第二种是在需要的时候才把技能内容“拉进”上下文业界叫 on-demand loading。具体做法是首先给模型提供一个简短的技能清单每个技能只有名字和一句话描述模型规划的时候发现自己需要某个技能就会通过一种特殊的“读取请求”触发系统去加载那个技能的完整文档然后模型再基于完整文档执行。第二种方式更像现实世界的行为模式但是实现复杂度要高不少。你需要额外维护一个技能清单之外的能力索引表还要处理模型频繁加载技能文档造成的额外请求耗时。我的建议是如果你的技能库超过十来个或者单个技能文档超过五千字就值得考虑第二种方案了否则先用第一种方案简单起步就好。3.3 技能库的组织批次加载、能力索引与冲突消解当技能数量真的多起来之后组织问题就会浮出水面。我见过有人一口气给 Agent 挂了四五十个技能然后模型就“决策瘫痪”了。每个技能描述看着都有关联模型无法准确判断当前任务到底该用哪一个经常选错甚至选了一个之后忘了还有更合适的存在。我的做法是把技能分群管理先给技能打上领域标签然后按批次加载。比如所有“文档解析”类技能放在一个批次所有“数据分析”类技能放在另一个批次Agent 先判断当前任务属于哪个领域再只加载对应批次的技能描述。这有点像一个图书馆你不可能把所有书都摊在桌面上而是先确定分类号再去对应书架找书。同时我还会专门维护一份“能力索引表”本质上是一个精简版的技能清单里面每一条只包含技能名、一句话功能、适合场景、不适合场景。模型先读这张索引表做规划确定到底需要哪一个技能之后再去加载对应的 SKILL.md。这么做既控制了上下文体积也提高了技能选择的准确率。冲突消解是另一个很实际的问题。两个技能功能相似模型选谁比如一个是“从文本中提取日期”一个是“从文本中提取所有时间信息”后者可能覆盖前者。我的经验是在技能库里保留一个偏功能完整的版本把另一个标注为废弃不要同时挂着两个都能干活的技能。遇到功能重叠优先合并而不是并存。3.4 技能与工具函数的配合什么逻辑放代码什么逻辑放模型这个取舍是你决定技能质量上限的关键问题。我反复强调一个原则模型擅长做判断和综合不擅长做计算和精确解析。所以技能内部的逻辑分配也应该遵循这个大原则。比如一个“合同关键条款审查”技能需要从合同文本里定位违约金条款、争议解决条款、付款条件并对这些条款做风险提示。“定位条款”这一步如果合同是规整的 PDF 或者 Word用代码进行关键词匹配和分节定位是又快又准的而“判断条款是否存在不利条件”这一步就需要模型从上下文语义来判断。所以这个技能我会写一个脚本做条款定位SKILL.md 里只让模型读脚本输出的定位结果再做风险语义分析。再举个例子“批量计算候选人技能匹配度”这一步就不该让模型自己算。你先用脚本把 JD 关键词和简历技能做一次词面匹配计算出基础分值模型负责判断那些没直接命中但语义相关的技能值不值得加分。这样既能保证计算的精确性又保留了语义判断的灵活性。有一个常见的反面教材一个人把整个数据处理链路全都交给模型让模型从 PDF 里读表格、做汇总、算平均值。结果模型在小规模数据上勉强能用数据量一上来就开始出错而且出错方式非常不可控。技能化之后用 Python 脚本做数据清洗和计算模型只负责解读结果这个问题瞬间消失。3.5 技能调试的关键步骤最小样例测试和边界案例验证技能写完不是结束而是调试的开始。我调试一个技能的标准流程是这样的。第一准备一个最小样例也就是最简单、最清晰的输入看模型能不能走通主干流程。如果最简样例都做不对说明技能文档的问题很基础步骤描述或者输出格式部分需要改。第二准备几个边界案例。比如简历里没有工作经历、合同里缺少违约金条款、PDF 扫描质量极差导致文字缺失。这些边界案例的目的不是期待模型完美处理而是看模型在异常情况下的行为是否符合预期——有没有如实报告缺失还是自由发挥假装字段存在。第三检查技能误触发的情况。拿一个完全不该用这个技能的任务去测试看模型会不会错误调用。比如一个“简历筛选”技能你给它一份产品说明书它不应该强行去提取候选人信息。如果误触发了说明技能触发条件写得太宽泛description 里的描述需要收紧要加上负向约束。我强烈建议每次调试都记录一下模型的输出、你的修改点和修改后的效果。这个调试日志积累下来会成为你优化技能的宝贵依据。如果你改完一个技能不知道改了什么、为什么改下次遇到同样问题你还要从头排查一遍。4. 常见问题与排查技巧实录4.1 技能不生效模型不调用已经注册的技能这个问题几乎每个做 Agent Skills 的人都会遇到。你辛辛苦苦写好了技能运行 Agent 的时候它却完全没提那茬直接凭自己的“常识”回答。排查思路从几个方向入手。首先检查技能描述是否清晰是否包含了明确的任务触发信号。如果你的描述写的是“一个协助用户的技能”模型当然不知道什么时候该用它。改成“当用户要求解析网页内容时使用本技能”这种直白描述情况会立刻改善。其次检查技能在上下文中的可见性。如果你的技能是依赖 on-demand 加载的那模型在规划阶段只能看到技能清单。如果清单里的描述也没说清楚用途那模型根本不会请求加载。这里就需要你在能力索引表里把每个技能的一句话说透。还有一种情况是技能描述藏在很长的上下文中被稀释了。这时候可以考虑把技能描述放在系统提示的开头位置或者单独开一个段落区隔出来帮助模型在注意力分布上优先注意到技能相关信息。4.2 技能过载技能文档太长导致模型抓不住重点这个坑我踩得最深。一开始我写技能文档心态是“写得越详细越严谨”结果一个 SKILL.md 写了上万字模型执行的时候反而频繁出错因为它读完了长文档之后对最核心的步骤的记忆反而被大量细节给盖住了。解决思路是分层。把文档分成两层主文档控制在 2000-3000 字以内只写触发条件、步骤框架、输出格式、核心注意事项把各种细节、边界案例、附录说明、补全逻辑放到 reference.md 里主文档中仅在对应环节标注一句“详情参考 reference.md 中的 XX 部分”。这个做法的原理是模型处理长文本时注意力集中在开头和结尾中间部分容易被弱化。所以一个技能文档的核心信息必须放在靠近开头的位置长尾细节靠按需加载而不是一股脑全塞进去。4.3 技能内部依赖外部工具时的路径问题一个技能要调用外部 API 或者本地脚本的时候经常出现路径错乱、环境不同导致的失败。比如你本地调试好好的部署到服务器上技能里的脚本路径就对不上了或者技能里写死了某个 Python 依赖在部署环境里根本没有装。我的建议是在技能目录里放一个 requirements.txt 或者 environment.md写明这个技能运行所依赖的环境和包。同时所有脚本路径不要写绝对路径一律使用相对路径以技能目录为锚点。如果你用的是常见的 Agent 框架它们一般会把技能目录路径暴露给执行环境你只要在脚本里从环境变量读取技能根目录即可。另外对外部 API 的请求技能里应该写好超时和降级逻辑。比如调用一个 OCR 服务失败的时候是直接报告失败还是用备用方案我会在 SKILL.md 里明确写上“如果外部服务超时直接输出 error 字段并提示用户重试不要自行编造提取结果”。4.4 多个技能之间的优先级混乱模型总选错技能这个问题在技能库达到一定规模后几乎必然出现。我之前做过一个数据处理 Agent里面同时有“数据清洗”“异常检测”“数据可视化”“数据导出”这些技能结果模型经常拿着“数据可视化”的任务去调“数据清洗”的技能整个流程乱套。我的排查方法是给每个技能文档加上“明确不适合场景”段落并且在能力索引表里添加一列“排除线索”。比如“数据可视化”的排除线索就写“如果用户只是想修改数据格式不要使用本技能如果用户需要分析数据先确认是否需要可视化再使用”。这些提示看着简单但能显著减少模型的选择错误。更进一步的方法是做一次技能去重和合并且。把容易混淆的技能合并成一个综合技能内部通过步骤分支处理不同场景效果往往比让模型在两个平行技能里做选择稳定得多。5. 技能的安全与可治理性上线前必须想清楚的事技能不是写出来就完事的它是一个会长期演化、需要持续维护的工程资产。在这个层面上有几个治理问题我建议你提前考虑。安全方面技能文档是可以被注入的。如果你的技能里有“读取外部网页并提取信息”这种功能恶意网页里的提示词可能影响模型行为。我上线所有技能前都会做一次“恶意输入模拟”看看在极端输入下技能会不会被带偏输出不该输出的内容。技能内部对模型提要求时也要注意权限意识一个处理文档的技能不应该被允许读取系统环境变量或者发送任意网络请求。版本管理方面每个技能模块都要纳入版本控制。我给每一个技能文件夹设置了一个版本号字段写在 SKILL.md 的开头修改技能时同步更新版本号。上线过程中如果发现新版本效果不如旧版本还能快速回滚。这点在多人协作的项目里尤其重要不然你根本不知道技能是昨天被谁改过的。最后就是技能的测试自动化。不要等到生产环境出问题再排查把每个技能的关键输入输出做成测试样例定期自动跑一遍监控技能行为漂移。模型升级、依赖库更新、文档改动都可能导致原本稳定的技能发生变化自动化回归测试是唯一的兜底手段。这个方向后来还能扩展出很多东西比如技能组合的自动化编排、技能库跨团队共享、技能运行时监控与自恢复。但目前对我来说把每个技能做成一个可靠、可调试、可回滚的能力单元已经让我的 Agent 项目从“实验玩具”变成了“能上线的工具”。希望这篇文章能帮你少走一点弯路至少别再写那种两万字的 Prompt 了。