출력 스키마는 왜 프롬프트가 아니라 API 버전 계약일까
LLM 출력 JSON이 잘 나오던 기능이 API/SDK 업데이트 후 깨졌다면, 우리는 프롬프트를 고칠까 아니면 스키마 계약을 버전 관리할까?
출력 스키마는 왜 프롬프트가 아니라 API 버전 계약일까
- 카테고리: ai_dev
- 예상 읽기 시간: 10분
- 오늘의 질문: LLM 출력 JSON이 잘 나오던 기능이 API/SDK 업데이트 후 깨졌다면, 우리는 프롬프트를 고칠까 아니면 스키마 계약을 버전 관리할까?
- 핵심 출처:
- Interactions API: Breaking changes migration guide (May 2026) - 2026-05, 확인일 2026-07-29
- Structured outputs - Interactions API - 게시일 미표기, 확인일 2026-07-29
- Structured outputs - Claude API - 게시일 미표기, 확인일 2026-07-29
- Get structured output from agents - 게시일 미표기, 확인일 2026-07-29
- Introducing Structured Outputs in the API - 2024-08-06, 확인일 2026-07-29
1. 왜 지금 봐야 하나
LLM 기능을 제품에 붙이면 “JSON으로 답해줘”는 금방 한계에 부딪힌다. 그래서 요즘 API들은 JSON Schema, Zod, Pydantic, strict: true, response_format 같은 structured outputs 기능을 제공한다. 여기까지는 이미 익숙한 이야기다. 그런데 운영에서 더 자주 터지는 문제는 따로 있다. 스키마 자체가 안정적인 계약으로 관리되지 않는 것이다.
Google의 Gemini Interactions API 마이그레이션 문서는 이 문제를 잘 보여준다. 2026년 5월 변경 안내에서 기존 outputs 배열은 steps 타임라인으로 바뀌고, response_mime_type, response_modalities, generation_config.image_config 같은 출력 설정은 polymorphic response_format으로 통합된다고 설명한다. 레거시 스키마는 2026년 6월 8일 제거되고, 전환 기간에는 Api-Revision 헤더로 새 스키마를 선택할 수 있었다.
Anthropic 쪽도 비슷한 신호가 있다. Claude API structured outputs 문서는 beta의 output_format 파라미터가 output_config.format으로 이동했고, 더 이상 beta header가 필요 없다고 안내한다. Claude Agent SDK 문서는 multi-turn agent가 도구를 사용한 뒤 최종적으로 검증된 JSON을 돌려주는 structured_output을 제공하지만, 검증 실패나 retry 초과를 별도 오류로 다뤄야 한다고 설명한다.
오늘 볼 원리는 단순하다. structured outputs는 “모델에게 예쁘게 말하게 하는 옵션”이 아니라 모델, API, SDK, 검증기, DB가 공유하는 버전 있는 인터페이스다. 이 관점이 없으면 모델은 정상인데 애플리케이션이 깨지고, 스키마는 맞는데 값은 틀리고, SDK 업데이트 후 파서가 조용히 다른 필드를 읽는 일이 생긴다.
2. 핵심 개념
출력 스키마를 운영 계약으로 보면 네 가지를 분리해야 한다.
첫째, 형식 보장과 의미 보장은 다르다. OpenAI의 Structured Outputs 글은 JSON mode와 schema conformance를 구분한다. JSON mode는 문법적으로 JSON인 텍스트에 가깝고, Structured Outputs는 개발자가 준 JSON Schema에 맞도록 출력을 제한한다. 하지만 스키마를 만족한다고 해서 risk_score가 업무적으로 맞거나, due_date가 실제 영업일 규칙을 지켰다는 뜻은 아니다. 형식 검증은 시작점이고, 도메인 검증은 별도 레이어다.
둘째, 스키마 subset을 알아야 한다. Gemini structured output 문서는 JSON Schema 전체가 아니라 지원되는 subset이 있다고 말한다. basic type, enum, required, additionalProperties, minItems 같은 일부 기능은 지원되지만, 너무 크거나 깊은 스키마는 거절될 수 있다. Anthropic 문서도 SDK가 unsupported constraints를 제거하거나 description으로 옮기는 변환을 할 수 있다고 설명한다. 즉 “우리 백엔드 타입을 JSON Schema로 뽑았다”와 “모델 API가 이 스키마를 그대로 강제한다”는 같은 말이 아니다.
셋째, 응답 envelope와 payload schema를 나눠야 한다. Gemini의 outputs → steps 변경은 모델이 생성한 JSON 내용이 아니라 API 응답을 감싸는 바깥 구조의 변경이다. 제품 코드에서 response.outputs[-1].text를 직접 읽고 있었다면, 모델 출력 schema가 그대로여도 파싱 코드는 깨진다. 반대로 interaction.output_text 같은 SDK convenience property를 쓰면 envelope 변경의 영향을 줄일 수 있다. 중요한 것은 “내가 검증하는 JSON”이 API envelope인지, 모델 payload인지 명확히 아는 것이다.
넷째, 실패를 정상 경로로 설계해야 한다. Claude Agent SDK 문서는 structured output 생성이 실패할 수 있고, retry limit을 넘으면 error_max_structured_output_retries 같은 결과가 나올 수 있다고 설명한다. 또한 성공 subtype이어도 structured_output이 없으면 실패로 취급하라고 안내한다. OpenAI 문서도 안전 거절(refusal)이나 중단된 generation은 schema output과 다르게 처리해야 한다고 말한다. 운영 코드는 “항상 JSON이 온다”가 아니라 “JSON, refusal, validation error, retry exhausted, interrupted”를 모두 다뤄야 한다.
3. 최신 이슈와 연결
Gemini Interactions API 변경에서 눈여겨볼 부분은 response_format이 단순 JSON 옵션이 아니라 모달리티와 출력 설정을 통합하는 상위 계약으로 이동했다는 점이다. 새 schema에서는 텍스트 JSON, 오디오, 이미지 같은 출력 형식을 response_format 항목으로 표현한다. 이는 앞으로 mid-flight steering, asynchronous tool calls 같은 기능을 넣기 위한 응답 타임라인 구조와 연결된다.
이 변화는 AI 개발자에게 세 가지 질문을 던진다.
1. 내 코드는 모델 payload schema만 버전 관리하는가, API response envelope도 버전 관리하는가?
2. SDK 업데이트가 response_format, output_text, steps 같은 접근 방식을 바꿀 때 테스트가 잡아내는가?
3. structured output 실패를 모델 오류로만 보지 않고 schema complexity, unsupported keyword, retry policy, refusal로 분류하는가?
Anthropic의 Claude API 문서도 API shape migration 관점에서 중요하다. output_format이 output_config.format으로 이동했다는 말은 “기능 이름은 structured outputs로 같아도 클라이언트 코드의 계약은 바뀔 수 있다”는 뜻이다. Agent SDK 문서의 draft-07, Zod 변환, Pydantic schema, retry 실패 처리도 같은 메시지를 준다. 타입 안전성은 모델 옵션 하나가 아니라 SDK, validator, schema dialect, retry loop까지 포함한 체인이다.
OpenAI의 structured outputs는 constrained decoding과 strict: true의 원리를 설명하는 좋은 기준점이다. 다만 이 기능을 운영에 넣을 때도 parallel_tool_calls 호환성, refusal, finish reason, 지원 schema subset 같은 제한을 확인해야 한다. 결국 최신 흐름은 “LLM이 JSON을 잘 만든다”가 아니라 “각 플랫폼이 모델 출력을 점점 더 강한 API 계약으로 다루고 있으며, 그 계약의 버전과 실패 모드가 제품 코드에 영향을 준다”로 읽어야 한다.
4. 개발자 관점 해석
예를 들어 고객 문의를 아래 형태로 분류하는 기능이 있다고 하자.
{
"intent": "refund | bug | sales | other",
"priority": "low | medium | high",
"needs_human": true,
"evidence": ["문장에서 근거가 되는 짧은 인용"]
}
초기에는 이 스키마만 검증하면 충분해 보인다. 하지만 운영에 들어가면 바뀌는 것은 스키마 내부만이 아니다.
- 모델 API가
response_mime_type에서response_format으로 이동한다. - SDK가
outputs[0].text대신output_text또는steps를 권장한다. priorityenum에urgent를 추가해야 한다.evidence가 너무 길어져 토큰 비용과 개인정보 노출 위험이 생긴다.- 모델이 안전 정책 때문에 JSON 대신 refusal을 반환한다.
- agent workflow에서는 도구 사용 후 최종 structured output이 retry 끝에 실패한다.
이때 프롬프트만 수정하면 사고가 반복된다. 필요한 것은 contract-first 흐름이다.
schema_name: support_triage_v3
schema_dialect: json-schema-draft-07-subset
api_envelope: gemini-interactions-steps-2026-05-20
validator: zod@x.y.z 또는 pydantic@x.y.z
failure_policy: refusal/escalate, validation_error/retry_2, retry_exhausted/human_review
consumer: tickets.priority, tickets.needs_human, audit.evidence
이렇게 적어두면 모델 교체, API revision 변경, SDK 업데이트, DB migration을 같은 변경 관리 대상으로 볼 수 있다. 반대로 이 정보가 없으면 “모델이 이상해졌다”는 말만 남고 실제 원인이 API envelope 변경인지, schema unsupported keyword인지, semantic validation 실패인지 구분하기 어렵다.
5. 내 프로젝트에 적용할 체크포인트
structured outputs를 쓰는 기능마다 아래를 확인하자.
- 스키마 이름과 버전:
invoice_extract_v1,support_triage_v3처럼 consumer가 알 수 있는 이름을 붙였는가? - API envelope 버전: Gemini
steps, Claudeoutput_config.format, OpenAIresponse_format처럼 바깥 응답 구조를 테스트하는가? - schema dialect/subset: JSON Schema draft 버전, 지원되지 않는 keyword,
additionalProperties: false요구사항을 문서화했는가? - runtime validator: 모델 API가 통과시킨 결과도 Zod/Pydantic/서버 validator로 다시 검증하는가?
- semantic validator: 날짜, 금액, ID 존재 여부, 권한, 업무 규칙은 별도 코드로 검증하는가?
- 실패 taxonomy: parse error, schema mismatch, refusal, interrupted, retry exhausted, semantic invalid를 나눠 기록하는가?
- migration test: SDK/API 업데이트 전에 golden payload 10개로 파서와 validator를 재실행하는가?
- 관측성: schema_version별 성공률, retry 횟수, human escalation 비율을 저장하는가?
핵심은 “스키마를 강하게 만들자”가 아니다. 너무 큰 스키마는 API가 거절하거나 모델이 반복 실패할 수 있다. 필요한 것은 작고 안정적인 계약을 만들고, 복잡한 업무 규칙은 후처리 검증으로 넘기는 균형이다.
6. 오늘 10분 액션
지금 만든 AI 기능 하나를 골라 아래 10분 점검을 해보자.
1분: 모델 출력 JSON을 소비하는 코드 위치를 찾는다.
2분: 응답 envelope 필드(outputs, steps, output_text, content block 등)를 적는다.
2분: payload schema 이름과 버전이 있는지 확인한다.
2분: validation 실패 시 retry, human review, fallback 중 무엇을 하는지 적는다.
2분: API/SDK 업데이트 후 다시 돌릴 golden payload 3개를 저장한다.
1분: 다음 이슈 제목을 만든다: “structured output contract card 추가”
작은 카드 하나면 충분하다. 중요한 것은 LLM 출력 형식을 프롬프트 문장으로 숨기지 않고, 변경 가능한 API 계약으로 밖에 꺼내는 것이다.
7. 더 볼 자료
- Google Gemini Interactions API 마이그레이션 문서는 API response envelope와 output format 설정이 함께 바뀔 때 어떤 코드가 영향을 받는지 보여준다.
- Google Gemini structured output 문서는 지원되는 JSON Schema subset, Pydantic/Zod 사용, validation 필요성을 확인하기 좋다.
- Anthropic Claude API structured outputs 문서는
output_config.format, strict tool use, schema limitation, SDK 변환을 확인할 수 있다. - Claude Agent SDK structured outputs 문서는 multi-turn tool workflow 후 최종 structured output을 검증하고 실패 subtype을 처리하는 방법을 보여준다.
- OpenAI Structured Outputs 글은 constrained decoding,
strict: true, JSON mode와 schema conformance의 차이를 이해하는 기준점이다.
중복 회피 메모
로컬 content/generated와 Supabase 최근 ai_dev 글을 확인했다. 2026-06-17 글은 Structured Outputs를 JSON mode보다 강한 schema/constrained decoding 계약으로 설명했고, 이후 글들은 RAG citation, tool calling, guardrail checkpoint, LLM-as-a-judge, agent eval harness, prompt caching을 다뤘다. 이번 글은 structured outputs의 기본 원리 반복이 아니라 Gemini Interactions API의 outputs → steps 마이그레이션, Claude output_config.format 전환, Agent SDK의 검증 실패 처리를 바탕으로 출력 스키마를 API/SDK 버전 계약과 migration test로 운영하는 방법에 집중한다.
핵심 출처
로그인하면 이 글을 북마크하고, 나만 보는 한 줄 메모를 남길 수 있어요.
댓글 0
최신순 ▾혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)