工具参数与校验
工具参数与校验 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
读完本课你能为课程检索工具定义参数结构,把模型传来的参数当不可信输入层层校验,用结构化错误拒绝无效输入和越权资源,并用四类坏参数完成测试。
问题场景
工具上线第二天,日志里出现这些调用:arguments 是一段不是 JSON 的话;query 缺失;track 传了 "ReactJS" 这种枚举外的值;更糟的是 get_lesson 被要求读一个不存在的课程编号。参数来自模型的「生成」,不是来自你的表单校验——它是和用户输入同级的不可信输入。
核心解释
以课程检索为例(本站 lib/ai/tools.ts 的真实校验,简化版):
const TRACKS = ["foundation", "react", "nextjs", "ai", "python", "go", "engineering"];
function searchCourses(argsJson: string): string {
// 第一层:解析防御——arguments 可能根本不是合法 JSON
let args: Record<string, unknown>;
try {
args = JSON.parse(argsJson || "{}");
} catch {
return JSON.stringify({ error: "参数不是合法 JSON" });
}
// 第二层:字段存在性与边界——缺参、空串、超长
const query = String(args.query ?? "").trim();
if (!query || query.length > 100) {
return JSON.stringify({ error: "query 参数缺失或过长" });
}
// 第三层:枚举白名单——可选参数也不能放行任意值
const track = typeof args.track === "string" ? args.track : null;
if (track && !TRACKS.includes(track)) {
return JSON.stringify({ error: `track 必须是:${TRACKS.join("/")}` });
}
return doSearch(query, track); // 真正执行
}
三条原则:
- 错误也返回结构化 JSON,不抛异常。工具的失败要作为结果回传给模型(A15 的「拒绝也是结果」),模型读到
{error: "..."}能修正参数重试;抛异常只会让整条请求崩掉。 - 越权资源用「查无此物」拒绝。
get_lesson收到不存在的课程编号时返回{error: "课程 XXX 不存在"}——查询永远限定在服务端自己的资源表里,模型报什么名字都只能命中白名单内的东西,这就是资源访问无法越权的根本原因。 - 校验失败要可归因。错误信息里写清哪个参数、为什么不行(「缺失或过长」「不在枚举内」),模型的下次修正才有依据——A09 的「带原因重试」在工具侧同样成立。
示例拆解
四类坏参数的测试用例,直接写成数组跑:
const CASES: Array<[string, string, string]> = [
// [用例名, arguments, 预期]
["缺参", "{}", "error"],
["超长", JSON.stringify({ query: "长".repeat(200) }), "error"],
["非法枚举", JSON.stringify({ query: "状态", track: "ReactJS" }), "error"],
["资源不存在", JSON.stringify({ course_code: "Z99" }), "error"],
["正常", JSON.stringify({ query: "状态", track: "react" }), "ok"],
];
for (const [name, args, expect] of CASES) {
const result = searchCourses(args);
const passed = expect === "error" ? !!JSON.parse(result).error : !JSON.parse(result).error;
console.log(`${passed ? "PASS" : "FAIL"} [${name}] -> ${result.slice(0, 60)}`);
}
关键点:
- 每个分支一条用例,校验代码改坏了立刻能发现;这组用例随工具一起提交进仓库。
- 「正常」用例是对照组:校验收得太紧(把合法输入也拒了)同样是 bug。
- 参数结构在声明(A15 的
parametersschema)和执行(这里的校验)两侧各写一次——声明是给模型的建议,校验才是给服务的保证,前者挡不住生成错误。
动手任务
- 给本站
get_lesson补上与search_courses同级的三层校验(course_code 非空、格式合法、必须存在于课程表)。 - 把四类坏参数用例跑通,然后故意删掉枚举校验那一层,观察哪条用例变 FAIL。
- 决策题:
get_lesson收到格式合法但课程编号大写的r08,返回错误还是宽容转换?写出你的选择和理由(本站选择toUpperCase()后再查——接口对大小写的宽容度要明确决策并写进文档)。
常见错误
- 信任声明不写校验:schema 只影响模型的倾向,挡不住它生成缺参或编造值。
- 校验失败抛异常:整条聊天请求 500,模型没有机会修正,用户只看到报错。
- 错误信息写成「参数错误」:没有哪个参数、什么原因,模型重试还是错。
- 只测正常路径:四类坏参数是工具接口的最低测试集,缺一类就埋一类雷。
检查与复盘
自查清单:
- 能说出解析、字段、枚举三层校验各拦什么
- 错误返回是结构化 JSON 且带可归因的原因
- 四类坏参数用例齐备且在仓库里
参考资料
- OpenAI 文档:Function calling —— 官方同样强调:参数必须校验,模型可能生成无效调用
- MDN:JSON.parse —— 解析防御的基础
- 下一课 A17 把单次工具调用扩展成受限的多轮循环。
读完这一课了?
标记完成后,工作台会安排接下来的学习。