ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Agent技能体系设计:从工具调用到编排评估的完整指南

2026/9/23 5:32:31 拓冰建站 浏览量
Agent技能体系设计:从工具调用到编排评估的完整指南 最容易让一个Agent项目翻车的不是模型选型也不是提示词工程而是技能层的设计。做了大半年agent-skills方向的实践我最大的感受是很多团队把Agent做成一个什么都能干的壳却没有告诉壳里的模型到底有哪些本事、怎么用这些本事、哪些本事组合起来会产生什么结果。最后模型要么疯狂编函数名要么在复杂任务里漏步骤。今天这篇我原原本本把技能体系的拆解思路、接口设计、注册发现机制、编排方法和评估流程梳理一遍都是实测过的方案踩过的坑也一并写出来。我最早接触agent-skills这个概念时第一反应是这不就是给Agent塞一堆工具函数嘛。后来发现完全不是一回事。工具函数只是最底层的能力原子而技能体系覆盖的是Agent能力的描述、注册、发现、编排、评估和迭代这整套生命周期。两者的边界在哪里、技能层到底解决了什么具体问题这是我想先掰扯清楚的问题。1. Agent技能系统解决了什么以及它和工具调用的边界1.1 技能不是工具的另一个名字先讲一个我自己的项目经历。一开始我只是用大型语言模型的函数调用能力写了一个订单查询工具注册成function之后让模型自行选择。流程大概是模型识别用户意图生成一段包含函数名的JSON我的代码解析JSON然后执行真实查询。这个链路在最开始很顺畅一个工具配一个描述模型基本不会选错。等到业务扩展之后问题开始集中爆发。我要给模型暴露的接口包括查订单、查物流、查优惠券、算满减、推荐商品、比价、生成售后工单、转人工客服光是function schema就有二十多段。system prompt被这些定义塞得满满当当每次请求的token开销肉眼可见地上涨模型也经常在两个相似功能之间犹豫比如查物流和查订单状态到底该调哪个。更痛苦的是每次新增或者修改一个工具都要重新调整Agent主逻辑里的注册代码项目维护成本急剧膨胀。后来我去看社区里各种agent-skills方向的设计思路才意识到我缺的不是更多工具而是一层技能管理。那技能和工具的本质区别到底是什么我的理解是工具是单个可执行动作是函数级别的能力切片技能则是对一类完整能力的封装它里面可以只包含一个工具也可以包含多个工具的组合调用并且额外携带描述、参数规范、前置条件、依赖关系、错误处理策略和评估方式。一句话说工具是能干一件事技能是能在合适场景下把一件事干好。1.2 没有技能层时Agent项目会碰到的四种典型信号如果你的Agent项目已经出现下面几个信号基本说明需要引入技能层了。第一提示词膨胀失控。系统提示词里堆了大量函数定义每次对话都要把这些定义发给模型响应时间变长token费用上升而且模型在大量相似描述中做选择的准确率会下降。这属于最直观的信号。第二功能接线散落在业务代码里。工具函数被直接import到各个业务流程中一旦某个工具的逻辑调整所有引用点都要跟着改。如果某天某个工具改变了参数结构你可能要全局搜索一遍才能找到所有断点。技能层会把该调什么这个决策统一收敛到一个注册表里业务方只跟技能名打交道。第三能力的复用性很差。我在项目A里做了一个很完善的营销文案生成功能到了项目B想复用发现代码耦合在A的对话流程里根本抠不出来。真正好的技能设计是独立部署、接口清晰、可插拔的把能力打包成一个一个技能包随时安装到新Agent上。第四个信号比较隐性但很致命无法评估Agent的行为质量。因为所有工具调用都散落在代码里你没有数据能回答模型选择技能的准确率是多少某项技能执行的成功率怎么样这轮改动到底让效果变好了还是变差了。没有评估就没有迭代最后只能凭感觉调prompt整个项目的优化基本靠玄学。2. 技能接口设计从能跑到好维护的关键2.1 一个技能的可维护信息结构我见过的技能定义方式五花八门有的用OpenAPI规范有的用JSON Schema有的干脆只写一段函数名加docstring。经过几个项目的磨合我的建议是一个技能最少包含下面这几个字段缺一个后面都会不舒服。技能标识符是很基础但容易出问题的点。它必须全局唯一且语义稳定建议用领域.动作的格式比如order.query、inventory.check、marketing.coupon_issue。别用v1、v2这种后缀做标识符因为版本和标识符是两码事技能升级后标识符不变版本号单独管理。描述字段是写给模型看的不是写给程序员看的。它的质量直接决定模型能不能在正确时机选中这个技能。好的描述要包含触发场景、行为边界、典型示例尽量写得像一段产品使用说明。参数定义是让模型知道它该填什么。我建议用严格的JSON Schema对每个字段都要说明类型、必填与否、取值范围和示例值。不要怕写得长这段schema就是你和模型之间的契约。执行器是真正干活的入口通常是一个可调用对象。我建议统一所有技能的执行签名比如全部接受一个dict作为输入、返回一个dict作为输出这样可以极大简化路由层的处理逻辑。下面给一个Python的定义范例from dataclasses import dataclass, field from typing import Callable, Any, Optional dataclass class SkillDefinition: skill_id: str name: str description: str parameters: dict executor: Callable[[dict], dict] dependencies: list field(default_factorylist) timeout_seconds: float 30.0 retry_times: int 1 tags: list field(default_factorylist) enabled: bool True这个数据结构看起来简单但它把技能的元信息和执行逻辑绑定在了一起。dependencies字段是留给技能编排用的比如一个下单技能依赖库存核验技能那么在执行前就要先跑依赖项。timeout和retry是给运行时用的防止某个技能卡死把整个Agent拖下水。2.2 描述字段与参数Schema是模型层面的产品文案很多工程师容易忽略一个问题技能定义有两个读者一个是执行引擎另一个是模型本身。执行引擎关心函数名和参数类型模型关心的是这个技能什么时候该用、用了之后会发生什么。所以描述字段的措辞风格要按产品文案的标准来写而不是按代码注释的标准来写。我举一个对比。一个查天气技能代码注释版描述是根据城市ID查询天气信息返回温度和天气状况。参数city_id是城市编号。模型看到之后可能就困惑了这个城市ID到底从哪来是用户说北京我就要翻译成ID吗于是模型可能随手编一个ID传进去。产品文案版描述则应该是当用户询问某地当前天气、温度、降雨概率或出行建议时使用本技能。从用户语句中提取城市名称通过内部地理编码模块转换为城市ID。若用户未指明城市先询问清楚再调用。例如用户说上海明天出门要带伞吗技能应返回上海的次日降水概率。这种描述把触发条件、参数来源、边界情况全部点透模型的调用准确率会明显提升。参数Schema方面也有一个常见误区误以为越宽松越好。我把参数定义为optional之后模型确实不会报错了但它可能干脆不传关键参数导致执行器收到一个不完整的输入只能在运行时报错。反过来如果你把值域限制得太死模型又可能因为拿不到精确值而频繁反问用户交互体验变差。我现在的折中方案是业务强依赖的参数设为必填并给出明确的枚举或格式业务可以兜底的参数设为可选并在描述中说明默认行为和兜底逻辑。schema里的每个字段都要写description这不只是规范问题实测对参数生成准确率的影响很明显。3. 技能注册与发现解耦Agent大脑与技能仓库3.1 注册表把技能变成可查询的数据有了技能定义结构之后下一个要解决的问题是Agent怎么知道当前环境里有哪些技能可用答案是需要一个注册中心。这个注册中心本质上是一个内存里的字典key是skill_idvalue是完整的技能定义。Agent的所有决策都通过这个注册中心来查询而不是直接import某个函数模块。我见过不少项目把技能列表直接写成一个Python列表挂在常量文件里这个方案在小规模Demo阶段没问题但一旦技能数量超过二十个、需要多人协作开发、或者有技能需要独立上线下线它就撑不住了。注册表的价值在于把技能存在哪些变成可动态查询的数据而不是写死的代码结构。我实现注册表的核心逻辑其实很短from typing import Dict, Optional class SkillRegistry: 技能注册表维护技能定义的可查询集合 def __init__(self): self._skills: Dict[str, SkillDefinition] {} def register(self, skill: SkillDefinition) - None: if skill.skill_id in self._skills: raise ValueError(f技能重复注册: {skill.skill_id}) self._skills[skill.skill_id] skill def unregister(self, skill_id: str) - None: self._skills.pop(skill_id, None) def get(self, skill_id: str) - Optional[SkillDefinition]: return self._skills.get(skill_id) def list_skills(self) - list: return list(self._skills.values()) def to_prompt_schema(self) - list: 把全部技能转成适合放进system prompt的schema列表 schemas [] for skill in self._skills.values(): if not skill.enabled: continue schemas.append({ name: skill.skill_id, description: skill.description, parameters: skill.parameters }) return schemas核心方法有三个register负责登记技能get负责按ID查询to_prompt_schema负责把技能列表转换成模型能理解的函数schema。Agent主流程只依赖这个类它不关心某个技能具体是谁写的、从哪个包里来的。这样新增一个技能就变成了往注册表里注册一个对象而不用动Agent的主逻辑。注册时机也值得考虑。我现在的习惯是项目启动时扫描若干指定的目录自动发现技能包并注册而不是在代码里一个个手动调用register。这样新成员添加技能时只需要在约定的目录下新增一个文件配置好元信息即可。3.2 技能发现与热加载怎么做到不用重启技能发现机制的作用是让系统在运行时能够感知技能的存在。最简单的发现方式就是文件扫描。你可以约定一个skills目录里面每个子目录代表一个技能包包含一个manifest.json和实现文件。启动时遍历目录读取manifest把定义注入注册表。这里我建议采用manifest来描述技能元信息而不是把描述和参数schema硬编码在代码里。好处是产品和运营同学也可以参与维护技能说明不用动代码。一个标准的manifest长这样{ skill_id: order.query, name: 订单查询, version: 1.2.0, description: 当用户查询订单状态、物流信息、签收情况时使用。, parameters: { type: object, properties: { order_id: {type: string, description: 订单编号}, keyword: {type: string, description: 用户提供的模糊关键词} }, required: [] }, entry: executor.py:run, dependencies: [], timeout_seconds: 10, enabled: true }entry字段指向实现文件里的入口函数。这样技能的执行逻辑和元信息完全分离执行逻辑更新时可以单独发布元信息的调整也不需要改代码。我甚至给项目做过一个简单的热加载器监听文件系统事件当manifest文件发生变化时自动更新注册表这在进行技能A/B测试时非常有用。不过热加载也有一个容易被忽视的坑代码文件更新后Python进程里import的模块还是旧版本的。我的处理办法是在注册表里额外维护一个module_versions字典发现技能文件变更时先把旧模块从sys.modules里弹出再重新import确保真正加载新代码。这个细节不处理你会以为热加载生效了实际上跑的还是旧函数。3.3 路由分发技能名到执行函数的调用细节注册表解决了知道有哪些技能接下来要解决知道选哪个技能、怎么执行。这一步我通常分为两段第一段是模型决策第二段是引擎调度。模型决策阶段Agent把注册表生成的schema列表放进system prompt让模型在回复中输出选中的技能和参数。这里我建议让模型输出一个结构化对象比如{action: order.query, arguments: {order_id: 123456}}引擎拿到这个结构化对象后在注册表里查询skill_id是否存在。存在就直接将参数传给执行器不存在则返回一条错误信息给模型提示它从可用技能列表中选择。这里有一个值得注意的细节模型偶尔会输出一个和可用技能很相似但实际不存在的名字比如注册的是order.query模型却输出order_query或者query_order。早期我直接把这个当作执行失败处理后来发现改成模糊匹配提示效果更好。引擎可以维护一个技能名到相似技能名的索引一旦命中选择列表之外的名字就给模型一个候选列表让它重新选择而不是直接终止流程。执行阶段还有两个参数处理要点。第一个是冗余参数过滤模型输出的参数可能包含schema之外的字段如果不过滤直接传给执行器可能导致函数签名报错。所以我在引擎里加了一层严格校验只保留schema中声明的字段其他全部丢弃。第二个是参数类型和值域校验用jsonschema库对模型生成的arguments做校验不合法就返回带具体错误信息的反馈让模型修正。这一层校验能拦截大量手动测试时才发现的问题。4. 技能编排让Agent学会组合动作而不是只调函数4.1 流水线编排与参数透传当技能数量多起来之后一个自然的业务需求就会出现很多任务不是调用一个技能就能完成的而是需要多个技能按一定顺序配合。比如查天气→推荐穿搭→生成文案→输出海报这四步每一步的输出都可能是下一步的输入。如果这些逻辑都靠模型在每次对话里自己组织结果会很不稳定模型经常漏步骤或者把参数传错。所以我们需要一个编排层把稳定的多步流程固化成新的复合技能。我的做法是把编排功能做进技能引擎里。一个技能的执行器可以不是单一函数而是一个由多个子技能组成的有向无环图。引擎执行这个技能时按拓扑顺序运行图中的节点并把上一个节点的输出映射为下一个节点的输入。最简单的顺序编排就像一个流水线。假设我有一个商品上新技能它内部包含三个子步骤生成商品描述、生成SEO关键词、生成社交媒体推广文案。每个子步骤的输出都是文本编排层只需要做拼接。但更多场景下子技能之间的数据格式并不一致比如库存模块返回的是dict而文案模块需要的是字符串这时候参数透传就需要一个转换器。我的设计是允许每个子技能声明input_map把上游输出的字段映射到自己的输入字段pipeline { name: product_launch, steps: [ {skill: product.info_query, output_name: product_info}, {skill: content.draft_generate, input_map: {product_name: product_info.name, features: product_info.features}, output_name: draft}, {skill: content.seo_generate, input_map: {body: draft.content}, output_name: seo} ] }input_map的语义是当前技能的某个输入参数 上游某个输出字段的路径。这个方案看起来很简单但能解决九成以上的参数传递需求而且每一步的输入输出都有明确来源调试时非常方便技能间不再隐式耦合。4.2 条件分支、重试与依赖处理真实业务不会只有顺序流程条件分支几乎避不开。还是以商品上新为例如果产品类型是服装可能需要额外生成尺码对照表如果是数码产品可能需要生成参数对比表。针对这类场景我在编排层加入了condition字段允许一个步骤声明可选执行条件。当条件字段在上游输出中存在且满足规则时才执行该步骤否则跳过。举一个实际的判断规则示例{skill: content.size_generate, condition: {field: product_info.category, eq: clothing}, input_map: {sizes: product_info.size_list}, output_name: size_table}实现上我会把condition编译成一个个小的谓词函数支持eq、ne、exists、in等几个常用操作不求覆盖所有场景但求简单可靠。重试和依赖处理也是编排层必须考虑的问题。技能执行时可能因为第三方接口抖动、网络超时等原因失败。我在技能定义里预设了retry_times引擎在捕获到可重试异常时会按照退避策略重新执行。但需要注意的是并非所有异常都适合重试比如参数校验失败这种确定性错误重试一万次也没用。所以我在异常体系里区分了可重试异常和非可重试异常只有前者触发重试。依赖处理则对应技能定义里的dependencies字段。在执行一个技能前引擎会先检查依赖列表如果依赖技能尚未执行或执行结果过期会先递归执行依赖技能。这个机制在跨技能的可复用逻辑上特别有用比如多个营销技能都依赖用户画像服务把用户画像获取定义为公共依赖后各个技能只需要声明依赖不用各自重复实现。4.3 一个组合技能示例从需求到执行一步到位综合上面这些能力我实际实现过一个比较有代表性的组合技能——活动素材生成。它把一个具体的运营场景封装成了一个高阶技能。组合技能的执行流程如下先调用用户意图分析技能把用户模糊的需求转成结构化需求然后调用商品数据库查询技能获取候选商品列表接着调用内容生成技能产出推广文案再调用图像生成技能根据文案生成配图最后调用素材归档技能把文案和图片打包成最终交付物。整个流程中有条件分支比如如果用户没有指定商品就需要先走一个推荐逻辑有依赖关系比如图像生成依赖文案输出有重试策略比如图像生成接口不稳定允许重试两次。这个组合技能定义好之后我只需要把它注册进技能库模型在识别到用户有做活动素材的意图时就会直接调用这个技能而不是在每一轮对话里重新尝试凑流程。业务上这个技能的稳定输出效果比模型每次自由组织流程要高出不少尤其在多步任务的完成率这个指标上提升非常明显。5. 技能质量评估没有评测就没有迭代5.1 四类核心指标缺一不可技能系统的迭代如果没有评测加持基本就是盲人摸象。我自己维护过一套评测体系核心看四类指标。第一类是技能选择准确率反映模型在给定场景下能否选中最合适的技能。评测方法比较简单准备一批标注好的用户请求—技能ID配对数据把请求发给Agent看模型最终选择的技能和标注是否一致。第二类是参数生成准确率包括参数完整性、字段正确性、值域合规性。因为很多情况下模型能选对技能但参数填得不对导致执行器报错或者返回错误结果。我会把参数错误细分为漏填、错填、多余填三类分别统计。第三类是执行成功率统计技能从开始执行到成功返回的占比。这个指标受两方面影响一方是技能本身的质量比如代码bug、依赖故障另一方面是参数生成质量。看这个指标时我会把两个因素分开排查。第四类是端到端任务完成率通常以组合技能或完整业务场景为单位统计。比如一个生成活动海报流程用户描述需求后最终能否拿到一张符合要求的图片。这个指标最能反映真实体验但代价是评测成本较高通常需要人工判断产出物质量。5.2 回归测试集要建而且要用版本管理有了指标之后还需要一套固定的测试集来跑回归。我的做法是维护一个评测数据集仓库里面按业务域划分目录每个目录包含若干测试用例。每个测试用例除了用户请求文本还要标注预期技能路径、预期关键参数、预期产出物要点。数据集建立初期不用追求大而全我建议先保证覆盖度确保每个技能至少有五到十条代表性用例覆盖正常调用、边界情况、相似技能区分、以及无相关技能时的拒答行为。这些用例的来源主要是线上真实流量抽样加人工标注辅以一些手工构造的困难样本。每次技能变更后都要跑一遍完整回归测试集对比变更前后的指标变化。我试过在没有评测体系的时候做个调整看起来某个技能的回答变好了结果另一个技能的选择准确率悄悄掉了好几个百分点。没有回归测试这种劣化会被掩盖直到线上用户投诉才发现。5.3 技能的灰度发布与回滚技能直接全量上线风险很高尤其是涉及生成类技能因为文本生成的质量波动很大很难在测试环境百分之百复现线上效果。我后来养成了灰度发布的习惯新技能在注册表里可以并存旧版和新版通过一个配置项控制流量分配比例。比如先让5%的请求走到新版技能观察指标是否优于旧版再逐步放大比例。这里有一个需要注意的细节技能除了要分版本还要把每个响应关联到具体的技能版本号。上线新版技能后线上请求日志里要记录skill_id加version这样复盘时才能精确地看出来某个异常响应是哪个版本产生的。我见过不止一个项目因为没记录版本号出了线上问题只能靠猜。当灰度指标不达标时回滚操作要足够快。因为技能注册表是集中管理的回滚只需把流量分配比例改回旧版不需要重新发版整个过程可以在分钟级完成。这也是我一直坚持把技能做成可配置的数据而不是散落的代码的原因。如果技能和业务逻辑焊死在一起回滚一次可能要动整个服务。6. 三处高频踩坑和我的应对6.1 参数Schema过宽带来幻觉参数前面说过参数Schema不能太宽这个坑我付出过不小代价。有一次我给一个入参技能设计参数时把很多字段都标记为optional并留了一个通用的extra字段本意是增加灵活性。结果模型在几个高频场景里疯狂往extra里塞东西而关键的业务参数却经常缺席。后来我把extra字段删掉并对每个参数补上明确的来源说明和示例值调用成功率才恢复正常。这个坑的本质是模型不是靠理解编程逻辑来填参的它是靠对描述文字的语义理解来推测参数的。schema里的每个字段对它来说都是一条线索可选的、语义模糊的字段等于在告诉它这些都可以随便填。所以参数设计一定要收敛能不开放的字段就不开放必填字段要明确。6.2 技能描述挂错调用时机另一个常见的坑是技能描述和实际调用时机不匹配。比如我的商品推荐技能起初描述里写的是根据用户偏好推荐商品听起来没什么问题。但实际模型在用户只是随便翻看、并没有明确购买意图的时候也频繁调用这个技能导致推荐内容显得很突兀。问题的根源是描述没有说清楚调用边界什么时候该用、什么时候不该用。后来我把描述改成了仅当用户明确表达需要购物建议、商品对比、赠礼挑选等需求时才调用。日常闲聊、浏览商品详情页时不要调用。加上边界说明之后误调用率降了很多。这里要提醒一点技能描述不是越文艺越好它是一份给模型的操作规范触发条件、参数来源、行为边界这三样东西缺一不可。6.3 编排层把异常全吞了在做技能编排的时候我最初在引擎里加了一个比较粗暴的异常捕获逻辑任何子技能报错都返回一句通用错误话术。这个设计本意是提高用户体验结果却导致线上各种错误被掩盖很多请求表面上是成功了实际上内部漏了步骤。直到某次运营反馈文案生成质量怎么看都不对劲我翻日志才发现其中一个生成步骤一直在抛异常但被通用捕获吃掉后流程继续走到了最后。我现在改成区分处理对于可重试异常按策略重试对于不可恢复的异常立刻终止编排流程并返回明确错误信息。错误信息里要包含是哪个技能、什么原因、已经尝试了几次这样既能让Agent回到模型层重新调整也能让开发者快速定位。6.4 个人经验先做小闭环再铺大规模最后分享一点我自己的习惯。如果你刚接触技能体系不要一上来就想用技能编排解决所有复杂业务先挑一个最痛最核心的场景把闭环跑通定义技能、注册、路由、执行、评估五个环节都通了再横向扩展。技能层的价值是在它稳定运行后慢慢体现出来的它不会帮你把模型变聪明但能让一个普通模型在清晰的能力边界内稳定地发挥。这个积累过程没有捷径技能描述、参数Schema、回归测试集都是一次次迭代磨出来的。把底子打好Agent项目后面的路才走得稳。