رفتن به محتوا

گرفتن خروجی ساختاریافته از ایجنت‌ها

خروجی‌های ساختاریافته به تو اجازه می‌دهند شکل دقیق داده‌ای را که می‌خواهی از ایجنت بگیری تعریف کنی. ایجنت می‌تواند از هر ابزاری که برای انجام کار لازم دارد استفاده کند و تو در پایان همچنان JSONِ معتبری مطابق با اسکیمای خودت دریافت می‌کنی. یک JSON Schema برای ساختاری که نیاز داری تعریف کن؛ SDK خروجی را در برابر آن اعتبارسنجی می‌کند و در صورت ناهماهنگی دوباره پرامپت می‌دهد. اگر اعتبارسنجی در محدوده‌ی تعداد تلاش‌های مجاز موفق نشود، نتیجه به‌جای داده‌ی ساختاریافته یک خطا خواهد بود؛ به مدیریت خطا نگاه کن.

برای ایمنی کاملِ نوع (type safety)، از Zod (در TypeScript) یا Pydantic (در Python) برای تعریف اسکیما و گرفتن آبجکت‌های قویاً تایپ‌شده استفاده کن.

چرا خروجی‌های ساختاریافته؟

Section titled “چرا خروجی‌های ساختاریافته؟”

ایجنت‌ها به‌صورت پیش‌فرض متنِ آزاد برمی‌گردانند که برای چت خوب کار می‌کند اما وقتی بخواهی خروجی را به‌صورت برنامه‌نویسی‌شده استفاده کنی، مناسب نیست. خروجی‌های ساختاریافته به تو داده‌ی تایپ‌شده می‌دهند که می‌توانی مستقیم به منطق برنامه، پایگاه‌داده یا کامپوننت‌های UI پاس بدهی.

یک اپلیکیشن دستور پخت را در نظر بگیر که در آن ایجنت در وب جست‌وجو می‌کند و دستورهای پخت را برمی‌گرداند. بدون خروجی‌های ساختاریافته، متنِ آزادی می‌گیری که باید خودت آن را تجزیه کنی. با خروجی‌های ساختاریافته، شکلی را که می‌خواهی تعریف می‌کنی و داده‌ی تایپ‌شده‌ای می‌گیری که مستقیماً در اپلیکیشن قابل‌استفاده است.

بدون خروجی‌های ساختاریافته
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" /* ... */]
}

داده‌ی تایپ‌شده‌ای که مستقیماً در UI خودت قابل‌استفاده است.

برای استفاده از خروجی‌های ساختاریافته، یک JSON Schema که شکل داده‌ی موردنظرت را توصیف می‌کند تعریف کن، سپس آن را از طریق گزینه‌ی outputFormat (در TypeScript) یا گزینه‌ی output_format (در Python) به query() پاس بده. وقتی ایجنت کارش تمام شد، پیامِ نتیجه شامل فیلد structured_output با داده‌ی اعتبارسنجی‌شده‌ی مطابق اسکیمای توست.

مثال زیر از ایجنت می‌خواهد درباره‌ی Anthropic تحقیق کند و نام شرکت، سال تأسیس و دفتر مرکزی را به‌صورت خروجی ساختاریافته برگرداند.

import { query } from "@anthropic-ai/claude-agent-sdk";
// Define the shape of data you want back
const schema = {
type: "object",
properties: {
company_name: { type: "string" },
founded_year: { type: "number" },
headquarters: { type: "string" }
},
required: ["company_name"]
};
for await (const message of query({
prompt: "Research Anthropic and provide key company information",
options: {
outputFormat: {
type: "json_schema",
schema: schema
}
}
})) {
// The result message contains structured_output with validated data
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" }
}
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
# Define the shape of data you want back
schema = {
"type": "object",
"properties": {
"company_name": {"type": "string"},
"founded_year": {"type": "number"},
"headquarters": {"type": "string"},
},
"required": ["company_name"],
}
async def main():
async for message in query(
prompt="Research Anthropic and provide key company information",
options=ClaudeAgentOptions(
output_format={"type": "json_schema", "schema": schema}
),
):
# The result message contains structured_output with validated data
if isinstance(message, ResultMessage) and message.structured_output:
print(message.structured_output)
# {'company_name': 'Anthropic', 'founded_year': 2021, 'headquarters': 'San Francisco, CA'}
asyncio.run(main())

