news 2026/10/14 8:29:50

AI Agent技能体系实战:从Schema设计到选路治理,把工具调用准确率拉满

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent技能体系实战:从Schema设计到选路治理,把工具调用准确率拉满

做一个 AI Agent,最让人头疼的不是大模型不智能,而是工具一多,模型就像进了菜市场:听见什么功能都能联想到。我在某多智能体协作系统里把十几个接口裸暴露给大模型后,用户说“查一下订单”,它能调成“发送营销消息”;说“明天天气”,它能传一个时区字段回来,参数基本靠猜。功能都能通,体验全在抽签。后来我把所有能力收敛成一套统一的 skill 体系——内部代号就叫 agent-skills——也就是给每个能力包上一层可解析的意图描述、输入契约和执行体,问题才真正被压住。

这套东西看起来只是给接口加了个 schema,但它决定了 Agent 的上限。所以这篇文章我不想只讲概念,而是把从零搭建 skill 注册表、设计 schema、治理选路冲突、上线评估的完整路径都捋一遍,适合那些已经跑通基础对话、正在做复杂工具的团队参考。

1. Skill 体系的真正价值:为什么十几行 schema 能决定 Agent 成败

1.1 从裸工具调用到有组织的技能清单

早期搭 Agent,大家最喜欢直接把函数塞给模型。模型确实学会了调用,但前提是函数量少、语义差异大。一旦功能超过七八个,函数描述之间开始互相干扰,模型的选择准确率会肉眼可见地掉。你给它 30 个工具,它反而像站在自助餐厅里,面前摆满了盘子,就是不知道先拿哪个。

agent-skills 的本质不是多一层包装,而是把“能不能调”升级成“该不该用”。每个 skill 自带三块核心信息:什么时候用、允许传什么参数、执行后如何处理结果。裸函数只回答了“是什么”,skill 还要回答“在什么场景下、基于什么条件触发”。

我用一个表格说清楚三者的区别:

维度裸提示词工具调用agent-skills 体系
注册成本低中中高
选路准确率低中高
参数约束能力无部分强
回归测试难度困难一般容易
适合阶段概念验证小产品多场景生产系统

实际项目里你会发现,工具调用只是“可执行”,agent-skills 才保证“选得准、传得对、失败有反馈”。这就是它值得单独立项的原因。

1.2 三要素模型:意图描述、输入契约、执行体

我用一个订单查询的例子来说明三要素长什么样:

