
1. 先聊聊 agent-skills 究竟在解决什么问题1.1 从“只会聊天”到“真正干活”的距离如果你最近半年一直在折腾大模型应用大概率会遇到一个尴尬场景模型聊得头头是道但一让它“把订单导成 Excel 发到邮箱”“抓取这个网页里的表格”“把本地日志按错误码聚一下”它就开始胡编乱造。原因不复杂——大模型本质上是个“文本接龙器”它擅长生成看起来合理的内容但并不会真的调用 API、读写文件、执行命令。这就是 agent 概念兴起的直接原因给模型装上“手脚”让它能调用外部工具、观察返回结果、再决定下一步动作。而 agent-skills 这种项目就是把这些“手脚”做成标准化的、可插拔的、能被模型自主调用的技能模块。你可以把它理解成给菜谱配了标准化的“备菜包”——不需要每次从切葱开始而是直接拿现成的料下锅。我见过很多团队第一个 agent 都是临时拼的一个巨大的 if-else 函数里面堆了十几个工具的调用逻辑模型选中哪个就执行哪个。这个模式跑通 demo 没问题但一旦技能数量超过二十个、参数开始嵌套、返回结果需要二次处理代码就会变成一团乱麻。agent-skills 的核心价值不是帮你写一个工具而是帮你形成一套“技能治理”的方法论每个技能有自己的名字、描述、入参定义、执行函数、错误处理甚至版本记录让 agent 的能力可以像积木一样持续叠加。1.2 skills、tools、MCP 这些概念到底差在哪这大概是新手最容易懵的地方。OpenAI 讲 function callingAnthropic 讲 tool use社区里又冒出 MCPModel Context Protocol现在还有 agent-skills 这种叫法。它们到底什么关系我的理解是tools 是“一个可调用的函数”skills 是“一组围绕某个能力的完整封装”MCP 是“让不同模型都能调用同一套工具的传输协议”。打个比方tools 是一个电源插座skills 是一整套接入插座的家电可能内部还包含多个插座、传感器和控制逻辑MCP 则是统一了插座规格的国家标准。也就是说skills 通常构建在 tools 之上可以包含多个底层工具调用、中间状态、结果加工甚至能调用其他 skill。所以当你看到一个叫 agent-skills 的仓库时并不需要把它理解成又一个“工具合集”。它更像一个技能管理框架定义技能的元数据规范、加载方式、执行上下文、错误协议再配上若干预置技能。方向和“技能市场”“技能编排”是同一个赛道只是粒度更细、更落地。1.3 为什么需要一个独立的 skills 仓库一种常见反问是我直接把技能函数写在 agent 代码里不行吗行但你很快就会遇到三个很现实的问题。第一技能数量增长后系统提示词会失控。每个技能都要有一段描述告诉模型“什么时候该用它”20 个技能的描述加起来可能上千字挤占宝贵的上下文窗口模型反而容易选错。第二技能之间会出现依赖关系。比如“查天气”这个技能可能依赖“获取用户位置”这个技能“导出报表”依赖“查询数据库”和“发邮件”两个能力。没有统一框架时这些依赖关系只能靠硬编码新加一个技能就要改一堆老代码。第三技能很难跨项目复用。这周给内部客服机器人写的“工单查询”下周做数据分析助手时可能也想用但因为耦合在项目里只能复制粘贴再改一堆变量名。把技能独立成库、按统一规范注册才能做到一套技能多处使用。所以 agent-skills 这个标题本质上谈的是一套“可复用智能体技能库”的建设和维护经验下面我按实际搭建的顺序展开。2. 核心设计一个可复用的技能库应该长什么样2.1 技能描述与元数据设计一切技能设计的起点是元数据。我见过有人直接在一个 Python 文件里定义def search_web(query):然后让模型闭眼猜这个函数是干嘛的结果模型经常在“搜索网页”和“搜索数据库”之间犯迷糊。想让 agent 正确选技能你必须给模型讲清楚三件事这个技能解决什么问题、在什么场景下优先用、输入输出大概长什么样。我自己习惯用一个 dataclass 定义技能的元数据核心字段包括字段作用示例值name技能唯一标识模型看到的就是它fetch_hn_top_storiesdescription人话描述说明用途和适用场景获取 Hacker News 当前 Top 30 的标题与链接适合做资讯汇总parameters声明入参的 schema类似 JSON Schema{ limit: { type: integer, default: 30 } }returns返回结构说明方便模型理解结果list[dict]每条含 title、url、scoreversion技能版本1.2.0tags分类标签用于按需加载news,fetch这里特别提醒一点description千万别写得含糊。模型不会像人一样“猜”你的意图它是靠描述做语义匹配的。你可以把 description 理解为给模型看的“使用说明书摘要”写得太短模型宁可瞎编也不调用写得太长上下文又扛不住。我通常控制在两句话以内第一句讲功能第二句讲典型使用场景。参数定义同样关键。早期版本我用 Python 原生类型注解def fetch_news(limit: int)后来发现没写minimum、maximum、enum这些约束时模型真的会传负数、传任意字符串。现在我全部改用 JSON Schema 风格的声明每个字段都给类型、必填性、默认值和范围约束。2.2 技能装载与执行器的核心逻辑元数据定义好了接下来要解决“怎么把技能变成可被调用的程序”。这里的核心思路是注册表模式启动时扫一遍技能目录每个技能把自己的元数据和执行函数注册到一个统一字典里模型需要调用某个技能时执行器从字典里取出对应函数完成参数注入并调用。我实际用下来最轻量的实现是每个技能单独一个 Python 模块里面固定导出两个对象METADATA和execute。# skills/fetch_hn.py METADATA { name: fetch_hn_top_stories, description: 获取 Hacker News 当前 Top 30 的标题与链接适合做资讯汇总, parameters: { type: object, properties: { limit: {type: integer, minimum: 1, maximum: 50, default: 30} }, required: [limit] }, returns: list[dict]每条含 title、url、score } def execute(params: dict, context: dict) - dict: limit params.get(limit, 30) # 实际抓取逻辑... return {items: [...]}这里有一个很容易被忽略的问题执行函数的签名千万不能设计成各写各的。我见过一个项目有人写execute(url)有人写run(params, ctx)还有人直接把函数挂在类里最后加载器里全是if分支项目直接翻车。统一约定execute(params: dict, context: dict)是更稳妥的做法params是模型给参数的最终结果context是执行环境注入的全局对象比如 API Key、数据库连接、文件句柄。2.3 让技能可组合的安全边界单技能是基础技能组合是进阶。所谓组合就是一个技能的执行结果可以作为另一个技能的入参或者一个技能内部主动调用其他技能。这很诱人但也容易失控。我推荐的做法分两层第一层是“顺序管道”定义一个工作流配置指定技能 A 的输出字段映射到技能 B 的入参字段。第二层是“树状编排”允许技能内部通过 context 里暴露的call_skill接口调用其他技能但必须限制调用深度防止出现 A 调 B、B 调 C、C 又调 A 的循环。安全边界同样要紧。技能要能访问外部资源但绝不能无限访问。我给每个技能配置了三类权限网络权限允许访问哪些域名、文件权限允许读写哪些目录、系统权限是否允许执行子进程。这些配置统一放在技能的permissions字段里执行器在真正调用前做一次校验。否则等技能库扩大到几十个你根本不知道哪个技能埋了个“雷”。3. 实操从零搭建一个轻量 agent-skills 库3.1 目录结构与最小实现纸上谈兵没意思直接动手。我建议技能库的目录结构长这样agent-skills/ ├── skills/ │ ├── __init__.py │ ├── fetch_hn.py │ ├── fetch_weather.py │ └── send_email.py ├── loader.py ├── executor.py ├── registry.py └── examples/ └── demo_agent.pyloader.py负责扫描skills/目录用importlib动态加载每个模块读取METADATA和execute然后注册进一个全局字典。这个过程最需要注意的是命名冲突两个技能模块如果同名后加载的会覆盖先加载的。我的做法是在模块前加前缀或者直接按“目录名.模块名”注册从根上避免碰撞。注册表本身很简单甚至可以就是一个全局字典加几个辅助方法# registry.py _REGISTRY {} def register(metadata: dict, func): name metadata[name] if name in _REGISTRY: raise ValueError(fduplicated skill: {name}) _REGISTRY[name] {metadata: metadata, func: func} def list_skills(): return {name: info[metadata] for name, info in _REGISTRY.items()} def get_skill(name): return _REGISTRY[name]启动时执行一遍scan_and_register(skills/)所有技能就位。调试时可以直接在交互式环境里调list_skills()看看到底加载了哪些东西比一个个 import 清晰得多。3.2 注册、选择与执行一条完整链路技能库本身不依赖任何大模型它只做“执行”。完整的链路是这样的第一步agent 根据用户问题生成一份“技能调用请求”格式可以简单定义为{name: fetch_hn_top_stories, arguments: {limit: 10}}第二步executor检查技能是否存在第三步校验参数是否符合元数据里的 schema第四步注入 context第五步真正执行函数并返回结果第六步把执行结果以结构化格式塞回给模型让模型继续判断下一步动作。这里有一个细节值得强调参数校验一定放在执行之前。我有一次调的技能是“发送邮件”模型生成的to字段变成了一个包含三个地址的数组而函数只按字符串处理结果直接把数组转成字符串发给了一个不存在的地址。后来我引入jsonschema校验参数类型和必填项不对就拦截宁可让模型重新生成参数也不要带病执行。执行器里我还会加一个超时控制。有些技能是网络请求可能卡很久而模型在等结果时整个 agent 流程都被阻塞。我用concurrent.futures给每个技能执行包了一层超时默认 15 秒超时直接返回“技能执行超时”的固定错误让模型决定是重试还是换方案。3.3 给技能加参数校验与错误兜底很多初学者写了技能函数就直接用完全没有兜底。但技能一旦交给模型调用你就要做好“模型会传任何东西”的心理准备。我的兜底分三层。第一层是 schema 校验上面已经说了主要卡类型、必填项、枚举值、数值范围。第二层是函数内部的防御性编程网络请求加 try/except文件读写前判断路径是否存在数据库查询后检查结果是否为空。第三层是统一的异常封装任何异常都要转成一个标准化的错误返回体比如{error: {code: SKILL_TIMEOUT, message: ...}}这样模型才能理解发生了什么并依据错误信息调整下一步动作。我见过最差的做法是技能内部直接raise Exception然后 executor 没有捕获整个 agent 进程崩溃。模型根本没机会看到错误信息用户只看到一个“系统错误”。所以建议在 executor 的最外层捕获所有异常把 traceback 塞进日志把精简错误信息返回给模型。这也是排查线上问题的关键——你把原始栈信息存日志把人话版本给模型两边都不耽误。4. 集成大模型时的关键细节4.1 把技能描述压进系统提示词的正确姿势技能库建好了怎么让模型知道有哪些技能可用最直接的做法是把每个技能的元数据转成文本拼进系统提示词。但直接全部塞进去会出事。我测过当技能描述超过 3000 字后模型开始频繁选错技能明明用户问天气它却调了“获取新闻”。原因可能是上下文太长稀释了关键信息也可能是前面技能描述里的关键词干扰了语义匹配。后来我改了策略把技能列表分成“全局常用”和“按需加载”两类系统提示词里只放 5 到 8 个高频技能其余技能根据用户问题先做一个初步意图分类只把候选技能的描述动态注入。还有个技巧每个技能的 description 首句务必以“动词名词”开头比如“搜索网络”“发送邮件”“计算数值”。实测下来这种祈使句结构比“该功能可以用于……”这种描述更容易被模型匹配相当于给模型做了关键词归一化处理。4.2 避免上下文爆炸技能摘要与按需加载上下文爆炸是 agent 应用最容易忽视的问题。每轮多轮对话历史消息会把上下文越撑越大如果再叠加 20 个技能的 description不到几轮就开始丢信息。我的做法是引入“技能摘要层”每个技能除了完整描述再写一个 30 字以内的“一句话摘要”。系统提示词里只放摘要模型决定要调用哪个技能后再把完整描述以 system 消息的形式追加进上下文这样既保证了模型有足够的决策信息又控制了每一轮的 token 成本。按需加载还可以做得更细。比如用户问题提到“天气”“下雨”“温度”就先把天气相关技能的完整描述加载进来如果用户明确提到“邮件”“发送”才把邮件技能的描述加进来。这本质上是把意图识别前置虽然会多一些规则逻辑但效果非常显著——实测同一轮对话的 token 消耗能降低一半以上。4.3 调用结果回填与多轮状态管理技能执行完拿到结果只是第一步关键问题是如何把结果回填给模型。如果你直接把原始返回值塞进消息模型很可能理解不了因为返回结构是给程序看的不是给人看的。我的习惯是让每个技能自己负责“结果转述”。也就是说execute返回的 dict 里除了原始数据还包含一个summary字段专门用于给模型看的文本摘要。比如抓取新闻的 skillsummary可以是这样“已获取 Hacker News Top 30其中 score 最高的是《...》共 450 分”。模型看到这句话就明白可以从这里继续往下聊了。多轮状态管理同样重要。有些 agent 场景需要把上一轮技能的结果传给下一轮使用比如先查天气再规划行程。我把 context 设计成可变的“共享状态对象”每个技能可以读写 context 里指定的字段。但这里要管住手只允许技能声明自己需要读写的 key执行时通过 context 的get(key)和set(key, value)访问不能直接操作整个 context否则两个技能同时写同一个 key 就会互相污染。5. 实操中踩过的坑和排查思路5.1 技能列表太长导致模型“选择困难”这是一个非常典型的性能陷阱。当技能数量超过 20 个后模型选择错误率明显上升而且这种错误特别隐蔽——它不报错而是选了一个“看起来相关但实际不对”的技能。比如用户问“上海的天气”模型可能选了“获取城市信息”而不是“获取天气”。排查思路分三步先看系统提示词里的技能描述是不是超过 1500 字超了就按 4.2 的方式做摘要化再看相似技能之间的描述是否区分度不够比如“发送邮件”和“发送消息”的 description 如果都写“发送通知给用户”模型自然分不清要给每个技能补充专属场景词最后看是否缺少“退路技能”也就是当模型无法匹配任何技能时应该返回“未找到合适的技能”而不是硬挑一个。加一个fallback技能后错误调用率能降一大截。5.2 参数解析失败与类型漂移模型调用技能时参数类型漂移是高频问题。明明 schema 里定义age是 integer模型可能传一个twenty也可能传20.0。纯靠jsonschema校验会发现类型不匹配然后拒绝但如果稍微宽松一点允许字符串数字20转为整数 20体验会好很多。我实现了一个coerce_params函数先严格校验失败后尝试类型转换转换失败再拒绝执行并返回错误。这个策略把参数通过的次数从七成提升到了九成以上。还要注意嵌套参数的解析。有些技能需要传一个对象数组比如发送邮件的收件人列表。模型生成复杂嵌套结构时容易出错尤其是少一个逗号或者括号不匹配。我的建议是让技能接收扁平字符串再在技能内部用json.loads解析解析失败就返回明确错误。宁可让模型多生成一次参数也不要在异构结构上反复试探。5.3 技能并发与脏状态当 agent 同时处理多个任务时技能可能会被并发调用。如果你的技能库里有“写文件”或“改数据库”这类有副作用的操作并发就是灾难。我第一次踩到是在一个批量发邮件的场景两个任务同时调用同一个邮件技能结果是收件人列表交叉了客户投诉时才暴露出来。解决办法分几种无副作用的技能查天气、查新闻可以放心并发有副作用的技能要么加锁串行执行要么要求调用方传一个请求 ID技能内部用这个 ID 做幂等。我更推荐后者因为锁的粒度不好控制而幂等才是分布式场景的正解——每个请求唯一 ID技能执行前先查是否已经执行过执行后记录结果重复请求直接返回旧结果。5.4 日志与可观测性设计技能库一旦跑起来日志就是救命稻草。我给每个技能加了三段式日志调用前记录入参执行后记录耗时和输出摘要异常时记录完整 traceback。别觉得麻烦这些日志在你调试“为什么模型选了错误技能”时会派上大用场——你一看日志发现模型连续三次都在调用同一个技能而实际需要的技能从没被选过问题定位就快多了。另外强烈建议给每次技能调用生成一个 trace ID贯穿整个 agent 的决策循环。这样你就能在日志里看到用户问题 - 模型选择技能 A - 执行结果 - 模型根据结果继续选择技能 B - …… 整条链路一目了然。我后来还加了一层采样统计统计每个技能被调用的次数和失败率一方面用来发现设计糟糕的技能另一方面也可以顺势把高频技能优化成更直接的服务。6. 关于技能库演进的一些个人体会做 agent-skills 这类项目我最大的感触是技能实现本身往往不难难的是对整个“技能生命周期”的管理——从定义、注册、描述优化、参数约束到观测和性能调优每个环节都有很多细碎的坑。这不是一个一蹴而就的项目它是随着 agent 应用场景扩展而持续迭代的工程。如果你现在正打算动手我的建议很简单先别急着上复杂的编排框架就从 registry loader executor 这个最小闭环开始写上三四个真实场景的技能跑通一遍。等你真的在日志里看到模型因为描述不清而选错技能等你真的在线上遇到并发污染你自然就知道下一步该加什么了。技能库不是写得越多越好而是要让每个技能都经得起“模型会误用、参数会漂移、服务会超时”这三重考验。最后再分享一个小技巧每隔一段时间把技能列表打印出来站在一个从零开始的新人视角重新读一遍每个技能的 description。你大概率会发现有些描述里藏着你当时知道但现在已经忘了的上下文而这些隐藏假设正是模型选错技能的根源。把这个习惯坚持下去你的 agent-skills 会越用越顺手。