اسکیماهای ایمن از نظر نوع با Zod و Pydantic

Section titled “اسکیماهای ایمن از نظر نوع با Zod و Pydantic”

به‌جای نوشتن دستیِ JSON Schema، می‌توانی از Zod (در TypeScript) یا Pydantic (در Python) برای تعریف اسکیما استفاده کنی. این کتابخانه‌ها JSON Schema را برایت می‌سازند و به تو اجازه می‌دهند پاسخ را به یک آبجکتِ کاملاً تایپ‌شده تجزیه کنی که در سراسر کدبیست با تکمیلِ خودکار و بررسیِ نوع قابل‌استفاده است.

مثال زیر اسکیمایی برای یک طرحِ پیاده‌سازیِ قابلیت تعریف می‌کند که شامل یک خلاصه، فهرستی از گام‌ها (هرکدام با سطح پیچیدگی) و ریسک‌های احتمالی است. ایجنت قابلیت را برنامه‌ریزی می‌کند و یک آبجکتِ تایپ‌شده‌ی FeaturePlan برمی‌گرداند. بعد می‌توانی به ویژگی‌هایی مثل plan.summary دسترسی پیدا کنی و روی plan.steps با ایمنیِ نوعِ کامل پیمایش کنی.

import { z } from "zod";
import { query } from "@anthropic-ai/claude-agent-sdk";
// Define schema with 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 to JSON Schema
const schema = z.toJSONSchema(FeaturePlan);
// Use in query
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 and get fully typed result
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}`);
});
}
}
}
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():
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 and get fully typed result
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}"
)
asyncio.run(main())

مزایا:

  • استنتاجِ کاملِ نوع (در TypeScript) و راهنمای نوع (در Python)
  • اعتبارسنجی در زمان اجرا با safeParse() یا model_validate()
  • پیام‌های خطای بهتر
  • اسکیماهای قابلِ ترکیب و قابلِ استفاده‌ی مجدد

گزینه‌ی outputFormat (در TypeScript) یا output_format (در Python) آبجکتی می‌پذیرد با این موارد:

  • type: برای خروجی‌های ساختاریافته روی "json_schema" تنظیم کن
  • schema: یک آبجکتِ JSON Schema که ساختار خروجی‌ات را تعریف می‌کند. می‌توانی این را از یک اسکیمای Zod با z.toJSONSchema() یا از یک مدلِ Pydantic با .model_json_schema() بسازی

این SDK از قابلیت‌های استانداردِ JSON Schema پشتیبانی می‌کند، از جمله همه‌ی انواعِ پایه (object، array، string، number، boolean، null)، enum، const، required، آبجکت‌های تودرتو و تعاریف $ref. برای فهرست کامل قابلیت‌های پشتیبانی‌شده و محدودیت‌ها، به محدودیت‌های JSON Schema نگاه کن.

این مثال نشان می‌دهد که خروجی‌های ساختاریافته چطور با استفاده‌ی چندمرحله‌ای از ابزار کار می‌کنند. ایجنت باید کامنت‌های TODO را در کدبیس پیدا کند، سپس برای هرکدام اطلاعات git blame را جست‌وجو کند. ایجنت خودش به‌طور مستقل تصمیم می‌گیرد از کدام ابزارها استفاده کند (Grep برای جست‌وجو، Bash برای اجرای دستورهای git) و نتایج را در یک پاسخِ ساختاریافته‌ی واحد ترکیب می‌کند.

اسکیما شامل فیلدهای اختیاری (author و date) است، چون ممکن است اطلاعات git blame برای همه‌ی فایل‌ها در دسترس نباشد. ایجنت هرچه را پیدا کند پر می‌کند و بقیه را حذف می‌کند.

import { query } from "@anthropic-ai/claude-agent-sdk";
// Define structure for TODO extraction
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 uses Grep to find TODOs, Bash to get git blame info
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}`);
}
});
}
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
# Define structure for TODO extraction
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 uses Grep to find TODOs, Bash to get git blame info
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']}")
asyncio.run(main())

