AI Agent技能库设计:从工具到可复用技能的实战指南 AI Agent 火了这两年我见过太多 demo 跑得飞起、一上真实业务就拉胯的案例。问题多半不是模型不够强而是 Agent 的“手”太短——模型再聪明没有一套组织良好的技能库它也只能在对话里打转做不了实事。我去年花了不少时间整理了一套自己的 agent-skills 工程实践把高频能力沉淀成标准化技能包后来好几个项目都靠这套东西把“演示级 Agent”变成了“能上生产的 Agent”。这篇文章就把这套实践从头到尾拆开讲一遍从技能库怎么设计、技能文件怎么写到怎么评测、怎么维护、怎么排查一次说清楚。这篇文章适合谁如果你正在做 Agent 应用发现模型老是乱调工具、输出不稳定、换一个场景就得重新写一遍逻辑那这篇就是给你准备的。哪怕你完全没接触过 Agent 开发我会把 skill、tool、workflow 这些概念掰开揉碎讲明白你照着做也能搭出一套自己的技能库。1. 先想清楚agent-skills 到底解决什么问题1.1 Agent 的“玩具感”从哪来现在很多 Agent 项目本质就是“模型 一堆函数”。模型负责理解用户意图函数负责干活。听起来很简单但实际一跑就露馅第一工具是零散的。今天给 Agent 挂一个查天气的函数明天加一个发邮件的函数每个函数的输入输出风格都不一样模型调用的时候经常张冠李戴。第二调用是碰运气的。同一个意图模型这次传对参数了下次可能就传错这次按你期望的格式返回下次可能给你一段废话解析逻辑直接被干碎。第三逻辑是没法复用的。A 项目里写了一个“抓取网页正文”的函数B 项目要用得改半天才能接上几乎等于重写。这三件事叠在一起就造成了 Agent 的“玩具感”演示的时候很惊艳真让它在生产环境里连续跑几百次就开始花式报错。agent-skills 的思路就是把“工具”升级成“技能”。技能不是单个函数而是一个完整的、自包含的能力单元它有清晰的名字和描述有结构化的输入输出协议有示例有校验逻辑甚至有独立的测试。模型用技能的时候不需要“猜”怎么调用只需要照着技能说明填参数。这样 Agent 的稳定性就会从“碰运气”变成“有保障”。1.2 Skill、Tool、Workflow 的边界这里必须先划清三个概念不然后面设计会乱套。Tool、Skill、Workflow 经常被混着说但它们的粒度完全不一样概念粒度典型例子是否可独立完成一个任务Tool最小操作单元发一个 HTTP 请求、读一个文件否通常只是动作Skill完成一个小任务的能力提取网页正文、生成周报草稿是包含逻辑和校验Workflow一串技能的组合搜集资料→总结→生成报告是编排层面的事我见过的很多失败项目问题就出在把 Tool 当 Skill 用。你只给模型一个fetch_url工具模型拿到 HTML 之后不知道怎么办是直接返回还是解析还是提取正文模型只能自己乱猜输出自然一团糟。而一个标准的 Skill最少要包含四样东西明确的功能定义、输入输出协议、调用示例、兜底校验。它把“工具能干什么”和“工具怎么用才对”都焊死在了一起。模型不需要理解底层逻辑它只需要按照协议调用就行这就像你不需要懂发动机原理只要会踩油门刹车就能开车一样。Workflow 则是在 Skill 之上做编排它是另一层的事。agent-skills 的核心关注点就是把中间这层“技能”做到位。你可以用 LangChain、Coze 这类框架做上层编排但技能层的质量决定了下限。2. 技能库的整体设计目录、接口与元数据2.1 目录结构按业务域分还是按技术域分技能库的目录怎么组织看起来是小事实际上决定了后续的扩展成本。我踩过坑之后最终的目录结构长这样agent-skills/ ├── skills/ │ ├── web/ │ │ ├── extract_content/ │ │ │ ├── SKILL.md │ │ │ ├── schema.yaml │ │ │ ├── run.py │ │ │ └── examples.json │ │ └── search_web/ │ │ ├── SKILL.md │ │ ├── schema.yaml │ │ ├── run.py │ │ └── examples.json │ ├── data/ │ │ └── csv_preview/ │ │ ├── SKILL.md │ │ ├── schema.yaml │ │ └── run.py │ └── office/ │ └── gen_meeting_minutes/ │ ├── SKILL.md │ ├── schema.yaml │ ├── run.py │ └── examples.json ├── registry.yaml ├── tests/ └── README.md分组我建议按业务域分而不是按技术域分。为什么因为 Agent 的场景是用户视角的用户不会说“帮我调个 API”用户会说“帮我看看这个网页讲了什么”。按业务域组织模型在理解技能的时候语义距离更短按技术域组织比如把所有网络相关的放一团模型很难分清“抓网页”和“查天气”到底哪个该用。每个技能目录下的文件职责也是固定的SKILL.md给模型看的说明书这是核心中的核心。schema.yaml给参数定义好结构方便做校验。run.py真正干活的代码不依赖任何 Agent 框架。examples.json几组输入输出例子帮助模型理解“什么情况该用、怎么用”。2.2 SKILL.md模型能不能用对技能全看它很多刚上手的人不理解为什么一个技能要单独写一份说明书因为模型不是人它读不懂你的代码它只能看到你给它的描述。描述写得烂再好的实现也白搭。我总结的 SKILL.md 模板如下每个技能都按这个模板写缺一不可name: extract_content description: 从给定 URL 中提取网页的标题和正文内容返回纯净文本。 when_to_use: 用户给出一个网址要求了解页面内容、总结文章、提取重点时使用。 when_not_to_use: - 用户没有给出具体 URL只想做普通搜索 - 网页需要登录才能访问 input: url: 网页链接必须是完整的 https 或 http 地址 max_length: 最大返回字符数默认 5000 output: title: 页面标题 text: 清洗后的正文纯文本 examples: - input: {url: https://example.com/blog/hello} output: {title: Hello World, text: 这篇文章介绍了……}这里面的关键不是name和input而是description、when_to_use和when_not_to_use。模型选择技能靠的就是这些描述。when_not_to_use特别容易被忽略但它非常有价值。比如“网页需要登录才能访问”这个边界条件写清楚模型在遇到某些网站时就不会白白调用这个技能然后失败而是会直接告诉用户“这个页面需要登录我拿不到内容”。这比调用失败后报错要体面得多。写 description 的时候还有两个原则第一动词开头说动作。不要写“网页内容提取工具”这种名词短语要写“从给定的 URL 中提取网页正文并返回纯文本”。第二明确边界。用不用、什么时候用、什么时候不用都要写清楚。描述里的每句话都会影响模型的判断。2.3 输入输出用 JSON Schema 管住边界大模型的输出天然带有不确定性我们不能寄希望于“它这次会好好传参数”。所以每个技能都要定义严格的输入输出协议并且用 JSON Schema 做校验。schema.yaml大概长这样input_schema: type: object properties: url: type: string format: uri description: 网页完整链接 max_length: type: integer minimum: 100 maximum: 20000 default: 5000 required: - url output_schema: type: object properties: title: type: string text: type: string required: - title - text有了 schema 之后技能内部第一件事就是校验输入不符合直接报错并返回给模型一个清晰的提示比如“url 格式不正确请提供 https:// 开头的完整网址”。这个“报错再喂回给模型”的机制是整个稳定性设计的核心。模型第一次参数传错了Agent 编排层拿到校验错误把错误信息连同原始任务一起再发给模型让它重新调用。这一步看似简单实测能让技能调用的最终成功率从七成左右拉到九成五以上。输出侧也一样。技能跑完先做 schema 校验再交给上层。宁可在这里多花一点校验时间也不要让脏数据流到下游。这跟传统后端接口做参数校验是同一个道理只是很多人做 Agent 的时候把这个常识丢了。3. 从零实现一个可复用的 Agent 技能3.1 先选框架还是先自己撸聊到实现很多人第一反应是上框架。LangChain、LlamaIndex、Coze 都行但我的建议是技能层不要绑定任何框架先用纯 Python 把技能写成普通函数再在需要的时候做一层薄适配。理由很简单技能是资产框架是工具。你今天用 LangChain 写了技能明天换了技术栈技能如果和框架强耦合全都得重写。而把技能写成纯函数任何框架都能调用——LangChain 可以把它包成一个 ToolOpenAI Function Calling 可以把它映射成一个 function自己写的编排代码也可以直接调。技能内部可以用依赖库比如提取网页正文用trafilatura解析 PDF 用pypdf这些都属于技能自己的实现细节不影响外部接口。3.2 手写一个“网页正文提取”技能我拿最常写的“网页正文提取”技能来讲。这个技能的应用场景极广让 Agent 总结一篇文章、分析竞品页面、抓取新闻都离不开它。run.py的核心实现extract_content 技能实现 import json import sys import trafilatura import requests from jsonschema import validate, ValidationError INPUT_SCHEMA { type: object, properties: { url: {type: string, format: uri}, max_length: {type: integer, minimum: 100, maximum: 20000}, }, required: [url], } OUTPUT_SCHEMA { type: object, properties: { title: {type: string}, text: {type: string}, }, required: [title, text], } def run(config: dict) - dict: try: validate(instanceconfig, schemaINPUT_SCHEMA) except ValidationError as e: return {ok: False, error: f输入参数不合法: {e.message}} url config[url] max_length config.get(max_length, 5000) try: resp requests.get(url, timeout20, headers{ User-Agent: Mozilla/5.0 (compatible; agent-skills/1.0) }) resp.raise_for_status() except Exception as e: return {ok: False, error: f页面请求失败: {str(e)}} content trafilatura.extract( resp.text, include_commentsFalse, include_tablesFalse ) if not content: return {ok: False, error: 未能从页面中提取到正文可能页面是动态渲染的} title trafilatura.extract(resp.text, output_formattxt) text content[:max_length] result {title: title, text: text} try: validate(instanceresult, schemaOUTPUT_SCHEMA) return {ok: True, result: json.dumps(result, ensure_asciiFalse)} except ValidationError: return {ok: False, error: 技能内部输出异常}注意几个细节第一技能入口统一接收一个 dict返回一个 dict。返回结构里带ok标志成功时result放结果失败时error放原因。这样上层编排只要判断ok处理逻辑会非常统一。第二请求失败、内容提取失败、输出校验失败全部有明确的错误信息。这些信息最终会被回传给模型模型才能“意识到”失败并调整策略。第三设置 User-Agent 和超时是生产环境的基本素养。不做这两件事技能上线后会因为各种反爬策略和慢响应把 Agent 卡死。3.3 效果评测别只看“能跑”要看“稳定跑”技能写完测一两次能跑不算完。你需要一个评测集反复跑、批量跑、看统计结果。我的做法是每个技能都建一个eval_cases.json放 20 到 50 个真实场景的输入覆盖正常情况、边界情况、异常情况。比如网页正文提取技能的评测集[ {url: https://example.com/blog/1, expected_contains: , note: 普通博客文章}, {url: https://example.com/empty, expected_contains: , note: 页面无正文}, {url: not-a-url, expected_contains: , note: 非法URL} ]评测跑完之后我重点看四个指标调用成功率整个技能运行下来没有抛异常的比例。输出合规率返回结果通过 output_schema 校验的比例。任务完成率人肉抽查输出内容确实符合用户需求的比例。平均耗时和消耗一次调用花多长时间、消耗多少 token。这四个指标里最难提升的是任务完成率因为它考验的是技能的“内功”——比如网页正文提取正文提取得干不干净标题拿没拿到直接决定了下游总结的质量。我会把评测结果记录在技能目录的EVAL.md里每次改动技能都重跑一遍评测防止改出回归问题。4. 质量、安全与维护4.1 技能常见的失败模式技能写得多了失败模式其实很集中我把最常见的几种列出来你对照着查自己的技能库第一种描述模糊。技能说“获取网页内容”但没说清楚是否需要登录、是否支持 JS 渲染。模型拿到一个需要动态渲染的页面就会瞎调用然后失败。解法就是在 SKILL.md 里把边界写死。第二种技能职责太宽。一个技能既想抓网页又想总结又想翻译。看起来万能实际上模型根本不知道它能带来什么结果。技能要像函数一样一个技能只做一件事把这一件事做到极致。第三种隐藏的系统依赖。技能代码里用了某个系统命令或者依赖了某个没在 requirements 里声明的库。换个环境直接跑不起来。技能一定要做环境隔离用虚拟环境、锁定依赖版本、声明所有外部依赖。第四种解析逻辑太脆。比如用正则去提取 HTML页面结构一改技能就挂了。尽量用成熟的解析库少用脆弱的字符串操作。4.2 权限与安全边界技能是会跑代码的跑代码就意味着有安全风险。这方面绝对不能偷懒。我给自己定了几条铁律第一技能运行在最小权限环境。能用只读权限绝不给写权限。涉及文件系统操作的技能一律放到沙箱目录里。第二外部请求做白名单控制。不是让 Agent 随便请求任何 URL尤其要防止 SSRF 攻击——如果 Agent 部署在内网被诱导请求内网地址是很大的泄露风险。我通常会上一个域名白名单和 IP 黑名单。第三密钥集中管理。技能里需要调用第三方 API 的密钥统一从环境变量或密钥管理服务里读绝不写死在代码里。代码仓库泄漏的时候至少不会连密钥一起泄漏。第四容器隔离跑高危技能。涉及代码执行、批量下载这种有副作用的技能我会用容器跑并且设置资源限制比如 CPU、内存、网络出口。安全这件事不容易出彩但一旦出事就是事故。宁可多花点时间做隔离也不要觉得“自己内部用没关系”。4.3 维护技能也要版本管理和测试技能库是一个长期演进的资产不是写完就完了。我用了一套常规但很有效的维护流程每个技能在 SKILL.md 里记录version字段改动就升版本号。协议有破坏性变更就升大版本。registry.yaml统一登记所有技能记录每个技能的版本、负责人、部署状态。每个技能配一组单元测试和一组集成测试。集成测试用录制好的真实响应来跑避免测试时频繁请求外部服务。进 CI改动技能必须过测试才能合并。这个流程一开始会觉得繁琐但技能多了之后没有自动测试你根本不敢改代码。例如tests/test_extract_content.py长这样def test_extract_content_success(): result extract_content.run({ url: https://example.com/blog/hello, max_length: 2000, }) assert result[ok] is True assert title in json.loads(result[result])这里的核心思想是技能不是脚本是产品。凡是没有测试的技能本质上都是未知状态。5. 常见问题与排查实录5.1 模型就是不调用技能这是最让人头秃的问题技能写得清清楚楚模型就是不用自己去编一个答案。我排查下来的原因通常有三个一是技能列表中技能太多模型被淹没。解决办法是引入“技能门控”根据用户意图先粗筛一批技能只把最相关的几个暴露出给模型而不是一股脑全塞进去。二是描述写得像文档不像决策依据。模型读到的描述如果都是“该工具用于……”它没法判断“现在该不该用”。把描述改成决策导向的比如“当用户给出具体网址并要求总结内容时使用此技能”效果立竿见影。三是缺少示例。有些模型的少量示例学习能力很强在 SKILL.md 里放两个例子说清楚“用户这么问的时候你怎么调用”模型就更愿意走这条路。5.2 调用了但参数传错参数传错也很常见。类型不对、缺字段、URL 没转义五花八门。我的处理方式是在编排层加一个“修复循环”校验失败 → 把错误信息喂回给模型 → 让模型重新生成调用。这个循环最多跑三次超过三次就放弃避免死循环消耗 token。还需要注意错误信息一定要具体。不要返回“参数错误”要返回“url 字段必须是合法的 http/https 链接当前值 xxx 不符合要求”。模型看到了具体错误才能修对。5.3 常见问题排查速查表我最后整理了一个速查表基本覆盖了我在实践中遇到的大部分问题可以直接抄症状可能原因先查什么解决办法模型不调用技能描述不够决策导向 / 技能太多SKILL.md 的描述重写描述加“当……时使用”句式加技能门控调用后参数格式乱schema 太宽松 / 无示例schema.yaml 的必填字段收紧 schema加 example加修复循环技能执行报错依赖缺 / 网络被墙 / 页面结构变了技能日志和异常信息锁定依赖版本配代理如有需要改用成熟解析库返回结果结构不稳输出侧没做校验output_schema 校验结果补输出校验解析失败时返回明确错误技能响应超时外部服务慢 / 没设超时requests 超时和调用日志设置合理超时加缓存考虑异步化技能库里技能越多效果越差模型选择困难技能列表长度动态门控 按用户意图粗筛排查这类问题最重要的一件事是——日志。技能的每次调用入参、出参、耗时、报错全部落日志。没有日志排查就跟大海捞针一样。我甚至建议给每个技能的调用链路加一个 trace_id从用户请求到技能执行完毕整条链路可以串起来看。遇到线上问题先捞日志再谈优化。我个人的体会是agent-skills 这套东西真正的门槛不在写代码而在“克制”。克制自己想要加新技能的手克制把技能范围扩大的冲动克制跳过测试和评测的侥幸心理。技能库的每一个技能都应该像正式产品一样对待有说明、有协议、有测试、有版本。你觉得这是小题大做等你哪天在凌晨两点被线上 Agent 的奇怪输出叫醒就知道这些工作有多值钱了。