做 Agent 项目做得久了你会发现一个很有意思的现象同样一个模型在 A 手里是个只会聊天的玩具在 B 手里却能稳定完成跨系统数据迁移、自动化测试、报表生成这些实打实的活儿。差别在哪里绝大多数时候不在模型本身而在于你给模型准备了什么样的技能包——也就是 agent-skills 这套东西。说直白点agent-skills 就是给智能体预置的一整套能力单元每项技能包含明确的触发条件、输入输出规范、执行逻辑和边界约束让模型在需要时能像人翻工具箱一样精准找到并调用合适的工具。这篇文章我会完整拆解我从一个 prompt 走天下到技能库驱动的转型过程包括技能定义规范、目录设计、评测迭代和常见坑。适合正在做 Agent 应用开发、或者想让自己自动化系统变得更智能的工程师参考。整个思路不绑定具体框架不管你是用开源 Agent 框架、商业平台的自定义工具还是自己写调度层这套方法论都能直接落地。1. 项目概述agent-skills 到底是什么1.1 从工具调用到技能体系的演进最早接触 Agent 开发时我其实是被工具调用这个概念带进门的。那时候做法很简单定义几个 Python 函数把函数名、参数 Schema 丢给模型让它根据用户指令决定调哪个。比如一个查天气的函数一个算日期的函数demo 跑起来效果惊艳领导看了直点头。但等真实业务场景铺开问题就来了。业务方提需求从来不是一个函数能解决的帮我拉一下这周线上订单的数据做一下趋势分析如果异常再告警。拆开看这涉及数据查询、统计分析、条件判断、通知发送四件事。如果我把四个函数都暴露给模型它在中间环节的选择就会失控——一会儿用错参数一会儿把统计结果直接当最终答案返回根本不知道后面还要做告警决策。后来社区里开始流行用 Skill技能这个概念替代 Tool工具思路一下就通了。工具是原子的你给它输入它返回结果没有状态、没有流程、没有上下文技能则是封装好的能力单元——它可以包含多个工具调用、内置一部分处理逻辑、附带使用条件说明和典型示例甚至能在内部调用其他技能。说得更直白一点工具是螺丝刀技能是换轮胎的整套流程。模型不需要理解每一颗螺丝怎么拧它只需要知道车辆无法行驶时应该调用换轮胎技能。这个概念转变直接改变了我的架构方式。1.2 agent-skills 解决的核心痛点我梳理了一下在做 agent-skills 之前的典型 Agent 项目通常会卡在三个地方Prompt 无序膨胀为了让模型正确使用函数你在系统提示词里写了一大段你有以下工具可用注意参数格式……工具越来越多提示词越来越长模型选择准确率反而下降这是典型的上下文污染。工具缺失上下文单个工具函数看不到其他工具的存在也不知道当前任务处于哪一步遇到需要协作的复合任务时模型只能硬着头皮乱调。复用与迁移困难换个项目所有工具定义、调用逻辑、测试用例全部要重写。做得再好的工具离开当前代码库就变成一堆废代码。agent-skills 的设计目标就是把这堆问题收敛成一个可维护的工程体系技能以独立文件的方式存在有统一元信息能单独测试能按需加载能跨项目复用。你可以把它理解成给 Agent 建立岗位说明书——每个技能都写清楚自己负责什么、不负责什么、什么情况下不该被调用。实际落地之后我最大的感受是模型的表现变得稳定可预期。不是说每轮回答都完美而是说错误的类型收敛了、可追溯了。出问题时你能直接定位到某个技能的描述或边界设置而不是对着一个 2000 行的 prompt 猜。2. 技能设计为什么定义比实现更重要2.1 技能描述是Agent的说明书在 agent-skills 体系里绕不开的第一个原则是技能的实现代码反而不难写难点全在描述上。为什么因为对于模型来说技能代码是黑盒。模型不会去读你 Python 文件里的业务逻辑它决策的唯一依据就是技能的元信息——名称、描述、参数说明、使用场景、典型示例。你可以把这段描述想象成外卖平台上的商家页模型就是顾客它只能通过页面文字和图片判断这家店卖什么、好不好吃你后厨再厉害页面写得含糊顾客照样不点单。我在实际写技能描述时总结了一个三段式结构职责声明一句话说清楚这个技能做什么动词开头避免形容词。触发与禁用条件什么情况应该用什么情况不应该用。很多人只写适用场景忽略了禁用场景而后者往往才是模型选错技能的根源。至少一个典型调用示例给模型看到真实输入输出的模样比任何规则都有效。举例来说一个解析订单导出文件的技能我一开始是这么写的将用户上传的订单文件解析为结构化数据。结果模型经常在用户只是想看文件内容概览的时候调用它甚至把它和另一个读取文件内容的技能搞混。后来改成技能解析订单导出文件。当用户提供订单相关的 CSV/Excel 文件并要求提取结构化数据如订单号、金额、状态时使用。不要将此技能用于查看文件内容概览或预览前几行——那是读取文件内容技能的职责。典型输入用户上传 orders_20250315.csv要求转成 JSON 并按金额排序。效果立刻就不一样了。核心原因是把与其他技能的边界写进了描述里模型的决策空间被收缩得很干净。2.2 输入输出Schema的边界设计技能的输入输出 Schema 看着是小事实际上决定了 Agent 和你系统之间的契约质量。我踩过一个大坑一开始所有技能都用宽松的字符串参数比如date: string然后靠运行时正则去解析。这样做的后果是模型在真实对话里给出的日期格式千奇百怪今天、三月中旬、上周五……你不可能靠正则覆盖所有自然语言表达。后来我改成在 Schema 里尽量使用结构化类型并且把约束写死在参数描述里日期统一用YYYY-MM-DD描述里直接给出正例和反例枚举值尽量用 Enum 而不是字符串让模型只能从固定选项里挑可选参数明确写默认值避免模型自作主张。另外一个容易被忽略的点是输出。很多人技能定义的返回值是自由格式文本模型拿到之后自己再解读一遍就会产生重复加工和信息损耗。我现在的习惯是技能的返回值如果不是最终答案就尽量结构化返回比如{status: 异常, metric: p95-latency, value: 320, unit: ms}让上层 Agent 只做翻译而不是推测。2.3 技能粒度拆得太细和太粗都不行技能粒度这个问题是我和团队争论最多的地方。拆细了模型选择负担大拆粗了技能内部什么都做边界就糊了。我最后形成了一套判断标准用三个问题衡量这个技能是否对应一个完整业务动作比如生成周报排查数据延迟——对应动作就值得独立而格式化字符串发送 HTTP 请求这种属于通用函数库而非业务技能。模型是否需要在这个技能内部做分支决策如果技能内部又要判断 A 情况走 A 逻辑、B 情况走 B 逻辑说明你应该把这个决策拆出去让模型看到而不是藏在技能实现里。这个技能能否被清晰描述成一段不超过 200 字的说明如果说不清大概率太杂。我把两个极端情况做成了一张对比表方便你直观感受粒度典型例子优点缺点过细发送HTTP请求、读取本地文件实现简单、复用灵活模型需自行组合决策路径过长容易乱适中拉取订单数据并汇总、检测磁盘水位并告警模型决策负担小行为可预期需要花心思设计边界与体系过粗处理所有数据相关任务描述简单内部逻辑黑盒模型不知何时该用调试困难我个人的选择标准宁可技能个数多 20%也不要在单个技能里塞太多隐式逻辑。因为 Agent 系统的可调试性比代码复用率重要得多。3. 实操过程从零搭建一套Agent技能库3.1 目录结构与元信息规范进入实操环节。我先给你看一套我目前在用的技能库目录结构它支撑了四个不同业务线的 Agent 系统agent-skills/ ├── manifests/ │ ├── order-analysis.yaml │ ├── latency-investigation.yaml │ └── report-generation.yaml ├── skills/ │ ├── order-analysis/ │ │ ├── SKILL.md │ │ ├── main.py │ │ └── tests/ │ └── latency-investigation/ │ ├── SKILL.md │ ├── main.py │ └── config.yaml ├── loader.py └── shared/ ├── date_utils.py └── es_client.py这套结构的核心思路是一个技能一个目录每个技能自包含说明书SKILL.md、实现代码main.py、测试用例tests/放一起。manifests 目录存放所有技能的元信息聚合方便上层系统快速遍历和索引。元信息我用 YAML 写因为可读性最好模型在解析时也不容易出错。每个技能的 manifest 长这样name: order_analysis version: 2.1.0 description: 分析订单导出文件并生成结构化摘要 triggers: - 用户提供订单CSV/Excel文件并要求统计 - 需要按状态/金额/时间维度汇总订单 not_to_use: - 仅查看文件内容或预览 - 涉及库存、供应链数据的分析 input_schema: file_path: type: string description: 订单文件绝对路径支持CSV或XLSX格式 group_by: type: array items: [status, amount_range, date] description: 分组维度默认[status] output_schema: type: object properties: total_orders: integer groups: array anomalies: array dependencies: - shared.date_utils关键在于triggers和not_to_use两块它们会直接被拼进系统提示词里作用我在第 2 节说过了。dependencies则让加载器知道需要提前注入哪些公共模块。3.2 技能文件的核心组成与写法每个技能目录的核心是SKILL.md。它跟 manifest 不重复——manifest 是给加载器用的机器可读元数据SKILL.md 是给模型看的人类可读说明书。我见过不少项目把这两者合一结果上下文里塞满了 JSON 一样的字段模型阅读效率很低。我的 SKILL.md 结构基本固定# 技能名称订单分析 ## 这个技能做什么 把订单导出文件CSV/XLSX转换成结构化摘要支持按状态、金额区间、日期分组统计并能识别明显异常如负金额、重复订单号。 ## 何时使用 - 用户要求分析订单文件、统计订单数量/金额、按条件分组 - 用户要求检查订单数据是否存在异常 ## 何时不要使用 - 用户只是上传文件想看看内容没有分析诉求 - 用户要求做的是销售预测、库存管理超出订单文件本身 ## 调用参数 - file_path: 文件路径必填 - group_by: 分组维度数组可选默认[status] ## 典型示例 用户帮我看看 orders_20250315.csv按状态统计下订单量。 调用order_analysis(file_path/data/uploads/orders_20250315.csv, group_by[status]) 返回{total_orders: 1240, groups: [{status: 已支付, count: 980}, ...]}写 SKILL.md 的时候有个细节典型示例里的输入输出一定要用真实数据最好是从测试集里抽出来的。模型会模仿示例的格式和风格一个带具体数字的示例比十句抽象描述都管用。3.3 示例日志分析技能的完整实现为了让你看到完整链路我拿日志异常分析技能做例子从实现到接入走一遍。实现部分我用 Python 写了个极简版本# skills/log-analysis/main.py from datetime import datetime from typing import Optional def analyze_log( log_path: str, time_range: Optional[list] None, error_patterns: Optional[list] None, ) - dict: 分析日志文件统计错误分布并识别异常时间段。 Args: log_path: 日志文件绝对路径 time_range: [start, end] ISO 格式时间可选 error_patterns: 需要关注的关键字列表如[ERROR, Timeout, Exception] Returns: 结构化分析结果 dict error_patterns error_patterns or [ERROR, Exception, Timeout] count_by_pattern {} time_distribution {} with open(log_path, r, encodingutf-8, errorsignore) as f: for line in f: matched None for pattern in error_patterns: if pattern in line: matched pattern break if not matched: continue count_by_pattern[matched] count_by_pattern.get(matched, 0) 1 # 提取时间部分做小时级分布统计 try: ts line[:19] # 假设日志行以 ISO 时间开头 hour datetime.fromisoformat(ts).strftime(%H:00) time_distribution[hour] time_distribution.get(hour, 0) 1 except (ValueError, IndexError): continue return { total_errors: sum(count_by_pattern.values()), by_pattern: count_by_pattern, by_hour: time_distribution, top_hour: max(time_distribution, keytime_distribution.get) if time_distribution else None, }这里的要点是函数签名必须和 manifest / SKILL.md 里声明的 Schema 完全一致。模型按 Schema 生成参数如果实现端参数名不一致运行时就会报错。对应 SKILL.md 里我会把示例写得更贴近真实用户场景## 典型示例 用户帮我看看 /var/log/app/backend.log 今天下午是不是有大量报错。 调用analyze_log(log_path/var/log/app/backend.log, time_range[2025-03-15T12:00:00, 2025-03-15T18:00:00], error_patterns[ERROR, Timeout]) 返回{total_errors: 328, by_pattern: {ERROR: 212, Timeout: 116}, by_hour: {15:00: 187, 16:00: 141}, top_hour: 15:00}注意我故意没有在技能内部实现时间窗口过滤——因为示例里用户问下午模型理应把time_range传进去。如果发现模型经常不传时间参数那就是描述和示例写得不够清楚应该去改 SKILL.md而不是在代码里兜底。这也是我的设计原则模型能学好的不要让代码硬扛代码兜底太多模型的错误会越来越隐蔽。3.4 技能加载与注册机制最后是加载端。我写了一个轻量 loader扫描 manifests 目录把技能元信息聚合后建立索引# loader.py import yaml from pathlib import Path SKILLS_ROOT Path(agent-skills) def load_all_skills() - list[dict]: 扫描 manifests 目录返回所有技能元信息列表。 skills [] manifests_dir SKILLS_ROOT / manifests for manifest_file in sorted(manifests_dir.glob(*.yaml)): with open(manifest_file, r, encodingutf-8) as f: meta yaml.safe_load(f) skills.append({ name: meta[name], description: build_skill_prompt(meta), input_schema: meta[input_schema], output_schema: meta[output_schema], entry: f{SKILLS_ROOT}/skills/{meta[name]}/main.py, }) return skills def build_skill_prompt(meta: dict) - str: 把技能元信息渲染成模型友好的一段描述文本。 lines [ f技能名称{meta[name]}, f功能说明{meta[description]}, 适用场景, ] lines [f - {t} for t in meta.get(triggers, [])] if meta.get(not_to_use): lines.append(禁止场景) lines [f - {t} for t in meta[not_to_use]] return \n.join(lines)build_skill_prompt是我反复调过的一个函数——它决定模型看到的技能描述长什么样。早期我把原始 YAML 直接塞给模型JSON 风格太重模型读起来费劲后来改成格式化文本后选技能准确率有明显提升。如果你不想把全部技能都塞进上下文可以考虑按名称做检索只把命中的技能详情注入这也是一种常见的剪枝策略后面第 5 节详细说。4. 技能评估与迭代让Agent真正会用4.1 单技能评测与回归测试技能写出来了怎么知道它靠谱我的答案是像对待软件工程一样对待技能建立评测集和回归测试。每个技能目录下的tests/文件夹除了放常规的单元测试验证函数逻辑我还会放一个cases.json里面保存真实或者半真实的用户案例[ { id: case_001, user_query: 帮我看下 app.log 昨天有多少行报错, expected_invocation: { skill: log_analysis, args: {log_path: app.log, time_range: [2025-03-14T00:00:00, 2025-03-14T23:59:59]} } }, { id: case_002, user_query: 这个日志文件里有没有数据库连接超时的情况, expected_invocation: { skill: log_analysis, args: {log_path: app.log, error_patterns: [database connection timeout, MySQL]} } } ]评测的方法很朴素拿固定的用户 query 跑一遍 Agent看模型是否调用了期望的技能、传参是否合理。这套评测集我会在每次修改 SKILL.md 之后全量跑一遍确保没有按下葫芦浮起瓢。我建议你从第一天就给每个技能建评测集哪怕只有三五个 case 也行。这个习惯的价值不是当下能发现多少 bug而是给后续迭代留下可对比的基线。没有基线你改进技能描述后就只能靠感觉好像变聪明了那不是一个工程化的状态。4.2 多技能协作时的冲突排查技能数量一多最典型的问题就是边界重叠。比如你同时有分析订单数据和生成销售报表两个技能用户说给我出一下这周的销售报表模型完全有可能调用订单分析技能——因为订单分析可以算出金额而模型觉得算出金额生成报表。排查这种冲突我的流程分三步打开调用日志看模型实际选了哪个技能、用了什么参数。很多冲突问题在日志里一眼就能看出来不需要猜。对照两个技能的 SKILL.md把triggers和not_to_use逐条比对找出语义重叠的部分。比如销售报表的 not_to_use 里就要明确写仅分析订单文件、未要求生成报表时不要使用本技能。构造边界测试用例故意用模糊的 query 去测模型。比如你看下这周订单有什么问题这种 query 两个技能都可能沾边如果模型每次选得不稳定说明边界还需要继续打磨。在技能设计层面还有一个更省心的做法给技能加优先级属性。在 manifest 里声明priority: high或者fallback: true让加载器在渲染技能列表时把高优先级技能放在前面。模型对列表前部的注意力天然更强这个技巧在处理多技能冲突时非常实用。4.3 版本管理与技能退役策略技能是活的业务一变技能就会变动。吃了两次亏之后我才开始做严格的版本管理。第一次我把一个技能的描述改了没有改版本号结果线上 Agent 还在用旧调用方式模型按新描述生成了新参数实现代码不兼容线上直接报错。第二次我删了一个没人用的技能结果某个业务方其实在悄悄调用它删了之后那个业务链路的自动流程全断了。现在的规则很简单每个技能的 manifest 里带version字段凡是描述、Schema、实现有变化版本号必须递增删除技能前先查看调用日志统计确认一周内没有调用记录并且和业务方书面确认旧版本技能保留在skills/下的archive/文件夹不参与加载但留档可追溯。版本号的好处除了可追溯还能帮你在评测集里标记用例——比如某个 case 在 v2.1 是通过的在 v2.2 挂了通过 git 对比马上能定位到是哪次改动引入了问题。5. 常见问题与排查技巧实录5.1 模型总是选错技能怎么办这是被问得最多的一个问题。我的排查顺序是先看是永远选错还是偶尔选错。永远选错说明技能描述和用户意图存在根本性错位比如你的技能其实根本不匹配用户需求或者描述里用了模型不理解的行话偶尔选错一般是边界描述不清晰需要补充 not_to_use。检查技能描述是否太长太抽象。超过 200 字模型在有限注意力下反而容易忽略关键信息建议压缩到职责一句 触发条件几条 禁用条件几条 示例一个。检查是不是技能数量太多。单次给模型刷 50 个技能出错概率远比 15 个技能高。如果确实需要 50 个考虑分层——先让模型决定领域再在该领域内选技能。5.2 技能描述冲突导致的幻觉调用这个坑我印象深刻。有一次我同时注册了查询订单状态和查询物流信息两个技能各自描述都写了排查影响用户体验的问题。结果是模型在用户问我的快递卡在路上了怎么办时莫名其妙调了订单状态技能返回了订单金额——用户一脸懵。后来定位原因两个技能描述里都用了排查问题这类宽泛词模型在模糊语义下随机选择。修复方式很简单就是给每个技能加上明确的专属信号词订单状态涉及支付、退款、订单状态、订单金额物流信息涉及快递、物流、配送、运单号、卡在路上。然后我在 not_to_use 里互相写清楚。改完之后这种行为就基本消失了。所以当你发现模型出现不符合逻辑的幻觉调用时第一反应不应该是模型不行而应该去检查技能描述里有没有模糊的共用词汇。5.3 长上下文下的技能说明书膨胀最后一个常见问题也是最容易被忽视的技能太多以后系统提示词越来越长模型不仅要理解用户需求还要在前面几十个技能描述里翻找注意力被稀释。这个问题的解法我在前面提到过——检索式加载不全量注入。我的具体做法是每个技能保底 20 字的摘要描述manifest 里的description字段第一句在加载器里对所有技能摘要做 embedding用户每轮对话向量化后做相似度检索只把 top K我常用的 K5技能详情注入上下文如果检索结果和用户实际意图不符在评测集里会暴露出来通过调整 embedding 模型或加大 K 来平衡。这个方案实测下来上下文从十几万的 token 降到两三万单轮响应速度和成本都有明显改善而且技能选择准确率没有下降反而因为干扰变少而提升。不过要注意检索式方案不适合技能之间强相关、必须互相感知的场景那种情况下还是得全量注入或者做分组注入。做 agent-skills 这套体系我前后迭代了快一年。最大的体会是给 Agent 定义技能本质上是在定义你和模型之间的协作边界。你描述得越清晰模型的自由度越小系统的行为就越可控。很多人追求模型更聪明但在我实际做项目的过程中收益最大的往往不是换更强的模型而是把技能描述写得再准确一点、把边界再划清楚一点。最后分享一个小技巧如果你刚开始做技能库别追求一步到位。先挑一个你最常让 Agent 干的活把它做成第一个技能配上三五个评测 case跑通整个流程。等这套流程顺手了再慢慢往里面加技能。你会发现当技能体系化之后Agent 项目的复杂度不再随着需求增长而失控——每一块能力都有归属每一个问题都有迹可循。这就是我理解的 agent-skills 真正的价值。