
最近在做agent-skills这个项目的时候我一直被一个问题困扰为什么同一个大模型在聊天场景下回答得头头是道一旦让它去实际操作软件、调用接口、处理文件就各种失灵后来我意识到问题不在模型本身而在于我们只给了模型“知识”却没给它“技能”。agent-skills要解决的正是这件事——把一项项具体操作封装成 Agent 可以理解和调用的技能单元然后像工具箱一样挂在智能体身上。这篇文章不聊虚的就讲清楚这个项目里技能设计、路由、构建、集成和踩坑的完整链路适合那些已经在做 Agent 应用、但觉得现有框架“不够顺手”的人参考。1. 为什么需要 Agent Skills从“知识”到“能力”的断层1.1 模型最缺的不是逻辑是可执行动作很多团队在落地智能体时会发现让模型写一段 SQL 很容易但让它自动连上数据库、执行查询、把结果整理成报表就很别扭。原因在于大模型本质上是一个“文本预测器”它擅长生成看起来合理的文本但真正要操作外部系统需要精确的 API 参数、认证方式、异常处理。这些东西没法靠提示词凭空生成只能通过预先定义好的技能注入。我最早做 Agent 时也走过弯路把大量操作逻辑塞进 system prompt让模型“自己看着办”。结果是简单任务还能应付一旦任务链超过两步模型就开始自创函数名、编造参数。后来我把这些操作全部拆成agent-skills里的技能模块每个模块都有明确的输入输出和运行环境模型的任务从“实现操作”降级成“选择并调用操作”成功率一下子拉起来了。1.2 技能不是 Prompt 模板而是可执行行为单元很多人会把技能理解成一套话术模板比如“当用户说订机票时请调用订票API”。这是很大的误解。Prompt 模板解决的是“怎么说话”技能解决的是“怎么做事”。一个合格的 Agent 技能至少包含三部分自然语言描述、执行代码、参数约束。描述让模型知道这个技能管什么代码让技能真正落地参数约束则保证模型不会把异常输入传进来。打个不严谨的比方模型就像一个实习生Prompt 是岗位说明书技能则是他手里的一套标准化作业清单。没有清单实习生只能凭感觉发挥有了清单他即使遇到没见过的情况也能按步骤走完大部分流程。agent-skills的核心工作就是替 Agent 把这些清单编好、编细、编到可以直接执行。1.3 从单体 Agent 到技能复用能力的“搭积木”思维另一个推动我搞agent-skills的现实问题是复用性。之前我给 A 客户做了“网页内容抓取”的功能给 B 客户做“PDF 信息提取”代码逻辑高度重复但因为耦合在各自的业务流里完全没办法直接搬过去用。技能化之后抓网页、读 PDF、查数据库这些动作都成了独立技能新的 Agent 只要声明“我需要哪些技能”就能像搭积木一样组合出新的能力。这也是为什么现在各大 Agent 框架都在推 Skills 概念的原因——它不是花架子而是规模化做 Agent 应用的必经之路。2. agent-skills 项目核心设计技能如何被表达和路由2.1 技能描述 Schema给模型看的“使用说明书”要让 Agent 正确选技能必须用模型容易理解的语言写技能元信息。我在agent-skills里做了这样的 Schema{ skill_name: fetch_web_page, description: 抓取指定URL的网页内容并提取正文文本。适用于读取公开网页、文章、新闻页面。, input_schema: { type: object, properties: { url: { type: string, description: 需要抓取的完整网页地址必须包含协议头如 https://example.com/article }, max_chars: { type: integer, description: 返回正文的最大字符数默认10000避免内容过长, default: 10000 } }, required: [url] }, executor: python, timeout_seconds: 30, allowed_environments: [sandbox, local] }这里最关键的是description字段。它不写“可以抓网页”这种笼统话而是写清楚适用场景、边界条件、常见坑。比如我会补一句“如果页面是 JS 动态渲染的此技能可能返回空请使用 fetch_web_page_rendered 技能”这样模型在选技能时能少犯错。input_schema是给模型看的参数规范也是给执行器的数据契约两者必须完全对应。2.2 发现与匹配让 Agent 知道“我有哪些工具可用”技能多了之后最大的问题不是写技能而是做路由。如果一次对话把 50 个技能全部塞进上下文模型的注意力会被稀释反而不知道该用哪个。我在项目里做了两层处理。第一层是技能索引。每个技能注册时会根据description生成向量索引当任务进来时先用 embedding 检索 Top 5 候选技能只把这 5 个候选的技能描述放入模型上下文。这一步能大幅压减 token 占用同时提升选择准确率。第二层是模型决策。系统会把候选技能描述和当前任务一起交给大模型让它用 JSON 格式输出“打算调用的技能名 参数”。如果模型认为所有技能都不合适它可以输出空我不强制它硬调。实测下来这种“先检索后决策”的路由方式比把全部技能堆给模型要稳定得多。2.3 执行器设计为什么用 Python 而不是 JSON有的 Agent 框架把技能定义成纯 JSON 配置执行时让模型自己生成代码。我试过这种方案效果很不稳定。模型生成的代码经常少 import、漏异常处理甚至写出不存在的方法。agent-skills的执行器方案是每个技能对应一个 Python 函数函数内部逻辑由人写死模型只负责填参数。这样就把“模型自由的创造力”限制在安全范围内执行逻辑保持确定性。下面是我项目里的技能函数示例# skills/web_fetch.py import requests from bs4 import BeautifulSoup def fetch_web_page(url: str, max_chars: int 10000) - str: headers {User-Agent: Mozilla/5.0} try: resp requests.get(url, headersheaders, timeout15) resp.raise_for_status() except Exception as e: return f抓取失败: {str(e)} soup BeautifulSoup(resp.text, html.parser) for tag in soup([script, style, nav, footer]): tag.decompose() text soup.get_text(separator\n, stripTrue) if len(text) max_chars: text text[:max_chars] ... return text模型的职责就是判断“用户想做的是不是抓网页”然后把符合规范的url传进来。至于页面能不能抓到、要不要处理反爬那是函数内部的事模型不需要也不应该操心。3. 从零构建技能库的实操步骤3.1 拆解业务动作技能的粒度怎么定建技能库最容易踩的坑是“粒度过粗”或“粒度过细”。粒度过粗比如“处理所有文件操作”模型根本不知道该怎么传参粒度过细比如“读取 txt 文件第一行”“读取 txt 文件第二行”技能数量爆炸检索成本上升模型也容易选错。我在项目中总结的判断标准是一个技能应该对应一个完整的用户意图。比如“读取 pdf 内容”“将文本写入 txt 文件”“对列表去重”都是合理粒度而“字符串转大写”“字符串转小写”这种原子操作除非是给开发人员专用 Agent 用否则没有必要单独做技能。因为它们太底层模型完全可以自己在代码里完成用技能反而浪费上下文。3.2 编写技能清单、参数约束与验证逻辑每个技能模块我固定分为四个文件放在同一目录下skill.md写给人看的设计文档说明技能背景、适用场景、不适用场景。schema.json机器可读的技能描述也就是上面那个 Schema。execute.py实际的 Python 实现。tests.py验证脚本包含至少 5 个典型输入用例和 2 个边界用例。参数约束里最容易忽略的是description内的约束说明。比如一个“发送邮件”技能收件人字段必须说明格式是支持逗号分隔还是必须传数组。如果不说清楚模型就会用自己的理解猜猜错的概率很高。我在 schema 里会写类似“recipients: 必填数组格式每一项是一个完整邮箱地址”这种话。3.3 注册进项目并跑通端到端调用写完技能函数后不是放进去就完了。我在agent-skills里做了一个注册机制启动时会扫描skills目录下的所有子文件夹读取schema.json将其注册进技能路由表。伪代码如下# registry.py import importlib, json, pathlib def discover_skills(skills_dirskills): registry {} for path in pathlib.Path(skills_dir).iterdir(): if not path.is_dir(): continue schema_path path / schema.json if not schema_path.exists(): continue schema json.loads(schema_path.read_text()) module importlib.import_module(f{skills_dir}.{path.name}.execute) registry[schema[skill_name]] { schema: schema, func: module } return registry注册之后我会先用一段固定测试文本跑通整体链路输入一个模拟任务比如“请抓取 https://example.com 的正文内容”看 Agent 能否检索到fetch_web_page、给出正确参数、执行并返回结果。这一步通过后才算真正的“技能可用”。3.4 给技能做一个“考官”自动化回归测试技能库会随着业务增长不断新增老技能很可能被新技能影响。比如两个技能都支持“获取网页内容”但描述类似模型可能路由错。所以我在项目里加入了一个“考官”脚本对每个技能预置若干条用户话术每次技能库更新后自动跑一遍全量话术检查模型的技能选择准确率低于 90% 就报警。测试用例不只是正向的还必须包含“不该调用”的用例。比如用户问“今天天气怎么样”技能库里有“按城市查询天气”但没有输入城市模型应该追问而不是乱传一个城市参数。这类“负样本”特别重要能有效防止模型乱用技能。4. 集成到主流 Agent 运行时接口、上下文与权限控制4.1 技能与模型上下文的预算博弈集成到 Agent 运行时时最现实的问题是“塞不下”。一个技能描述平均 200 到 300 token如果一次让模型看到 10 个技能就是 3000 token再加上对话历史和工具返回结果很容易把 8K 上下文窗口撑爆。我用两个办法缓解一是技能检索后只保留 Top 3 候选缩小上下文二是把技能描述压缩成“一句话摘要 可展开详情”的形式模型需要深入了解时再通过“查看技能详情”动作获取完整信息。这套机制有点像一个搜索引擎摘要页放最核心的信息用户感兴趣了再点进详情页。目前我用普通 32K 窗口的模型跑agent-skills日常能稳定控制在 12K token 以内。4.2 权限白名单与危险操作熔断技能一旦能执行真实操作权限就必须认真对待。我在agent-skills里给每个技能打了一个权限标签比如“无副作用”“只读操作”“可写操作”“高风险操作”。执行前Agent 编排层会检查当前环境是否允许该权限。规则如下权限级别示例默认允许L0 只读读取网页、读文件、检索数据库 SELECT沙箱内允许L1 本机写入写临时文件、更新内存变量沙箱内允许L2 外部影响发送邮件、发布评论、写入生产数据库需显式授权L3 危险操作删除文件、执行 shell 命令默认拒绝比如用户在端侧配置了“禁止所有 L2 及以上操作”那么即便是 Agent 完成了邮件内容撰写发送动作也会被熔断返回给用户一条提示信息等用户手动确认后再执行。这个设计虽然看起来简单但真的能拦住很多事故。4.3 从单 Agent 到多 Agent 技能复用技能化的另一个好处是天然支持多 Agent 协作。agent-skills里技能是全局注册的任何子 Agent 都能按需加载。我给一个客服场景搭了三个子 Agent一个负责查订单一个负责生成回复一个负责执行拦截操作。它们共享同一套技能库但每个子 Agent 只加载自己需要的技能描述。这样既避免了重复开发又防止了权限越界。这里要特别提醒多 Agent 场景下技能调用最好加上“调用来源”字段方便追踪是哪个 Agent 触发了该技能。在日志里打上 agent_id排查问题的时候能省下一半时间。5. 我在 agent-skills 项目上踩过的坑与优化细节5.1 技能描述太长模型反而不敢用第一次写技能描述时时追求“详尽”把各种边界条件全写进去一段描述 800 字。结果模型在决策时经常犹豫甚至直接回复“无法确认调用哪个技能”。后来我把描述精炼成“功能一句话 常用场景 禁忌”长度控制在 150 字以内路由准确率显著回升。说明模型不是读得越多越好核心信息要放在最前面。比如同样是“发送企业微信消息”技能我最终定稿的描述是发送普通文本消息到指定的企业微信群。适用于向群内推送通知、告警、日报。不要在用户未授权的情况下群发如果消息需要 所有人请先与用户确认。简短、直白模型一眼就知道该不该用。5.2 并发调用时共享状态的坑我一开始用模块级全局变量保存技能执行状态比如“当前用户的上一次搜索结果”。后来多个请求并发进来状态互相覆盖出现了严重的串数据问题。修复方案是所有技能执行器改为无状态纯函数需要暂存数据时显式传入context对象请求结束后释放。如果确实需要跨技能传递数据比如先查订单再发通知那么把临时结果放到context.setdefault(order_no, ...)里而不是写进全局变量。这一点在异步高并发场景尤其重要我因为这个 bug 还被测试同事追着改了两版。5.3 让模型学会“拒绝调用”而不是强行使用很多 Agent 框架默认“模型选了技能就必须执行”这会让模型为了避免失败而瞎选。我在agent-skills的编排逻辑里给了模型一个出口输出skill_namenull并附带 reason。这样当用户问的是纯知识问题或者当前技能库确实没有合适能力时模型可以直接拒绝。从数据看加入了“可拒绝”选项后技能调用的整体准确率反而提升了因为不必要的误调用变少了。我还进一步要求如果模型决定不调用技能它必须以普通聊天的方式继续回答用户。这样就解决了“一问就失灵”“只会接技能不会聊天”的尴尬局面。5.4 实测对照有技能库的 Agent 成功率差异为了验证agent-skills的实际价值我在一个内部知识库问答 报表生成的场景里做了对照实验。同样使用同一款基础模型一组用纯 Prompt 让模型自己调用 API另一组接入技能库。任务包括查询最近 7 天订单数、按渠道汇总销售额、解析上传的 Excel 并生成汇总表。每组各跑 100 次结果如下任务类型纯 Prompt 成功率接入 agent-skills 成功率查询订单数61%94%按渠道汇总48%89%解析 Excel 生成汇总23%86%纯 Prompt 组失败的原因分散在“伪造参数”“SQL 语法错误”“Excel 解析字段不对”等技能库组失败则主要集中在外围异常比如超时、网络波动。这说明把能力沉淀为技能确实是把 Agent 从“会聊天”推向“会干活”的关键环节。5.5 技能版本管理老版本技能的兼容策略最后提一个容易被忽略的细节技能也会迭代。让我比较头疼的是新技能往往不兼容旧参数。后来我在 Schema 里加了一个deprecated_since字段老版本的技能不会被立即删除而是标记为“已废弃”但依然可以调用。模型路由时会优先选择未废弃版本如果因为业务需要必须使用旧版可以在技能描述里明确标注“仅当用户要求使用旧格式时使用”。这个平滑迁移策略让我不用频繁中断线上服务也给了技能使用者适应的时间。根据自己的实际体验agent-skills这类技能化改造最值得投入的阶段是当你发现 Agent 的聪明只体现在“说”而不是“做”的时候。技能库本身不神秘难的是克制地设计技能边界、严谨地约束输入输出、耐心地做回归测试。做完这些事情之后你再回头看 Agent会发现它终于从一个“嘴强王者”变成了一个“靠谱执行者”。如果这篇文章能帮你少走几个弯路那就值了。