Bỏ qua để đến nội dung

Lấy structured output từ agent

Bài viết được dịch tự động từ bài viết gốc, chưa được kiểm tra lại bởi con người. Chỉ những bài viết có dấu tick xanh cạnh tiêu đề là đã được kiểm tra.

Structured output cho phép bạn định nghĩa chính xác hình dạng dữ liệu bạn muốn nhận về từ một agent. Agent có thể dùng bất kỳ tool nào nó cần để hoàn thành tác vụ, và bạn vẫn nhận JSON đã validate khớp schema của mình ở cuối. Định nghĩa một JSON Schema cho cấu trúc bạn cần, và SDK validate output theo schema đó, tự prompt lại nếu không khớp. Nếu validation không thành công trong giới hạn retry, kết quả là một lỗi thay vì structured data; xem Xử lý lỗi.

Để có full type safety, dùng Zod (TypeScript) hoặc Pydantic (Python) để định nghĩa schema và nhận về object đã được gõ kiểu mạnh (strongly-typed).

Mặc định, agent trả về text tự do, hợp cho chat nhưng không hợp khi bạn cần dùng output đó trong code. Structured output cho bạn dữ liệu có kiểu mà bạn có thể truyền thẳng vào logic ứng dụng, database, hay UI component.

Hãy xét một app công thức nấu ăn nơi một agent tìm kiếm trên web và mang về công thức. Không có structured output, bạn nhận text tự do mà bạn phải tự parse. Với structured output, bạn định nghĩa hình dạng mình muốn và nhận dữ liệu có kiểu để dùng thẳng trong app.

Không có structured output:

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
...

Để dùng cái này trong app, bạn phải tự parse ra title, chuyển “15 minutes” thành số, tách ingredient khỏi instruction, và xử lý định dạng không nhất quán qua các phản hồi.

Có structured output:

{
"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" /* ... */]
}

Dữ liệu có kiểu bạn dùng thẳng trong UI.

Để dùng structured output, định nghĩa một JSON Schema mô tả hình dạng dữ liệu bạn muốn, rồi truyền nó cho query() qua tùy chọn outputFormat (TypeScript) hoặc output_format (Python). Khi agent hoàn tất, result message gồm một trường structured_output chứa dữ liệu đã validate khớp schema của bạn.

Ví dụ dưới đây yêu cầu agent nghiên cứu về Anthropic và trả về tên công ty, năm thành lập, và trụ sở dưới dạng structured output.

import { query } from "@anthropic-ai/claude-agent-sdk";
// Định nghĩa hình dạng dữ liệu bạn muốn nhận về
const schema = {
type: "object",
properties: {
company_name: { type: "string" },
founded_year: { type: "number" },
headquarters: { type: "string" }
},
required: ["company_name"]
};
try {
for await (const message of query({
prompt: "Research Anthropic and provide key company information",
options: {
outputFormat: {
type: "json_schema",
schema: schema
}
}
})) {
// Result message chứa structured_output với dữ liệu đã validate
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" }
}
}
} catch (error) {
// Một query() single-shot throw lỗi sau khi yield một result lỗi, như
// error_max_structured_output_retries; xem phần Xử lý lỗi.
console.error(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
# Định nghĩa hình dạng dữ liệu bạn muốn nhận về
schema = {
"type": "object",
"properties": {
"company_name": {"type": "string"},
"founded_year": {"type": "number"},
"headquarters": {"type": "string"},
},
"required": ["company_name"],
}
async def main():
try:
async for message in query(
prompt="Research Anthropic and provide key company information",
options=ClaudeAgentOptions(
output_format={"type": "json_schema", "schema": schema}
),
):
# Result message chứa structured_output với dữ liệu đã validate
if isinstance(message, ResultMessage) and message.structured_output:
print(message.structured_output)
# {'company_name': 'Anthropic', 'founded_year': 2021, 'headquarters': 'San Francisco, CA'}
except Exception as error:
# Một query() single-shot raise lỗi sau khi yield một result lỗi, như
# error_max_structured_output_retries; xem phần Xử lý lỗi.
print(f"Session ended with an error: {error}")
asyncio.run(main())

Thay vì viết JSON Schema thủ công, bạn có thể dùng Zod (TypeScript) hoặc Pydantic (Python) để định nghĩa schema. Các thư viện này tự sinh JSON Schema cho bạn và cho phép bạn parse phản hồi thành một object có kiểu đầy đủ, dùng được xuyên suốt codebase với autocomplete và type checking.

Ví dụ dưới đây định nghĩa schema cho một kế hoạch triển khai feature gồm tóm tắt, danh sách bước (mỗi bước có mức độ phức tạp), và các rủi ro tiềm ẩn. Agent lập kế hoạch cho feature và trả về một object FeaturePlan có kiểu. Bạn có thể truy cập property như plan.summary và duyệt plan.steps với type safety đầy đủ.

SDK validate schema với JSON Schema draft-07, nên schema khai báo version mới hơn sẽ bị từ chối. Zod nhắm tới draft 2020-12 theo mặc định, nên hãy truyền target: "draft-7" khi convert schema của bạn.

import { z } from "zod";
import { query } from "@anthropic-ai/claude-agent-sdk";
// Định nghĩa schema với Zod
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>;
// Convert sang JSON Schema dùng target draft-7 mà SDK cần
const schema = z.toJSONSchema(FeaturePlan, { target: "draft-7" });
// Dùng trong query
try {
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: schema
}
}
})) {
if (message.type === "result" && message.subtype === "success" && message.structured_output) {
// Validate và lấy kết quả có kiểu đầy đủ
const parsed = FeaturePlan.safeParse(message.structured_output);
if (parsed.success) {
const plan: FeaturePlan = parsed.data;
console.log(`Feature: ${plan.feature_name}`);
console.log(`Summary: ${plan.summary}`);
plan.steps.forEach((step) => {
console.log(`${step.step_number}. [${step.estimated_complexity}] ${step.description}`);
});
}
}
}
} catch (error) {
// Một query() single-shot throw lỗi sau khi yield một result lỗi, như
// error_max_structured_output_retries; xem phần Xử lý lỗi.
console.error(`Session ended with an error: ${error}`);
}
import asyncio
from pydantic import BaseModel
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
class Step(BaseModel):
step_number: int
description: str
estimated_complexity: str # 'low', 'medium', 'high'
class FeaturePlan(BaseModel):
feature_name: str
summary: str
steps: list[Step]
risks: list[str]
async def main():
try:
async for message in query(
prompt="Plan how to add dark mode support to a React app. Break it into implementation steps.",
options=ClaudeAgentOptions(
output_format={
"type": "json_schema",
"schema": FeaturePlan.model_json_schema(),
}
),
):
if isinstance(message, ResultMessage) and message.structured_output:
# Validate và lấy kết quả có kiểu đầy đủ
plan = FeaturePlan.model_validate(message.structured_output)
print(f"Feature: {plan.feature_name}")
print(f"Summary: {plan.summary}")
for step in plan.steps:
print(
f"{step.step_number}. [{step.estimated_complexity}] {step.description}"
)
except Exception as error:
# Một query() single-shot raise lỗi sau khi yield một result lỗi, như
# error_max_structured_output_retries; xem phần Xử lý lỗi.
print(f"Session ended with an error: {error}")
asyncio.run(main())

