第 10 章:用 Zod 定义工具参数

5 阅读2分钟

第 10 章:用 Zod 定义工具参数

本章目标

这一章聚焦工具参数。Tool Calling 能不能稳定工作,关键不只在模型,也在 Schema 设计。

本章效果

工具参数会在调用轨迹中显示出来,方便确认模型是否按 Zod Schema 生成了正确参数。

Zod 工具参数转存失败,建议直接上传图片文件

为什么 Zod 很重要

模型调用工具时,需要知道:

  • 工具有哪些参数
  • 每个参数是什么类型
  • 哪些参数必填
  • 参数含义是什么
  • 取值范围是什么

Zod 不只是 TypeScript 类型工具,也是给模型看的参数说明。

一个糟糕的 Schema

z.object({
  value: z.string(),
  type: z.string()
});

问题是字段太模糊。模型不知道 value 是价格、城市还是用户 ID,也不知道 type 有哪些选项。

一个更好的 Schema

const orderStatusSchema = z.object({
  orderId: z.string().describe("订单编号,例如 SO202605190001"),
  includeLogs: z
    .boolean()
    .default(false)
    .describe("是否返回订单流转日志,默认 false")
});

字段名清楚,描述明确,默认值合理。

业务查询工具

import { tool } from "langchain";
import * as z from "zod";

export const getOrderStatus = tool(
  async ({ orderId, includeLogs }) => {
    const order = {
      id: orderId,
      status: "已发货",
      carrier: "顺丰",
      trackingNo: "SF1234567890",
      logs: ["已付款", "仓库已出库", "已发货"]
    };

    return JSON.stringify({
      id: order.id,
      status: order.status,
      carrier: order.carrier,
      trackingNo: order.trackingNo,
      logs: includeLogs ? order.logs : undefined
    });
  },
  {
    name: "get_order_status",
    description: "根据订单编号查询订单状态和物流信息",
    schema: z.object({
      orderId: z.string().describe("订单编号,例如 SO202605190001"),
      includeLogs: z.boolean().default(false).describe("是否包含订单流转日志")
    })
  }
);

Schema 设计原则

字段名要具体:

好:orderId, startDate, maxResults
差:id, value, data

枚举要收窄:

z.enum(["pending", "paid", "shipped", "done"])

可选字段要说明默认行为:

z.number().optional().describe("最多返回多少条,不传默认 10 条")

危险参数要避免开放:

不要让模型直接传 SQL
不要让模型直接传文件路径
不要让模型直接传任意 URL 并由服务端请求

参数校验失败怎么办

模型可能生成不合法参数。处理方式:

  • 让 Zod 拦截
  • 返回可理解的错误信息
  • 必要时让模型重新生成
  • 高风险动作不要自动重试

实战任务

完成:

  • get_order_status 工具
  • 一个带枚举参数的 search_policy 工具
  • 对工具参数做边界限制
  • 测试模型是否能正确选择工具

常见坑

不要把工具参数设计成“万能参数”。万能参数等于没有 Schema。

不要假设模型一定会填对。服务端必须校验。

不要把工具描述写得像注释。它是模型选择工具的重要依据。

本章小结

Zod Schema 是工具调用稳定性的核心。下一章会把多个工具组合成一个业务 Agent。