ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agent技能化实战:从散装工具到可复用技能库的完整指南

Agent技能化实战:从散装工具到可复用技能库的完整指南 最近好几个做 Agent 应用的朋友都在问同一个问题工具也接了流程也搭了为什么 Agent 用起来还是像一堆散装函数我猜他们真正缺的不是更多 API而是一套 agent-skills 的思路——把能力封装成可被模型按需调用的标准技能单元。这篇就聊聊为什么技能化能解决问题、技能到底怎么定义、技能库怎么分层、落地时有哪些细节最后把我踩过的坑也一并交代。适合正在做 Agent 原型的开发者、想给团队沉淀 AI 能力的架构师也适合对 Prompt 工程和工具调用机制好奇的爱好者。先说结论如果你把 Agent 当成一个不断变强的实习生技能就是它的岗位说明书说明书不清晰实习生的能力再强也容易把事情搞砸。1. 先搞清 agent-skills 解决的是哪一类问题1.1 一个失控的 Agent 调用现场先还原一个真实场景。项目里有个语音助手最初只挂了三个工具查天气、设提醒、开灯。前两周挺正常后面需求多了加了查交通、订咖啡、播音乐、找文件、看股票等等工具从 3 个变成 40 个。问题开始出现用户说“明天早上九点提醒我带合同”时模型一会儿调提醒工具一会儿调日程工具用户说“帮我找个文档”时模型会连着把搜索、打开、分享三个动作一起做了尽管我们只希望它先搜索再确认。调试的时候要同时看几百行工具定义改一个返回值还可能影响另一个工具的语义。这不是模型变笨了而是我们的能力组织方式跟不上一堆平铺的函数定义没有边界、没有层次、没有“什么时候该用”的说明书。当时我做的事很简单把所有工具按职责重新分组给每组写清楚适用场景和触发条件然后让模型先经过一个“路由层”再决定交给谁。效果立刻不一样误调用少了响应也稳了。这个实践后来慢慢长成了我理解中的 agent-skills不是把 Agent 能力做成一个巨大的工具列表而是做成一套可以独立定义、独立测试、按需加载的技能集合。1.2 技能、工具、插件到底差在哪很多团队分不清三者的边界我用自己的话定义一下。工具最底层的能力原子负责一个确定的操作比如“查询天气 API”“写入数据库”。它不关心怎么被调用只关心入参出参是否符合约定。插件围绕某个外部系统的一组工具集合比如 IM 系统插件包含发送消息、建群、拉人。它解决的是“某个系统的接入问题”。技能面向任务的能力封装强调“什么场景下选我”以及“选了我之后完成什么目标”。一个技能可以只用一个工具也可以编排多个工具甚至外部流程。用生活类比工具是厨房里的锅碗瓢盆插件是整套厨具的包装箱技能则是一道菜的完整做法——不仅包括用哪口锅还包括什么时候开火、什么时候关火、成品长什么样才算合格。Agent 如果只会盲选厨具做出来的菜大概率是黑暗料理如果你给它菜谱它才可能稳定复现。维度函数/工具插件Agent Skill关注点单个动作系统集成任务完成粒度细中粗模型需要理解什么参数接口和权限触发条件、目标、边界可测试性容易中等需要完整场景测试复用范围代码级系统级跨项目跨场景这个差别解释了为什么很多 Agent 项目工具数量一多就失控模型需要从几十个平铺的接口里猜“现在应该做什么”而不是在一个明确的任务空间里做选择。agent-skills 的核心就是把“猜”改成“查说明书”。技能描述做得越好模型选择越稳定。这也是为什么我会反复和团队强调写技能描述不是在写注释而是在给模型做决策支持。2. 给技能下定义一次完整的 Skill 拆解过程2.1 技能的最小单元能力描述、参数契约、执行体要落地一套技能我最常用的拆分方式是三个部分能力描述、参数契约、执行体。这三者缺一个技能在真实场景里就会出问题。能力描述是模型决定“要不要选我”的依据也是技能里最容易被低估的部分。参数契约是技能与其他代码交互的边界必须能校验、能报错、能兜底。执行体则是实际跑的逻辑可以是脚本、函数、API 调用甚至一个短暂的人工确认流程。不要把所有逻辑塞进描述里也不要指望模型理解执行体的内部实现它只需要知道三件事我什么时候该用你我需要准备哪些信息你做完会给我什么。以我自己项目里的“会议纪要生成”技能为例。能力描述只有两行“当用户提供会议录音或文字稿希望生成结构化纪要时使用。不适用于实时转写。”参数契约包含 source_type、content、meeting_title 这三个字段其中 source_type 只能是 audio/transcript/text。执行体是一个 Python 脚本负责转写、分段、抽取结论。这样模型做决策时负担很小执行体也能独立测试。2.2 描述不是写给人看的是写给模型决策的这是 agent-skills 设计里最关键的一条心得。很多人写技能描述像写 README会写“这个模块负责会议纪要的生成支持音频转文字、文本摘要、结构化输出内置了基于大模型的抽取逻辑”。模型看完知道这个技能存在但不知道什么时候调用最合适也不知道哪些情况不该用它。我建议把描述拆成三个独立块What这个技能完成什么任务用一句动词短语说清楚。When什么场景下必须用我什么场景下千万别用我。Output我给回什么结构是否保证成功失败时怎么办。When 这块尤其重要。明确写“不适用于……”能显著降低误调用率。比如“日程管理”技能里写“不要用于纯提醒场景提醒请走 reminder 技能”模型在“明天九点提醒我带合同”这种句子上就不会纠结。真实项目里我统计过加上负向声明之后误调用率能降一到两成。这是个很值得先做的低成本优化。2.3 一个可以照抄的 Skill 定义骨架下面是我目前在用的一个简化模板字段可以根据场景增删但这个结构本身很稳skill: schedule_event description: 当用户希望创建日程、修改日程或查询日程安排时使用。 不要用于创建提醒事项提醒请使用 reminder 技能。 如果没有明确的时间必须先询问再创建。 params: - name: title type: string required: true description: 日程标题 - name: start_time type: string required: true description: 开始时间ISO 8601例如 2025-06-01T09:00:00 - name: end_time type: string required: false description: 结束时间ISO 8601缺省时按一小时计算 - name: attendees type: array required: false description: 参与人邮箱列表 output: type: object fields: [event_id, title, start_time, end_time, status] on_error: 返回 error_code 与可读提示不假装成功注意模板里我故意不写实现细节。执行体是 Python 脚本、云函数还是一个标准 API 请求都无所谓。技能层只关心接口契约执行层只关心实现这两个关注点分开之后技能库才能规模化。3. 技能库的分层设计原子、组合与路由3.1 原子技能一个动作只做一件事规模上来以后光有“会议纪要”这种任务级技能还不够它内部可能还要拆成转写音频、会议分段、抽取结论、写草稿这四个动作。我会先把每次只做一件事的动作沉淀成原子技能明文规定输入输出没有复杂的业务分支模型或上层代码随时可以调用。原子技能的测试成本最低往往用一个单元测试就能覆盖。比如“转写音频”技能输入是音频路径输出是带时间戳的文本没有其他副作用。这种技能可以在所有高层技能里被任何组合复用也是整个技能库稳定性的底座。我见过一些团队跳过了这一层直接写大而全的技能结果技能与技能之间互相调用时参数还要做各种适配那已经不是技能系统而是另一个意大利面条项目。3.2 组合技能把原子技能编排成工作流组合技能负责把多个原子技能串起来体现业务闭环。“会议纪要生成”可以定义为转写音频 - 分段 - 抽取行动项 - 写入指定文档。组合技能里的每一步都可能失败所以要定义清楚中止条件是继续还是报错是跳过还是重试。模型通常不需要感知组合内部的每一步它只需要知道调用组合技能能够拿到什么结果组合技能在内部可以自己判断状态机。实际项目中我一般会用状态流转对象来管理组合技能的执行。比如“生成纪要”状态从 pending、transcribing、segmenting、finalizing 到 done任何一步失败都记录到上下文字段里方便模型向用户解释“卡在哪个环节”。有了这层编排上层 Agent 就不需要具备复杂的工作流管理能力技能自己就能把任务吃掉。3.3 路由技能让模型学会找谁干活当技能库超过十几二十个时模型在每一步都做全局选择会越来越犹豫。我的做法是加一个路由技能它的能力描述不是某个具体任务而是“判断用户意图并把请求交给合适的技能”。路由技能的输入是用户当前的意图和上下文输出是一个 skill_name 和置信度。它不需要执行具体业务只做分发。比如用户的“帮我参加会议并记录结论”路由会先匹配到“会议参与”技能和“会议纪要”技能再根据场景顺序编排。有一个好的路由技能后面新增技能时只需要在路由描述里加一行匹配规则全局的稳定性不会被破坏。路由技能听起来像一个“分发器”确实如此。它和前端的网关很像不做业务只做寻址。但这层存在让整个技能库形成了清晰的层次路由在最上层组合在中间原子在最底部。模型需要做的决策空间被压缩得很小因此准确率也更容易提升。3.4 目录与命名规范技能多了以后命名和目录会成为第一个影响开发效率的地方。我当前使用的结构是skills/ schedule_event/ SKILL.md handler.py tests/ meeting_notes/ SKILL.md handler.py actions/ transcribe_audio/ segment_text/ tests/ router/ SKILL.md handler.py tests/命名统一用动词开头的 snake_case表示“做什么事”。目录名就是技能名SKILL.md 是模型可读的描述handler.py 是执行入口。子技能放在 actions 子目录里表示它们是内部编排的一部分不直接对外暴露。这个约定不一定适合所有团队但它是我们踩了很多坑之后沉淀下来的至少能保证一个新同学接手技能库时不用猜。4. 落地细节两个高频技能的实现思路4.1 日程管理时间解析与冲突校验是最大的坑日程管理是几乎所有 Agent 都会接的技能但也是最容易做得想摔键盘的技能。原因是自然语言里的时间表达太灵活“下周一上午”“明天晚上八点”“周四之前”都可能是日程时间。如果直接把字符串丢给日历 API一定会在真实用户场景里炸。我的做法是在技能内部做两层解析。第一层是时间表达式识别用本地化的时间解析库把“下周一上午”转成候选时间窗口第二层是候选时间校验和现有日程做冲突检查并把冲突结果并入返回值。如果模型在参数里没有给出完整时间技能要先返回一个“需要信息”的错误码让 Agent 去追问用户绝对不要在时间缺失时猜测。有一个我反复强调的细节技能返回给模型的信息里不仅要包含“创建成功”还要包含“在哪一天、几点到几点”。因为 Agent 后续很可能要跟用户复述确认你没有完整的回显它就只能编。日程技能的参数契约里我会把 start_time 和 end_time 都设计成字符串而不是时间戳因为模型更容易输出 ISO 字符串解析成功后再在代码里转成时间戳。这里不要做“模型会自己处理格式”的假设接收端要宽容发送端要明确宁可多写一行校验也不要相信输入。4.2 信息检索搜索、提取、引用三段式信息检索是另一个高频需求。常见错误是只做一个“搜索并返回结果”的技能让模型去阅读所有链接。这在 token 消耗、延迟、可信度上都是灾难。我把检索类能力拆成三段独立技能search_web 只拿到候选标题和链接extract_content 拿到具体网页正文并去重cite_sources 把最终答案中的每句话对应到证据链接。search_web 的输出是一个列表每条包含 title、url、snippet 和 content_hash。extract_content 接收一个 url输出净化后的文本、标题和抓取时间。cite_sources 接收答案文本和证据列表输出带引用的 Markdown。模型在使用这三个技能时逻辑变得非常透明先搜再读最后引用。任何一步失败都可以定位到具体技能而不是“搜索功能坏了”这种模糊状态。这个三段式设计还有一个额外收益它可以单独做缓存。url 和 content_hash 不变时extract_content 的结果可以直接复用减少重复抓取。我在项目里用这个方式把重复查询的成本降了一半。如果你只是把“搜索”写成一个全能技能这些优化基本无从下手。4.3 返回值里要带“证据感”4.1 里提到回显4.2 里提到引用这两者背后其实是同一个原则技能返回给模型的数据要带有“证据感”让模型知道结果是从哪来的。原因很简单模型在开放式对话里很容易自信地补全缺失信息而如果它的每一步都基于技能返回的明确字段幻觉率会低很多。具体来说任何技能的输出都应该包含一个不太占 token 但足够说明来源的字段创建时间、来源 URL、记录 ID、执行状态等。这样即使 Agent 后面做了自由发挥至少核心事实是被约束住的。这一点越早设计越好等技能上线后再补输出字段代价会是想象不到的大因为所有调用场景都要回归。5. 技能不是写完就完评估、回归与灰度5.1 埋点记录每一次技能被选择和不被选择技能上线之后测试不是跑一遍 handshake 就结束了。真正能让技能越用越稳的是埋点。我会在每个技能的入口和出口打三类日志模型是否选择了这个技能、技能执行是否成功、执行结果是否被后续对话使用。模型“选择了但最后没用上”往往比“没选择”更值得关注它说明描述可能过度承诺了能力。另外还要记录“负向选择”在包含相似功能的技能并存时模型是否频繁选错。比如用户问“下午四点提醒我开会”结果模型调了 schedule_event 而不是 reminder。这类误用如果高频出现就去检查两个技能描述的边界是否足够清晰。埋点不需要一开始就做得很重我一般先用结构化的 JSON 日志后面再根据分析需要建看板。5.2 用一个人工标注的回归集当“技能守护者”每个技能都应该有一组真实场景的回归用例。这不是单元测试而是“给模型看的场景问答对”用户会怎么说、期望触发哪个技能、参数填什么、返回值怎么被使用。我通常在新增一个技能时至少准备 20 条人工标注用例其中 15 条正向、5 条负向。负向用例专门写“类似但不该触发”的表达比如“查看天气”不该触发日程技能“叫我起床”不该触发日程技能。回归集在每次技能描述或路由规则变更后自动跑一遍。我经历过一次改了参数描述导致“添加待办”误触发“日程创建”的问题正是回归集在 CI 里拦住了。没有这套回归的话模型层的回归很难被发现因为单看代码根本看不出问题。5.3 灰度先小流量验证再全量铺开技能改动和普通代码改动一样需要灰度。我的策略是给技能版本加一个 enable_rate新版本跑 5% 流量观察触发率、成功率和误用率稳定后再逐步放量。放量时尤其关注一个指标成功率持平甚至上升但触发率可能因为描述改动而变化需要人工判断变化方向是否符合预期。如果触发率大幅下降很可能描述里的某个关键词把模型带偏了。灰度发布还有一个实操细节新旧版本要能同时存在且被路由正确分流。所以技能版本号要写进参数契约和日志不能靠改文件名区分。我见过团队直接把技能逻辑覆盖了结果灰度时想回滚只能靠 git revert非常被动。正确做法是每个技能包带上版本号发布系统按版本号加载回滚只是改指针。6. 我踩过的几个坑希望你绕开6.1 别让描述变成大杂烩第一次设计技能时我犯过最蠢的错误为了让模型“理解得更深”把技能的背景、历史、相关项目、使用范例全写进描述。结果模型反而抓不住重点选择率变差。后来我把描述压到 300 字以内只留 What、When、Output并且把负向声明单独放一行。描述不是文档是模型在一堆选择里快速识别你的“标签”。标签越清晰命中越准。6.2 参数校验写不严模型会给你“惊喜”另一个高频坑是参数校验只做类型检查不做业务约束。日程技能一开始没有校验 end_time start_time结果有一次模型把结束时间写成了开始时间的前一天接口竟然创建成功用户差点收到一条来自过去的日程。后来我在参数契约里明确规定值域范围、依赖关系和业务规则凡是不满足的直接返回错误码并给出可读的修正建议。模型看到错误码以后通常会自我纠错但前提是我们的错误信息要足够明确。6.3 权限与技能耦合上线时进退两难第三个坑是把权限控制写在技能内部。比如文件操作技能里管理员能删文件、普通用户只能读这个判断如果混在 handler 里技能复用时就得复制一份带权限的版本维护成本翻倍。现在我把权限放到路由层或网关层统一处理技能只负责“能不能做成”权限负责“该不该让这个人做”。两者解耦以后同一套技能可以安全地服务于不同用户角色。6.4 给技能做版本管理包括描述和测试最后是版本管理。技能仓库里要管的不仅是 handler.py还有 SKILL.md 和回归用例集因为影响模型行为的主要是描述。我现在的习惯是技能包采用 Git 仓库按目录管理每次描述调整必须带上对应回归结果merge 之前先看 diff不光是代码 diff还有描述 diff。很多隐蔽的行为漂移都是某一次“只改两个字”的描述优化引起的。6.5 小步快跑比憋大招靠谱我在早期倾向于把一个技能写得很完美再上结果要么拖很久要么一上线就发现真实场景和预期完全不一样。后来改成“最小可用技能先行”先保证核心路径能跑通、描述只有一个明确触发点、参数只留必需项然后靠埋点去迭代。技能系统最怕的不是不完善而是不透明有了日志、回归和灰度不完善的技能也能越改越好。等一个技能被反复使用后再逐渐补边界能力和更细的参数校验反而比一次性设计高效得多。就我个人实际操作而言agent-skills 最大的价值倒不是某个技能写得多漂亮而是它逼着我把 Agent 的能力从“模型自由发挥”变成“系统化组织”。现在凡是在三个以上场景里出现的重复能力我都会第一时间抽成独立技能凡是上线超过一周的技能必须有对应的回归用例和埋点。这套习惯已经帮我省下了大量排查时间如果你正准备整理自己的技能库不妨也从这三个动作开始。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进