
说句实在话做 Agent 应用做到第三个月我一度被工具函数越写越多、Agent 却越来越笨这个问题搞到怀疑人生。模型从 7B 换到 70B工具从三五个加到三五十个结果 Agent 该选错还是选错该跑偏还是跑偏。后来我把所有代码摊在桌面上看了一整晚终于明白问题不在模型、不在工具而在组织方式我一直在给 Agent 发零件却从没教过它一套完整的工序。这个认知直接催生了我现在维护的开源项目 agent-skills。agent-skills 是一套面向 AI Agent 的技能库。它和普通工具函数最大的区别是每个技能不只是一段能跑的代码而是一份完整的行为契约包含触发条件、执行步骤、依赖资源、输出格式和验证方法让 Agent 在接到任务时先选对一个技能再按规范执行最后自我检查结果。这篇文章我会把 agent-skills 从设计思路、目录规范、三个典型技能实现到实测中踩过的坑、版本维护的完整过程讲一遍适合已经在做 Agent 应用开发、或者正准备从零搭建技能库的工程师参考。1. 为什么 Agent 需要技能而不是更多的提示词1.1 从会聊天到会干活Agent 的进化路径大模型刚火起来的时候大家觉得能聊天就等于能干活。后来发现完全不是一回事你让模型写一首诗它写得飞快你让它把服务器上所有日志按错误级别统计一下它就有点发懵需要你一步步告诉它先看哪个目录、用什么命令、统计哪个字段、结果怎么排。于是有了工具调用function calling。我们把查天气、算日期、取订单等操作封装成函数模型通过结构化输出决定调哪个函数、传什么参数。这一步确实让 Agent 往前走了一大截但很快又暴露新问题工具是散装的。Agent 要完成分析销售报表并生成周报这个任务可能需要先读取文件、再清洗数据、再做统计、再调绘图接口、最后格式化成 Markdown——每一步都要模型在运行时临场发挥稍微复杂一点就容易出错。我的进化路径基本是这样工具函数阶段维持了大概两个星期Agent 准确率大概在 60% 左右再往上怎么调都上不去。让我真正突破瓶颈的是把工具升级成技能一个技能内聚了完成某类任务所需的知识、步骤、脚本和验证规则Agent 只需要做选择题——选哪个技能去做事而不用做论述题——思考每一步怎么做。1.2 技能Skill到底是什么菜谱、零件和质检单的合体我习惯用一个类比工具是厨师手里的食材提示词是顾客那句给我做道下饭菜而技能是一份完整的菜谱。菜谱里不只有原料清单还有切配方式、下锅顺序、火候大小、调料用量甚至出锅前怎么尝一口判断咸淡。放到 Agent 体系里一个技能通常包含以下要素触发条件什么任务应该选这个技能什么任务不该选。这是描述文件的核心。执行步骤把任务拆成可执行的步骤尽量让模型照着做而不是自由发挥。资源与脚本技能内部的实际代码、模板、配置文件以及它依赖的第三方库。输入输出契约明确接收什么字段、输出什么结构。验证方式执行完成后技能自检是否达成预期比如文件是否存在、返回 JSON 是否合法、数值范围是否合理。我见过很多人把技能理解成更长的提示词这个偏差很致命。提示词是写给模型看的话术技能是让模型不仅知道该做什么、还拥有实际执行能力、并且能够验证结果的系统。区别在于单靠提示词模型可能在第二步就开始幻觉编造一个不存在的命令而技能里每一步都有脚本兜底模型只需要调用脚本、解析输出出错概率因此大幅下降。1.3 技能库不等于工具库组织方式决定了 Agent 的瓶颈把几十个工具函数堆在一起和把几十个技能组织成结构化的技能库表面看只是形式不同实际差异很大。工具函数的组织方式通常围绕系统能力文件操作、网络请求、数据库查询。但 Agent 的真实任务往往是一条龙的它需要的是主题技能比如抓取一个网页并抽取正文分析一份 CSV 并给出统计结论每周五上午汇总团队进展并发送邮件。前者是零件后者是工序加质检。这也是我最初踩坑的地方。我按照文件操作API 调用数据处理分目录放工具结果 Agent 面对真实任务时要在多个工具之间来回切换、自己设计流程每一步都有失败风险整体成功率自然低。而把它改造成技能库之后每个任务对应一个高度内聚的技能Agent 的主流程变得很短选技能、执行、检查结果。流程一短出错点就少了。2. 一个可复用的技能长什么样agent-skills 的架构设计2.1 最小可用技能目录结构与元信息在设计 agent-skills 时我给自己定了一个原则一个技能必须能在不做任何修改的情况下从一台机器复制到另一台机器独立运行。为此我规定了一个标准目录结构skills/ └── extract_web_content/ ├── SKILL.md # 技能行为契约Agent 主要读这个 ├── scripts/ │ └── main.py # 实际执行脚本 ├── requirements.txt # 声明依赖及版本 ├── assets/ # 模板、静态资源 └── tests/ └── test_main.py # 可自动化的验证用例SKILL.md 是技能的灵魂。它既是给 Agent 看的使用说明书也是给人类开发者看的设计文档。文件头部用 YAML 格式的元信息声明技能的名称、描述、版本、依赖和输入参数后面用 Markdown 写清执行步骤和输出说明。元信息里最关键的是description字段。Agent 在运行时会把所有候选技能的description拿来和自己的任务做匹配这个字段写得越精准技能选对的概率越高。我在 1.0 版本里曾写过提取网页内容这种三流描述后来改成下面这样name: extract_web_content description: 从给定 URL 提取网页正文内容返回标题与结构化 Markdown 文本。 适用于新闻文章、技术博客、文档页面 不适用于需要登录验证的页面、PDF 文件、图片内容或 JS 动态渲染的单页应用。 version: 1.2.0注意看这个 description 不仅说了能干什么还强调了三类边界场景。这个边界信息帮助很大Agent 遇到 PDF 任务时就不会误选它了。2.2 SKILL.md 里的 description 是选技能的地图我第一次被选错技能整破防是真的一次线上事故。用户问帮我查一下上季度华南区的销售额Agent 转身就去调了一个数据库查询技能结果账号根本没有对应库表权限。实际数据在同事发来的 Excel 里用户的本意是帮我看看这个文件。那次之后我彻底懂了description 是 Agent 选技能的地图地图画错了导航必然翻车。写好 description 有几条经验值得分享用什么时候用来写而不是这是什么。好的例子是当用户提到 CSV、Excel、表格数据统计任务时使用差的例子是CSV 分析技能。明确排除项。把相似技能之间的边界写清楚比如本技能不处理 SQL 数据库查询需求能显著减少误选。写一个典型调用场景。在 SKILL.md 里放一到两个示例用法比如把 https://example.com/article 的正文提取出来模型看到示例会更容易进入正确状态。不要写废话。什么这是一个非常强大的技能这类语气词模型不仅不关心还可能干扰语义匹配。我当时为了验证 description 质量搞了一个最简单的方法把同一段用户请求分别丢给 3 个不同模型看它们能不能从 20 个技能里选对目标技能。初期准确率只有 65%我花了两周逐条调整描述把准确率推到 92% 左右。这个过程很枯燥但收益率极高推荐大家照做。2.3 输入输出契约把大概意思翻译成必须这样技能的输入输出契约决定了 Agent 能不能把任务结果拿回来继续加工。在 agent-skills 里每个技能都必须声明自己的input和output格式。输入我倾向用 JSON Schema 定义让模型知道哪些字段必填、哪些可选、取值范围是什么。输出则规定统一的结构最好带上状态字段status和错误信息error这样上层 Agent 才能判断技能到底成没成功。以 extract_web_content 为例输入声明是这样的input: url: type: string required: true description: 网页完整 URL max_length: type: integer required: false default: 5000 description: 返回的文本最大长度超出部分截断 output: format: json fields: - status - title - content_markdown - source_url - fetch_time这份契约最大的价值是给 Agent 一个明确的预期。技能执行完Agent 看到status: ok就直接拿content_markdown去写摘要看到status: failed就读取error决定是重试还是换技能。没有契约的话技能返回一段乱糟糟的文本Agent 还要花很多推理时间去猜意思成本和错误率都高。3. 从零搭建 agent-skills 技能库我的落地过程3.1 目录规划与命名规范技能库也是一套代码库技能库不是文件夹随便堆堆它本质上是一套要长期维护的代码库所以命名和分类要自己立规矩。agent-skills 目前按领域分六个顶层目录web/网页抓取、内容提取、链接分析data/CSV/Excel 处理、数据清洗、统计汇总file/文件读写、格式转换、编码处理comm/邮件、消息通知、定时提醒rag/文档检索、向量库读写、摘要生成system/进程管理、日志查看、环境检测命名规范我定为动词_对象比如extract_web_content、analyze_csv、send_email、schedule_reminder。纯中文场景下我也试过中文目录名技术层面没问题但混合团队协作时英文命名更稳妥。分类和命名做好之后检索模块的工作量会少一半。因为很多场景只需要在对应领域子集里做语义匹配而不是全库范围大海捞针。我做过一次对比加了领域过滤之后技能检索准确率提升了 7 个百分点响应速度也快了一倍。3.2 三个典型技能案例网页正文提取、CSV 数据分析、定时提醒光讲架构太虚我拿三个实际技能说下落地方法。第一个是extract_web_content做它的原因是 Agent 接网页抓取任务时模型经常直接把整段 HTML 塞进上下文又占用大量 token 又没法看。技能内部用 readability 抽取正文、BeautifulSoup 做清洗最后输出干净的 Markdown 文本和一个状态字段:import argparse import json import requests from readability import Document from bs4 import BeautifulSoup def extract(url, max_length): resp requests.get(url, timeout10, headers{User-Agent: agent-skills/1.2}) resp.raise_for_status() doc Document(resp.text) soup BeautifulSoup(doc.summary(), html.parser) text soup.get_text(separator\n, stripTrue) if max_length and len(text) max_length: text text[:max_length] \n...[truncated] return { status: ok, title: doc.title(), content_markdown: text, source_url: url, fetch_time: datetime.utcnow().isoformat(), } if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--url, requiredTrue) parser.add_argument(--max_length, typeint, default5000) args parser.parse_args() try: print(json.dumps(extract(args.url, args.max_length), ensure_asciiFalse)) except Exception as e: print(json.dumps({status: failed, error: str(e)}, ensure_asciiFalse))第二个是analyze_csv。这个技能的出发点很朴素让 Agent 分析一个带表头的 CSV它经常需要被重复引导才能给出正确统计结果。我把统计分析常用的操作封装成脚本并支持用户输入columns和operations两个参数比如[amount: [sum, avg]]脚本直接输出分类汇总结果。这样 Agent 不需要自己写 pandas 代码只需要把任务翻译成参数正确率高了一大截。第三个是schedule_reminder。做这个技能是为了让 Agent 能过段时间再干活这在纯对话模型里做不到。技能本质是一个带持久化存储的定时任务注册器把提醒写入 SQLite后台轮询到期触发。Agent 只需要调用技能完成注册后续触发由进程负责。这里有一个关键细节技能必须提供查询已注册提醒和取消提醒两个子命令否则 Agent 无法管理自己创建的定时任务用户要改时间就只能干瞪眼。3.3 让 Agent 会选技能检索与评分策略技能数量一旦超过 20 个把全部 SKILL.md 塞进上下文就不现实了token 消耗太大模型也容易在长文本里迷失重点。我的做法是两级检索加评分首先按任务领域粗筛再用语义模型精确匹配。粗筛靠一个简单的关键词路由判断用户请求属于 web、data、file 还是 comm 领域直接砍掉一多半候选。精匹配用 sentence-transformers 做语义向量检索每个技能的description和tags提前编码存好运行时把用户请求编码后算余弦相似度。实现并不复杂from sentence_transformers import SentenceTransformer import numpy as np model SentenceTransformer(paraphrase-multilingual-MiniLM-L12-v2) # 离线阶段把每个技能的 name, description, tags 拼成文本编码入库 skill_embeddings model.encode(skill_docs) def retrieve_skills(query, top_k5): q_vec model.encode([query])[0] scores [cosine(q_vec, e) for e in skill_embeddings] top_idx np.argsort(scores)[::-1][:top_k] return [(skills[i][name], scores[i]) for i in top_idx]检索之后我还会叠一个简单的评分策略相似度得分权重 0.7技能历史调用成功率权重 0.2最近更新时间权重 0.1。这个策略上线后技能选择准确率稳定在 94% 左右特别是存在多个相似技能时Agent 会更倾向于选择之前被验证过成功的那个而不是每次都拿用户需求冒险尝试冷门技能。4. 实测中的四个坑从选错技能到上下文爆炸4.1 一个典型的选错技能排查过程有一次用户让 Agent看看这个日志文件里有没有异常请求结果 Agent 去调了file_find把整个目录扫了一遍返回的是一堆不相关的文件名。我第一时间查了运行时日志看到 Agent 在技能选择时确实匹配到了analyze_csv和file_find最终选了后者。排查下来发现两个问题。一是我的file_finddescription 里写了搜索文件中包含关键词的行这描述和查找异常请求语义上太接近模型混淆了。二是我没有一个专门的scan_log技能——本该存在的当时偷懒没做。这个案例给我提了个醒描述写得再精准如果技能库里压根没有对口的技能Agent 就只能在错误技能里矮子里拔将军。所以接到新场景需求时第一反应不应是改描述而是先问需不需要新技能。4.2 依赖冲突技能不是复制粘贴是带环境跑技能越做越多之后依赖冲突问题立刻暴露。analyze_csv需要 pandas 2.0另一个数据处理技能只兼容 pandas 1.5还有一个技能又需要 mysqlclient 编译安装。我把它们装进同一个 Conda 环境结果每隔两周就要为版本偏移收拾一次烂摊子。最后我采用的方案是每技能独立 requirements 统一运行器隔离执行。轻量场景下给每个技能建一个独立的 Python 虚拟环境用 subprocess 调用。这个方案简单粗暴但管用代价是首次执行要花几秒做环境复用检查换来了长期的稳定性。重量级场景就考虑用容器一个技能一个镜像彻底隔离系统依赖。这里提醒一句包括我在内很多人刚开始都懒得做依赖隔离觉得反正都是 Python。等你技能超过 30 个、换了一台机器部署、又撞上操作系统版本差异的时候就会明白依赖隔离不能拖到后期再补。4.3 失败反馈回路技能必须知道自己没做成技能执行失败不可怕可怕的是 Agent 不知道它失败了。早期我的技能经常静默返回空结果或者一段程序 tracebackAgent 会拿这个当正常输出继续往下编最后给用户一个全然错误的答案。这比报错更糟糕因为用户根本不知道哪里出了问题。我现在要求每个技能都必须输出结构化结果成功时status: ok失败时status: failed加error字段。此外在 SKILL.md 里还会写明失败时的推荐兜底动作比如网页提取超时后可以重试、网络请求报 403 时可以换 UA、CSV 字段缺失时建议用户补充列名。Agent 拿到 failed 结果后有两条路可走按约定重试或者主动向用户说明这个任务需要额外信息。实测下来这类显式的反馈回路能把用户体验拉高一个层次至少用户知道 Agent 在哪个环节卡住了而不是收到一堆看似合理的垃圾输出。4.4 上下文窗口爆炸多用流式输出与摘要模式长文提取是上下文管理的老大难。extract_web_content一次性输出 8000 字正文紧接着 Agent 还要做摘要、提炼要点整个上下文立刻满了。我后来给技能加了一个summary_mode参数当用户只需要要点时技能内部先完成抽取和关键词提取只把压缩后的摘要返回给 Agent。另一个教训是大输出别直接交给主模型硬吞优先让技能脚本在本地完成结构化处理输出尽量精简的 JSON。这样主 Agent 的 token 资源可以集中用于决策而不是浪费在处理冗余文本上。上下文管理不是模型层的优化技能层的输出设计同样关键。5. 技能库的维护与生态化从一个人到一个团队5.1 技能的版本管理description 变了就是 breaking change软件工程的语义化版本号SemVer在技能库里要重新理解一下对于一个技能v1.2.0改成默认参数是 feature但如果改了description里任何一句涉及触发条件的描述那就是 breaking change版本号必须大版本递增。原因很简单description 直接影响 Agent 的选择行为行为变了效果就是不可逆的。我在发布日志里会记录三类变更行为变更、描述变更、性能优化。行为变更包括输入输出格式变化、脚本逻辑变化描述变更专门标注会影响技能选择结果需要重新跑一遍选择准确率测试。这个看似繁琐的规范在技能数量超过 50 个之后就显得特别珍贵——没有它你根本不知道一次改动会波及多少个下游 Agent。5.2 团队共享技能库的治理命名空间、CI 与质量分一个技能库从个人维护变成团队共享治理成本会跳一个台阶。我现在的做法是每个技能在元信息里增加owner字段定义负责人CI 流程在推送时自动执行tests/下的用例、校验 SKILL.md 格式、检查 requirements.txt 依赖是否可安装再配一个评分面板展示每个技能的实际调用量、成功率、平均耗时。引入这些之后最明显的变化是技能质量不再靠个人自觉而是靠流程兜底。有一次一个同事提交的数据库技能漏了status: failed分支CI 质量分直接从 92 掉到 70他被迫在提交前补齐了测试。过程中会有一点摩擦但长期看一个带质检流程的技能库让团队所有人都能安全地往库里加东西又不必担心弄坏别人的任务链路。5.3 后续扩展方向组合编排、技能推荐与跨 Agent 复用技能做多了自然会产生组合需求。一个生成销售周报的任务可能要调用analyze_csv、extract_web_content和一个制图技能。我不想让用户手动编排所以在考虑引入轻量的工作流定义把任务拆成有序的技能调用序列类似 YAML 里声明步骤。这样一来Agent 面对复合任务时先选一个工作流模板再按模板逐步调用技能比让它现场自由组合要稳定得多。另一个方向是技能推荐记录 Agent 在历史任务中的技能使用序列下次遇到相似请求时直接优先推荐热门路径。这个思路和推荐系统很像核心是积累真实的使用反馈。目前我还在收集数据阶段但每周都能看到技能使用次数的分布变化这个数据本身就能指导我下一步该优化哪个技能。跨 Agent 复用是我觉得最有价值的方向。我同时维护着两个 Agent一个偏数据分析一个偏内容写作。原来它们是两套互相独立的工具集后来我学聪明了把底层技能彻底打通所有 Agent 共享同一个技能库只在配置层给不同 Agent 绑定不同技能子集。这个改造一做完新增技能的成本从写两套方案降到写一套、两边受益。agent-skills 这个项目走到现在我最大的体会是技能质量的判断标准从来不是实现了多少功能而是 Agent 在真实场景下第一次就选对、执行稳、失败可解释。我建议别一开始就幻想把技能库铺得又大又全先挑一个自己每天都在重复的高频场景做出一个高质量技能把选技能、执行、验证的闭环跑通再慢慢扩充。最后再分享一个小技巧给每个技能内置一个--dry-run模式让 Agent 先展示完整执行计划再真正跑一遍调试时看着它一步步决策很多隐蔽问题都会在这个环节自己暴露出来。