← All guides

Claude API 구조화 출력(Structured Output) 완전 가이드 2026

Claude API로 JSON, XML, 마크다운 등 구조화된 형식으로 출력 받는 방법. Pydantic 모델, 스키마 강제, 파싱 에러 처리, 실전 데이터 추출 예제 포함.

🇺🇸 Read in English →

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개 에이전트 레시피. 지금 확인 →

도구와 자료