API·자동화

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

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

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

목차 (8)

프롬프트만으로 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·설명·"판단 불가" 값을 활용하고, 내용의 정확성은 따로 검증합니다.

출처·참고 링크

자주 묻는 질문

프롬프트로 'JSON으로 답해'라고 하면 안 되나요?
대부분은 잘 따르지만 가끔 앞뒤에 설명 문장이 붙거나 필드가 빠질 수 있습니다. 결과를 코드에서 바로 처리해야 한다면 구조화 출력 기능으로 스키마를 강제하는 편이 안정적입니다.
구조화 출력은 모든 모델에서 되나요?
현재 주요 모델에서 지원하지만, 모델과 플랫폼에 따라 지원 범위가 다를 수 있습니다. 사용 전 공식 문서에서 지원 모델을 확인하세요.
스키마를 지켜도 내용이 틀릴 수 있나요?
네. 구조화 출력은 형식(필드, 타입)을 보장할 뿐 내용의 정확성을 보장하지는 않습니다. 값의 범위나 논리적 타당성은 코드에서 따로 검증해야 합니다.

이 글은 공개 자료와 직접 사용해 본 결과를 바탕으로 AI 도구를 활용해 초안을 만들고, AGPTEDU 편집부가 사실을 확인해 다듬었습니다. 앤트로픽(Anthropic)과 관계없는 비공식 가이드입니다.