资讯动态

手写Tool Schema:从零打造高质量的LLM函数调用操作手册

发布时间:2026/10/1 13:56:17 来源:尧图企业网站定制
1. 为什么要手动创建tool schema1.1 先从一个真实翻车场景说起上个月我在做一个客服工单自动分类的Agent项目工具函数很简单就是一个create_ticket接收部门、紧急程度、问题描述几个参数。最初我图省事直接用TypeScript的函数签名自动生成了一份tool schema结果联调的时候问题一堆。最典型的场景是用户在对话里说“我的网络老掉线很急”Agent居然把department填成了“很急”把description填成了“网络老掉线”然后把紧急程度留空。乍一看好像也在“工作”但工单进系统之后全是错的。后来我手动重写了这份schema给每个字段加了边界、枚举约束、示例值还专门告诉模型“什么情况下不要调用这个工具”。同一套对话识别准确率直接从惨不忍睹提升到了能用的水平。说真的tool schema在外行眼里就是一堆JSON字段说明但在实际工程里它就是Agent的“操作手册”。模型不认识你的函数它只认识你给它的schema。schema写得糙Agent的行为就糙这不是模型能力问题是你没告诉它规则。1.2 “自动生成”天然缺少三层信息很多框架和语言都提供了“从函数签名自动生成tool schema”的能力比如Python的Pydantic、TypeScript的Zod都能从类型定义推导出JSON Schema。这种方案做原型很快但投入真实业务的时候你很快会发现自动生成的schema天然缺三层东西。第一层是业务语义。函数签名里的name: string到了真实场景可能是客户姓名、产品名称、订单号这些语义只有你能写清楚。第二层是约束条件。函数参数有取值范围、格式要求、依赖关系比如“紧急程度只能是0到5的整数”“手机号必须是11位数字”类型系统表达不了这些需要手动写进schema。第三层是使用策略也就是什么场景下该用这个工具、什么场景下不该用。三个工具、几十个参数摆在模型面前如果缺少这层说明模型就会像没见过世面的实习生拿到什么都想试一下。自动生成适合开发阶段的快速验证但到了打磨线上效果的时候手动创建tool schema就是绕不开的一步。它不是“返工”而是Agent工程质量的关键环节。2. tool schema的核心设计拆解2.1 schema的本质一份写给LLM看的“操作契约”很多人把tool schema理解成“接口文档”或者“参数校验规则”其实都不完整。它的真实身份是模型和你的系统之间的一份“操作契约”。所谓契约意味着双方都要遵守。你的系统承诺“只要你按格式给参数我就执行对应的功能”模型承诺“我按你的schema理解每个字段的含义并正确填充”。这份契约以JSON Schema的形式呈现本身就是模型上下文的一部分。也就是说schema不只是校验工具它还会被模型逐字阅读。这就引出一个关键推论你在schema里写了什么、怎么描述、用什么措辞都会直接影响模型的行为。举个例子同一个参数如果你写description: 紧急程度模型可能传0到10之间任意值如果你写description: 紧急程度0为不紧急5为非常紧急仅在用户明确表示时间紧迫时填5模型的填充准确率会显著提升。原因很简单模型的判断依据就是你给的描述描述越具体模型的猜测空间越小。所以我会把schema当成“写给AI的产品说明书”来写而不是当成“给后端开发看的接口文档”来写。这是两种完全不同的写作方式。2.2 手动创建的工具选型JSON Schema原生的力量在正式开始之前先统一一下技术选型。目前各大Agent框架的tool schema基本都兼容OpenAI定义的JSON Schema格式。无论你用的是OpenAI Function Calling、Claude的tool use还是开源的Agent框架最终的tool参数基本都逃不出JSON Schema的范畴。我建议手动创建时直接以JSON Schema为基准。有两个原因一是JSON Schema是一个标准规范有大量现成校验器和工具链支持比如ajv、python-jsonschema二是JSON Schema的表达能力足够强支持枚举、正则、嵌套对象、数组、条件约束一个业务工具90%以上的参数约束都能表达清楚。如果你用的是TypeScript或Python也可以先用Zod或Pydantic“描述”约束然后通过工具转成JSON Schema。但这里有个陷阱转换工具只负责语法层面的映射不负责语义层面的丰富。比如Zod的z.string()转出来就是一个{ type: string }你对字段的业务说明、枚举约束、示例值还是得手动补上。所以这篇文章的核心方式就是先理清业务逻辑然后逐个字段手动打磨最后落地成JSON Schema。如果实在需要代码生成辅助生成之后也必须人工review和补充。3. 手动创建实操从零到一份可用的tool schema3.1 第一步先把函数签名写清楚再反推参数结构手动创建schema的第一步不是写JSON而是先定义工具函数本身。我习惯把工具函数当作“系统对外暴露的一个能力”它的签名就是能力的边界。拿我之前做的工单系统举例工具函数长这样def create_ticket( department: str, urgency: int, title: str, description: str, customer_id: int, tags: list[str], ) - str: 创建一条客服工单。 返回工单编号。 ...有了函数签名之后再反推schema结构。这一步的核心原则是凡是要让模型判断的东西都要显式列出来凡是可以由系统确定的东西不要暴露给模型。举个例子customer_id可以通过对话上下文或用户系统自动获取就不要让模型来选。很多项目翻车就是因为把大量内部字段暴露给模型导致模型瞎猜。我在实操中会把字段分成三类模型必须提供的如描述、紧急程度、模型可以从上下文中推断的如部门、客户ID、用户在没有明确提及时可以留空的如标签。分类之后再决定哪些进schema、哪些由代码填充。3.2 第二步逐字段打磨类型、约束与边界字段结构定好之后最关键的部分来了逐个字段写类型、约束、描述。这里我重点说几个高频易错的点。第一能用枚举绝不用自由字符串。比如部门字段如果你写成{ type: string }模型可能给你填出“技术支持部”“技术支撑”“tech support”各种版本处理起来非常头疼。手动定义枚举模型的选择空间就锁死了。JSON Schema里这样写{ type: string, enum: [technical, billing, sales, other], description: 工单所属部门。technical表示技术问题billing表示账单问题sales表示销售咨询other表示其他。 }第二数值范围要写清楚。紧急程度如果定义成{ type: integer }模型不知道范围有多大可能填个99。加minimum和maximum之后模型的行为立刻收敛。而且注意描述里还要写“什么情况算紧急”这是数值边界替代不了的信息。第三字符串长度与格式建议加上约束。比如title超过100字会让下游系统截断就加maxLengthdescription要求最少10个字就加minLength。这些约束既能让模型填出更合适的值也能在模型出错时让后端校验快速暴露问题。第四数组和嵌套对象要有内部约束。比如tags是一个字符串数组就要写明每个元素的类型和最大数量。很多人只写了顶层type: array结果模型塞了一个对象数组进来JSON解析都过不了。加上items和maxItems等于把嵌套规则也钉死了。3.3 第三步把描述当作写Prompt一样认真对待我个人的体感是schema里description字段对模型行为的影响力比类型和约束加起来还大。自动生成工具最缺的就是这块手动创建的精髓也几乎全在这里。描述里要写什么不是写“这是工单标题”而是写清楚这个字段的实际含义、取值范围背后的业务规则、什么场景下填什么值、拿不准的时候怎么处理。我甚至会在描述里直接给出示例{ type: string, description: 工单主题一句话概括用户遇到的问题。应包含核心关键词例如网络频繁掉线、账单金额异常等。不要包含标点符号和情绪化表达。 }这样写的好处是模型在生成参数时会“看到”示例从而模仿示例的风格来填充。我遇到过不少情况同一个字段描述从一句干巴巴的“问题描述”改成“包含核心关键词的一句话长度控制在20字以内”之后生成质量立刻上了一个台阶。还有一个小技巧在schema的顶层描述里写“这个工具在什么情况下使用”。很多框架允许在tool的description字段设置一句话说明可别浪费了。比如“当用户报告系统故障并希望创建工单时使用本工具。若用户只是在询问流程而不要求创建不要调用此工具。”这相当于给模型划了一条使用红线能有效减少无效的工具调用。3.4 第四步一个完整的精修版schema示例下面给出我最终打磨出来的create_tickettool schema。这份schema在真实项目里测试过多轮命中率和参数准确率都比较稳定{ name: create_ticket, description: 创建一条客服工单并返回工单编号。当用户明确表示需要报修、投诉、咨询并需要后续跟进时使用。如果用户只是闲聊或询问流程不要调用此工具。, parameters: { type: object, properties: { department: { type: string, enum: [technical, billing, sales, other], description: 工单所属部门。technical为技术故障billing为账单或扣费问题sales为购买或产品咨询other为以上都不属于的情况。若无法判断请选other。 }, urgency: { type: integer, minimum: 0, maximum: 5, description: 紧急程度0为可等待5为极度紧急。当用户使用立刻马上宕机等强烈词汇时为4或5当用户表达不满但未说明时间要求时为2或3。 }, title: { type: string, minLength: 4, maxLength: 50, description: 工单主题一句话概括问题包含核心关键词不要使用标点符号和情绪化表达。 }, description: { type: string, minLength: 10, maxLength: 500, description: 用户问题的详细描述。尽量还原用户原话中的关键信息包括出现时间、频率、已尝试的操作、具体报错内容。不要遗漏可帮助定位问题的细节。 }, tags: { type: array, items: { type: string, enum: [network, login, payment, crash, device] }, maxItems: 3, description: 问题标签最多选择3个。network表示网络问题login表示登录问题payment表示支付问题crash表示程序崩溃device表示设备硬件。 } }, required: [department, urgency, title, description], additionalProperties: false } }你可以看到这份schema的每一个字段都不是随便写的枚举值 描述里的业务规则 边界约束 示例风格。这就是手动创建的意义把“模型可能会猜错”的地方全部都提前堵上。4. tool schema实战中的常见问题与排查4.1 问题一模型调用工具了但参数填得乱七八糟这是最常见的问题工具被正确选中但title是一整段对话原文description里混着用户的情绪宣泄urgency填了一个远超范围的值。遇到这种情况我的排查顺序是固定的。先看是不是类型约束太宽。比如title只写了type: string没有maxLength模型有可能会把整句话都塞进去。加上了minLength和maxLength模型反而会更倾向于生成精炼的标题。再看是不是描述里缺少“怎么写”的指导。描述只写“工单主题”模型当然不知道怎么概括描述里明确要求“包含核心关键词、控制在20字以内”模型就会照做。最后看是不是缺枚举。自由填写的字符串字段模型每次填法都不同这是结构性问题不是改一下描述能解决的直接换枚举。4.2 问题二schema看起来没问题但模型就是不调用工具还有一类更隐蔽的问题你提供了一个工具但模型宁愿自己编一个答案也不用你的工具。这种时候问题往往不在parameters而在tool级别的description上。模型需要在很短的上下文里判断“现在该不该用这个工具”。如果tool的description写得太笼统比如“创建一个工单”模型会认为只有在用户字面说出“创建工单”时才该用而用户的真实表达往往是“我的网连不上了快帮我处理”。这不是模型笨而是你没让它理解“什么场景等于创建工单”。我的方法是把用户可能的表达方式直接写进tool描述里。比如这样当用户报告故障、投诉、服务咨询并且事件需要后续跟进时使用本工具。触发场景示例“网络掉线了”“账单扣错了”“登录不上”“我要投诉”。description里放进触发场景示例之后工具调用的召回率能明显提升。类似的效果在OpenAI官方文档里也有提到“description应描述工具的真实子任务而不是重复工具名”。4.3 问题三复杂嵌套结构模型解析直接失败业务复杂的时候工具参数免不了出现嵌套对象。最典型的比如订单查询工具{ type: object, properties: { filters: { type: array, items: { type: object, properties: { field: { type: string }, operator: { type: string }, value: { type: string } } } } } }这种结构模型不是填不出来而是很容易填出不稳定结果比如operator填了一个程序完全不认识的自定义符号或者field填了不存在的列名。嵌套结构越深模型生成的自由度就越大越容易出错。我的建议是嵌套对象里的每个字段同样要加枚举和描述。operator就用枚举限制住只允许eq、gt、lt、containsfield也用枚举限制为真实存在的列名。嵌套结构本身没问题问题是嵌套结构里的自由度没被控制住。自由度越小模型生成越稳定这是tool schema设计中一条贯穿始终的原则。4.4 常见问题速查表与我的排障顺序整理一张速查表方便大家对照排查现象可能原因优先处理方式工具被调用但参数错误描述缺少业务规则逐字段补充示例与取值说明模型不调用工具tool描述未写触发场景在description中写入用户典型表达枚举字段填了可接受之外的值未使用enum约束一律改为enum并覆盖未知值用other兜底数值字段填了离谱值未设置minimum/maximum显式写出范围并说明业务含义数组字段解析失败未约束items与maxItems为items增加类型和枚举限制长度解释性字段内容过冗长未设置maxLength添加长度约束并在描述里给出句式模板模型调用工具过于频繁description未写明“不使用”的场景增加反面说明明确舍弃的情况object内字段缺失required未包含必填字段重新检查必填项补全required排障的顺序也很重要。我通常先做“合法性检查”也就是用ajv或python-jsonschema工具验证schema本身有没有语法问题比如required数组里写了不存在的字段、类型写错等这类低级错误浪费了我不少时间。然后做“调用实验”用几个典型用户query去测模型观察它选不选工具、填什么参数。最后才微调描述和约束。这样每一步都有明确的方向不会白忙。5. 手动创建schema的工程化落地5.1 直接写JSON还是用代码生成器到了这个阶段你应该已经理解了为什么需要手动创建。但工程落地还有一层选择是直接在代码仓库里维护JSON文件还是用Zod/Pydantic定义约束再转成JSON Schema我的答案是维护方式看团队但最终产物必须是一份可校验的JSON Schema。理由有三条。第一JSON Schema是跨语言的Python后端、TypeScript前端都可以用统一的标准校验第二JSON Schema可以脱离代码单独review产品、测试甚至业务同事都能读得懂第三大多数Agent框架接收的输入就是JSON Schema转换步骤在线上反而是多余的一环。如果你用TypeScriptZod的z.string().describe(...).enum([...])写法更贴近开发习惯配合zod-to-json-schema可以生成标准schema。但请记住转换只解决“类型表达”问题不解决“描述丰富度”问题。你依然要在Zod链上手动补describe补完之后再人工读一遍生成的JSON看有没有丢失内容。5.2 为schema建立版本管理意识tool schema上线之后不是一成不变的。业务部门改了流程紧急程度从5级变成3级或者新增了一个部门枚举值这些变化都要同步到schema里。一旦多个Agent共享同一个工具schema的变更就必须有版本意识。我的习惯是这样每个tool schema放在独立文件里命名带版本号例如create_ticket.v2.json。字段变更时更新版本并在变更说明里写清楚“哪个字段动了、为什么动、影响的Agent有哪些”。这样出问题的时候回溯成本会低很多。另一个值得做的是在CI流水线里加一个schema校验步骤确保任何提交到Agent配置库的改动都是合法JSON Schema。这个自动化检查看起来简单但能拦住很多手误。5.3 测试你的schema三个维度的实测方案手动创建的schema到底好不好用不能靠感觉要做三类测试。第一类是结构校验测试用ajv去校验模型输出的参数JSON是否符合schema。这一步是底线保证入参合法。第二类是覆盖率测试准备一组真实的用户对话样本覆盖各种触发场景记录“该调用时是否调用”“调用时参数是否完全正确”。这组测试直接反映schema描述的质量是优化schema的主要依据。第三类是边界压力测试故意使用模糊、歧义、极长的用户输入观察模型的工具调用行为。比如用户说“这个订单不对你们到底行不行”模型能不能识别出需要创建投诉工单还是把空洞的情绪描述填进description跑了。边界测试能暴露描述里的盲区。我见过一些团队用LLM as a Judge的方式让另一个大模型去评估工具调用的质量效果也不错。但成本有点高日常优化还是“人工看对话样本 统计调用准确率”性价比最高。5.4 从手动到半自动沉淀一份自己的schema模板库最后说一个长期价值很高的习惯把常用工具的schema模板沉淀成库。比如工单类、搜索类、订单类、内容生成类这些业务形态高度相似schema结构也有很多共通之处。做过的项目多了之后你会发现大量描述写法、约束模式、枚举设计都是可以复用的。我自己收了一套模板库里面分三块一是各业务模块的典型参数结构二是常用的枚举值写法包括“无法判断时填other”这种兜底策略三是描述撰写的模板句式。新项目接入一个新工具时我直接从这个库里复制、改字段而不是每次都从零开始想。这个习惯大大提升了我的交付效率也保证了多个Agent项目之间工具风格的一致性。如果你刚接触手动创建tool schema我也建议你从第三个工具开始就留意沉淀不要等到积累了几十个工具再回头补。到那会儿你已经记不清每个工具当时是怎么设计的了。6. 最后聊一点我的实际体会做Agent开发这段时间我最大的感受是工具调用准确率不高的时候先别急着换模型、换框架先把tool schema翻出来逐字段问自己几个问题这个字段模型真的知道怎么填吗不填会怎样填错了会怎样描述里有没有把业务规则说透很多看起来像是“模型不够聪明”的问题根源其实是“schema没把话说清楚”。模型从来不知道你的函数内部逻辑它只能在schema描述的范围内做选择。你把边界划得越清楚它跑偏的概率就越小。手动创建tool schema说到底就是在做Agent的“行为塑造”把一份薄薄的接口定义变成一份富含业务规则的操作手册。这件事没有太多炫技的空间靠的是耐心和细致但它对Agent效果的影响远比大多数人想象的要大。希望这篇梳理能帮你在自己的Agent项目上少走一些弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