☰
Agent技能设计:从工具调用到完整能力契约
2026/10/21 19:38:48 网站建设 项目流程

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 就是failed,error_hint 里写一句人话,例如“页面结构不完整,无法提取正文”。模型拿到这种结构,不需要自己猜语义,直接就能决定下一步是重试、换方案还是提示用户。

技能这套“契约”设计,本质上是把原来属于开发者的逻辑判断,前移到了模型的动作选择阶段。你越往上层的 Agent 加技能,就越会发现,真正决定 Agent 能力上限的,不是底层模型有多强,而是这些技能“契约”写得有多清楚。

3. 从需求到技能:一个真实任务的拆解全流程

光讲理论没用,我拿我们实际做过的一个“竞品信息收集与分析”Agent 当例子,完整走一遍从自然语言需求到技能群的拆解过程。这个需求看起来很简单:给一个竞品网站列表,Agent 要输出一份对比分析报告。

如果你直接把整件事做成一个“技能”,模型大概率会崩。因为一个技能内部要做的事情太多了:抓网页、清洗内容、抽取信息、横向对比、生成报告,每一步都可能出各种异常。技能设计的第一原则是单一职责,复杂任务要靠多个技能组合来完成,而不是硬塞进一个技能里。

3.1 第一步:把需求拆成原子动作

我们先把“输出竞品分析报告”这句话拆成几个无法再细分、且结果可以被后一个步骤直接使用的动作:

  1. 抓取并清洗多个 URL 的正文内容。
  2. 从单篇正文中抽取关键事实(价格、功能、优缺点等)。
  3. 把多个实体的关键事实放到统一维度下做对齐和对比。
  4. 生成一份结构化报告。

这四个动作,各自都有明确的输入输出边界,而且中间的任一动作都可以独立测试。我们对应建立了四个技能:网页抓取技能、事实抽取技能、对比矩阵技能、报告生成技能。

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 个动作做成技能,跑通一条真实链路,让团队看到效果、建立习惯,后面再逐步把低频能力补进来。技能这件事有多少收益,往往不是设计出来的,而是沉淀出来的。一开始少设计一点,把每个技能都做得扎实,后面的路会顺很多。

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

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

立即咨询