☰
AI Agent 如何告别笨拙?agent-skills 技能库实战解析
2026/10/7 1:44:45 网站建设 项目流程

说句实在话,做 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, timeout=10, 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", strip=True) 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", required=True) parser.add_argument("--max_length", type=int, default=5000) args = parser.parse_args() try: print(json.dumps(extract(args.url, args.max_length), ensure_ascii=False)) except Exception as e: print(json.dumps({"status": "failed", "error": str(e)}, ensure_ascii=False))

第二个是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_k=5): 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 不知道它失败了。早期我的技能经常静默返回空结果或者一段程序 traceback,Agent 会拿这个当正常输出继续往下编,最后给用户一个全然错误的答案。这比"报错"更糟糕,因为用户根本不知道哪里出了问题。

我现在要求每个技能都必须输出结构化结果:成功时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 先展示完整执行计划再真正跑一遍,调试时看着它一步步决策,很多隐蔽问题都会在这个环节自己暴露出来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询