{ "name": "order.query.status", "version": "1.2.0", "description": "当用户想查询已登录账号下的订单状态、物流进度、发货或签收信息时使用。如果用户想取消订单,请使用 order.cancel,而不是本 skill。", "inputSchema": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,优先从用户消息中直接提取" }, "status_filter": { "type": "string", "enum": ["pending", "paid", "shipped", "finished", "canceled"] } }, "required": ["order_id"] }, "executor": "query" }

三个部分各有分工。description是给大模型看的,决定选路;inputSchema是给参数校验器看的,决定传输合法性;executor是给业务系统看的,决定最终动作。你平时写接口文档时把三者揉在一起,模型选错时不奇怪,校验失败时不意外。

我在 agent-skills 里还加了一个容易被忽略的字段:prerequisite,比如“需要先登录”“仅限本人查询”。这相当于告诉模型一个前置条件,能大幅减少“模型根本没上下文就强行调用”的情况。该字段并不参与业务执行,只是参与选路,但对生产环境的稳定帮助非常大。

1.3 多少个功能值得引入 skill 体系

我自己的判断线是:超过 5 个能由大模型直接触发的动作,或者存在两个及以上语义相近的接口时,就该动手整理。过早引入会有文档负担,过晚引入会让模型形成错误调用惯性。我见过一个项目,只有三个接口但用户体验很好,因为每个函数描述足够清晰;也见过 20 多个接口全部包装成 skill,却因为 description 写得像使用手册,选路照样混乱。

所以引入 skill 体系不是越早越好,而是当“选路难度”超过“维护成本”时再上。大多数做 Agent 的团队,基本上在第一个 Demo 上线后就会撞到这道坎。

2. 从零搭建一套可运行的 skill 结构

2.1 目录规划与命名规范

agent-skills 的命名我建议直接用“域.动作.对象”的格式,比如order.query.status、shop.coupon.calculate、user.address.update。这样注册表里一目了然,日志里也能快速定位。不要用doSomething这类毫无识别度的名字,更不要用中文名当 skill 的 id,中文描述放在 description 字段里就够了,ID 越稳定越好。

整个目录结构可以这样组织:

skills/ order/ query_status.py cancel.py shop/ coupon_calculate.py user/ address_update.py

每个 skill 文件只负责定义自己的元数据和执行函数。我习惯在文件底部统一导出一个SkillConfig,包括 name、version、description、inputSchema、executor。这样注册过程变成遍历目录,而不是手工在代码里 add。

2.2 Schema 就是技能合约,别相信模型生成的结构

大模型生成参数经常出现三种问题:字段名拼错、枚举值用口语、时间格式不统一。schema 的意义就是把这三种问题挡在业务系统之外。

我用jsonschema做校验,再附加一层安全清洗,例如把“明天”“后天”这类相对时间先交给时间解析器转成绝对时间,再写入参数。模型给出的原始字符串是“明天下午三点”,业务系统一般不接受这种值,skill 层的职责就是转译。

实现时我保持执行体只有两种输出:Success(payload)和Failure(code, detail)。不管底层调用成功与否,返回格式统一。否则每个 skill 各写各的错误格式,Agent 后续就很难做通用兜底。

这里给出一个极简的注册表实现示例:

class SkillRegistry: def __init__(self): self._skills = {} def register(self, skill_config): if skill_config.name in self._skills: raise KeyError(f"duplicated skill: {skill_config.name}") self._skills[skill_config.name] = skill_config def get(self, name): return self._skills.get(name) def list_skills(self): return list(self._skills.values()) def load_from_dir(self, path): for file in path.glob("skills/**/*.py"): module = importlib.import_module(file.stem) self.register(module.SkillConfig)

上面这段是骨架,实际项目中还会加上版本兼容和权限字段。但核心思路不变:注册表必须能从代码里自动发现 skill,而不是每次新增一个功能都要改主流程代码。这样后续维护才能持续下去。

2.3 一套执行体的三段式容错

执行体我设计成三段:入参校验、业务调用、错误归一化。凡是在第一段失败的就不要进入第二段,否则业务系统会收到一堆非法请求。

def execute(skill_name, raw_args, context): config = registry.get(skill_name) validated = validate(raw_args, config.inputSchema) if not validated.ok: return Failure("PARAM_ERROR", validated.errors) try: payload = call_executor(config.executor, validated.args, context) return Success(payload) except PermissionDenied: return Failure("PERMISSION_ERROR", "需要用户授权") except Timeout: return Failure("SYSTEM_ERROR", "服务超时")

错误码设计成五类就够用了:PARAM_ERROR表示参数不合法,PERMISSION_ERROR表示没有权限,SYSTEM_ERROR表示底层服务异常,CONTEXT_NEEDED表示信息不足需要追问用户,UNSUPPORTED表示当前环境不满足前置条件。Agent 拿到这五类错误,可以各自走不同的话术和重试策略,而不是把所有失败都当作“对不起,我做不到”。

3. 选路工程:让模型在几十个 skill 里稳定选中正确的那个

3.1 描述文案决定选路准确率

在 agent-skills 里,最重要的不是标点、不是注释,而是 description 的措辞。很多团队把它写成接口说明——“本函数用于查询订单”,这是典型的错误写法。既然模型是靠语义匹配做判断,描述就应当写成意图声明:

  • 错误示例:查询订单状态的函数,接受订单号参数
  • 正确示例:当用户想查询订单状态、物流进度、发货信息或签收情况时使用

我会再多加一句排除说明:“如果用户要取消订单,请使用订单取消 skill,不要使用本技能”。这一句能把相似意图的误选率压下一大截。别小看这种负样本提示,它在实测里比给模型加 system prompt 有效得多,因为提示是贴在具体能力旁边的,模型在决策前一步就能看到。

3.2 参数约束要替模型“减负”

模型填参时最讨厌开放式字段。你让它填一个keyword,它能给你发挥出各种花样。我会在 schema 里尽量使用枚举和正则:

{ "transport_type": { "type": "string", "enum": ["express", "mail", "self_pick"] } }

枚举能让模型的生成空间收敛。比如用户说“快递给我送来”,模型能把 transport_type 填成express,而不是快送服务。时间参数统一用 ISO 8601 并要求带时区,减少后续解析负担。对可选参数,我的建议是:能不给默认值就不给。默认值往往会让模型误以为某个参数是必填,从而编造一个不存在的值填充进去。更好的方式是允许缺省,并在描述中写清楚“缺省时执行方按用户最近使用场景推断”。

3.3 相似 skill 的冲突消解

skill 一多,必然出现“查询订单状态”和“查询物流轨迹”这种高度相似的功能。我的处理顺序是三层:

第一,给 skill 增加prerequisite和禁止条件;第二,在 description 里写清边界;第三,如果还是分不清,那就合并成一个 skill,把差异化放到参数里。比如查询订单状态和查询物流轨迹完全可以合并成order.query.status,用query_type参数区分。与其让模型在两个相似功能里猜,不如让它在两个参数值里选,准确率会高很多。

还有一个容易被忽略的思路:给相似 skill 设置“互斥标签”。注册表在整理 loading 阶段会扫描同域 skill,如果发现两个 description 相似度过高,直接报构建警告。这在自动化测试里能有效拦截问题。

3.4 选不中的时候要能体面退出

不可能每次选择都正确,选不中也必须有反馈路径。我会在 skill 列表最后放一个兜底 skill:common.clarify,专门负责“信息不足时继续追问”。模型如果对当前用户意图没有把握,应当调用它来反问,而不是硬猜一个技能执行。这个兜底跟传统客服里的“抱歉我没有理解”不一样,它要求模型把猜测说出来,然后让用户确认,比如“您是想查订单还是查物流呢”。

实测中这个兜底 skill 的重要程度被严重低估。它起到了“降级”的作用,让整个系统即使选错也不至于直接执行错动作,用户体验会好一个档次。

4. 四类高频 skill 的实战写法与常见坑

4.1 查询类 skill:先把边界定义清楚

查询类是最好写也最容易漏坑的。我在订单查询 skill 上踩过最深的坑是时区。用户说“昨天买的”,模型直接把它翻译成 UTC 的昨天,而业务系统的订单时间是按北京时区存的,结果查不到数据。后来我把时间参数限定成带timezone_offset的组合结构,并在 schema 描述里注明“所有时间均以用户所在时区为准”。

另一个坑是状态枚举。用户口语表达是“退了的”,但枚举里只有canceled。模型可能会把两者映射失败,干脆传了 null。所以状态枚举一定要把用户常用口语词写进 description,例如“退了的= canceled,签收= finished”。描述不是写给文档读者看的,是写给模型看的,口语化一点没关系。

4.2 操作类 skill:幂等和确认缺一不可

做订单取消、发送消息、修改密码这类操作 skill,第一原则就是幂等。大模型在遇到手滑时,同一个取消指令可能会在几轮对话里被触发两次。如果底层接口没有天然幂等,skill 就必须自己生成一个request_id,并在上下文里记住已经执行过的操作,重复请求直接返回上次结果。

更稳妥的做法是在操作步骤前插入显式确认。我一直坚持:任何不可逆动作,skill 执行体不允许直接执行,必须返回CONFIRM_REQUIRED,让 Agent 先用自然语言询问用户。很多人觉得这样体验变重了,但它能把误操作率从灾难级降到可控级。

4.3 生成类 skill:不要把全文塞回上下文

让 Agent 写日报、总结文档、生成代码时,skill 返回的结果往往很长。如果执行体把一整篇文档原封不动放回对话上下文,很快会把模型上下文窗口撑爆。我在 agent-skills 里的做法是:执行体返回结果摘要和引用地址,完整文本写入对象存储或文件仓库,由前端提供查看入口。

例如一个生成周报的 skill,返回结构是:

{ "summary": "已生成周报,共 12 个部分,覆盖项目进展与风险", "file_id": "report-1024", "size_bytes": 18320 }

模型只需要把 summary 讲给用户,文件通过file_id打开。这样做以后,用户的对话里不会再塞进几千字的长文,后续多轮追问也有据可查。

4.4 多步骤协调类 skill:职责别越界

最后一种经常被搞砸的是“流程型 skill”。比如“帮我下单并申请发票”,有人会把它做成一个巨大的 skill,内部依次调用三个子接口。我一开始也这么干,后来发现一个问题:只要中间任何一步失败,模型的上下文里就残留半截流程状态,用户一句“那算了”,整个上下文全乱掉。

多步骤协调不应该属于 skill,而应该属于编排层。agent-skills 里的每个 skill 保持“单步可完成”的状态,跨步骤的状态管理放到 Agent 的对话上下文或独立编排器里。如果确实需要封装为 skill,我会严格控制它在内部最多调用两层子 skill,并设置执行深度计数器,避免递归死循环。

4.5 我踩过的三个高频坑汇总

坑表象修复方式
description 写太长模型找不到重点,选了一个风马牛不相及的 skill限制在 80 字内,先说意图边界再说排除项
可选参数给了默认值模型凭空填值,导致数据被污染去掉默认值,用缺省表达“未知”
技能名称语义太宽order命名,既查订单又改订单,选路全乱拆成order.query.*和order.cancel等窄域技能

这些坑都不是模型能力问题,而是职责划分和描述规范问题。只要把这些细节收拾干净,Agent 的稳定性会显著提升。

5. Skill 治理:权限边界、可观测性与灰度上线的三板斧

5.1 权限模型要贴着 skill 走

给 skill 加权限不是为了让系统显得“安全”,而是让大模型的每一个动作都有边界可查。我在每个 skill 上声明一个权限级别,常用三种:

等级典型 skill策略
只读查询订单、搜索商品直接执行,不需要用户确认
受限写更新草稿、加入收藏执行后通知用户
高权限支付、取消订单、发消息必须用户显式确认,记录审计日志

权限校验的位置放在执行体入口,跟业务系统解耦。这样即便某个 skill 重复注册,或者被其他 Agent 误调用,权限边界也能兜底。

5.2 超时、沙箱和返回裁剪

每个 skill 必须单独设置超时,不能跟 Agent 的等待时间共用一套默认值。查询类可以 5 秒,生成文档类的可以放宽到 30 秒。执行体内部如果调用第三方命令或加载不可信代码,需要放进独立进程或沙箱环境里跑,避免一个 skill 崩溃拖垮主进程。

返回裁剪也很重要。我在注册表里增加max_payload_size,执行结果超过阈值就只保留关键摘要,详细内容走文件引用。这既保护上下文窗口,也防止异常数据泄漏到整个对话里。

5.3 可观测性:记录选路轨迹而不是只记录报错

Agent 调试难,难在不知道模型当时为什么选了这个 skill。我建议每条调用链都记录:trace_id、用户原始输入、选中 skill 名称、命中版本、参数内容哈希、执行状态、耗时。这样可以复现“模型在 3 个相似 skill 之间犹豫但最终选错”的现场。

指标含义目标值
选路准确率离线测试集中选中期望 skill 的比例95% 以上
参数合法率校验一次通过的比例90% 以上
执行失败率进入执行体后失败的比例低于 5%
兜底触发率触发 clarify/追问的比例参考值,非越低越好

我在生产环境里主要盯“兜底触发率”。如果它突然下降,说明模型开始乱猜而不是主动追问,反而更危险。

5.4 版本管理与灰度

skill 只要上线就会被业务依赖,改动必须走版本管理。我采用语义化版本号,注册表允许多个版本并存,同时提供一个default_version指针。灰度发布时让 2% 的流量走新版本 skill,观察执行失败率和用户侧响应。一旦异常,立刻把指针拨回去,全量回滚在物理上只需要改注册表一个字段。

版本管理还能解决另一个问题:不同设备上的 Agent 版本差异。移动端旧的上下文里可能引用 v1 参数结构,新 skill 注册表要保留兼容入口,不要直接删掉旧版本。

6. 评估体系与上线之后我学到的几件事

6.1 离线行为测试集比人工对话测试更靠得住

我发现靠几个人在测试环境里跟 Agent 反复对话,很难全面验证 skill 的选路情况。更好的做法是搭一套行为测试集:每个样例包含用户输入、期望选中的 skill、期望参数断言。跑一次全部用例,得到选路准确率和参数命中率。

一个测试集里的样例经过整理可以长这样:

用户输入期望 skill期望参数断言
帮我看看 2024-1123 那单发货没order.query.statusorder_id == "2024-1123"
我想退掉刚买的那个耳机order.cancel无硬性参数,需确认
推荐一款适合跑步的耳机shop.product.searchcategory 包含 "耳机"

每次修改某个 skill 的描述或 schema 后,都可以快速回测全部样例,不会出现“改了一个 skill 把另一个功能的准确率带崩”的情况。

6.2 线上金丝雀与回归节奏

离线测试集再完善,线上场景永远会有新的表达方式。所以上线时我会做金丝雀发布,先放 1% 流量,观察核心指标稳定后逐步放大。如果发现某个用户群体反复触发兜底,就补充对应的测试样例,而不是临时改 prompt 把问题压下去。

回归节奏建议放在每次description变更之后。一个描述文案的小改动,在离线测试集上可能看不出差别,但线上选路分布会明显偏移,所以必须循环走“改动—离线回测—金丝雀—全量”。

6.3 错误聚类复盘:最常被忽视的一步

每周我会把线上的失败轨迹拉出来,按“用户真实意图 vs 实际选中的 skill”做聚合。结果往往不是随机错误,而是集中在某几个相似 skill 对之间。这时候修复动作就很明确:给这两个 skill 的 description 加互相排他提示,或者合并成一个 skill,用参数区分。

这种复盘的核心是纠偏,不是惩罚模型。模型选错,多数时候是因为我们提供的技能边界描述跟用户语言习惯不匹配。出现误选,先从 skill 描述找原因,而不是怪大模型不听话。

6.4 我从这套实践里学到的最大经验

现在回看这个 agent-skills 体系,最深刻的感受是:决定一个 Agent 好用程度的,不是模型知识有多强,而是我们能不能把自己的能力边界描述得足够清楚。模型负责探索,skill 负责收敛;没有好的技能边界,再强的模型也只会乱撞。

最后分享一个我一直保留的习惯:每次新加 skill,先模拟三个最刁钻的用户问题去测它,专门挑那些可能混杂其他意图、需要追问的句子。只要这些刁钻案例能通过,正常场景基本不会崩。这套体系跑了大半年,整体稳定性和可调性都远超之前的裸工具方案,也让我真正相信:把基本功做扎实,比追任何新框架都值。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 8:29:37

LiteLLM实战:搭建统一大模型API网关,告别多模型适配之痛

如果你维护过哪怕一个需要对接多个大模型API的项目,你对下面这种“割裂感”一定不陌生:每个厂商都有自己的SDK、各自的鉴权方式、互不兼容的响应结构,甚至连token计费口径都不一样。项目里光写适配代码就占了小半本需求文档,换一个…

作者头像 李华
网站建设 2026/10/14 8:29:33

改进多元宇宙优化算法在电力故障定位中的应用

1. 项目概述 电力系统故障定位是保障电网安全运行的关键技术环节。传统方法在面对复杂电网拓扑时往往存在定位精度不足、计算效率低下等问题。本文将分享一种基于改进多元宇宙优化算法(MVO)的故障定位方法,该方法通过算法重构和工程适配&…

作者头像 李华
网站建设 2026/10/14 8:29:06

Java开发必知:PO、DO、DTO等对象模型详解与实践

1. 对象模型概念解析 在Java企业级开发中,我们经常会遇到各种以"O"结尾的缩写对象模型,这些看似简单的字母组合背后,实际上体现了软件工程领域对于职责分离和架构分层的深刻思考。记得我刚入行时,曾经在一个项目里把DTO…

作者头像 李华
网站建设 2026/10/14 8:29:02

React Native跨平台Toast组件开发与鸿蒙适配实践

1. 跨平台移动开发的现状与挑战移动应用开发领域长期面临着平台碎片化的难题。作为一名经历过多个跨平台框架迭代的开发者,我深刻体会到在不同操作系统间保持功能一致性的痛苦。React Native 作为目前最成熟的跨平台解决方案之一,其"Learn once, wr…

作者头像 李华
网站建设 2026/10/14 8:28:24

基于SSM与Vue.js的房屋租赁系统开发实践

1. 项目概述苗苗找房系统是一个基于SSM(SpringSpringMVCMyBatis)和Vue.js技术栈开发的房屋租赁与生活服务综合管理平台。作为一名长期从事Web开发的工程师,我认为这个系统的核心价值在于解决了传统租房市场的三大痛点:信息不对称、…

作者头像 李华
网站建设 2026/10/14 8:28:20

UV像素化技术解析:从原理到游戏影视应用

1. 像素化UV节点技术解析在三维图形处理领域,UV映射的质量直接影响纹理贴图的最终呈现效果。PixelateUV节点作为一种特殊的着色器工具,能够对UV坐标进行像素化处理,为数字内容创作带来独特的视觉风格。这种技术最初源于8-bit游戏的复古美学需…

作者头像 李华