Claude API에서 구조화 출력을 얻는 가장 신뢰할 수 있는 방법은 시스템 프롬프트에 스키마를 명시하고, 응답을 파싱할 때 검증 로직을 추가하는 것입니다. claude-sonnet-4-5 이상에서는 지시를 잘 따르므로 구조화 출력 신뢰도가 95% 이상입니다.
기본 JSON 출력
import anthropic
import json
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system="""항상 다음 JSON 형식으로만 응답하세요. 다른 텍스트는 출력하지 마세요:
{
"answer": "답변 내용",
"confidence": 0.0~1.0,
"sources": ["근거1", "근거2"]
}""",
messages=[{"role": "user", "content": "Claude Code의 최대 컨텍스트 창 크기는?"}]
)
data = json.loads(response.content[0].text)
print(data["answer"])
print(f"신뢰도: {data['confidence']}")
Pydantic으로 타입 안전하게
from pydantic import BaseModel, Field
from typing import List, Optional
import anthropic, json
client = anthropic.Anthropic()
class ProductReview(BaseModel):
rating: int = Field(ge=1, le=5, description="1-5점")
sentiment: str = Field(pattern="^(positive|negative|neutral)$")
key_points: List[str] = Field(max_length=5)
recommended: bool
summary: str = Field(max_length=200)
def extract_review(review_text: str) -> ProductReview:
schema = ProductReview.model_json_schema()
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system=f"""다음 JSON 스키마에 맞게 리뷰를 분석하세요:
{json.dumps(schema, ensure_ascii=False, indent=2)}
JSON만 출력하고 다른 텍스트는 추가하지 마세요.""",
messages=[{"role": "user", "content": f"리뷰 분석:\n\n{review_text}"}]
)
raw = response.content[0].text
# JSON 블록 처리
if "```json" in raw:
raw = raw.split("```json")[1].split("```")[0].strip()
return ProductReview.model_validate_json(raw)
# 사용
review = extract_review("배송 빠르고 품질 좋아요. 다만 가격이 좀 비쌈. 재구매 의향 있음")
print(f"평점: {review.rating}/5")
print(f"감성: {review.sentiment}")
print(f"추천: {review.recommended}")
복잡한 중첩 구조
class Address(BaseModel):
street: str
city: str
postal_code: str
country: str = "KR"
class ContactInfo(BaseModel):
name: str
email: Optional[str] = None
phone: Optional[str] = None
address: Optional[Address] = None
class CompanyProfile(BaseModel):
name: str
founded_year: Optional[int] = None
industry: str
employees: Optional[int] = None
contacts: List[ContactInfo] = []
description: str
def extract_company_info(text: str) -> CompanyProfile:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
system=f"""다음 스키마에 따라 회사 정보를 추출하세요. 없는 정보는 null로:
{CompanyProfile.model_json_schema()}""",
messages=[{"role": "user", "content": text}]
)
return CompanyProfile.model_validate_json(
response.content[0].text
)
재시도 로직 (파싱 실패 시)
from tenacity import retry, stop_after_attempt, wait_exponential
import logging
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(min=1, max=4)
)
def reliable_structured_output(prompt: str, schema: type[BaseModel]) -> BaseModel:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system=f"""다음 JSON 스키마에 정확히 맞는 JSON만 출력하세요:
{schema.model_json_schema()}
중요: JSON 외 텍스트 절대 금지""",
messages=[{"role": "user", "content": prompt}]
)
text = response.content[0].text.strip()
# JSON 블록 추출
if "```" in text:
text = text.split("```")[1]
if text.startswith("json"):
text = text[4:]
try:
return schema.model_validate_json(text)
except Exception as e:
logging.warning(f"파싱 실패, 재시도: {e}\n응답: {text[:200]}")
raise
배치 데이터 추출
문서에서 구조화 데이터를 대량으로 추출:
import asyncio
async def extract_batch(documents: list[str], schema: type[BaseModel]) -> list[BaseModel]:
client = anthropic.AsyncAnthropic()
schema_str = json.dumps(schema.model_json_schema(), ensure_ascii=False)
async def extract_one(doc: str) -> BaseModel:
response = await client.messages.create(
model="claude-haiku-3-5", # 배치는 Haiku로 비용 절약
max_tokens=512,
system=f"스키마에 맞게 JSON으로 출력:\n{schema_str}",
messages=[{"role": "user", "content": doc}]
)
return schema.model_validate_json(response.content[0].text)
return await asyncio.gather(*[extract_one(doc) for doc in documents])
# 100개 문서 병렬 처리
results = asyncio.run(extract_batch(documents, ProductReview))
주요 실전 패턴
이메일 분류
class EmailClassification(BaseModel):
category: str # inquiry, complaint, praise, spam
priority: int # 1-5
requires_response: bool
estimated_response_time: str # immediate, 24h, 3days
summary: str
def classify_email(subject: str, body: str) -> EmailClassification:
# ... 위 패턴 동일
코드 리뷰 구조화
class CodeIssue(BaseModel):
severity: str # critical, warning, info
location: str # 파일명:라인번호
description: str
suggestion: str
class CodeReview(BaseModel):
score: int # 1-10
issues: List[CodeIssue]
strengths: List[str]
summary: str
Frequently Asked Questions
Claude가 항상 정확한 JSON을 반환하나요?
claude-sonnet-4-5 이상에서 명확한 스키마를 제공하면 95% 이상 정확한 JSON이 나옵니다. 나머지 5%를 위해 파싱 실패 시 재시도 로직을 반드시 구현하세요.
JSON 외 텍스트가 섞여 나올 때 처리 방법은?
응답에서 첫 번째 {와 마지막 } 사이만 추출하거나, ````json ... ``` ` 블록을 파싱하는 헬퍼 함수를 만들어 두면 됩니다.
스키마가 복잡할수록 비용이 올라가나요?
스키마를 시스템 프롬프트에 포함하면 입력 토큰이 증가합니다. 프롬프트 캐싱(cache_control)을 적용하면 반복 호출 시 90% 이상 절약됩니다.
Anthropic의 공식 structured output API가 있나요?
2026년 기준 Anthropic은 OpenAI처럼 별도의 structured output 엔드포인트를 제공하지 않습니다. 프롬프트 엔지니어링 + 파싱 로직으로 구현합니다.
관련 가이드: Claude API Python 한국어 · Claude API JSON 출력 한국어 · Claude Agent SDK 한국어 가이드
P2 Claude Agent SDK Cookbook — Pydantic 기반 구조화 출력 패턴 포함한 15개 에이전트 레시피. 지금 확인 →