클로드 API 구조화 출력: JSON으로 안정적으로 받는 방법

클로드 API 구조화 출력: JSON으로 안정적으로 받는 방법 대표 이미지

API로 고객 문의를 분석해 DB에 저장하는 스크립트를 만들었는데, 가끔 오류가 났습니다. 원인을 보니 클로드가 JSON 앞에 "분석 결과는 다음과 같습니다:"라는 문장을 붙인 경우였습니다. 사람이 읽기엔 친절하지만 프로그램에게는 오류입니다. 이 문제를 해결하는 것이 구조화 출력(Structured Outputs)입니다.

프롬프트만으로 JSON을 요청할 때의 문제

프롬프트로만 JSON을 요청했을 때 생기는 문제

다음 문의를 분석해서 JSON으로만 답해. 필드: category, urgency, summary

대부분 잘 되지만, 대량으로 돌리면 이런 일이 생깁니다.

  • JSON 앞뒤에 설명 문장이 붙음
  • 코드 블록(```json)으로 감싸서 옴
  • 필드 이름이 가끔 바뀜 (urgency → priority)
  • 정해진 값 외의 값이 들어옴 ("높음" 대신 "매우 높음")

1,000건 중 몇 건만 이래도 예외 처리 코드가 복잡해집니다.

구조화 출력: 스키마로 형식을 강제

구조화 출력은 요청에 JSON 스키마를 함께 보내, 응답이 그 스키마에 맞도록 강제하는 기능입니다. 요청의 output_config.format에 스키마를 지정합니다.

import json
import anthropic

client = anthropic.Anthropic()

schema = {
    "type": "object",
    "properties": {
        "category": {"type": "string", "enum": ["배송", "교환", "환불", "기타"]},
        "urgency": {"type": "string", "enum": ["높음", "보통", "낮음"]},
        "summary": {"type": "string"},
    },
    "required": ["category", "urgency", "summary"],
    "additionalProperties": False,
}

response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    output_config={"format": {"type": "json_schema", "schema": schema}},
    messages=[{
        "role": "user",
        "content": "다음 고객 문의를 분석해 줘: 주문한 지 일주일이 넘었는데 아직 배송 출발도 안 했어요. 선물이라 이번 주 안에 받아야 해요.",
    }],
)

text = "".join(b.text for b in response.content if b.type == "text")
data = json.loads(text)
print(data)
# {'category': '배송', 'urgency': '높음', 'summary': '...'}

enum으로 허용 값을 정하면 "매우 높음" 같은 값이 들어오지 않습니다.

파이썬 SDK의 parse로 더 간단하게

파이썬 SDK는 Pydantic 모델을 스키마로 쓰고 결과를 객체로 받는 messages.parse()를 제공합니다.

from typing import Literal
from pydantic import BaseModel

class Inquiry(BaseModel):
    category: Literal["배송", "교환", "환불", "기타"]
    urgency: Literal["높음", "보통", "낮음"]
    summary: str

result = client.messages.parse(
    model="claude-sonnet-5",
    max_tokens=1024,
    output_format=Inquiry,
    messages=[{"role": "user", "content": f"다음 고객 문의를 분석해 줘: {inquiry_text}"}],
)

inquiry = result.parsed_output
print(inquiry.category, inquiry.urgency)

SDK 버전에 따라 파라미터 이름이 다를 수 있으니, 사용하는 버전의 공식 예제를 확인하세요. 핵심은 스키마를 코드로 정의하고, 검증된 객체로 받는다는 것입니다.

툴 입력에도 엄격 모드

툴 사용에서 클로드가 툴에 넘기는 인수도 툴 정의에 strict: true를 지정하면 스키마에 정확히 맞게 받을 수 있습니다. 툴을 호출하는 에이전트를 만든다면 함께 쓰면 좋습니다.

스키마 설계 요령

구조화 출력 JSON 스키마 설계 요령

  1. 필드는 꼭 필요한 것만: 필드가 많을수록 모든 값을 채우느라 품질이 흩어질 수 있습니다.
  2. enum을 적극 활용: 분류 결과처럼 값이 정해진 필드는 반드시 enum으로.
  3. 설명(description) 추가: 각 필드가 무엇을 뜻하는지 스키마에 설명을 넣으면 정확도가 올라갑니다.
  4. "모름" 선택지 두기: 판단할 수 없는 경우를 위한 값(예: "판단 불가")이 없으면, 클로드가 억지로 아무 값이나 고를 수 있습니다.

주의할 점

  • 형식 보장 ≠ 내용 보장: 스키마는 맞아도 분류 자체가 틀릴 수 있습니다. 샘플을 뽑아 사람이 정확도를 확인하세요.
  • 값 범위 검증: 숫자 필드라면 음수나 비현실적인 값이 들어오지 않는지 코드에서 확인합니다.
  • 거절 응답 처리: 요청이 정책상 거절되는 경우 등, 응답 상태(stop_reason)를 먼저 확인한 뒤 파싱하세요.

형식이 완벽할수록 내용 검증을

구조화 출력을 적용한 뒤 파싱 오류 처리 코드를 거의 다 지울 수 있었습니다. 예전에는 "JSON 앞의 문장 제거", "코드 블록 벗기기" 같은 방어 코드가 잔뜩 있었는데, 이제는 스키마 하나로 끝납니다. 대신 내용 검증에 더 신경 씁니다. 형식이 완벽하니 오히려 틀린 내용이 그럴듯해 보일 수 있기 때문입니다.

마무리

  1. 프롬프트만으로 JSON을 요청하면 대량 처리 시 형식이 가끔 어긋납니다.
  2. output_config.format의 JSON 스키마나 SDK의 parse로 형식을 강제하세요.
  3. enum·설명·"판단 불가" 값을 활용하고, 내용의 정확성은 따로 검증합니다.

출처·참고 링크

🤔 자주 묻는 질문 (FAQ)

Q. 프롬프트로 'JSON으로 답해'라고 하면 안 되나요?

대부분은 잘 따르지만 가끔 앞뒤에 설명 문장이 붙거나 필드가 빠질 수 있습니다. 결과를 코드에서 바로 처리해야 한다면 구조화 출력 기능으로 스키마를 강제하는 편이 안정적입니다.

Q. 구조화 출력은 모든 모델에서 되나요?

현재 주요 모델에서 지원하지만, 모델과 플랫폼에 따라 지원 범위가 다를 수 있습니다. 사용 전 공식 문서에서 지원 모델을 확인하세요.

Q. 스키마를 지켜도 내용이 틀릴 수 있나요?

네. 구조화 출력은 형식(필드, 타입)을 보장할 뿐 내용의 정확성을 보장하지는 않습니다. 값의 범위나 논리적 타당성은 코드에서 따로 검증해야 합니다.
#구조화출력 #StructuredOutputs #JSON #ClaudeAPI #파이썬