第 05 章
结构化输出
结构化输出 — 让 AI 返回 JSON
这章学会让 AI 不再返回自由文本,而是乖乖返回你定义好格式的 JSON 数据。
为什么需要结构化输出
先看一个痛点:
typescriptconst turn = await thread.run("分析这段代码有什么问题");
console.log(turn.finalResponse);
// "这段代码有3个问题:第一,变量命名不规范...第二,缺少错误处理...第三,..."AI 回了一段自由文本。如果你想提取"有几个问题""每个问题是什么",就得自己写正则或者字符串处理——又累又不稳定。
结构化输出的思路:给 AI 一个 JSON 模板,它按模板填空,你直接 JSON.parse() 拿数据。
打个比方:不给模板,AI 写自由作文;给了模板,AI 填表格。填表格的结果你当然更好处理。
基础用法:JSON Schema
thread.run() 的第二个参数 turnOptions 里有个 outputSchema,传入一个 JSON Schema 对象:
typescript// structured-basic.ts
import { Codex } from "@openai/codex-sdk";
async function main() {
const codex = new Codex();
const thread = codex.startThread({ skipGitRepoCheck: true });
// 定义 JSON Schema — AI 必须按这个格式返回
const schema = {
type: "object",
properties: {
summary: {
type: "string",
description: "一句话总结",
},
issues: {
type: "array",
items: {
type: "object",
properties: {
severity: {
type: "string",
enum: ["low", "medium", "high", "critical"],
description: "严重程度",
},
description: {
type: "string",
description: "问题描述",
},
file: {
type: "string",
description: "涉及的文件",
},
},
required: ["severity", "description"],
additionalProperties: false,
},
},
score: {
type: "number",
description: "代码质量评分 0-100",
},
},
required: ["summary", "issues", "score"],
additionalProperties: false,
} as const;
const turn = await thread.run("分析当前项目的代码质量", {
outputSchema: schema,
});
// turn.finalResponse 现在是 JSON 字符串!
const result = JSON.parse(turn.finalResponse);
console.log(`📊 评分: ${result.score}/100`);
console.log(`📝 总结: ${result.summary}`);
console.log(`🔍 发现 ${result.issues.length} 个问题:`);
for (const issue of result.issues) {
const icon = { low: "💬", medium: "⚠️", high: "❌", critical: "🔥" }[issue.severity];
console.log(` ${icon} [${issue.severity}] ${issue.description}`);
}
}
main().catch(console.error);关键点:
additionalProperties: false必须加 — 防止 AI 返回多余字段required必须列全 — 确保所有字段都有description强烈建议加 — AI 能看到,帮它理解每个字段该填什么
Zod 进阶:类型安全的结构化输出
手写 JSON Schema 又臭又长,一不留神就写错。Zod 是更优雅的方案:用 TypeScript 代码定义 schema,自带类型推导。
安装:
bashnpm install zod zod-to-json-schema改写上面的例子:
typescript// structured-zod.ts
import { Codex } from "@openai/codex-sdk";
import { z } from "zod";
import zodToJsonSchema from "zod-to-json-schema";
// 用 Zod 定义 schema — 又短又清晰
const ReviewSchema = z.object({
summary: z.string().describe("一句话总结"),
issues: z.array(
z.object({
severity: z.enum(["low", "medium", "high", "critical"]).describe("严重程度"),
description: z.string().describe("问题描述"),
file: z.string().optional().describe("涉及的文件"),
})
),
score: z.number().min(0).max(100).describe("代码质量评分"),
});
// 自动推导 TypeScript 类型 — 不用手写 interface!
type Review = z.infer<typeof ReviewSchema>;
async function main() {
const codex = new Codex();
const thread = codex.startThread({ skipGitRepoCheck: true });
const turn = await thread.run("分析当前项目的代码质量", {
// 关键:target 必须设为 "openAi"
outputSchema: zodToJsonSchema(ReviewSchema, { target: "openAi" }),
});
// 用 Zod 解析 — 如果格式不对会直接报错
const review: Review = ReviewSchema.parse(JSON.parse(turn.finalResponse));
console.log(`📊 评分: ${review.score}/100`);
console.log(`📝 ${review.summary}`);
review.issues.forEach((issue) => {
console.log(` [${issue.severity}] ${issue.description}`);
});
}
main().catch(console.error);Zod vs 手写 JSON Schema 对比
| 对比项 | 手写 JSON Schema | Zod |
|---|---|---|
| 代码量 | 30+ 行 | 10 行 |
| 类型安全 | 需要手写 interface | 自动推导 z.infer |
| 运行时校验 | 需要自己写 | .parse() 自带 |
| 可读性 | JSON 嵌套,眼花缭乱 | TypeScript 代码,一目了然 |
| 建议 | 简单 schema 可以用 | ✅ 推荐所有场景 |
Schema 设计技巧
1. 给字段加 description
AI 能看到 description,这就像给它写了个填表说明:
typescriptz.string().describe("用中文回答,不超过 50 字")2. 用 enum 限制选项
typescriptz.enum(["bug", "security", "performance", "style"])AI 只能从这几个里选,不会瞎编。
3. 控制嵌套深度
Schema 别搞太复杂,3 层嵌套以内为佳。太深了 AI 容易出错。
4. 用 optional() 标记非必填字段
typescriptz.object({
name: z.string(), // 必填
email: z.string().optional(), // 可选
age: z.number().optional(), // 可选
})错误处理
虽然有了 Schema,还是建议做好错误处理:
typescript// 方案一:try-catch
try {
const result = ReviewSchema.parse(JSON.parse(turn.finalResponse));
} catch (err) {
if (err instanceof z.ZodError) {
console.error("AI 返回的格式不对:", err.issues);
} else {
console.error("JSON 解析失败:", err);
}
}
// 方案二:safeParse(推荐)
const parsed = ReviewSchema.safeParse(JSON.parse(turn.finalResponse));
if (parsed.success) {
const review = parsed.data; // 类型安全
console.log(review.score);
} else {
console.error("格式错误:", parsed.error.issues);
}safeParse() 不会抛异常,返回一个 { success, data, error } 对象,更适合生产环境。
实战小例子:让 AI 做技术选型
typescriptconst TechDecisionSchema = z.object({
recommendation: z.string().describe("推荐的技术方案"),
reason: z.string().describe("推荐理由,100字以内"),
alternatives: z.array(
z.object({
name: z.string(),
pros: z.array(z.string()),
cons: z.array(z.string()),
})
).describe("备选方案"),
confidence: z.enum(["low", "medium", "high"]).describe("推荐置信度"),
});
const turn = await thread.run(
"我需要给 Node.js 项目选一个 ORM,数据库是 PostgreSQL,要支持 TypeScript",
{ outputSchema: zodToJsonSchema(TechDecisionSchema, { target: "openAi" }) }
);
const decision = TechDecisionSchema.parse(JSON.parse(turn.finalResponse));
console.log(`推荐: ${decision.recommendation} (置信度: ${decision.confidence})`);
console.log(`理由: ${decision.reason}`);小结
- 结构化输出 = 给 AI 一个 JSON Schema,它按格式返回
- 基础用法:
run(prompt, { outputSchema: schema }) - 推荐用 Zod:代码短、类型安全、自带运行时校验
- 记得加
{ target: "openAi" }给zodToJsonSchema - 生产环境用
safeParse()做安全解析
下一章:高级特性 — 全局配置、线程恢复、多模态输入、取消控制。