一家新开的餐厅里,领班小智刚上任。他口才极好、学识渊博,能陪客人从诗词歌赋聊到人生哲学,却唯独不知道后厨能做什么菜。客人满怀期待地说“来一份招牌菜”,他只能手足无措地站在原地。直到老板递给他两样东西:一本菜品手册,写明每道菜的名字、风味和适用场合;一叠标准点菜工单,把菜名、数量、口味、忌口都画好了格子。从此,小智才真正学会了“接单”。 大模型就像这位领班。它擅长“聊天”,却无法触碰外部世界——查不了天气、读不了数据库、订不了机票。工具调用(Tool Calling)赋予了它“行动力”:当用户请求超出模型的知识边界时,模型会输出一段结构化的调用指令,由外部程序代为执行。而要让这段指令准确、可靠,离不开两个核心设计:工具描述(让模型知道“有什么工具、何时用”)和 JSON Schema(让模型知道“参数怎么传、传什么”)。它们就像菜品手册和点菜工单,一个解决“选什么”,一个解决“怎么填”。 这篇文章将带你从零吃透这组概念:先看工具调用的整体流程,再深入描述编写与 Schema 约束,最后落到工程实践与结构化输出。

岗位关联

学会工具描述与 JSON Schema,你就掌握了 AI Agent 与外部世界对话的“通用语言”,这是构建可靠智能体的核心技能,直接决定你的 Agent 能否从玩具走向生产。

目录
  1. 可视化演示
  2. 工具调用基础:让AI从“聊天”走向“办事”
  3. 工具描述编写:一份让人一眼就懂的“菜单文案”
  4. JSON Schema基础:点菜工单的“规范排版”
  5. 参数约束声明:工单上的“填空格与下拉框”
  6. 组合模式运用:套餐、自选与“如果…就…”的规则
  7. 工程实践要点:后厨管理中的防错与优化
  8. 结构化输出:从“填好工单”到“标准化出菜”
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示LLM工具调用五步闭环、JSON Schema七种类型体系及结构化输出保证机制,交互切换三种模式。

本节配有交互动效(进入视口后自动加载,约 3KB)。

'+CAPS.mode1+'

工具调用基础:让AI从“聊天”走向“办事”

领班小智翻开后厨手册,第一页写着餐厅的服务理念:先听懂客人,再解决问题。客人说“我饿了”,他得判断是推荐招牌菜还是先上杯水;客人说“空调太冷了”,他得决定是调温度还是送毯子。这和大模型的处境一模一样——模型面对用户输入,第一步是决策:这个问题我能直接回答,还是需要借助外部工具?

大模型本质上是“文字接龙大师”,它基于海量文本预测下一个 token,但并不真正“行动”。工具调用(Tool Calling)打破了这层壁垒:当模型判断需要外部信息时,它会输出一个特殊结构的 JSON,声明“我要调用工具 A,参数是 B”。程序拦截这段 JSON,执行真实的函数调用,再把结果作为新消息回传给模型,让模型组织最终回答。整个流程可以概括为:用户输入 → 模型决策 → 工具执行 → 结果回填 → 最终回答。

在这个闭环中,工具描述回答“有什么工具、什么时候用”,JSON Schema 回答“参数怎么传、传什么”。二者是模型做决策的“参照系”:描述帮助模型从众多工具中选出正确的那一个,Schema 帮助模型生成格式正确的参数。假设模型面前有 N 个工具,它会估算每个工具的“匹配概率”:

领班小智懂了:手里这本手册不是用来背的,而是用来“查”的。好的手册能让他在一瞬间锁定正确的菜;而一本语焉不详的手册,只会让他在厨房门口反复徘徊。