تولید خروجیِ ساختاریافته می‌تواند شکست بخورد، وقتی ایجنت نتواند JSONِ معتبرِ مطابق با اسکیمای تو را تولید کند. این معمولاً وقتی پیش می‌آید که اسکیما برای کار بیش‌ازحد پیچیده است، خودِ کار مبهم است، یا ایجنت در تلاش برای رفع خطاهای اعتبارسنجی به سقفِ تعداد تلاش‌ها می‌رسد. همچنین می‌تواند بدون هیچ شکستِ اعتبارسنجی رخ بدهد: یک بازگشت به مدلِ جایگزین (model fallback) می‌تواند خروجیِ ازپیش‌تکمیل‌شده‌ای را در میانه‌ی استریم پس بگیرد، و اگر هیچ تلاش مجددی جایگزین آن نشود، اجرا با همان خطا پایان می‌یابد. برای تشخیص این دو علت از هم پیش از اشکال‌زدایی اسکیما، متنِ errors در نتیجه را بررسی کن.

وقتی خطایی رخ بدهد، پیامِ نتیجه یک subtype دارد که نشان می‌دهد چه اشتباهی پیش آمده است:

Subtypeمعنا
successخروجی با موفقیت تولید و اعتبارسنجی شد
error_max_structured_output_retriesپس از چند تلاش هیچ خروجیِ معتبری باقی نماند (شکست‌های اعتبارسنجی، یا پس‌گرفتنِ خروجی بر اثر بازگشت به مدل جایگزین بدون تلاش مجددِ موفق)

مثال زیر فیلد subtype را بررسی می‌کند تا مشخص شود خروجی با موفقیت تولید شده یا باید یک شکست را مدیریت کنی:

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) {
// Use the validated output
console.log(msg.structured_output);
} else if (msg.subtype === "error_max_structured_output_retries") {
// Handle the failure - retry with simpler prompt, fall back to unstructured, etc.
console.error("Could not produce valid output");
}
}
}
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:
# Use the validated output
print(message.structured_output)
elif message.subtype == "error_max_structured_output_retries":
# Handle the failure
print("Could not produce valid output")

نکته‌هایی برای جلوگیری از خطا:

  • اسکیماها را متمرکز نگه دار. اسکیماهای عمیقاً تودرتو با فیلدهای الزامیِ زیاد سخت‌تر ارضا می‌شوند. ساده شروع کن و در صورت نیاز پیچیدگی اضافه کن.
  • اسکیما را با کار هماهنگ کن. اگر ممکن است کار همه‌ی اطلاعاتی را که اسکیمایت لازم دارد نداشته باشد، آن فیلدها را اختیاری کن.
  • پرامپت‌های شفاف بنویس. پرامپت‌های مبهم تشخیصِ خروجیِ موردنظر را برای ایجنت سخت‌تر می‌کنند.
  • مستندات JSON Schema: نحوِ JSON Schema را برای تعریف اسکیماهای پیچیده با آبجکت‌های تودرتو، آرایه‌ها، enumها و قیدهای اعتبارسنجی یاد بگیر
  • خروجی‌های ساختاریافته در API: از خروجی‌های ساختاریافته مستقیماً با Claude API برای درخواست‌های تک‌نوبتی بدون استفاده از ابزار استفاده کن
  • ابزارهای سفارشی: به ایجنتت ابزارهای سفارشی بده تا پیش از برگرداندنِ خروجیِ ساختاریافته، در حین اجرا آن‌ها را فراخوانی کند