Trước đây, việc lấy được JSON hợp lệ từ mô hình ngôn ngữ lớn luôn là bài toán đau đầu — bạn phải viết prompt cầu kỳ rồi dùng regex để parse kết quả. Ngày nay, cả OpenAI lẫn Anthropic đều hỗ trợ structured output, đảm bảo response luôn là JSON hợp lệ theo đúng schema bạn định nghĩa. Bài viết này hướng dẫn bạn triển khai structured output với cả hai API, từ cơ bản đến nâng cao.
Tại Sao Structured Output Quan Trọng
Khi tích hợp LLM vào ứng dụng, bạn hiếm khi cần text tự do. Bạn cần dữ liệu có cấu trúc mà code có thể parse được: mô tả sản phẩm với các field cụ thể, kết quả phân loại kèm điểm tin cậy, hay các entity được trích xuất theo format nhất quán. Nếu không có structured output, bạn phải viết logic parsing dễ vỡ, hỏng mỗi khi model thêm lời chào hay thay đổi format.
Structured output giải quyết vấn đề này bằng cách ràng buộc response của model theo JSON schema do bạn định nghĩa. Model không thể tạo ra JSON không hợp lệ hay lệch khỏi schema — output được đảm bảo luôn parse được.
Bước 1: JSON Mode Cơ Bản Với OpenAI
Cách đơn giản nhất là JSON mode của OpenAI — đảm bảo response là JSON hợp lệ (nhưng chưa chắc khớp schema cụ thể).
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": "You extract product information and return it as JSON with fields: name, price, category, description."
},
{
"role": "user",
"content": "The new AirPods Pro 3 cost $249 and feature hearing health capabilities with improved noise cancellation."
}
]
)
data = json.loads(response.choices[0].message.content)
print(data)
# {"name": "AirPods Pro 3", "price": 249, "category": "audio", "description": "..."}Lưu ý quan trọng: Bạn phải đề cập đến "JSON" trong system hoặc user message khi dùng JSON mode, nếu không API sẽ trả lỗi.
Bước 2: Structured Output Nghiêm Ngặt Với OpenAI
Với ứng dụng production, bạn cần đảm bảo schema chính xác. Structured outputs của OpenAI với json_schema bảo đảm response khớp hoàn toàn với schema bạn định nghĩa.
from openai import OpenAI
from pydantic import BaseModel
class ProductInfo(BaseModel):
name: str
price: float
currency: str
category: str
features: list[str]
in_stock: bool
client = OpenAI()
response = client.beta.chat.completions.parse(
model="gpt-4o",
response_format=ProductInfo,
messages=[
{
"role": "system",
"content": "Extract product information from the user's text."
},
{
"role": "user",
"content": "The Sony WH-1000XM6 headphones ($348) feature 40-hour battery life, multipoint connection, and improved ANC. Currently available."
}
]
)
product = response.choices[0].message.parsed
print(product.name) # "Sony WH-1000XM6"
print(product.features) # ["40-hour battery life", "multipoint connection", "improved ANC"]Cách này dùng Pydantic model để định nghĩa schema. SDK tự động chuyển model thành JSON schema và parse response trở lại thành typed object.
Bước 3: Structured Output Với Claude (Anthropic)
Claude của Anthropic hỗ trợ structured output thông qua cơ chế tool use. Bạn định nghĩa một tool với output schema mong muốn, và model sẽ "gọi" tool đó với dữ liệu có cấu trúc.
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=[
{
"name": "extract_product",
"description": "Extract structured product information",
"input_schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"},
"currency": {"type": "string"},
"category": {"type": "string"},
"features": {
"type": "array",
"items": {"type": "string"}
},
"in_stock": {"type": "boolean"}
},
"required": ["name", "price", "currency", "category", "features", "in_stock"]
}
}
],
tool_choice={"type": "tool", "name": "extract_product"},
messages=[
{
"role": "user",
"content": "Extract product info: The Sony WH-1000XM6 headphones ($348) feature 40-hour battery life, multipoint connection, and improved ANC. Currently available."
}
]
)
# Dữ liệu có cấu trúc nằm trong tool use block
tool_use_block = next(
block for block in response.content if block.type == "tool_use"
)
product_data = tool_use_block.input
print(product_data["name"]) # "Sony WH-1000XM6"
print(product_data["features"]) # ["40-hour battery life", ...]Tham số then chốt là tool_choice — khi set nó thành tên tool cụ thể, model buộc phải trả về dữ liệu có cấu trúc theo schema của tool thay vì text tự do.
Bước 4: Xử Lý Schema Lồng Nhau Và Phức Tạp
Ứng dụng thực tế thường cần cấu trúc lồng nhau. Cả hai API đều xử lý tốt.
# OpenAI với Pydantic model lồng nhau
class Address(BaseModel):
street: str
city: str
country: str
postal_code: str
class ContactInfo(BaseModel):
email: str
phone: str | None
address: Address
class CompanyProfile(BaseModel):
name: str
industry: str
founded_year: int
contacts: list[ContactInfo]
annual_revenue_usd: float | None# Claude với JSON schema lồng nhau
nested_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"industry": {"type": "string"},
"founded_year": {"type": "integer"},
"contacts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"email": {"type": "string"},
"phone": {"type": ["string", "null"]},
"address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"country": {"type": "string"},
"postal_code": {"type": "string"}
},
"required": ["street", "city", "country", "postal_code"]
}
},
"required": ["email", "address"]
}
}
},
"required": ["name", "industry", "founded_year", "contacts"]
}Bước 5: Xử Lý Lỗi Và Trường Hợp Đặc Biệt
Dù có structured output, bạn vẫn cần xử lý các edge case một cách cẩn thận.
import json
from openai import OpenAI
client = OpenAI()
def extract_with_fallback(text: str) -> dict:
try:
response = client.beta.chat.completions.parse(
model="gpt-4o",
response_format=ProductInfo,
messages=[
{"role": "system", "content": "Extract product information."},
{"role": "user", "content": text}
]
)
# Kiểm tra trường hợp model từ chối
if response.choices[0].message.refusal:
return {"error": "Model refused", "reason": response.choices[0].message.refusal}
return response.choices[0].message.parsed.model_dump()
except Exception as e:
return {"error": "Extraction failed", "reason": str(e)}Với Claude, hãy kiểm tra stop reason là tool_use thay vì end_turn — nếu là end_turn nghĩa là model trả về text thay vì dùng tool.
Mẹo Thực Tế
Chọn đúng cách tiếp cận:
- Dùng
json_objectmode của OpenAI cho prototype nhanh khi bạn tin model sẽ tuân thủ instructions - Dùng
json_schemamode của OpenAI cho app production cần đảm bảo schema tuyệt đối - Dùng tool-use pattern của Claude khi cần structured output kết hợp với reasoning
Cân nhắc về hiệu suất:
- Structured output thêm rất ít latency so với response tự do
- Schema phức tạp với nhiều object lồng nhau có thể tăng token usage
- Cả hai API đều hỗ trợ streaming với structured output — hữu ích cho response lớn
Mẹo thiết kế schema:
- Giữ schema phẳng nhất có thể để tăng độ tin cậy
- Dùng tên field mô tả rõ ràng — model dùng chúng như gợi ý
- Thêm
descriptioncho schema properties khi tên field chưa đủ rõ nghĩa - Đánh dấu field là optional (nullable) khi thông tin có thể không có trong input
Test structured output:
- Luôn test với edge case: thiếu dữ liệu, input mơ hồ, nhiều cách hiểu đúng
- Validate output ngoài schema compliance — kiểm tra giá trị trích xuất có đúng về mặt ngữ nghĩa không
- Log và monitor chất lượng extraction trong production
Try openai
Bắt đầu với structured output API của OpenAI. Tài khoản mới được tặng credit miễn phí để thử nghiệm JSON mode và schema-strict response.
Try claude
Claude của Anthropic hỗ trợ structured output qua giao diện tool-use, với hiệu suất tốt cho các tác vụ trích xuất phức tạp.