T^=arg⁡max⁡Ti∈TP(Ti∣Xuser,D1,D2,…,Dn)\hat{T} = \arg\max_{T_i \in \mathcal{T}} P\left(T_i \mid X_{\text{user}}, D_1, D_2, \dots, D_n\right)
PM 公式通俗说明:模型在所有候选工具中使用概率最大的那个;”概率“同时受用户消息 X 和所有工具描述 D 的影响。
  1. 最终选择的工具$\hat{T}$ 表示模型经过权衡后决定调用的那个工具,是整个公式的输出结果。
    T^=arg⁡max⁡Ti∈T\hat{T} = \arg\max_{T_i \in \mathcal{T}}
  2. 候选工具集合$\mathcal{T} = \{T_1, T_2, \dots, T_n\}$ 是所有已注册工具的集合,模型只能从中选择,不能凭空创造。
    Ti∈TT_i \in \mathcal{T}
  3. 条件概率$P(T_i \mid X_{\text{user}}, D_1, \dots, D_n)$ 表示“在已知用户问题和全部工具描述的条件下,选择工具 $T_i$ 的概率”,描述写得越清晰,这个概率越可靠。
    P(Ti∣Xuser,D1,D2,…,Dn)P\left(T_i \mid X_{\text{user}}, D_1, D_2, \dots, D_n\right)
  4. 最大化选择$\arg\max$ 的含义是“取使括号内表达式最大的那个下标 i”,也就是概率最高的工具胜出。
    arg⁡max⁡Ti∈TP(⋅)\arg\max_{T_i \in \mathcal{T}} P(\cdot)

工具描述编写:一份让人一眼就懂的“菜单文案”

小智发现,后厨手册里每道菜的介绍都很有讲究。“招牌红烧肉:肥而不腻,入口即化,配米饭一绝”——短短十几个字,把食材、口感、搭配场景全讲清了。反观另一些菜,只写着“秘制料理”,小智永远不敢推荐。工具描述就是给大模型看的“菜单文案”,它的质量直接决定模型能否在关键时刻做出正确选择。

在 OpenAI、Anthropic 等主流平台的 API 设计中,每个工具(function)都有一个 description 字段。这段文字会与用户的问题一起被编码成向量,参与模型的语义匹配。从沟通视角看,描述承担两个职责:一是说清工具的能力边界(能做什么、不能做什么),二是给出触发场景的线索(用户在什么情况下应该想到这个工具)。例如:

这段描述用词与用户常用表达保持一致(“查询”“天气”“温度”),同时主动声明了“不用于查询历史气候”,等于给模型划了一条虚拟的“禁区边界”。当系统注册了多个工具时,描述之间的区分度尤其重要——如果两个工具的描述大量重叠,模型就会陷入“选择困难症”。描述匹配的强度,本质上由用户问题与描述文本的语义相似度决定:

小智总结出菜单文案的秘诀:短而准,场景化。与其写满一百个字的“百科词条”,不如用三句话讲清“是什么、何时用、何时不用”。毕竟,领班在客人面前翻菜单的时间,只有短短几秒。

sim(q,d)=vq⋅vd∥vq∥×∥vd∥=cos⁡(θ)\text{sim}(q, d) = \frac{\mathbf{v}_q \cdot \mathbf{v}_d}{\lVert \mathbf{v}_q \rVert \times \lVert \mathbf{v}_d \rVert} = \cos(\theta)
余弦相似度把用户问题 q 和工具描述 d 各自映射成一个向量,两个向量方向越一致,相似度越接近 1,模型就越倾向选择这个工具。
  1. 向量点积$\mathbf{v}_q \cdot \mathbf{v}_d$ 是两个向量对应分量相乘再求和,衡量“方向重合的程度”,值越大表示二者越“同向”。
    vq⋅vd\mathbf{v}_q \cdot \mathbf{v}_d
  2. 向量模长乘积$\lVert \mathbf{v}_q \rVert \times \lVert \mathbf{v}_d \rVert$ 将点积结果归一化,消除文本长度的影响,避免“长描述天然占便宜”。
    ∥vq∥×∥vd∥\lVert \mathbf{v}_q \rVert \times \lVert \mathbf{v}_d \rVert
  3. 余弦值$\cos(\theta)$ 最终落在 $[-1, 1]$ 区间,越接近 1 表示问题与描述在语义空间中越“同向”,工具被选中的概率越高。
    cos⁡(θ)\cos(\theta)
json4 行
{  "name": "get_weather",  "description": "查询指定城市的实时天气。当用户询问天气、温度、降雨概率时使用;不用于查询历史气候。"}

JSON Schema基础:点菜工单的“规范排版”

菜单解决了“选哪道菜”,可小智很快发现新问题:客人说“来份红烧肉,多加点饭”,后厨却问“加一碗还是两碗?少糖还是多糖?”——口头传话太容易走样。老板于是引入了标准点菜工单:一张印好的表格,菜名、数量、口味、忌口各占一格,客人只需在格子里填内容。再也不会有“口口相传”的误差。

JSON Schema 就是大模型世界里的“标准工单”。它是一种用 JSON 描述 JSON 结构的规范:规定一个对象应该有哪些字段、每个字段是什么类型、哪些字段必填。在工具调用场景中,工具对象的 parameters 字段就是一个 JSON Schema,例如:

这段 Schema 告诉模型:调用时需要提供两个字段,city 必须是字符串,date 也必须是字符串,二者缺一不可。更关键的是,Schema 不仅用于事后校验,还参与事中引导——模型解码时会倾向生成满足约束的 token,从源头降低出错概率。其验证语义可以严谨地表达为:

有了工单,后厨的效率直线上升。小智终于明白:菜单负责“心动”,工单负责“不出错”——工具描述负责让模型选对工具,JSON Schema 负责让模型把参数传对。

validate(v,S)={True,∀k∈KS: checkk(v)False,∃k∈KS: ¬checkk(v)\text{validate}(v, S) = \begin{cases} \text{True}, & \forall k \in K_S:\ \text{check}_k(v) \\ \text{False}, & \exists k \in K_S:\ \lnot \text{check}_k(v) \end{cases}
通俗说明:数据 v 能通过 Schema S 的校验,当且仅当 S 里的每一条约束规则 check_k 都成立;只要有一条不成立,校验就失败。
  1. 校验函数$\text{validate}(v, S)$ 输入待校验的参数对象 $v$ 和模式 $S$,输出“合法/不合法”的布尔结论。
    validate(v,S)\text{validate}(v, S)
  2. 约束规则集合$K_S$ 是 Schema $S$ 中所有约束关键字的集合,包括 type、required、enum、minimum 等,每一条都是一个“检查器”。
    k∈KSk \in K_S
  3. 全称成立$\forall k \in K_S:\ \text{check}_k(v)$ 要求所有检查器都通过,对应“工单每一格都填得合法”。
    ∀k∈KS: checkk(v)\forall k \in K_S:\ \text{check}_k(v)
  4. 存在失败$\exists k \in K_S:\ \lnot \text{check}_k(v)$ 表示一旦存在任意一个检查器不通过,整体校验即失败,对应“一个格子填错,整张工单退回”。
    ∃k∈KS: ¬checkk(v)\exists k \in K_S:\ \lnot \text{check}_k(v)
json8 行
{  "type": "object",  "properties": {    "city": { "type": "string", "description": "城市名,如北京" },    "date": { "type": "string", "description": "日期,YYYY-MM-DD" }  },  "required": ["city", "date"]}

参数约束声明:工单上的“填空格与下拉框”

小智仔细端详工单,发现不同栏目的填写方式不一样:菜名是自由填空,辣度是“不辣/微辣/中辣/特辣”四选一,忌口可以多填几个词,份数只能写 1 到 10 的整数。这些规则让客人不能随心所欲地乱写,后厨也才能标准化出餐。参数约束就是给模型设置的“填写规则”,它把自由生成的参数空间一步步收窄。

JSON Schema 提供了一套丰富的约束关键字,每个关键字都是一条“填写规则”:

  • type(类型):规定参数必须是 string、number、integer、boolean、array、object 或 null。模型生成的值类型不匹配,校验立即失败。
  • required(必填):列出对象中必须出现的字段。未声明的字段模型可以自由决定是否给出。
  • enum(枚举):给出合法取值的白名单,如 ["economy", "business", "first"],模型只能从中挑选。
  • minimum / maximum(数值范围):约束数字的最小/最大值,如人数最多 10 人。
  • pattern(正则):约束字符串的格式,如日期要求匹配 ^\d{4}-\d{2}-\d{2}$。
  • default(默认值):当模型未提供该字段时使用的回退值。

多个参数的约束组合起来,构成了一个高维的“合法参数空间”。假设一个工具需要三个参数,参数 1 的合法集合是 $T_1$,参数 2 是 $T_2$,参数 3 是 $T_3$,那么所有合法的参数组合恰好是它们的笛卡尔积:

不过小智也学到一条教训:工单的格子不是越多越好。如果每道菜都要客人填写十项内容,客人会直接摔门走人;同样,required 字段过多,模型“凑不齐”参数的概率也会升高,反而拉低调用成功率。

D=T1×T2×⋯×Tn\mathcal{D} = T_1 \times T_2 \times \cdots \times T_n
通俗说明:每个参数都在自己的“格子”里选一个合法值,笛卡尔积把所有可能的组合拼起来,相当于“每格都填了合法值”的全部点菜组合。
  1. 参数空间$\mathcal{D}$ 是所有可能参数组合的集合,也叫“搜索空间”。约束越强,$\mathcal{D}$ 越小,模型越不容易跑偏。
    D\mathcal{D}
  2. 单个参数取值域$T_i$ 是第 $i$ 个参数的合法取值集合,由 type、enum、minimum 等约束共同界定。
    TiT_i
  3. 笛卡尔积$\times$ 运算符把多个集合组合成“有序元组”的集合,例如 $\{a,b\} \times \{1,2\} = \{(a,1),(a,2),(b,1),(b,2)\}$。
    T1×T2×⋯×TnT_1 \times T_2 \times \cdots \times T_n

组合模式运用:套餐、自选与“如果…就…”的规则

餐厅发展到一定规模,工单不够用了:客人开始点“超值套餐”——主食三选一、饮品三选一、甜点二选一,还可以加备注“不要葱”。一张固定排版的基础工单根本无法描述这种组合式规则。老板只好升级工单系统,支持“模式套模式”。JSON Schema 的组合关键字,正是为这类复杂场景而生。

allOf(同时满足):将多个子 Schema 合并,数据必须同时满足全部子约束。就像“套餐必须包含主食和饮品,且份数≥1”。anyOf(满足其一):数据只需满足任意一个子 Schema,适合描述多态参数——“要么传城市名,要么传经纬度”。oneOf(恰有一个):比 anyOf 更严格,要求恰好满足一个分支,避免模型同时匹配多条路径造成歧义。条件约束(if-then-else):依据某个字段的值动态启用另一组规则,比如“如果支付方式是信用卡,则必填卡号”。

这些组合关系用集合视角看非常直观:

小智把组合规则贴在后厨墙上:先判断客人的点法属于哪一类,再用对应的分支规则去套。一份工单搞不定,就用“工单套工单”;而每一层组合,都在告诉模型:世界是复杂多样的,但规则必须清晰。

VallOf=V1∩V2∩⋯∩Vm,VanyOf=V1∪V2∪⋯∪VmV_{\text{allOf}} = V_1 \cap V_2 \cap \cdots \cap V_m, \quad V_{\text{anyOf}} = V_1 \cup V_2 \cup \cdots \cup V_m
通俗说明:allOf 是求交集——满足全部子模式的元素才合法;anyOf 是求并集——只要满足其中一个子模式的元素都合法。
  1. 交集语义$V_{\text{allOf}}$ 是各子模式合法集合的交集,元素必须同时属于所有集合,对应“每一项要求都达标”。
    V1∩V2∩⋯∩VmV_1 \cap V_2 \cap \cdots \cap V_m
  2. 并集语义$V_{\text{anyOf}}$ 是各子模式合法集合的并集,元素只需属于其中一个集合,对应“满足任一分支即可”。
    V1∪V2∪⋯∪VmV_1 \cup V_2 \cup \cdots \cup V_m
  3. 组合使用组合关键字还可以嵌套,例如用 allOf 包裹一个 anyOf,实现“必填字段 + 多选一形态”的复杂建模。
    allOf[anyOf[A,B],C]\text{allOf}[\text{anyOf}[A, B], C]

工程实践要点:后厨管理中的防错与优化

工单系统上线一个月,小智发现新的烦恼:偶尔有客人把“辣度”一栏留空,或者把份数写成“很多”。后厨看到不合格的工单,是直接退单还是帮忙猜一个?退单得罪客人,猜错浪费食材。小智和老板商量出一套流程:先重试确认,再按规则修正,最后记录在案。这正是工具调用工程化的核心思路。

在生产环境中,模型生成的参数不可能 100% 符合 Schema。工程实践需要建立一套“防错-纠正-优化”的闭环:

  • 校验失败处理:解析模型输出的 JSON,若失败则用 temperature=0 重新生成一次,或把错误信息作为反馈让模型修正;多次失败后降级为向用户询问缺失参数。
  • 描述与 Schema 的迭代优化:把每次“选错工具”和“校验失败”的样本记录下来,分析是描述产生了歧义,还是 Schema 约束过紧,针对性修改后观察带来提升。
  • 工具数量控制:注册的工具有几十个时,模型的选择负担急剧上升。可以采用“先粗筛后精排”的二次路由,或用语义检索把候选工具裁剪到前 5 个。
  • 可观测性:对工具选择结果、校验结果、执行结果全链路打日志,让每次失败都可回溯、可归因。

衡量这套体系是否健康的指标,正是端到端的调用成功率:

小智在后厨立了规矩:任何一张退货工单都不许扔进垃圾桶,必须贴上原因标签归档。有了这些“失败标本”,菜单文案和工单模板才能越改越好——AI Agent 的工程优化,本质上也是一门“用失败喂养进步”的手艺。

Success=NokNtotal\text{Success} = \frac{N_{\text{ok}}}{N_{\text{total}}}
通俗说明:成功率等于“最终成功完成调用并返回有效结果的次数”除以“全部调用尝试次数”。这个指标指导我们持续迭代描述与 Schema。
  1. 成功次数$N_{\text{ok}}$ 是“校验通过 + 工具正常执行 + 返回结果可解析”的次数,一次调用只有全程无差错才算成功。
    NokN_{\text{ok}}
  2. 总尝试次数$N_{\text{total}}$ 是包含重试在内的所有调用次数,分母越大、成功率越难维持,因此它激励我们把问题前置解决。
    NtotalN_{\text{total}}
  3. 比率指标$\frac{N_{\text{ok}}}{N_{\text{total}}}$ 将两条计数整合成一个 0 到 1 的比率,便于跨版本对比:改一次描述,看一次成功率,用数据说话。
    NokNtotal\frac{N_{\text{ok}}}{N_{\text{total}}}

结构化输出:从“填好工单”到“标准化出菜”

工单填得再漂亮,如果后厨出菜时随手一扣、酱汁乱洒,客人依然不会满意。小智发现,餐厅的最后一个环节也藏着标准:每道菜都有固定摆盘,传菜口统一核对后才放行。这就对应了大模型世界里的“结构化输出”——不仅要让模型“说人话”,还要让它按固定的 JSON 格式“说标准话”。

工具调用本质上就是受 JSON Schema 约束的结构生成:模型每一步解码出的 token,必须拼成一个合法的 JSON 对象。OpenAI 的 Structured Outputs 功能将该约束推到极致——模型生成时动态构造“合法 token 白名单”,凡是会让 JSON 偏离 Schema 的 token 一律屏蔽,从机制上保证输出 100% 符合约束。这种技术在学术界称为受限解码(Constrained Decoding),它把“事后校验”升级为“事中引导”,把错误扼杀在解码的每一步。

给定 Schema $S$,模型每一步的概率分布被一个“合法 mask”修正:

小智站在传菜口前,看着一道道按照标准摆盘的菜被端出,终于理解了整条流水线的意义:从菜单(描述)到工单(Schema),再到标准出菜(结构化输出),每一环都在减少“不确定性”。当 AI Agent 的每一个动作都清晰、可预期,它才真正值得被信任。

---

P′(yt∣x,y<t)={P(yt∣x,y<t),if y<t⊕yt∈L(S)0,otherwiseP'(y_t \mid x, y_{<t}) = \begin{cases} P(y_t \mid x, y_{<t}), & \text{if } y_{<t} \oplus y_t \in \mathcal{L}(S) \\ 0, & \text{otherwise} \end{cases}
通俗说明:模型原本可能生成任何 token;受限解码把“会导致 JSON 违反 Schema 的 token”概率置零,只允许拼接后仍合法的 token 参与采样。
  1. 原始概率$P(y_t \mid x, y_{<t})$ 是模型在已生成前文 $y_{<t}$ 时,对下一个 token $y_t$ 的原始预测概率。
    P(yt∣x,y<t)P(y_t \mid x, y_{<t})
  2. 拼接合法检查$y_{<t} \oplus y_t \in \mathcal{L}(S)$ 表示“已生成部分加上新 token 后,仍属于满足 Schema $S$ 的合法序列集合”,是放行的前提条件。
    y<t⊕yty_{<t} \oplus y_t
  3. 非法置零分支结构中 $0$ 表示一旦拼接结果不合法,该 token 的概率被强制改为 0,模型就永远不会生成它。
    P′(yt∣x,y<t)P'(y_t \mid x, y_{<t})

AI 应用场景

智能客服Agent的订单查询

阶段:工具调用与参数校验

解决的问题:传统 IVR 语音菜单层级深,用户说“我上周买的耳机能退吗”,系统无法从长句中抽取订单线索。引入客服 Agent 后,模型通过工具描述匹配 get_order(查询订单详情)和 get_refund_policy(查询退款政策),再由 JSON Schema 约束 order_id 与 user_id 为必填字符串,确保每一个查询请求都结构完整。

具体案例:用户输入“我的 AirPods 订单怎么还没发货”,Agent 先调用 get_order 拿到订单状态,再调用 get_logistics 查询物流轨迹,两次调用的参数都经过 Schema 校验,最终用自然语言汇总给用户。整个过程从原来的 3 分钟菜单导航缩短到 8 秒。

数据分析Agent的BI指标查询

阶段:工具路由与语义匹配

解决的问题:数据分析平台中指标口径混乱,非技术用户不懂 SQL 和表结构。通过为每个指标函数编写精准的 description(如“查询近 7 日活跃用户数,支持按渠道分组”),并让 JSON Schema 用 enum 限定合法的渠道枚举值、用 required 强制日期范围,系统能自动将“上周各渠道涨了多少”转换为合法的查询参数组合。

具体案例:某电商运营追问“618 当天华东区成交额是多少”,Agent 从 20 多个BI工具中精准路由到 query_metrics,参数 channel 从枚举中选出 east_region,metric 选 gmv,date_range 落在一个合法区间内,随后自动执行查询并生成图表。一次对话完成过去需要 BI 工程师半天的取数工作。

Python 演示

python87 行
# -*- coding: utf-8 -*-"""工具描述与 JSON Schema 校验演示"""import json TOOLS = {    "get_weather": {        "description": "查询指定城市的实时天气。当用户问天气、温度、会不会下雨时使用。",        "parameters": {            "type": "object",            "properties": {                "city": {"type": "string", "description": "城市名,如北京"},                "date": {"type": "string", "description": "日期 YYYY-MM-DD"}            },            "required": ["city", "date"]        }    },    "book_flight": {        "description": "预订机票,需要出发地、目的地和日期。当用户要求订机票时使用。",        "parameters": {            "type": "object",            "properties": {                "origin": {"type": "string"},                "dest": {"type": "string"},                "date": {"type": "string"},                "cabin": {"type": "string", "enum": ["economy", "business", "first"]}            },            "required": ["origin", "dest", "date"]        }    }}  def validate_schema(data, schema):    """手写精简 JSON Schema 校验器,支持 type/required/enum/minimum/maximum"""    if schema.get("type") == "object":        if not isinstance(data, dict):            return False, "期望 object,实际 {}".format(type(data).__name__)        props = schema.get("properties", {})        for k in schema.get("required", []):            if k not in data:                return False, "缺少必填参数: {}".format(k)        for k, v in data.items():            if k not in props:                continue            sub = props[k]            if sub.get("type") == "string":                if not isinstance(v, str):                    return False, "参数 {} 应为 string".format(k)                if "enum" in sub and v not in sub["enum"]:                    return False, "参数 {} 不在枚举中: {}".format(k, v)            elif sub.get("type") == "integer":                if not isinstance(v, int) or isinstance(v, bool):                    return False, "参数 {} 应为 integer".format(k)                if "minimum" in sub and v < sub["minimum"]:                    return False, "参数 {} 小于最小值 {}".format(k, sub["minimum"])    return True, "OK"  def mock_llm_tool_call(query):    """模拟大模型根据工具描述选择工具并生成参数"""    if any(w in query for w in ["天气", "温度", "下雨"]):        return "get_weather", {"city": "北京", "date": "2025-06-10"}    if any(w in query for w in ["订机票", "航班", "机票"]):        return "book_flight", {"origin": "北京", "dest": "上海", "date": "2025-07-01", "cabin": "business"}    return None, None  def main():    query = "帮我查一下北京明天的天气"    print("用户问题:", query)    tool, args = mock_llm_tool_call(query)    if tool is None:        print("未匹配到合适的工具")        return    print("AI 选择的工具:", tool)    print("工具描述:", TOOLS[tool]["description"])    print("AI 生成的参数:", json.dumps(args, ensure_ascii=False, indent=2))    ok, msg = validate_schema(args, TOOLS[tool]["parameters"])    if ok:        print("校验通过,执行工具...")        print("  返回结果: {'temperature': 26, 'condition': '晴'}")    else:        print("校验失败:", msg)  if __name__ == "__main__":    main()
目的:演示 AI Agent 工具调用的完整链路——模型根据工具描述选工具、生成参数,再用 JSON Schema 校验参数合法性。
  1. TOOLS = { ... }定义工具注册表,每个工具包含 description 和 parameters(JSON Schema)两部分
  2. "description": "查询指定城市的实时天气..."工具描述,告诉模型何时用这个工具
  3. "required": ["city", "date"]必填约束,模型生成参数时必须包含这两个字段
  4. "enum": ["economy", "business", "first"]枚举约束,cabin 只能取三个合法值之一
  5. def validate_schema(data, schema):手写校验器,逐条检查 Schema 中的约束规则
  6. if k not in data:检查必填字段是否缺失,相当于“工单必填格是否为空”
  7. def mock_llm_tool_call(query):用关键词匹配模拟大模型的工具选择与参数生成
  8. ok, msg = validate_schema(args, TOOLS[tool]["parameters"])执行校验,得到通过/失败结论
  9. json.dumps(args, ensure_ascii=False, indent=2)格式化输出参数 JSON,便于查看和调试

常见误区

误区“描述里信息越多越好,最好把返回字段、内部实现、历史版本都写进去,这样模型肯定能理解。”于是把 description 写成了一篇三百字的小作文。

正解模型对描述文本的注意力是有限的,过长的描述会稀释关键词的权重,反而让“何时用、何时不用”的核心信息被淹没。好的描述应该像菜单文案:一句话讲清功能,一句话给出触发场景,一句话划清边界,总长控制在 50 字左右效果最佳。

为何常见:人们惯性认为“信息量 = 理解深度”,但在大模型的注意力机制下,信息密度远比信息量重要。

误区“Schema 是给后端做数据验证用的,模型生成质量好不好,主要看 prompt 怎么写。”很多开发者在初期完全忽略了 Schema 的设计。

正解JSON Schema 会直接影响模型生成参数的成功率。enum 会让模型更倾向在候选值中选择,type 声明能显著减少类型错误,required 则会引导模型优先补全必备字段。它不只是拦截错误的后端工具,更是引导生成的“护栏”。

为何常见:传统 API 开发中,Schema 确实是“最后一道闸门”;但在生成式场景里,它变成了“前向引导器”,角色完全不同。

误区“把所有参数都设为必填,就能保证模型永远传齐参数,调用就不会缺胳膊少腿。”

正解required 越多,模型的“凑单压力”越大。当某些参数在用户问题里根本没有线索时(如用户没提舱位等级,Schema 却强制要求 cabin),模型只能猜测或虚构,反而拉低整体成功率。正确做法是:只把工具运行真正不能缺失的字段设为必填,其余用 optional + default 兜底。

为何常见:直觉上“强制 = 安全”,但忽略了生成模型的本质——它没有“无限信息”,只能从用户输入中推断参数。

误区“枚举是在限制模型,我把选项尽量列全,模型发挥空间就大,不容易报错。”

正解enum 是白名单而非“建议列表”。选项越多,模型越容易在语义相近的选项之间犯错(比如把 business 选成 premium_economy),同时模型也越难记住所有选项的差异。应控制枚举数量,必要时用 description 描述每个选项的含义,帮助模型区分。

为何常见:把“给模型更多自由”等同于“给模型更多选项”,实际上自由来自清晰的边界,而非模糊的广阔。

面试题

Q1. 工具描述在 AI Agent 中扮演的核心角色是什么?

单选题 · easy

  • A.定义工具返回结果的字段格式
  • B.帮模型理解工具功能和适用场景← 正确答案
  • C.控制外部工具调用的权限范围
  • D.替代 JSON Schema 做参数格式校验

答案:B。工具描述是写给大模型看的“菜单文案”,作用在于让模型理解“这个工具是干什么的、什么时候该用”。返回格式由 Schema 和执行端负责,权限控制由平台负责,描述不能替代 Schema。就像菜单的作用是让人决定“点这道菜”,而不是规定“这道菜怎么摆盘”。

Q2. JSON Schema 中 type 关键字的作用是什么?

单选题 · easy

  • A.规定参数允许的数据类型← 正确答案
  • B.指定参数的默认取值
  • C.标记参数是否为必填项
  • D.定义参数的最大长度

答案:A。type 声明参数的类型,如 string、integer、object。默认值由 default 声明,必填由 required 声明,长度由 maxLength 声明。类比点菜工单上的“格子类型”:有的格子只能填文字,有的格子只能填数字。

Q3. "required": ["city"] 在工具参数 Schema 中的含义是?

单选题 · easy

  • A.city 参数必须有默认值
  • B.模型生成参数时必须包含 city← 正确答案
  • C.city 参数只允许字符串类型
  • D.调用工具前必须经过授权

答案:B。required 列表中的字段是模型生成参数时必须给出的,缺了它校验就会失败。它不规定类型(那是 type 的职责),也不涉及授权或默认值,相当于工单上标注了“必填”的格子。

Q4. 关于工具描述的长度与质量,下列说法正确的是?

单选题 · mid

  • A.描述越长信息越全,模型选择越准
  • B.描述应简短,讲清功能和触发场景← 正确答案
  • C.描述中应包含完整的内部实现代码
  • D.描述只需写出工具的名称即可

答案:B。过长的描述会稀释关键信息,干扰模型的语义匹配。最佳实践是精炼地写清“功能 + 触发场景 + 边界”,而不是堆砌信息。就像菜单上的招牌菜介绍,两三句话讲透风味和搭配,远比一篇小作文更让人想点。

Q5. JSON Schema 中 enum 关键字的作用是什么?

单选题 · mid

  • A.限定参数只能取枚举白名单内的值← 正确答案
  • B.输出参数的所有可选名称列表
  • C.规定参数的数值上限与下限
  • D.声明参数是否为嵌套对象类型

答案:A。enum 给出合法值白名单,模型只能在列表内取值,任何不在列表中的值都会导致校验失败。它像“辣度勾选框”:只有不辣、微辣、中辣、特辣四个格子,客人不能自创一个“微微辣”。

Q6. 模型生成的参数未通过 JSON Schema 校验,最合理的处理方式是什么?

单选题 · mid

  • A.直接丢弃请求并报错给用户
  • B.重试重新生成参数或反馈修正← 正确答案
  • C.跳过校验继续执行工具调用
  • D.把校验逻辑全部写进提示词

答案:B。校验失败是生成式模型的常态,工程上应设计重试与修正机制:用更低温度重新生成,或把校验错误信息回传给模型让它自我修正。跳过校验会导致 API 调用崩溃,直接报错则牺牲用户体验,把校验写进提示词也只能降低概率而非根除问题。

Q7. 下列哪些是 JSON Schema 官方支持的类型?

多选题 · mid

  • A.string 字符串← 正确答案
  • B.integer 整数← 正确答案
  • C.list 列表
  • D.object 对象← 正确答案

答案:ABD。JSON Schema 的合法类型包括 string、number、integer、boolean、array、object、null。list 不是标准类型,数组在 JSON 中叫 array。这就像工单上只有固定几种“格子格式”,没有自定义格子。

Q8. 关于 allOf 与 anyOf 组合关键字的区别,正确的是?

单选题 · hard

  • A.二者没有任何区别,只是写法不同
  • B.allOf 要求全部子约束成立,anyOf 只需任一成立← 正确答案
  • C.allOf 只能用于数组类型,anyOf 只能用于对象
  • D.anyOf 比 allOf 的校验执行速度更快

答案:B。集合视角下,allOf 是子模式合法集合的交集,每条约束都不能破;anyOf 是并集,数满足任意一条即通过。它们与类型无关,也没有性能差异。类比套餐规则:allOf 是“主食+饮品+甜点都得有”,anyOf 是“主食三选一即可”。

Q9. 以下关于工具描述影响模型工具选择的说法,正确的有哪些?

多选题 · hard

  • A.描述中包含明确的触发条件可提升选择准确率← 正确答案
  • B.描述中写出内部实现细节会增强选择效果
  • C.描述只影响响应速度不影响选择正确性
  • D.描述与用户问题的语义匹配度影响工具选择← 正确答案

答案:AD。工具描述是模型选工具的“唯一依据”,触发场景和语义匹配度直接决定选择准确率。内部实现细节对模型没有帮助,反而引入噪音。描述既影响正确性也影响速度(更长描述增加编码耗时),但不影响选择正确性的说法是错的。

Q10. 关于受限解码(Constrained Decoding)与工具调用的关系,正确的理解有哪些?

多选题 · extreme

  • A.它在每个 token 生成时屏蔽非法选项← 正确答案
  • B.它可以在生成前预先知道完整参数值
  • C.它从机制上保证输出满足 Schema 约束← 正确答案
  • D.它等价于对生成结果的事后 JSON 解析

答案:AC。受限解码的核心思想是“事中引导”——每步解码时动态维护一个合法 token 集合,将导致 JSON 偏离 Schema 的 token 概率置零,因而能从机制上保证合法。它无法预知完整参数值(参数是边生成边确定的),也不同于事后解析。类比“传菜口一碟一碟检查摆盘”,而不是等整桌菜上完才拍照核对。