首页 / 资讯中心 / 文章详情

大模型工具调用失败?从Skill命名到参数Schema的9条最佳实践

大模型工具调用失败?从Skill命名到参数Schema的9条最佳实践 ★ FEATURED ARTICLE
前几天同事跑来找我说他的文件解析 Skill 上线两天接口一条请求都没收到。他在测试台手动传参调了十几遍全部正常可一到大模型对话里就彻底隐身。我把他的 Skill 名称和描述调出来看了一眼又对比了平台上另一个功能相近的内置能力问题一下就清楚了当用户说帮我看看这个 PDF 里写了什么时模型在工具清单里同时看到了他的 Skill 和另一个描述更直观的能力最终选了后者而他的 Skill 从头到尾都没被看见。这个场景在接入大模型能力的开发里太常见了。很多人以为 Skill 不被调用就是执行代码有 bug实际上十次有八次问题出在更靠前的位置——模型拿到手的那份工具清单就已经决定了它会不会选你。这篇文章会把一次 Skill 调用从注册到执行的完整链路拆开然后把我们团队在多个项目中总结出来的 9 条最佳实践逐一展开。内容覆盖命名、描述、参数、路由、缓存、日志和超时这些环节无论你是刚接触 Skill 机制的新手还是已经被线上 0 调用折磨过的老手都能从里面找到可以直接落地的排查方法。1. 拆解触发链路你的 Skill 是在哪个环节被拦下的很多人在排查Skill 不被调用时第一反应是看执行日志和接口监控。这没错但如果问题根本没走到执行这一步你看到的自然是一片空白。所以先搞清楚 Skill 从定义到发生调用的完整链路比什么都重要。1.1 一条 Skill 从注册到执行的四个环节一个 Skill 要走完被调用这条路通常要经过四个环节注册你在平台或配置文件里定义 Skill 的元信息包括唯一标识、名称、描述、参数声明、执行端点和权限。这一步的产出是一份结构化清单相当于把我有哪些能力告诉给了平台。注入每次会话请求开始前平台会把当前可用的 Skill 清单拼进发给大模型的上下文里通常是 system prompt 或工具列表。这里有一个非常容易被忽略的限制清单不是无限长的模型能同时看到的工具数量受上下文窗口限制。路由模型根据用户当前这句话与 Skill 名称、描述的匹配程度决定要不要调用、调用哪一个。即使你的 Skill 出现在清单里模型也可能认为另一个更合适或这次根本不需要工具。执行路由命中后平台把用户请求中的关键信息抽取成参数交给你的接口去跑再把返回结果拿回来交给模型组织回答。这里面最容易出问题的就是注入和路由因为它们的结果看不见、摸不着不像接口报错那样直观。想定位问题就要先判断失败发生在哪一环。1.2 四类失败分型不可见、不匹配、被拒、被弃根据我排查过的线上问题Skill 不被调用基本可以分成四种类型。判断的依据很简单看加载日志、命中日志、校验日志和执行日志四段里你的 Skill 消失在哪一段。失败类型典型现象常见原因定位手段不可见模型完全没提过该功能命中日志无记录未注册成功、被下线、清单被截断、被平台过滤打印真实请求的注入内容搜 Skill 名不匹配模型调用了另一个 Skill或回复没有这个功能名称/描述与用户意图匹配度不够功能重叠用真实用户原话做命中测试看路由结果被拒路由命中但接口没收到请求或参数校验报错必填参数缺失、类型不对、枚举值不合法看校验阶段日志复现一次带参数的调用被弃有请求日志但执行超时或返回结构不合规接口响应太慢、返回值与声明不一致看执行阶段耗时和响应体大多数0 调用的问题都集中在第一类和第二类。换句话说你的 Skill 要么根本没出现在模型眼前要么出现了但模型不认为它该被调用。1.3 把模型看到的清单想象成菜单我经常跟同事打一个比方模型就像一个顾客Skill 清单就是餐厅菜单。菜单上没这道菜顾客自然点不了菜单上有这道菜但描述写得含糊不清旁边又有好几道差不多的菜顾客大概率会随便选一个或者干脆不点。你写的名称、描述、参数说明就是这份菜单上唯一能打动顾客的文字。所以后面讲的 9 条最佳实践本质上是围绕一件事让模型在任何可能用到你的场景里都能快速、准确地认出你并且知道怎么把参数填对。2. 声明阶段的三件小事名称、描述、参数 Schema 写不好后面全白搭Skill 声明是后面所有环节的数据源头。我见过太多人随便写个名称和一句话描述就发布了结果线上数据一片惨淡。多花十分钟把这三样打磨好比事后排查节省一整天。2.1 名称唯一、短、用动词宾语模型在做路由选择时会同时看名称和描述做语义匹配。名称如果和用户表达高度一致会显著提高被选中的概率如果起得太抽象或者与其他 Skill 重名平台可能只保留一个路由时还会随机。我自己习惯用的格式是动词宾语语义直接、一眼可懂。不要带版本号比如 parse_file_v2 这种版本变更不会因为名称变化获得更好的路由反而增加混淆感。名称写法评价理由do_stuff差完全看不出功能模型无法匹配意图fileprocessing一般语义泛用户不会用处理文件来触发parse_uploaded_file推荐动词 parse 宾语 uploaded_file清晰直接平台一般有技能命名唯一性约束如果重名往往会导致其中一个不可见。所以发布前搜一遍现有 Skill 列表确认没有同名是最基本的动作。2.2 描述写什么情况下调用别写功能说明这是九个实践里性价比最高的一条但也是最容易被忽视的。很多人写的描述是解析用户上传的文件并返回内容这本质上是一句功能说明书它告诉模型我能干什么但没告诉模型用户怎么说的时候该叫我。模型的匹配逻辑是基于语义相似度打分的。你越贴近用户的真实表达习惯分数越高。正确写法应该是先写触发场景再给 2-3 个典型表达最后把与其他 Skill 的边界说清楚。错误示例解析用户上传的文件并返回内容。正确示例当用户上传或提到一份文档、图片、表格并希望读取或提取其中内容时调用 例如帮我看看这个PDF、提取Excel里的数据、这个图片里的文字是什么。 注意生成总结或摘要请调用另一个Skill summarize_content本Skill只负责提取原文。关键点有三个写触发场景、给典型表达、明确边界。描述也不是越长越好太长会占用模型上下文控制在 100-200 字内把高频场景和互斥说明放在最前面。你要不要在描述里写清不要做什么答案是一定要写因为模型在犹豫选哪个时边界说明就是它做决定的关键依据。2.3 参数 Schema必填、枚举、示例值一个都不能少路由命中之后模型要根据参数声明去猜该怎么传参。如果必填字段没标清楚模型可能直接跳过如果字段格式没有示例值模型可能填入一些你无法解析的内容。我看过一个真实的报错日期字段在 schema 里声明成 string没写格式模型在对话里听到用户说明天就把明天传了过去接口解析直接失败。这种情况完全可以通过 schema 优化避免。推荐写成这样{ type: object, properties: { file_url: { type: string, description: 用户提供或上传文件的访问地址必须以 https:// 开头。如果用户只提到文件名请先调用文件查询接口获取 URL。, format: uri }, extract_type: { type: string, enum: [text, table, image], description: 要提取的内容类型默认 text, default: text } }, required: [file_url] }注意几个细节每个字段 description 要写清楚如果用户没有直接给全该怎么补;有默认值的字段给 default模型就不用纠结要不要传;能用枚举的不要用自由字符串模型填空的能力没有你想象的那么强。Schema 是给模型看的 API 文档文档写得不清楚模型的调用请求自然不准确。3. 路由层那几个看不见的规则重叠、遮蔽、截断就算名称、描述、参数都过关了路由层还有几道隐形的坎。这几个坎有一个共同点它们不会直接报错只会悄悄降低你的命中率甚至让你的 Skill 完全消失。3.1 功能重叠等于互相稀释当一个场景同时有两个 Skill 在竞争时模型的选择会变得不稳定。比如一个叫文本总结一个叫会议纪要提炼当用户说帮我把这段会议记录总结一下时模型可能在两者之间随机选择也可能反复切换最终导致两个 Skill 的命中率都很低。解决办法是职责拆分要彻底功能重叠时要么合并要么在描述中明确互斥。一个简单自检方法把两个 Skill 的描述并排放在一起遮住名称后如果分不清谁是谁那模型也会困惑。合并之后即使逻辑内部区分场景也要保证对外只有一个入口。3.2 同名与同功能遮蔽内置能力优先很多平台自带一批内置能力比如提醒、计时、搜索。内置能力在路由优先级上往往高于自定义 Skill。如果你再定义一个定时提醒Skill用户说提醒我三点开会时模型大概率会选内置能力而不是你的自定义实现。我不建议硬碰硬去覆盖内置能力除非你的 Skill 有明确的差异化价值。比如内置提醒只支持单次提醒你的 Skill 支持按周循环、按节假日跳过这就能成为被选择的理由。但前提是你要把这种差异写进描述的前两行让模型一眼就看出这个自定义版本有内置能力给不了的东西。3.3 工具列表截断模型一次只看得到前 N 个上下文 token 是有限的平台通常只把 Skill 清单的前 N 条注入给模型。如果你的 Skill 排在最后而总数量超过上限它根本不会出现在模型眼前——这不是匹配度的问题是彻底的不可见。要验证这一条最直接的办法是打印一次真实请求的完整提示词或工具列表在原文里搜你的 Skill 名。如果搜不到说明它被截断了。应对方法是在不影响语义的前提下精简描述让高优先级的 Skill 排到前面。Skill 特别多的时候也可以考虑两级路由设计一个调度类Skill让模型先调用调度器再由调度器根据参数决定实际执行哪个子模块。调度器本身只做路由不承载业务逻辑描述里讲清楚这个 Skill 负责判断并转给合适的处理模块就够了。4. 上线后真正的敌人缓存、日志、超时和返回结构很多人以为 Skill 发布上线就万事大吉实际上线上问题往往出在你看不见的环节缓存让模型拿着旧描述跑了半天日志缺失让你无从下手超时让 Skill 在关键时刻被弃用。这一章讲的四个坑每一个我都踩过。4.1 发布新版本后模型拿到的还是旧描述Skill 清单在构建时会被打包进一个产物这个产物经过 CDN 或缓存层分发。你改了描述并发布但如果缓存没有失效模型拿到的还是上一次打包的旧内容。最典型的表现是你在后台看到新描述已经生效测试台调用也正常但线上真实请求里依然是旧版本。所以发布后的第一件事不是写代码而是确认模型实际拿到的清单版本。具体办法是调取任意一个真实会话的注入日志检查 Skill 名称、描述、参数声明是否与最新版一致同时确认构建时间和版本号变化。必要时在构建流水线里给产物加上 hash 作为缓存标识从根上避免改了没生效的问题。4.2 日志要分成四段来打只打执行日志是排查不被调用时最大的痛点。如果路由阶段就没命中你连一条执行日志都看不到等于什么都没有。我建议按四个阶段分别埋点加载日志记录请求开始时模型拿到的 Skill 清单至少包含数量和名称列表。路由日志记录模型最终选择了哪个 Skill。如果没有选中任何 Skill记录模型的原始回复文本。校验日志记录参数解析结果失败的请求要存下原始参数和校验报错。执行日志记录调用耗时、状态码、返回体以及返回给模型之前做过的后处理。四段日志配合才能准确判断失败属于不可见不匹配被拒被弃中的哪一类。日志不用打得很详细但关键字段必须有。比如路由日志这一行要能区分模型没看到这个 Skill和模型看到了但没选它。4.3 超时、并发和返回结构Skill 执行一般有超时上限常见是 3-5 秒。一旦超时平台会判定调用失败并终止该 Skill 的后续调用模型可能直接告诉用户这个功能暂时不可用。所以外部接口一定要套一层超时控制宁可快速失败也不要拖到整体超时。快速失败的好处是模型拿到错误后还可以重试一次或者给出友好的兜底回复。返回给模型的结构也要稳定。模型需要从返回结果中提取信息来组织最终回答如果你返回纯文本、大段 HTML 或一个结构不明的 JSON模型解析效率会很低。推荐固定成这样的结构{ status: success, summary: 共提取到 3 个段落其中表格 1 个, data: { paragraphs: [...], table_count: 1 } }summary 字段给模型一个快速了解结果的口径data 字段放结构化的详细内容。返回结构一旦和参数声明呼应起来模型的回答质量和稳定性都会有明显提升。5. 一次真实排查从0 调用到定位并修复的全过程前面讲的是方法论这一章给你看一个完完整整的排查案例。这个案例发生在我们的模拟项目 X 里过程很典型你可以对照着自己的情况复现排查思路。5.1 现象接口一条请求都没有同事 A 开发了一个会议纪要要素提取Skill功能是提取会议记录中的待办事项、负责人和截止时间。上线两天接口监控显示请求量为 0。他在测试台手动传参功能完全正常所以第一时间排除了执行代码的问题。从现象看这属于典型的不匹配甚至不可见直接进入中间链路排查。5.2 三步定位加载、命中、校验第一步确认 Skill 在不在模型可见的清单里。我调取了一个真实会话的注入日志搜 Skill 名称发现它在清单里字段完整。这一步排除了不可见。第二步模拟真实用户原话做路由测试。我们拿帮我把这段会议记录里的待办事项提取出来这句话反复测试发现模型总是倾向于调用另一个文本总结Skill而不是他的要素提取。把两个 Skill 的描述并排对比后发现问题同事 A 的描述只写了一行提取会议记录中的结构化信息既没有典型用户表达也没有和文本总结做边界说明。模型看到总结和提取两个词同时出现时选了描述更泛、措辞更接近用户原话的那个。第三步看参数校验。同事 A 在手动测试时总是传 full_url 和 extract_type但真实对话里模型经常只抽取到 full_url把 extract_type 当成无关字段丢掉了。由于参数声明里没有 default模型也默认它可以不传导致接口侧拿不到预期的输入一次请求都发不出去。5.3 根因与修复根因有两个一是描述信息不足没有触发场景和边界说明导致模型不愿意选它二是参数声明里 extract_type 缺少默认值模型认为这个字段可省接口却要求一定要传。修复方案也很直接在描述里补上触发场景和 2-3 个典型表达比如把这段记录里的待办列出来这个会议有哪些负责人。在描述末尾加上注意生成摘要请调用 summarize_content本 Skill 只负责提取结构化要素把边界讲清楚。在参数 schema 的 extract_type 字段里加上 default 值并声明枚举范围。重新发布后用同一条用户原话再做命中测试确认路由结果已经指向正确的 Skill。5.4 这个 case 教会我的事排查过程走完最大的感受是Skill 不被调用往往不是代码的问题而是你写给模型的那份菜单出了问题。描述是给模型看的 API 文档schema 是给模型看的参数使用说明这两样东西写得不清楚执行代码写得再好也白搭。另外一定要用真实用户的表达方式去测试别总是用自己设计好的标准语句那会掩盖掉大量真实场景里会出现的措辞问题。我自己现在的习惯是每次发布新 Skill 之前把它想象成一个第一次进店、对菜品一无所知的顾客用三句话向他介绍这道菜什么时候该点、点了之后服务员会不会把菜上对。能说清楚的上线之后的命中率一般都不会差。要是你们团队也遇到过类似的诡异问题不妨按这条思路走一遍大概率会在描述或者参数声明里找到答案。
阅读完成 · 觉得有帮助?
咨询建站