
Agent 默认返回自由文本,但应用需要的是可直接入库、渲染 UI 的结构化数据。本篇完整拆解 outputFormat 参数的接入方式、Zod/Pydantic 类型安全用法、多步工具调用下的结构化约束、error_max_structured_output_retries 错误处理,以及五条避免验证失败的实践建议。
query() 默认返回自由文本,但你的应用通常不需要一大段话——你要的是能直接入库、渲染 UI、触发下一步逻辑的结构化数据。结构化输出(Structured Outputs)就是为这个场景设计的:定义 JSON Schema,SDK 验证 Agent 输出是否匹配,不匹配就自动重试,超过重试上限才报错。Here's a classic chocolate chip cookie recipe!
**Chocolate Chip Cookies**
Prep time: 15 minutes | Cook time: 10 minutes
Ingredients: - 2 1/4 cups all-purpose flour - 1 cup butter, softened ...{
"name": "Chocolate Chip Cookies",
"prep_time_minutes": 15,
"cook_time_minutes": 10,
"ingredients": [
{ "item": "all-purpose flour", "amount": 2.25, "unit": "cups" },
{ "item": "butter, softened", "amount": 1, "unit": "cup" }
],
"steps": ["Preheat oven to 375°F", "Cream butter and sugar"]
}query() 的 outputFormat,然后从 ResultMessage 的 structured_output 字段取结果。import { query } from "@anthropic-ai/claude-agent-sdk";
// 第一步:定义你要的数据形状
const schema = {
type: "object",
properties: {
company_name: { type: "string" },
founded_year: { type: "number" },
headquarters: { type: "string" }
},
required: ["company_name"]
};
// 第二步:传入 outputFormat
for await (const message of query({
prompt: "Research Anthropic and provide key company information",
options: {
outputFormat: {
type: "json_schema",
schema: schema
}
}
})) {
// 第三步:从 ResultMessage 取 structured_output
if (message.type === "result" &&
message.subtype === "success" &&
message.structured_output) {
console.log(message.structured_output);
// { company_name: "Anthropic", founded_year: 2021, headquarters: "San Francisco, CA" }
}
}outputFormat.type 固定传 "json_schema"ResultMessage 的 structured_output 字段,不是普通消息流structured_output 之后还要自己做类型断言。更好的方案是用 Zod(TypeScript)或 Pydantic(Python):定义一次 schema,自动生成 JSON Schema 传给 SDK,再用 safeParse()/model_validate() 把返回值解析成完全类型化的对象。import { z } from "zod";
import { query } from "@anthropic-ai/claude-agent-sdk";
// 用 Zod 定义 schema(含枚举类型)
const FeaturePlan = z.object({
feature_name: z.string(),
summary: z.string(),
steps: z.array(
z.object({
step_number: z.number(),
description: z.string(),
estimated_complexity: z.enum(["low", "medium", "high"])
})
),
risks: z.array(z.string())
});
type FeaturePlan = z.infer<typeof FeaturePlan>;
// 转成 JSON Schema
const schema = z.toJSONSchema(FeaturePlan);
for await (const message of query({
prompt: "Plan how to add dark mode support to a React app. Break it into implementation steps.",
options: {
outputFormat: { type: "json_schema", schema }
}
})) {
if (message.type === "result" &&
message.subtype === "success" &&
message.structured_output) {
// safeParse:拿到完全类型化的对象
const parsed = FeaturePlan.safeParse(message.structured_output);
if (parsed.success) {
const plan: FeaturePlan = parsed.data;
console.log(`Feature: ${plan.feature_name}`);
plan.steps.forEach((step) => {
console.log(`${step.step_number}. [${step.estimated_complexity}] ${step.description}`);
});
}
}
}from pydantic import BaseModel
from claude_code_sdk import query, ClaudeCodeOptions, OutputFormat
class Step(BaseModel):
step_number: int
description: str
complexity: str # "low" | "medium" | "high"
class FeaturePlan(BaseModel):
feature_name: str
summary: str
steps: list[Step]
risks: list[str]
async def main():
schema = FeaturePlan.model_json_schema()
async for message in query(
prompt="Plan dark mode support for a React app.",
options=ClaudeCodeOptions(
output_format=OutputFormat(
type="json_schema",
schema=schema
)
)
):
if message.type == "result" and message.subtype == "success":
if message.structured_output:
plan = FeaturePlan.model_validate(message.structured_output)
print(plan.feature_name)safeParse()/model_validate() 二次校验;schema 可复用在其他地方(数据库校验、API 文档生成等)。1
z.object() + z.infer<> 拿到类型,Python 用 BaseModel + model_validate(),两套写法实现等价的类型安全。AI 生成示意图 outputFormat(TypeScript)/ output_format(Python)接受一个对象:| 字段 | 值 | 说明 |
|---|---|---|
type | "json_schema" | 目前只有这一个值 |
schema | JSON Schema 对象 | 可以手写,或用 z.toJSONSchema() / .model_json_schema() 生成 |
enum、const、必填字段 required、嵌套对象、$ref 定义。1git blame 找出每条 TODO 的作者和日期,最后汇总成结构化列表:import { query } from "@anthropic-ai/claude-agent-sdk";
const todoSchema = {
type: "object",
properties: {
todos: {
type: "array",
items: {
type: "object",
properties: {
text: { type: "string" },
file: { type: "string" },
line: { type: "number" },
author: { type: "string" }, // 可选:git blame 可能找不到
date: { type: "string" } // 可选
},
required: ["text", "file", "line"] // 必填字段最小化
}
},
total_count: { type: "number" }
},
required: ["todos", "total_count"]
};
// Agent 会自动决定:先用 Grep 搜 TODO,再用 Bash 跑 git blame
for await (const message of query({
prompt: "Find all TODO comments in this codebase and identify who added them",
options: {
outputFormat: { type: "json_schema", schema: todoSchema }
}
})) {
if (message.type === "result" &&
message.subtype === "success" &&
message.structured_output) {
const data = message.structured_output as {
total_count: number;
todos: Array<{
file: string; line: number; text: string;
author?: string; date?: string;
}>;
};
console.log(`Found ${data.total_count} TODOs`);
data.todos.forEach((todo) => {
console.log(`${todo.file}:${todo.line} - ${todo.text}`);
if (todo.author) console.log(` Added by ${todo.author} on ${todo.date}`);
});
}
}
ResultMessage.subtype 告知失败原因:| subtype | 含义 |
|---|---|
success | 输出生成并通过验证 |
error_max_structured_output_retries | 多次重试后仍无法产出合法输出 |
for await (const msg of query({
prompt: "Extract contact info from the document",
options: {
outputFormat: { type: "json_schema", schema: contactSchema }
}
})) {
if (msg.type === "result") {
if (msg.subtype === "success" && msg.structured_output) {
// 正常处理
console.log(msg.structured_output);
} else if (msg.subtype === "error_max_structured_output_retries") {
// 降级处理:用更简单的 prompt 重试,或回退到非结构化流程
console.error("Could not produce valid output");
}
}
}
ResultMessage.subtype 区分验证成功与重试耗尽,失败时可降级重试或回退到非结构化流程。AI 生成示意图 required 数组控制到最小集合。Agent 可能获取不到所有信息,强制要求可选项只会增加验证失败概率。没有把握必然有值的字段,默认设为可选。estimated_complexity: "low" | "medium" | "high",prompt 里最好也提到这个分类维度,让 Agent 知道该判断什么。prompt 越模糊,Agent 越难给出符合约束的输出。type 字段、写错 $ref 引用。Zod 和 Pydantic 在写 schema 时就有类型检查,生成的 JSON Schema 更规范,后续拿到结果还能二次 parse 拿到类型化对象。structured_output 参数更直接;如果你需要 Agent 先搜索/执行再给结果,才用 SDK 的 outputFormat。stream-json 模式与 SDK 流式消费的完整细节,敬请期待。docs.anthropic.com
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.