很多人第一次看到“agent-skills”这个标题第一反应是这不就是给 Agent 塞一堆工具函数吗其实远没那么简单。我自己在把一套 RAG 问答机器人改造成能独立执行多步任务的 Agent 时最头疼的不是模型选型也不是推理框架而是怎么让 Agent“稳定地会干活”而不是“偶尔会干活”。后来我把所有可复用的能力整理成一个结构化的技能库问题才真正开始解决。这篇文章就围绕“agent-skills”这个主题聊聊我如何设计、编写、维护一套面向 AI Agent 的技能库。它不是什么学术概念而是每个做 Agent 落地的人迟早都要面对的一件实事。无论你是在做自动化办公助手、代码仓库智能体还是客服机器人这套思路都能直接用上。1. 为什么 Agent 需要一套“技能库”而不是一堆“提示词”1.1 从“会聊天”到“会干活”的跨越先说说我观察到的普遍现象。很多团队做 Agent 的第一版都是在系统提示词里写“你是一个乐于助人的助手你可以使用以下工具……”然后把函数列表一贴。跑几个 Demo 没问题一旦进入真实业务场景问题全来了模型经常搞错工具参数、漏掉必要的操作步骤、同一个任务这次成功下次失败。原因很简单。提示词是“告诉模型怎么做”而技能库是“给模型一套经过验证的操作规程”。前者依赖模型临场发挥后者把经验固化下来。就好比你教一个实习生如果只说“你帮我把项目部署一下”他大概率会手忙脚乱但如果你给他一份《部署操作手册》加一个《故障自查清单》他就能按部就班地完成。agent-skills 本质上就是这份手册和清单的有机结合。我在实际项目里最早尝试的技能描述大概是这样的把工具的用途、参数、使用场景、注意事项全部写进工具说明里然后让 Agent 每次调用前先读一遍。效果有提升但不够稳定。后来我把“读工具说明”变成了“加载技能模块”Agent 在执行任务前会主动加载对应技能而不是在上下文里塞一大坨 JSON Schema。这个转变很关键——从“让模型理解工具”变成了“让模型使用技能”。1.2 技能库和大模型提示词的关键差异很多人觉得技能库不就是把提示词分了个类吗还真不是。我总结下来技能库和普通提示词至少有四点本质区别第一技能库是可执行的。一个技能不只是文字描述它包含触发器、执行步骤、参数校验规则、输出格式、常见错误处理。这些内容可以被代码解析和校验而提示词只是给模型“看”的文本。比如我写了一个“网页内容抓取”技能里面不但写“什么时候用这个技能”还写了“如果 HTTP 返回 403 应该怎么处理”“如果页面是 JS 渲染的应该切换到无头浏览器模式”这些分支逻辑。第二技能库是版本化的。每个技能有独立的版本号、更新日志、维护人。线上 Agent 用的技能版本是固定的不会因为改了技能描述就导致行为漂移。我用 Git 管理技能库每个技能一个目录合并请求通过后才能发版这样即使某个技能改坏了也能快速回滚。第三技能库是有依赖关系的。一个复杂技能可能依赖于多个基础技能。比如“生成月度数据分析报告”这个技能依赖“数据库查询”“图表绘制”“Markdown 排版”三个子技能。提示词做不到这种模块化组装但技能库可以。第四技能库是可测试的。每个技能配一组验证用例通过 mock 数据跑一遍通过才能算完成。我在实践中发现提示词改几个字业务效果可能上下波动 20%没有测试机制根本没法迭代。1.3 什么项目适合构建 agent-skills 技能库不是所有项目都需要技能库。如果你的需求只是“帮我写一封邮件”这种单轮、无状态的任务直接在提示词里写清楚规则就够了。但如果出现下面四种情况我强烈建议你上技能库任务链路超过三步。比如“查询订单→判断状态→生成处理建议→发送通知”这种链路如果靠提示词编排很容易中间断了。需要复用同一套能力。多个业务场景共用同一个能力比如“OCR 识别发票”“从 PDF 提取表格”写成独立技能可以多处调用减少重复配置。需要稳定的输出格式。下游系统依赖 Agent 的输出做结构化处理技能库能约束输出 Schema避免模型自由发挥。需要持续迭代升级。业务规则经常变技能库的版本管理能力能让你在不停机的情况下升级 Agent 的行为。我实际维护的一个客服工单分类 Agent早期靠提示词识别工单类型准确率只有 87%。后来我把“工单分类”做成技能库里面包含了 12 个常见类别的判定规则、边界案例、人工复核兜底策略准确率直接提到 96%。这个提升完全不是模型变聪明了而是我们把领域经验真正沉淀到了可执行的代码和结构化规则里。2. 技能库的整体设计与目录规范2.1 分层架构基础技能、领域技能、业务技能我在设计技能库时一开始把所有技能平铺在一个目录下写了一百多个技能文件后自己都找不着。后来参考后端服务的分层思想把技能分成三层结构立刻清晰了。基础技能层Atomic Skills和具体业务无关的通用能力比如 HTTP 请求、JSON 解析、时间日期处理、正则匹配、文件读写。这层技能是整个技能库的地基。基础技能的关键是“单一职责”一个技能只做一件事做得足够稳定。领域技能层Domain Skills面向某一类业务场景的组合能力比如金融行业的“账单解析”“交易流水核对”运营领域的“用户分群分析”“活动效果复盘”。领域技能会调用基础技能是基础技能的封装和编排。业务技能层Workflow Skills面向具体业务目标编排出来的完整流程比如“每日自动生成销售报表并推送钉钉群”。这层技能可能有较长的执行链路内部会调用多个领域技能和基础技能同时带有决策逻辑和异常处理分支。我强烈建议在目录结构上就体现这个分层。我的技能库目录大概长这样agent-skills/ ├── categories/ │ ├── atomic/ │ │ ├── http_request/ │ │ ├── json_path_query/ │ │ └── date_calculate/ │ ├── domain/ │ │ ├── invoice_ocr/ │ │ ├── sales_analyze/ │ │ └── customer_segment/ │ └── workflow/ │ ├── daily_sales_report/ │ └── order_exception_handling/ ├── shared/ │ ├── templates/ │ └── validators/ ├── tests/ │ ├── atomic/ │ ├── domain/ │ └── workflow/ ├── registry.yaml └── README.md2.2 技能元信息让 Agent 知道“什么时候用”技能库不是写给人看的首先要让 Agent 知道“这个技能是什么、什么时候该用”。所以我给每个技能定义了一套元信息放在 YAML 文件头部Agent 在技能检索阶段会优先扫描这些字段。一个标准技能的元信息大概包括name: web_content_fetcher version: 2.1.0 description: 抓取指定网页正文内容自动处理登录墙和反爬限制 trigger: - 用户要求打开某个网址 - 需要获取网页上的文章或商品信息 - 需要把网页内容保存为 Markdown input: - name: url type: string required: true description: 目标网页地址 - name: wait_time type: integer default: 3 description: 页面加载等待秒数 output: - name: content type: string description: 页面正文 Markdown 格式 - name: title type: string description: 页面标题 dependencies: - http_request - html_to_markdown error_handling: - when: HTTP 403 then: 使用无头浏览器模式重试 - when: 页面内容为空 then: 切换为移动端 UA 后重试这个元信息设计有两个关键点。第一是trigger字段它相当于技能的“触发条件”Agent 在执行任务时会先比对用户的意图和这些条件决定要不要加载这个技能。第二是dependency字段它告诉执行引擎这个技能依赖哪些基础能力编排系统可以据此保证依赖先被加载。我在写技能的 description 时有个小技巧不用空泛的形容词而是直接写触发场景。比如不要写“高效的网页抓取工具”要写“当用户需要获取网页内容、需要把链接变成文字时使用”。因为 Agent 判断技能是否适用靠的是语义匹配具体的场景词比形容词有用得多。2.3 技能的注册与检索机制技能库建好了Agent 怎么找到它需要的技能我踩过不少坑最后形成了一套“索引 评分 回退”的检索机制。索引阶段就把所有技能的元信息汇总成一个索引文件registry.yaml每个技能只保留 name、description、trigger 三个字段加起来非常短。Agent 不需要把全部技能塞进上下文只需要看索引就能确定该加载哪个技能。评分阶段就是让 Agent 根据用户请求对索引里的技能做相关性打分选择得分最高的 1 到 3 个技能完整加载。回退阶段的做法是如果 Agent 认为索引里没有匹配的技能需要允许它返回“未找到技能”而不是硬编。这个设计很重要——宁可明确说不会也不要硬做然后出错。我在一个物流查询 Agent 里遇到过模型在技能库里找不到“订单修改”技能时竟然自己编了一个“假装修改成功”的结果这个真实发生的案例让我彻底坚定了回退策略必须有。3. 核心技能编写规范与实操要点3.1 技能描述里的措辞直接影响调用准确性技能描述这事一开始我觉得很简单但写了几十个之后发现里面的门道非常多。同样一个“发送邮件”技能不同的 description 写法调用准确率完全不同。我的经验是技能描述要尽量贴近用户的自然表达习惯。比如用户不会说“请调用 email_sender 技能”而是说“帮我给张三发一封邮件告诉他明天开会”。所以正确的描述应该是“当用户要求发送邮件、给某人写信、回复邮件时使用。如果用户提到收件人姓名或邮箱应优先使用邮箱地址若不明确则向用户确认。”还有一点很重要描述里要明确互斥边界。技能 A 和技能 B 如果功能相近必须写明 A 管什么、B 管什么避免模型选错。例如我有“发送邮件”和“定时发送邮件”两个技能我会在前者里写“不负责定时发送定时发送请使用 schedule_email_skill”后者里写“必须先创建草稿不允许立即发送”。3.2 参数定义决定调用成功率在 Agent 开发过程中真正让我头疼的不是技能逻辑而是技能之间的参数传递。模型调用工具时经常出现的问题是给了错误的参数名、遗漏必填参数、参数值格式不对。这不能全怪模型更多时候是我在技能定义里没有把参数规则写清楚。所以现在我的每个技能参数都会包含这些信息名称和类型参数名叫什么是 string 还是 integer 还是 array。是否必填用 required 标记并且说明默认值。枚举约束如果参数只允许几个值必须列出来比如“语言仅支持 zh-CN、en-US、ja-JP”。关联参数填写某个参数后必须同时填写另一个参数例如“填写了日期范围就必须填写时区”。格式示例给一个完整的示例值模型照抄就不容易错。我还会在技能内部加一层参数校验逻辑。Agent 调用技能前执行引擎会根据参数定义做一次校验不合法就直接返回错误信息给 Agent不需要进到技能内部才发现问题。这套机制让我家 Agent 的工具调用失败率从 31% 降到了 9%。在实际项目中我见过太多团队忽略参数校验这层。他们觉得模型能力强了参数应该不会传错。但真实环境里用户的表达千奇百怪模型很容易把“下周二”理解成 2025-02-18 还是别的日期如果参数定义里没有时区和日期格式约束结果就会非常不稳定。3.3 技能执行中的异常处理与降级策略一个技能如果只有“正常流程”那它还只是个半成品。真正能上生产的技能必须把异常场景考虑进去。我给自己定了一个规矩每个技能至少要考虑三种异常情况怎么处理。以“网页内容抓取”技能为例第一种异常是目标网站反爬。HTTP 返回 403 或者 429 时我会配置降级方案先等几秒重试一次再不行就换无头浏览器模拟真实用户还不行就返回“暂时无法获取该网页”并给用户一个建议。第二种异常是页面结构异常。有些网页本身有内容但 HTML 结构不规范普通的 HTML 解析器提取不到数据。这种情况我会配置一个“宽松模式”直接根据标签特征做全文抓取最后再用正则清理无用信息。第三种异常是内容为空。网页能打开但正文是空的这通常是前端 JS 渲染导致的。我会在技能里配置一个判断如果抓取内容长度小于指定阈值就切换浏览器渲染引擎再抓一次。这个处理在很多新闻网站和 SPA 应用上非常管用。3.4 一个完整的技能编写示例为了让你更直观地理解我完整展示一个技能。就以“订单超时催付”为例这在电商项目里很常见。首先是元信息name: order_overdue_reminder version: 1.3.0 description: 当有订单超过付款时限但未完成支付时生成催付通知内容。适用于电商场景中提醒用户完成付款。 trigger: - 用户提到订单未支付、超时订单、催付 - 系统任务要求生成催付文案 input: - name: order_list type: array required: true description: 订单对象列表必须包含 order_id、user_name、amount、created_at - name: remind_type type: string enum: [sms, app_push, wechat] default: app_push description: 通知渠道类型 output: - name: messages type: array description: 每个订单生成的催付文案列表然后是这个技能的执行逻辑我用 Python 伪代码来描述def execute(order_list, remind_typeapp_push): results [] for order in order_list: overdue_hours calculate_overdue_hours(order.created_at) if overdue_hours 24: # 首次催付语气温和提示订单保留时间 message ( f{order.user_name}您好您的订单{order.order_id}尚未完成支付 f订单将在{24 - int(overdue_hours)}小时后自动取消 f请尽快完成支付。 ) elif overdue_hours 72: # 二次催付语气加重说明商品库存紧张 message ( f{order.user_name}您好订单{order.order_id}即将超时取消 f目前该商品库存紧张请尽快完成支付以免错失订单。 ) else: # 最后一次催付说明需要重新下单 message ( f{order.user_name}您好订单{order.order_id}已超时取消 f如仍需要该商品请重新下单。 ) results.append({ order_id: order.order_id, channel: remind_type, content: message }) return results这个示例看起来很简单但它其实涵盖了一个完整技能的核心要素触发条件、结构化输入、分场景处理逻辑、结构化输出。Agent 接到“催付”这个任务时会加载这个技能按输入模板抽取订单信息调用执行逻辑生成文案最后按输出格式返回。整个过程是可预测、可测试的。我在技能执行逻辑里还有一个习惯尽量用确定性的代码代替模型的随机生成。比如催付文案很多人会交给模型自由发挥但实际业务中文案里的关键信息订单号、时间、链接绝对不能错所以我用模板生成模型只负责决定选哪个模板不负责创作。这样既保证了准确率又保留了灵活性。4. 全套测试方案与效果评估方法4.1 单元测试给技能加上“安全网”技能是给 Agent 用的代码那它就得有测试。我在技能库里建立了双层测试机制第一层是纯代码级的单元测试第二层是模型级的效果评估测试两者不能互相替代。单元测试很简单就是针对技能的执行函数写测试用例覆盖正常输入、边界输入、异常输入三类场景。还是拿催付技能举例正常场景订单未超时、超时 30 小时、超时 100 小时分别验证文案的准确性。边界场景超时时间正好 24 小时整、订单列表为空、用户名为空。异常场景order_list 里缺字段、created_at 格式非法。这些测试用 pytest 跑每改一次技能代码都能立刻收到反馈。我的经验是技能的代码测试覆盖率最好做到 90% 以上因为技能一旦上了生产出错影响的是很多用户的真实体验不像普通 Chatbot 说错一句话那么轻描淡写。4.2 模型级评估验证技能在真实场景中的表现代码测试通过只说明“逻辑没 bug”不等于“Agent 能正确调用这个技能”。所以我还维护了一套模型级评估用例专门测试 Agent 面对真实用户问题时能不能选对技能、正确填参、完整执行完流程。评估用例的基本格式是一个“用户输入 - 期望行为”对。例如用户输入“我昨天下的订单还没发货帮我看看”期望技能order_status_query期望参数{“order_ref”: “昨天下的订单”期望结果查询订单状态并返回物流信息我每隔一段时间就会把积累的评估用例跑一遍每轮大概有一百多条。跑完后看三个指标技能选择准确率、参数填充正确率、任务完成率。任何一个指标下滑说明最近改的东西有问题继续排查。4.3 线上日志埋点与回溯优化测试只能覆盖已知场景线上才是终极考场。我在 Agent 的调用链路上埋了详细的日志每次技能调用都会记录Agent 选择了哪个技能、传入的参数是什么、执行结果如何、用户有没有反馈问题。这批日志是最宝贵的优化素材。我每个星期会抽两个小时的“人工审查日志”时间把调用失败或执行结果偏离预期的案例挑出来归类。看多了以后我发现很多失败是可以提前预判的比如某类用户提问总是触发了错误的技能那就说明技能的 trigger 描述写得不够清楚或者两个技能的边界需要重新划分。这个方法听起来费时间但做久了收益特别大。每次优化都来自真实问题而不是拍脑袋猜。到后期线上调用成功率的提升会非常明显。我的技能库从第一版到现在线上任务成功率从 74% 一路提升到 93%靠的就是这条“测试-线上-回归”的闭环。5. 常见问题与排查技巧实录5.1 技能命中不准调 trigger 和 description症状用户提出某个明确需求Agent 却没有加载对应技能而是答非所问或者直接编造一个结果。排查思路先看日志里 Agent 的“思考过程”如果框架支持的话确认它是压根没看到技能还是看到了但觉得不匹配。如果是前者可能是技能索引构建有问题如果是后者问题多半出在 trigger 和 description 的关键词覆盖不够。我的调整手法很朴素把用户最常用的几种问法写进 trigger。比如我做“发票识别”技能时一开始 trigger 只写了“识别发票”“发票 OCR”但实际用户常说的是“帮我看看这张发票能不能报销”“拍了个发票帮我提取下信息”这些说法根本匹配不上。把真实语料补充进 trigger 之后命中率立竿见影提上来了。5.2 参数反复传错加校验和示例症状Agent 能选中正确的技能但调用时参数总是不对要么缺字段要么格式错。排查思路打开日志看它实际传了什么参数对比我期望的参数格式。很多时候我发现问题出在我的参数定义没有给示例。模型对抽象的描述理解有限但如果你给它一个具体的示例值它能模仿得非常好。比如有个“日期范围查询”参数我原先写“start_date 和 end_date格式为 YYYY-MM-DD”模型经常传错。后来我在参数说明里加了示例: start_date2025-01-01, end_date2025-01-31错误率立刻降了一大截。参数示例是最便宜的防错手段没有之一。5.3 技能更新导致老场景崩了引入版本管理症状某次技能迭代后之前跑得好好的场景突然出问题。排查思路先对比两个版本之间有哪些字段、逻辑发生了变化。如果发现是依赖冲突就检查技能间的依赖关系。如果发现是描述变化导致行为偏移就回滚到上一版。我自己早期吃过一次亏当时改了一个基础技能的 description从“获取网页标题”扩展成“获取网页标题和描述信息”结果所有依赖这个技能的上层技能全部收到多余信息输出格式全乱了。从那以后我坚持两条规矩基础技能只做加法不做减法改动必须配套跑一次依赖它的上层技能测试。5.4 技能库越做越大导致检索变慢症状技能数量超过一百个后Agent 选择技能的时间明显变长而且经常选错。排查思路技能多了以后索引文件本身也变长了Agent 扫描索引时上下文负担加重。我的解决办法是给技能加“分组标签”Agent 先根据用户意图判断属于哪个组再在组内做技能匹配。这相当于把一次大范围检索拆成两次小范围检索效果非常明显。举个例子我有一个电商业务技能库里面有 120 个技能。以前全部塞进一个索引里Agent 每次都要读整个索引。后来我按业务模块分成用户、订单、商品、营销四个组Agent 先判断业务域再读取对应组的技能索引。这样每次只需要比较 30 个左右的技能准确率反而提高了。6. 技能库产品的演进路线与一个实在建议6.1 从“单 Agent”到“多 Agent 共享技能库”技能库的另一个重要价值是它可以在多个 Agent 之间复用。我最早是给客服 Agent 写技能库后来发现运营分析 Agent 也用得上其中一部分技能。于是我把技能库从业务项目里抽出来变成独立的内部组件不同 Agent 声明自己依赖哪些技能分组再由统一的技能服务分发。这样做的好处非常明显技能只维护一份修好一次所有 Agent 受益。坏处是跨团队的变更管理更复杂了所以必须搭配前面说的版本机制和测试机制。如果你们团队有超过三个 Agent 共用一套技能我建议尽早把技能库独立出来。6.2 社区共建技能库一个低成本起步方式如果一个人从零开始构建技能库压力确实大。后来我发现了一个更轻量的起步方式先做一个社区共享的技能库把不同的技能像开源软件一样共享和维护。你可以把自己的技能剥掉业务敏感信息后开源也可以先拉取别人写好的技能往自己的项目里试。这样做的核心价值是“避免重复造轮子”。我在开源技能库里看到了很多比我写得好的实现直接拿来借鉴省了不少时间。而且社区技能库天然带有各种真实场景的测试用例用来补充自己的评估集非常划算。6.3 一个实在建议从规模最小的技能开始建库讲了这么多如果你打算动手做自己的技能库我给的建议是从最小技能开始。不要一上来就想做一个全能的超级 Agent 技能库而是把你现在最高频、最容易出错的三个工具调用沉淀成三个技能跑顺了再扩展。我自己的技能库就是从三个技能起步的网页抓取、Markdown 转换、关键词提取。当时就这三样已经解决了不少自动化问题。后面随着业务需要慢慢增加一步步完善成现在覆盖多个业务域的大型技能库。这个从“解决实际问题的技能”入手的路线比起先设计一套庞大体系再填充内容要落地得多。最后分享一个个人心得Agent 项目的成败很多时候不取决于模型选得多先进而取决于你给模型准备了多少“靠得住的技能”。我见过太多团队今天换模型明天调提示词但效果始终不稳定原因就是他们忽略了技能库这个真正能沉淀经验、抵御变化的基础设施。技能写得扎实Agent 才扛得住真实业务里那些千奇百怪的用户输入。不妨先挑一个你手头最高频的需求写一个技能试试你会很快感受到这种模式带来的确定性。
