
AI의 답변을 「반드시 JSON」으로 만들기 —— Claude 구조화 출력 (Structured Outputs) 입문
요약
Claude API의 '구조화 출력(Structured Outputs)' 기능을 사용하여 AI의 답변을 반드시 유효한 JSON 형식으로 강제하는 방법을 소개합니다. 프롬프트 기반의 요청과 달리 JSON 스키마를 통해 물리적으로 형식을 제약함으로써 프로그램 연동 시 발생하는 파싱 에러를 방지합니다.
핵심 포인트
- JSON 스키마를 통해 AI가 스키마를 위반하는 문자를 출력하지 못하도록 제약함
- 프롬프트에 의존하는 방식보다 훨씬 높은 데이터 구조 안정성 제공
- Python 예제를 통해 필수 항목 누락 및 불필요한 서론 방지 방법 설명
- additionalProperties 설정을 통해 정의되지 않은 필드 추가를 차단 가능
시작하며 —— 「AI의 답변을 프로그램에서 사용할 수 없는 문제」
AI에게 "이 문장에서 이름과 이메일 주소를 추출해서 JSON으로 반환해줘"라고 부탁해 본 적이 있는가?
대부분은 제대로 반환해 준다. 하지만 가끔 배신을 당한다.
- JSON 앞에 "네, 추출했습니다!"와 같은 불필요한 말이 붙는다
- 마지막 괄호가 닫히지 않았다
- 요청하지 않은 필드(Field)가 늘어나 있다
사람이 읽기에는 문제가 없다. 하지만, 프로그램에 그대로 전달하면 즉시 에러가 발생한다.
비유하자면, AI의 답변은 "자유 작문"이다. 내용은 맞지만, 쓰는 방식이 매번 조금씩 다르다. 프로그램이 원하는 것은 자유 작문이 아니라, **모든 칸이 채워진 "신청서"**이다. 빈칸이 있거나 여백에 낙서가 있으면 접수되지 않는다.
이 "자유 작문을 신청서로 바꾸는" 기능이 바로 Claude API의 **구조화 출력 (Structured Outputs)**이다. 이 기사에서는 Python으로 최소한의 동작 예제를 만들면서, 실수하기 쉬운 포인트와 함께 확인해 보겠다.
전체상 —— 「부탁」에서 「서식의 강제」로
지금까지도 "JSON으로 반환해줘"라고 프롬프트(Prompt)로 부탁할 수는 있었다. 하지만 그것은 어디까지나 부탁이다. 들어줄지 말지는 AI의 기분(정확히는 확률)에 달려 있었다.
구조화 출력은 메커니즘이 근본적으로 다르다.
음악으로 비유하자면 —— 프롬프트로 하는 부탁은 "이 악보대로 연주해줘"라고 구두로 전달하는 것뿐이다. 구조화 출력은, 애초에 악보에 없는 소리가 나지 않는 악기를 건네주는 이미지다. 물리적으로 틀릴 수 없다.
기술적으로는, 이쪽에서 전달한 "JSON 스키마 (JSON Schema)" (= 신청서 포맷 정의)를 API 측에서 문법으로 변환하여, 스키마를 위반하는 문자를 AI가 애초에 출력할 수 없도록 제약한다. 따라서 반환되는 JSON은 반드시 파싱(Parsing)할 수 있으며, 필수 항목이 누락되는 일도 없다.
최소한의 동작 예제 만들기
준비
Python과 Anthropic의 API 키가 필요하다. 키는 환경 변수에 넣어둔다.
pip install -U anthropic
export ANTHROPIC_API_KEY="본인의 API 키"
코드 전체
메일 문구에서 "이름 · 이메일 · 관심 있는 플랜"을 추출하는 예제.
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
...
실행하면 다음과 같이 반환된다. 불필요한 서론은 일절 없다.
{"name": "田中太郎", "email": "tanaka@example.com", "plan": "Pro"}
한 줄씩 파헤치기
client = Anthropic()
— API로 향하는 창구를 만든다. 환경 변수의 키를 자동으로 읽어온다 -
model="claude-haiku-4-5"
— 가장 저렴한 Haiku를 지정. 연습은 우선 이것으로 충분하다 -
max_tokens=1024
— 답변 길이의 상한선. 너무 짧으면 JSON이 중간에 끊기므로 주의 -
output_config={"format": ...}
— 이것이 핵심. "이 신청서대로 작성해"라고 API에 전달하는 부분 -
"type": "json_schema"
— 서식의 종류. 지금은 이것 하나만 기억해도 OK -
"properties"
— 신청서의 "기입란" 정의. 칸의 이름과 타입 (문자열, 숫자 등)을 결정한다 -
"required"
— "필수 항목" 리스트. 여기에 적은 칸은 반드시 채워져서 반환된다 -
"additionalProperties": False
— "여백에 쓰기 금지". 멋대로 필드를 추가하는 것을 방지한다 -
response.content[0].text
— 답변의 본문. 여기에 반드시 유효한 JSON이 들어있다
동작하지 않을 때 조사하는 방법
무턱대고 코드를 수정하기 전에, 위에서부터 순서대로 분리해 보자.
1. 우선 에러의 "번호"를 본다
에러 메시지의 영어를 전부 읽을 필요는 없다. 처음에는 숫자만 봐도 된다.
401 → API 키 문제. 키 설정 오류이거나 환경 변수를 읽지 못함 -
400 → 요청 내용의 문제. 대부분 스키마 작성 방식이 원인 -
404 → 모델 이름 오타가 많음 -
429 → 과다 사용. 잠시 기다릴 것
2. 스키마를 최소한으로 되돌리기
400 에러가 발생하면, 스키마를 필드 1개(예: name만)로 줄여서 실행한다. 작동한다면, 하나씩 필드를 다시 추가해 나간다. 어떤 필드를 추가한 순간에 깨졌는지 알 수 있다면, 원인은 그 필드에 있다.
3. 답변이 이상할 때는 stop_reason을 확인하기
에러는 발생하지 않는데 JSON이 중간에 끊긴다면—그럴 때는 response.stop_reason을 출력해 본다.
print(response.stop_reason)
max_tokens라고 나온다면, 답변이 길이 제한으로 인해 중단된 것이다. max_tokens 숫자를 늘리면 해결된다.
4. 그래도 안 된다면 환경을 의심하기
pip show anthropic으로 SDK 버전을 확인하고, 오래되었다면 업데이트한다. 코드나 설정의 문제가 아니라 "도구가 오래되었을 뿐"인 결말이 의외로 많다.
자주 하는 실수 · 무의미한 행동
프롬프트로 "JSON으로 답해줘"라고 계속 간청하기 (무의미)
구조화 출력 (Structured Outputs)을 사용한다면, 프롬프트에 "반드시 JSON으로", "불필요한 문장은 쓰지 마"라고 길게 쓸 필요가 이제 없다. 형식은 스키마가 보장해 준다. 프롬프트는 "무엇을 추출하고 싶은지"에 대한 설명에 집중하자.
처음부터 복잡한 스키마 만들기 (진전이 없는 패턴)
중첩 (Nest)이 깊은 완벽한 스키마를 처음에 설계하고 싶겠지만, 대개 400 에러로 막혀 의욕이 꺾인다. 먼저 필드 1개로 작동시킨 뒤, 키워나가가는 것이 결국 가장 빠르다.
스키마를 매번 조금씩 바꾸기 (불필요한 비용)
API 측은 처음 보는 스키마를 내부적으로 "문법"으로 컴파일한다. 첫 실행 시에만 조금 느린 것은 이 때문이며, 동일한 스키마라면 24시간 동안 캐시가 적용되어 빨라진다. 테스트할 때마다 스키마 구조를 건드리면 매번 다시 컴파일해야 한다. 구조를 확정한 뒤에 실행 횟수를 늘리자.
해서는 안 될 일
- API 키를 코드에 직접 작성하여 GitHub에 올리기. 이것만은 절대로 안 된다. 키를 도난당해 고액 청구로 이어질 수 있다. 반드시 환경 변수를 사용할 것.
- 보장이 필요한 필드임에도
required필드에 "아마 채워지겠지"라는 생각으로 생략하는 것. 그것이 바로 이 기능의 핵심 사용처다.
요약
- AI의 답변은 "자유 작문"이다. 프로그램에 전달하려면 "신청서"로 바꿀 필요가 있다.
- Claude API의 구조화 출력은
output_config에 JSON 스키마를 전달하는 것만으로 반드시 유효한 JSON을 반환한다. - 막힌다면 "에러 번호 → 스키마 최소화 → stop_reason → 환경" 순으로 원인을 분류한다.
- 스키마는 작게 만들어 키워나간다. 프롬프트로 "JSON으로 부탁해"라고 말하는 단계는 이제 졸업하자.
"AI의 답변을 프로그램에 연결하는 것"은 자동화의 첫 번째 관문이다. 여기를 넘어서면 이메일 정리, 데이터 추출, 리포트 생성 등—만들 수 있는 것이 단번에 넓어진다. 우선 필드 1개짜리 신청서부터 함께 시작해 보자.
참고 링크
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기