Lợi ích:

  • Type inference đầy đủ (TypeScript) và type hint (Python)
  • Runtime validation với safeParse() hoặc model_validate()
  • Thông báo lỗi tốt hơn
  • Schema có thể tái sử dụng, ghép nối được

Tùy chọn outputFormat (TypeScript) hoặc output_format (Python) nhận một object gồm:

  • type: đặt "json_schema" cho structured output
  • schema: một object JSON Schema định nghĩa cấu trúc output của bạn. Bạn có thể sinh cái này từ một Zod schema bằng z.toJSONSchema(schema, { target: "draft-7" }) hoặc một Pydantic model bằng .model_json_schema()

SDK hỗ trợ các tính năng JSON Schema chuẩn gồm mọi kiểu cơ bản (object, array, string, number, boolean, null), enum, const, required, nested object, và $ref definitions. Để biết danh sách đầy đủ tính năng được hỗ trợ và giới hạn, xem JSON Schema limitations.

Một schema không phải JSON Schema hợp lệ sẽ làm run thất bại ngay khi khởi động với lỗi nêu rõ vấn đề. Trước v2.1.205, một schema không hợp lệ bị âm thầm bỏ qua và agent trả về text không có cấu trúc.

Từ khoá format, như "format": "email", được chấp nhận như một annotation và không được validator của SDK áp dụng cưỡng chế. Trước v2.1.205, bất kỳ schema nào chứa format đều bị coi là không hợp lệ.

Ví dụ này minh hoạ cách structured output hoạt động cùng với việc dùng tool nhiều bước. Agent cần tìm các comment TODO trong codebase, rồi tra thông tin git blame cho từng cái. Nó tự quyết định dùng tool nào (Grep để tìm kiếm, Bash để chạy lệnh git) và kết hợp kết quả thành một phản hồi có cấu trúc duy nhất.

Schema gồm các trường tuỳ chọn (authordate) vì thông tin git blame có thể không có sẵn cho mọi file. Agent điền vào những gì tìm được và bỏ qua phần còn lại.

