Agent技能设计:从工具调用到完整能力契约
1. 为什么“给Agent加技能”和“给Agent加工具”完全是两码事最近大半年我一直在折腾各种 Agent 形态的东西从简单的单轮问答到多步任务执行踩了不少坑之后发现一个很反直觉的问题很多团队做 Agent一开始就急着往里面塞 API、塞工具结果模型确实会“调用”了但干出来的活一塌糊涂——回答张冠李戴、步骤乱跳、失败后不会补救整个 Agent 就像个只学会了按键、没学会打游戏的新手。后来我把思路换了一下不再天天琢磨“我还要接什么工具”而是开始认真设计“技能”。这里说的技能英文对应就是Agent Skills。它和普通工具 API 有个本质区别工具是一只手技能是一套完整的动作。比如“查天气”是一个工具但“帮用户判断今天适不适合户外跑步”才是一个技能它需要串联天气查询、空气质量判断、紫外线强度、用户身体数据等多个环节还要在拿不到数据时给出合理的兜底建议。这篇文章我想把这些东西系统地捋一遍到底什么叫 Agent 技能一个技能内部应该有什么结构怎么把一个实际需求拆成三四个可独立落地的技能怎么让模型稳定地选中正确技能而不是瞎选多个技能之间如何编排以及怎么证明这套技能体系真的可靠。如果你正在做一个有点复杂的 Agent 项目或者你已经被“工具调用”搞得头大这篇文章应该能给你一套可以直接照搬的思考框架。事先说明一下这里不绑定任何具体的模型或平台涉及的示例也都是我们自己项目里的抽象总结你完全可以迁移到自己的技术栈上。2. 拆开看一个技能从“一段能力”到“一份可执行的契约”先说一个常见的误区。很多人觉得技能不就是给模型加个函数嘛函数写得够清楚模型就会用。问题恰恰出在这函数只是“能力声明”而技能需要的是“能力契约”。一个完整的技能在运行时至少要包含四个部分触发边界、输入协议、执行逻辑、输出归一化。这四个部分缺一不可。为了好理解我拿“网页正文提取”这个技能来举例。2.1 触发边界什么情况下这个技能才该被用触发边界其实是一段写给模型看的“使用说明书”它得说清楚什么场景用这个技能、什么场景绝不能用。很多项目在这里栽跟头就是因为技能描述写得太宽泛导致模型什么都往里面套。比如你写“从网页里提取内容”模型面对“用户发来一个PDF链接”时很可能也去调这个网页提取技能结果返回一堆乱码。正确的边界描述应该类似这样在需要从 HTML 网页中获取正文文本、且用户提供的目标是 URL 时使用。如果目标是 PDF、图片、视频或需要登录的页面不要使用本技能改用对应的解析技能或明确告知用户无法处理。这段话看起来很朴素但它就相当于给模型画了一个“围栏”把它往正确的路径上引。我们实测过加了这段边界描述之后技能误用率能下降一半以上。2.2 输入协议机器看得懂模型才好填输入协议就是参数的 Schema。这里的重点不是写几个字段就行而是要严格区分“必填参数”和“可选参数”并且给每个参数配上清晰的类型、格式说明和取值范围。还是用网页提取技能来说输入参数可以设计成下面这样{ url: { type: string, format: uri, required: true, description: 需要提取正文的网页地址必须以 http:// 或 https:// 开头 }, max_length: { type: integer, required: false, default: 8000, description: 提取出的正文字符上限超出部分将从末尾截断 }, need_links: { type: boolean, required: false, default: false, description: 是否额外返回页面中的超链接列表适用于需要进一步串联抓取的场景 } }写到这里你们可能会觉得这不就是写 JSON Schema 嘛有什么难的。真正的坑在于模型在生成参数时非常容易偷懒。比如该填必填参数时不填或者把 URL 写成带中文的搜索词导致执行端一接就报错。我的经验是必须在执行层做一次严格的入参校验校验不过就立刻返回一个标准化错误而不是直接把错误堆栈抛给模型。模型看到干净的报错信息才会自己尝试修正看到一堆堆栈大概率会开始胡说八道。2.3 执行逻辑技能内部的“手艺活”执行逻辑是技能真正干活的部分它可以是调用外部 API、运行一段爬虫脚本、读数据库、算数值甚至组合多个子调用。这里我不展开具体技术栈因为每个团队都不一样但有一条原则值得强调执行逻辑必须是可重入的。什么叫可重入就是同一个技能在相同输入下执行两次结果应该保持一致至少不能污染全局状态。我们早期写技能时没有这个概念某个技能内部悄悄缓存了上一次的解析结果导致第二次调用拿到的是旧数据排查了很久才发现。后来所有技能都强制要求无副作用设计写操作和读操作彻底分离问题才彻底消失。举一个可重入设计的实际例子如果技能需要调用外部搜索接口不要直接在技能内部维护一个全局搜索历史而是把历史查询记录作为入参传进来。这样每次执行都是完全独立的Agent 回溯时也能清楚看到每次搜索的上下文。2.4 输出归一化让上层 Agent 不用猜最后一个部分是输出归一化。这也是最容易被忽略的。很多技能执行完直接把 API 的原始返回丢给模型里面可能夹着一大堆无关字段、HTML 标签或者不同接口风格完全不一致的结果结构。模型每次都要现猜这些结果是什么意思推理负担一重后续步骤就开始飘。输出归一化的目标是让所有技能返回的结果都遵循一套统一的简单格式。我通常建议至少包含状态、数据、错误提示三个字段{ status: success, data: { title: 网页标题, content: 清洗后的正文文本, links_count: 12 }, error_hint: null }如果失败status 就是failederror_hint 里写一句人话例如“页面结构不完整无法提取正文”。模型拿到这种结构不需要自己猜语义直接就能决定下一步是重试、换方案还是提示用户。技能这套“契约”设计本质上是把原来属于开发者的逻辑判断前移到了模型的动作选择阶段。你越往上层的 Agent 加技能就越会发现真正决定 Agent 能力上限的不是底层模型有多强而是这些技能“契约”写得有多清楚。3. 从需求到技能一个真实任务的拆解全流程光讲理论没用我拿我们实际做过的一个“竞品信息收集与分析”Agent 当例子完整走一遍从自然语言需求到技能群的拆解过程。这个需求看起来很简单给一个竞品网站列表Agent 要输出一份对比分析报告。如果你直接把整件事做成一个“技能”模型大概率会崩。因为一个技能内部要做的事情太多了抓网页、清洗内容、抽取信息、横向对比、生成报告每一步都可能出各种异常。技能设计的第一原则是单一职责复杂任务要靠多个技能组合来完成而不是硬塞进一个技能里。3.1 第一步把需求拆成原子动作我们先把“输出竞品分析报告”这句话拆成几个无法再细分、且结果可以被后一个步骤直接使用的动作抓取并清洗多个 URL 的正文内容。从单篇正文中抽取关键事实价格、功能、优缺点等。把多个实体的关键事实放到统一维度下做对齐和对比。生成一份结构化报告。这四个动作各自都有明确的输入输出边界而且中间的任一动作都可以独立测试。我们对应建立了四个技能网页抓取技能、事实抽取技能、对比矩阵技能、报告生成技能。3.2 第二步给每个技能定义边界和接口我不建议一开始就把接口设计得特别复杂先让每个技能只管一件小事情接口越简单越好。网页抓取技能输入是 URL 列表输出是清洗后的正文列表。如果某个 URL 抓取失败直接标记该条为 failed不阻塞其他 URL。事实抽取技能输入是单篇正文输出是事实列表每条事实带置信度。置信度高的才进对比环节。对比矩阵技能输入是多个实体的事实列表输出是一个二维矩阵行为维度列为实体。报告生成技能输入是对比矩阵输出是带分析结论的 Markdown 报告。这一步的产出物其实就是一份“技能清单表”可以很直观地和团队对齐技能名称输入输出失败兜底策略网页抓取URL 列表清洗后正文列表单条标记 failed跳过继续事实抽取单篇正文事实列表含置信度置信度低于阈值则丢弃对比矩阵多实体事实列表统一维度对比矩阵缺失维度填“未获取”报告生成对比矩阵Markdown 报告数据为空时明确告知完成为止3.3 第三步定义技能之间的“前后置关系”技能拆好之后还要明确它们之间的依赖关系。在我们的例子里四个技能就是典型的链式依赖抓取 → 抽取 → 对比 → 报告。但真实场景里这个依赖往往没那么严格。比如用户只想分析一个竞品这时候就不需要对比矩阵技能抓完直接跳到报告生成。所以我在设计技能群的时候会额外写一个人工定义好的“路由逻辑”数据体量小时自动跳过部分中间步骤数据量大时甚至可以把多个网页抓取技能并行执行。这一步看起来像是给自己的 Agent 写死逻辑但实际非常有必要。完全依赖模型自己规划步骤在步骤少的时候没问题步骤一多就很容易出现漏步骤或者顺序颠倒的情况。先人工把关键路径和分支规则定好再让模型在既定框架内做选择稳定性和可控性会好一个数量级。3.4 第四步用真实输入做端到端试跑拆解完并不代表结束我会习惯用一个最小集跑通全流程。比如只给两个竞品 URL要求输出一份两列对比的短报告。跑通之后再逐步加 URL 数量、加复杂页面、加异常情况。这个试跑阶段最容易暴露的问题有三个抽取技能对某一类网页结构完全不适应、对比矩阵的技能维度定得过粗导致看不出差异、报告生成技能在数据缺失时表现很蠢。每一个问题都会倒逼我回去调整对应技能的描述和参数设计所以技能设计不是一个“一次性建模”的活儿而是一个快速迭代的过程。一开始不建议追求全量技能先跑通最小闭环再慢慢扩展。4. 让模型“选对技能”描述设计和运行时防呆两手抓技能拆好了、接口写清楚了接下来要面对的核心问题就是模型真的会选对这个技能吗我见过太多项目技能定义得很漂亮一跑起来却发现模型调用得乱七八糟。这背后有两个层面的原因需要分开治理。4.1 技能描述写法名字短描述细先说第一个层面技能描述。很多人在给模型暴露技能时描述写得太笼统。比如“获取网页信息”这几乎是万能钥匙什么请求都能套进去。模型一看到这种描述自然就倾向于乱选。我自己总结了一套描述模板你可以直接照着套用技能名网页正文提取 用途从 HTML 网页中提取干净的正文文本返回标题和正文内容。 适用场景 - 用户提供 URL希望阅读或总结网页内容 - 需要批量获取多个网页的正文用于后续分析 不适用场景 - 目标为 PDF、图片、视频文件 - 目标页面需要登录才能访问 - 用户只想做搜索、不打开具体页面这里的关键是“不适用场景”必须写清楚。模型在动作选择时读到的文字越多、边界越明确选错概率越低。这一条是我们实测下来收益最大、也最容易被忽略的改进点。4.2 入参校验宁可拦下来也别放过第二个层面是执行端的防呆设计。即使描述写得再好模型仍然可能在入参上犯错比如把 URL 字段填成了网站名称、把数值型参数填成了字符串。我的建议是在执行层加一道严格校验违反 Schema 的直接返回invalid_arg错误不要尝试“聪明地”帮模型补全参数。你可能会有顾虑这样会不会太死板模型不会填了怎么办实际上不会。干净利落的入参错误提示会促使模型重新阅读技能描述并修正参数。反而是那些“宽容”的执行层默默把非法参数转换成默认值模型根本不知道自己做错了会一路错下去。我在一个模拟项目中做过统计加了入参校验之后技能调用的最终成功率从 71% 提升到了 92%。中间大概有三成请求第一次会失败但模型看到明确的错误提示后能自我纠错纠错之后大多数都能成功。这说明运行时防呆不是给模型添麻烦而是在帮模型建立正确的行为习惯。4.3 技能数量与选择准确率的关系还有一个很值得聊的问题一个 Agent 到底暴露多少个技能给模型才合适我们做过压力测试当技能数量在 5 个以内时模型的选择准确率可以到 95% 以上超过 10 个之后准确率明显下滑超过 15 个时模型会开始混淆两个名称相似的技能。所以如果你发现自己要往 Agent 里塞二三十个功能点不要硬塞。应该先把功能归类做一个“技能路由层”比如所有和搜索相关的技能统称为搜索路由由路由层根据内部关键词再决定具体调用哪个底层技能。这样模型只要面对一小撮高层技能选择压力就低很多准确率也更稳。这套“先路由、再调用”的结构早期会觉得有点绕但技能一多它的价值就马上体现出来了。这也是我们在做过几个实际项目之后最想建议你提前考虑的一点。5. 技能编排的策略链式、并行、选择、重试怎么取舍技能拆好、也能被模型正确选中之后剩下的问题就是怎么把它们组合成一次完整的任务。编排方式不同Agent 的效率和稳定性差异巨大。我根据实际经验把编排策略分成了四类你可以在设计自己的技能流时对照着用。5.1 链式编排上一步输出就是下一步输入这是最基础也最好理解的编排方式。就像第三节里的竞品分析流程抓取结果给抽取、抽取结果进对比矩阵。链式编排下上游输出格式是否稳定、下游是否做了足够的前置校验就变得至关重要。我会在链式编排里强制要求每个技能的输出都符合统一结构比如数据永远在data字段下错误说明永远在error_hint字段下。这样下游技能不需要关心上游是哪个技能、内部怎么实现只认统一格式就行。这里还有一个实操建议链式编排里尽量加入“断点检查”。也就是在执行到特定环节时让模型看一眼当前数据是否正常、是否值得继续由模型决定继续、换路还是终止。这比一条流水线跑到底要可靠得多因为真实的数据经常会缺胳膊少腿硬跑只会越来越跑偏。5.2 并行编排同质任务一起跑省时间并行编排适合那种“一批同类型的活要一起干”的场景。比如竞品分析里要抓 10 个网页一个一个抓显然太慢完全可以并行发起多个抓取任务等全部完成后再汇总。但并行也不是想开就开。我通常会加两个限制条件并行任务之间不能有数据依赖彼此必须完全独立。对并行任务的总数设上限防止一次开太多导致资源被打爆或者外部接口限流。5.3 选择编排让模型当少数关键分的裁判选择编排是四种方式里最依赖模型智能的。它适合的场景是同一个目的有多个实现路径需要模型根据现状判断走哪条路。举一个我们项目里的例子一个“信息查询 Agent”同时具备网页搜索和数据库查询两个技能。用户问的问题如果偏向事实性、时效性模型应该选网页搜索如果偏向个人历史记录、内部数据模型应该选数据库查询。这两种选择本身没有对错关键看用户的意图。这类场景不适合写死规则强行写规则只会把代码变成一团乱麻。正确的做法是把选择条件写进每个技能的“适用场景/不适用场景”里让模型自己判断。实测下来表述得越具体判断就越准。5.4 重试与降级任何技能都可能失败最后也是最重要的编排策略失败处理。技能执行失败时首先要区分失败类型。如果是参数错误模型应该自动修正重试如果是外部服务超时等几秒重试如果是数据本身不存在就不要傻乎乎地重试了而是降级处理。我常用的降级策略是准备一个“轻量替代方案”。比如网页抓取失败时可以用一个只提取 meta 描述和少量正文的替代技能先顶上事实抽取置信度不够时可以降级为关键词高亮至少让用户看到相关信息。降级之后的结果当然不如完整方案漂亮但总比一整个流程中断强。到目前为止说的都是结构化编排。但实际项目里你会发现模型经常不按你编排的路子走它会自己尝试做一些组合。我的态度是结构化编排和自由调用并不冲突。关键路径用编排卡住非关键路径放开让模型自由发挥。这样既有稳定性又有灵活性两边都占得住。6. 调试与评估怎么证明这套技能体系是可靠的技能和编排都写完了如果只是“跑通了”说实话我心里是不踏实的。要给团队一个“这套东西真的可用”的交代必须要做系统性的调试和评估。这里讲讲我实际用的方法论和工具思路。6.1 给每个技能写“单测”受测试驱动开发的思想启发我给每个技能都配了一组极其轻量的测试用例。这类测试的形态很简单写几个固定的输入断言输出是否符合预期。拿“事实抽取技能”来说我会准备好几篇不同风格的样本文本结构规整的新闻稿、口语化的评论区、满是广告噪音的营销页。然后逐个跑抽取人工判断事实列表是否准确。这种单测的技术含量不高但对防止回归特别有用。谁改动过技能内部逻辑跑一遍单测就知道有没有把老功能搞坏。单测不追求覆盖所有情况我一般每个技能准备 5 到 10 个用例就够。6.2 建立评估集做“端到端回归”单测是验技能的局部分解能力端到端回归则是验技能组合起来的整体表现。我会积累一批真实的用户请求比如“帮我分析这几个竞品官网的功能差异”“把这篇英文文章翻译并写一个摘要”。这些请求保持原样不做过多的整理因为真实世界的输入就是这么乱的。每次改动完技能定义、编排逻辑或提示词我都跑一遍回归把成功率、失败原因记录下来。评估端的判断可以自动化一部分比如技能调用是否成功、关键动作是否执行但输出质量的判断建议还是人工评分至少抽样评分。这里我把评估的观察维度列成了一张表你可以直接拿来用维度具体看什么可接受的底线技能选择准确率模型是否选对了技能单个 Agent 内 90% 以上参数生成合法率入参校验第一次能通过的比例70% 以上其余靠自纠断点检查正确率模型在关键节点是否做出正确决策人工抽查定性评估任务完整率从发起到拿到最终结果的成功比例简单任务 95%复杂任务 80%降级兜底比例主路径失败时降级方案被启用的次数越低越好但必须有6.3 可观测性比“跑通”更重要调试 Agent 系统最头疼的事情是模型选了哪个技能、填了什么参数、执行到什么步骤经常像黑盒一样。想快速定位问题必须给技能调用过程加上必要的可观测性。我在模拟项目里用的方法是把每一次技能调用记录成结构化日志日志至少包含五个字段调用时间、被调技能名、完整入参、执行状态、失败原因。遇到任务结果不对时先不看最终输出直接根据日志逆推看是哪一步选择出错、哪一步参数异常、哪一步外部服务返回了脏数据。有了这套日志之后我甚至能发现一些意想不到的规律比如某个技能在特定时间段成功率突然下降经排查发现是某个外部接口时不时 429再比如某些描述相近的技能在连续多次调用后模型会突然切换错目标。这些问题光靠肉眼观测是根本发现不了的。6.4 小步快跑每周做一次技能盘点最后我会建议评估不要只做一次而是每周固定做一次。技能体系是活的东西用户需求在变底层模型能力在变外部接口行为也在变。坚持每周小步快跑每次只改一个变量不要一口气大改特改。改完之后立刻跑评估集用数据确认改动是正向的再继续下一项优化。这套节奏虽然慢但胜在稳踩坑成本也被控制到了最低。7. 把技能沉淀成团队的共享资产技能这玩意儿一旦做完其实是非常典型的高价值沉淀物。一个好用的技能换个场景、换个 Agent 完全可以复用。但现实是很多团队的技能散落在各自的脚本和 Notebook 里没有统一管理用了多少次都不知道改没改过也没记录。这里分享一点我们把技能资产化管理的心得。7.1 技能也要有版本号和变更记录技能不是写完了就固定不变。底层接口升级了要改技能发现某类输入处理不了要改技能模型换新版本技能描述可能要跟着调。所以技能必须版本化至少要记录三个层面的信息版本号、变更说明、兼容状态。我们内部有一个约定技能版本号用三段式主版本号变化代表接口不兼容次版本号代表新增能力补丁版本号代表内部逻辑修正。任何一次变更都要在技能仓库里留下变更说明一句话也好总比半年后看着代码猜当初为什么这么写来得强。7.2 技能之间也会有依赖关系技能不是孤岛。比如我们的事实抽取技能依赖底层的基础清洗技能报告生成技能又依赖工作区渲染能力。技能多起来以后用一段文本形式的依赖清单来管理依赖关系而不是让每个人凭印象去猜。依赖关系变化时下游技能必须重新跑一遍单测和回归。这个事没有捷径你只能靠流程强制。我们吃过亏底层清洗逻辑改了一个正则结果把某个下游技能的输出格式搞乱排查了很久才定位到根因。从那以后“变更必验下游”成了铁律。7.3 技能仓库谁来都能上手用沉淀技能的最终形态是一个团队共享的技能仓库。仓库里每个技能都带一份标准的说明文档至少要有用途说明、适用与不适用场景、输入输出 Schema、依赖关系、典型示例、已知边界。任何新成员接手打开这个技能仓库就能快速复用而不是去翻聊天记录、找老同事问东问西。这一步做起来不难难得是坚持。我的建议是技能仓库的维护不搞“专人负责”而是“谁改谁负责”合并前必须过测试清单否则不收。这样既不需要额外的人力投入又能保持技能质量的底线。最后再分享一个我自己踩过几次坑之后总结出来的体会技能体系这件事最忌讳的就是一开始追求大而全试图把未来可能用到的能力全都设计好。实际上你只需要把当前最高频的 5 到 6 个动作做成技能跑通一条真实链路让团队看到效果、建立习惯后面再逐步把低频能力补进来。技能这件事有多少收益往往不是设计出来的而是沉淀出来的。一开始少设计一点把每个技能都做得扎实后面的路会顺很多。