claude api json 출력 — output_config 사용법
Claude API에서 JSON으로 답을 받으려면 요청에 output_config.format을 넣고 {"type": "json_schema", "schema": {...}}로 원하는 모양을 알려 주면 됩니다. 그러면 응답의 텍스트 블록에 스키마를 지킨 JSON이 들어옵니다. OpenAI의 json_object 같은 "JSON mode"는 따로 없고, 이 구조화 출력(Structured outputs)이 그 자리를 대신합니다. 파이썬이라면 client.messages.parse(output_format=모델클래스) 한 줄로 끝납니다. 아래 예제는 anthropic 파이썬 SDK 1.11.0으로 직접 돌려 확인했습니다.
1. claude api json 출력 — 요청 한 번으로 보기
공식 문서의 예제를 그대로 옮긴 curl 요청입니다. 일반 메시지 요청에 output_config 하나만 더 붙었습니다.
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."}
],
"output_config": {
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"plan_interest": {"type": "string"},
"demo_requested": {"type": "boolean"}
},
"required": ["name", "email", "plan_interest", "demo_requested"],
"additionalProperties": false
}
}
}
}'
응답은 평소처럼 content 배열의 text 블록으로 옵니다. 다른 점은 그 텍스트가 스키마에 맞는 JSON 문자열이라는 것뿐입니다. 공식 문서에 실린 결과는 이렇습니다.
{
"name": "John Smith",
"email": "john@example.com",
"plan_interest": "Enterprise",
"demo_requested": true
}
공식 문서가 꼽는 장점은 세 가지입니다. 출력을 제약된 디코딩(constrained decoding)으로 만들기 때문에 JSON.parse() 에러가 없고, 필수 필드와 타입이 보장되며, 스키마 위반 때문에 다시 요청할 일이 없습니다. 프롬프트에 "JSON으로만 답해"라고 부탁하던 방식과는 보장 수준이 다릅니다.
API 키가 아직 없다면 claude api key 발급 글부터 보세요.
2. claude api json mode는 따로 없다
"claude api json mode"로 검색해 오는 분이 많은데, Claude API에는 OpenAI의 {"type": "json_object"}처럼 "문법만 맞는 JSON"을 켜는 옵션이 없습니다. JSON을 받는 공식 방법은 스키마를 함께 주는 output_config.format 하나입니다. 공식 문서는 구조화 출력을 두 기능으로 나눕니다.
| 기능 | 켜는 곳 | 무엇을 고정하나 |
|---|---|---|
| JSON outputs | output_config.format | Claude가 답하는 내용의 형식 |
| Strict tool use | 도구 정의의 strict: true | Claude가 도구를 부를 때 넘기는 인자 |
둘은 한 요청에서 같이 써도 됩니다. 이 글은 첫 번째, 답 자체를 JSON으로 받는 쪽을 다룹니다.
{로 미리 채워(prefill) JSON을 유도하는 요령이 많이 쓰였습니다. 공식 문서 기준으로 메시지 prefill은 JSON outputs와 함께 쓸 수 없습니다. output_config로 옮겼다면 prefill은 지웁니다.
3. 파이썬 예제 — messages.parse와 Pydantic
파이썬 SDK는 Pydantic 모델을 스키마로 바꿔 보내고, 받은 JSON을 다시 그 모델로 만들어 줍니다. 공식 문서가 권하는 방법입니다.
from anthropic import Anthropic
from pydantic import BaseModel
client = Anthropic() # 환경변수 ANTHROPIC_API_KEY 사용
class ContactInfo(BaseModel):
name: str
email: str
plan_interest: str
demo_requested: bool
response = client.messages.parse(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "이 메일에서 정보를 뽑아 줘: ..."}],
output_format=ContactInfo,
)
info = response.parsed_output # ContactInfo 객체
print(info.name, info.demo_requested)
이 코드가 실제로 무엇을 보내는지 보려고, 네트워크 대신 가짜 서버를 끼워 요청 본문을 가로채 봤습니다. 인자 이름은 output_format인데, 실제로 나가는 키는 output_config였습니다(title 줄은 생략).
"output_config": {
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"plan_interest": {"type": "string"},
"demo_requested": {"type": "boolean"}
},
"additionalProperties": false,
"required": ["name", "email", "plan_interest", "demo_requested"]
}
}
}
additionalProperties: false는 SDK가 알아서 붙입니다. 가짜 응답을 돌려주자 parsed_output에는 ContactInfo(name='John Smith', email='john@example.com', plan_interest='Enterprise', demo_requested=True)처럼 이미 파싱된 객체가 들어 있었습니다. json.loads를 따로 할 필요가 없습니다.
타입스크립트 SDK는 공식 문서상 Zod 스키마를 zodOutputFormat()으로 감싸 client.messages.parse()에 넘기는 방식입니다. 이 글에서는 직접 돌려 본 파이썬만 다룹니다.
4. claude api json schema를 직접 넘길 때
스키마를 파일이나 OpenAPI 명세에서 읽어 오는 경우라면 Pydantic 없이 messages.create에 그대로 넘깁니다. 결과는 텍스트 블록의 문자열이니 직접 파싱합니다.
import json
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
},
"required": ["name", "email"],
"additionalProperties": False,
}
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "이름과 이메일만 뽑아 줘: ..."}],
output_config={"format": {"type": "json_schema", "schema": schema}},
)
data = json.loads(response.content[0].text)
print(data["email"])
직접 쓸 때는 SDK가 해 주던 일을 손으로 맞춰야 합니다. 공식 문서의 지원 범위를 정리하면 이렇습니다.
| 구분 | 내용 |
|---|---|
| 꼭 지킬 것 | 모든 object에 additionalProperties: false (다른 값은 안 됨) |
| 되는 것 | 기본 타입 전부, enum, const, anyOf, allOf($ref와 함께는 불가), 내부 $ref, default, 배열 minItems 0·1 |
| 문자열 format | date-time, time, date, duration, email, hostname, uri, ipv4, ipv6, uuid |
| 안 되는 것 | 재귀 스키마, 외부 $ref, minimum·maximum·multipleOf, minLength·maxLength, minItems 0·1 외 배열 제약 |
안 되는 기능을 넣으면 400 에러가 납니다. 정규식 pattern은 간단한 것만 됩니다. 역참조(\1), 전방·후방 탐색((?=...)), 단어 경계(\b)는 지원하지 않습니다.
SDK는 minimum 같은 제약을 어떻게 처리하나
Pydantic에 price: int = Field(ge=100)처럼 API가 모르는 제약을 넣어도 parse는 에러 없이 요청을 보냅니다. 가로챈 본문을 보니 minimum이 빠지고 설명(description)으로 옮겨져 있었습니다.
"price": {"type": "integer", "title": "Price", "description": "{minimum: 100}"}
모델은 설명으로 힌트만 받고, 대신 SDK가 응답을 원래 제약으로 다시 검증합니다. 가짜 응답에 price: 50을 넣자 parse가 ValidationError(Input should be greater than or equal to 100)를 던졌습니다. 즉 서버는 "정수"까지만 보장하고, "100 이상"은 내 코드에서 걸러지는 구조입니다.
5. claude api json 안될 때 — 확인할 것
① parse가 ValidationError를 던진다 → stop_reason 확인
공식 문서는 두 경우에 출력이 스키마와 맞지 않을 수 있다고 적습니다. 안전상의 이유로 거절하면 stop_reason이 "refusal"(상태 코드는 200, 토큰 비용도 청구), 토큰 한도에 걸리면 "max_tokens"입니다. 직접 재현해 보니 SDK 1.11.0의 parse는 두 경우 모두 parsed_output을 비워 주는 대신 예외를 던졌습니다. 잘린 JSON은 json_invalid(EOF while parsing a string), 거절 문장도 JSON이 아니라 같은 ValidationError였습니다.
from pydantic import ValidationError
try:
response = client.messages.parse(
model="claude-opus-5-5",
max_tokens=1024,
messages=messages,
output_format=ContactInfo,
)
except ValidationError:
# 거절(refusal)이거나 max_tokens로 잘렸을 수 있다
# 원인을 나눠 보고 싶으면 messages.create로 받아 stop_reason부터 확인한다
raise
원인을 갈라 처리해야 한다면 4번처럼 messages.create로 받아 response.stop_reason이 "end_turn"인지 먼저 보고 json.loads하는 편이 깔끔합니다. max_tokens라면 값을 늘려 다시 요청합니다.
② TypeError: unexpected keyword argument 'output_format'
베타 시절 글을 보고 messages.create(output_format=...)로 쓰면 SDK 1.11.0에서 바로 TypeError가 납니다. 베타의 output_format 파라미터는 output_config.format으로 옮겨졌고 지원 중단(deprecated) 상태입니다. create에는 output_config를, Pydantic을 쓸 땐 parse(output_format=...)를 씁니다. 같은 이름이 두 곳에서 다른 뜻으로 쓰여 헷갈리기 쉬운 부분입니다.
③ 400 "Schema is too complex for compilation."
공식 문서상 한 요청의 모든 strict 스키마를 합쳐 선택(optional) 파라미터 24개, anyOf·타입 배열 파라미터 16개, strict 도구 20개가 한도입니다. 이걸 지켜도 문법이 너무 커지면 이 400이 납니다. 선택 필드를 required로 바꾸고, 중첩을 줄이는 게 문서가 권하는 순서입니다.
④ 첫 요청만 느리다 → 정상
스키마를 문법으로 컴파일하느라 처음 쓰는 스키마는 지연이 생기고, 컴파일 결과는 마지막 사용부터 24시간 캐시됩니다. 스키마 구조를 바꾸면 다시 컴파일합니다(name·description만 바꾸면 유지).
⑤ 키 순서가 스키마와 다르다
출력은 required 필드가 먼저, 선택 필드가 그 뒤에 나옵니다(각각은 스키마 순서). 순서가 중요하면 모든 필드를 required로 둡니다.
⑥ enum 값의 대소문자가 다르게 온다
공식 문서에 따르면 문자열 enum·const의 대소문자는 보장되지 않습니다. 띄어쓰기 뒤 첫 글자가 대문자로 바뀌는 식이고, 에러도 나지 않습니다. 비교는 대소문자를 무시하고, 대소문자만 다른 enum 값은 만들지 않습니다.
호출 자체가 막히는 경우라면 JSON 설정이 아니라 키·한도 문제일 수 있습니다. 상태 코드별 원인은 클로드 코드 api 에러 글에 정리해 두었습니다.
자주 묻는 질문 (FAQ)
Q. 모든 모델에서 되나요?
아닙니다. 2026년 10월 5일 기준 공식 문서의 지원 목록에는 Opus 5.5·Sonnet 5.5·Fable 5.1 같은 현행 모델과 Opus 4.5 이후, Sonnet 4.5 이후, Haiku 4.5가 올라 있습니다. 옛 Claude 3 계열은 목록에 없습니다.
Q. 토큰이 더 드나요?
조금 더 듭니다. 구조화 출력을 켜면 형식을 설명하는 시스템 프롬프트가 자동으로 붙어 입력 토큰이 약간 늘어납니다. 또 output_config.format을 바꾸면 그 대화의 프롬프트 캐시가 무효가 됩니다.
Q. 스트리밍이나 배치에서도 쓸 수 있나요?
네. 공식 문서상 스트리밍, 토큰 카운팅, Batch 처리(50% 할인)와 함께 쓸 수 있습니다. 반대로 Citations(인용)와는 같이 쓸 수 없고, 켜면 400 에러가 납니다.
Q. ChatGPT·Gemini는 방식이 어떻게 다른가요?
개념은 같고 파라미터 자리가 다릅니다. OpenAI는 text.format(또는 response_format)에 strict: true를 함께 주고 chatgpt api json 글에 정리했습니다. Gemini는 response_format을 쓰며 gemini api json 글에 있습니다. Claude만 JSON mode가 따로 없다는 점이 다릅니다.
마무리
정리하면, Claude API에서 JSON을 받을 땐 output_config.format에 type: "json_schema"와 스키마를 넣고, 파이썬이라면 messages.parse(output_format=Pydantic모델)로 parsed_output을 꺼내면 됩니다. 스키마를 직접 쓸 땐 모든 객체에 additionalProperties: false를 넣고 숫자·길이 제약은 빼며, 받는 쪽에서는 refusal·max_tokens로 생기는 ValidationError를 처리해 두세요.
SDK와 지원 모델은 자주 바뀝니다. 이 글은 anthropic 파이썬 SDK 1.11.0과 2026년 10월 5일 공식 문서 기준입니다.
📚 참고 출처 (2026년 10월 5일 확인 · anthropic-python 1.11.0)
· Structured outputs — Claude API Docs

COMMENTS