import { query } from "@anthropic-ai/claude-agent-sdk";
// Định nghĩa cấu trúc để trích TODO
const todoSchema = {
type: "object",
properties: {
todos: {
type: "array",
items: {
type: "object",
properties: {
text: { type: "string" },
file: { type: "string" },
line: { type: "number" },
author: { type: "string" },
date: { type: "string" }
},
required: ["text", "file", "line"]
}
},
total_count: { type: "number" }
},
required: ["todos", "total_count"]
};
// Agent dùng Grep để tìm TODO, Bash để lấy thông tin git blame
try {
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}`);
}
});
}
}
} catch (error) {
// Một query() single-shot throw lỗi sau khi yield một result lỗi, như
// error_max_structured_output_retries; xem phần Xử lý lỗi.
console.error(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
# Định nghĩa cấu trúc để trích TODO
todo_schema = {
"type": "object",
"properties": {
"todos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"text": {"type": "string"},
"file": {"type": "string"},
"line": {"type": "number"},
"author": {"type": "string"},
"date": {"type": "string"},
},
"required": ["text", "file", "line"],
},
},
"total_count": {"type": "number"},
},
"required": ["todos", "total_count"],
}
async def main():
# Agent dùng Grep để tìm TODO, Bash để lấy thông tin git blame
try:
async for message in query(
prompt="Find all TODO comments in this codebase and identify who added them",
options=ClaudeAgentOptions(
output_format={"type": "json_schema", "schema": todo_schema}
),
):
if isinstance(message, ResultMessage) and message.structured_output:
data = message.structured_output
print(f"Found {data['total_count']} TODOs")
for todo in data["todos"]:
print(f"{todo['file']}:{todo['line']} - {todo['text']}")
if "author" in todo:
print(f" Added by {todo['author']} on {todo['date']}")
except Exception as error:
# Một query() single-shot raise lỗi sau khi yield một result lỗi, như
# error_max_structured_output_retries; xem phần Xử lý lỗi.
print(f"Session ended with an error: {error}")
asyncio.run(main())

Việc sinh structured output có thể thất bại khi agent không thể tạo ra JSON hợp lệ khớp schema của bạn. Điều này thường xảy ra khi schema quá phức tạp cho tác vụ, tác vụ mơ hồ, hoặc agent chạm giới hạn retry khi cố sửa lỗi validation. Nó cũng có thể xảy ra mà không có lỗi validation nào: một model fallback có thể rút lại một output đã hoàn tất giữa chừng, và nếu không có retry nào thay thế nó, run kết thúc với cùng loại lỗi. Kiểm tra danh sách errors trên result message để phân biệt hai nguyên nhân này trước khi debug schema của bạn.

Khi có lỗi xảy ra, result message có một subtype cho biết chuyện gì đã xảy ra:

SubtypeÝ nghĩa
successOutput đã được sinh và validate thành công
error_max_structured_output_retriesKhông còn output hợp lệ nào sau nhiều lần thử (lỗi validation, hoặc model-fallback rút lại mà không có retry thành công)

Một result cũng có thể kết thúc với subtype success nhưng không có giá trị structured_output, ví dụ khi run hoàn tất mà agent không tạo ra structured output. Hãy coi trường hợp đó cũng là thất bại. Ví dụ dưới đây chỉ coi result là thành công khi subtypesuccessstructured_output có mặt, và xử lý mọi result khác như thất bại:

import { query } from "@anthropic-ai/claude-agent-sdk";
const contactSchema = {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string" }
},
required: ["name"]
};
try {
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) {
// Dùng output đã validate
console.log(msg.structured_output);
} else if (msg.subtype === "error_max_structured_output_retries") {
console.error("Could not produce valid output");
} else {
console.error("Run ended without a structured output");
}
}
}
} catch (error) {
// Một query() single-shot throw lỗi sau khi yield một result lỗi. Nếu
// thất bại là một result lỗi, các nhánh error subtype ở trên đã chạy;
// lỗi kết nối hay process không yield result message nào.
console.log(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
contact_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
},
"required": ["name"],
}
async def main():
try:
async for message in query(
prompt="Extract contact info from the document",
options=ClaudeAgentOptions(
output_format={"type": "json_schema", "schema": contact_schema}
),
):
if isinstance(message, ResultMessage):
if message.subtype == "success" and message.structured_output:
# Dùng output đã validate
print(message.structured_output)
elif message.subtype == "error_max_structured_output_retries":
print("Could not produce valid output")
else:
print("Run ended without a structured output")
except Exception as error:
# Một query() single-shot raise lỗi sau khi yield một result lỗi. Nếu
# thất bại là một result lỗi, các nhánh error subtype ở trên đã chạy;
# lỗi kết nối hay process không yield result message nào.
print(f"Session ended with an error: {error}")
asyncio.run(main())

Mẹo tránh lỗi:

  • Giữ schema tập trung. Schema lồng sâu với nhiều trường bắt buộc khó thoả mãn hơn. Bắt đầu đơn giản và thêm độ phức tạp khi cần.
  • Khớp schema với tác vụ. Nếu tác vụ có thể không có đủ thông tin schema của bạn yêu cầu, hãy làm các trường đó tuỳ chọn.
  • Dùng prompt rõ ràng. Prompt mơ hồ khiến agent khó biết cần tạo output gì.
  • Tài liệu JSON Schema: học cú pháp JSON Schema để định nghĩa schema phức tạp với nested object, array, enum, và ràng buộc validation
  • API Structured Outputs: dùng structured output trực tiếp với Claude API cho các yêu cầu một lượt không dùng tool
  • Custom tools: cho agent của bạn custom tool để gọi trong lúc thực thi trước khi trả về structured output