Structured Outputs는 왜 ‘JSON으로 답해줘’보다 강한 계약일까
LLM 결과를 API·DB·워크플로에 바로 연결하려면, 우리는 무엇을 프롬프트가 아니라 스키마 계약으로 고정해야 할까?
Structured Outputs는 왜 ‘JSON으로 답해줘’보다 강한 계약일까
- 카테고리: ai_dev
- 예상 읽기 시간: 10분
- 오늘의 질문: LLM 결과를 API·DB·워크플로에 바로 연결하려면, 우리는 무엇을 프롬프트가 아니라 스키마 계약으로 고정해야 할까?
- 핵심 출처:
- Introducing Structured Outputs in the API - 2024-08-06, 2026-06-17 확인
- Claude Platform API Release Notes - 수시 업데이트, 2026-06-17 확인
- Increase output consistency - 문서 페이지, 2026-06-17 확인
- GenerationConfig - Vertex AI REST v1 - 2025-04-08, 2026-06-17 확인
- dottxt-ai/outlines: Structured Outputs - 최신 릴리스 v1.3.0 2026-05-13, 2026-06-17 확인
1. 왜 지금 봐야 하나
LLM 기능이 데모일 때는 “JSON으로만 답해줘”가 꽤 그럴듯해 보인다. 하지만 결과가 주문 상태 변경, 고객 티켓 분류, DB insert, 에이전트 tool call, 승인 플로우로 이어지는 순간 출력 형식은 말투가 아니라 시스템 경계가 된다.
최근 주요 플랫폼은 이 경계를 프롬프트 팁이 아니라 API 기능으로 끌어올리고 있다. OpenAI는 Structured Outputs에서 JSON mode와 schema conformance를 구분하고, strict: true와 constrained decoding으로 모델 출력이 JSON Schema를 따르도록 만든다고 설명한다. Anthropic 문서도 “항상 특정 JSON schema를 따라야 한다면 일반 출력 일관성 기법 대신 Structured Outputs를 쓰라”고 안내한다. Vertex AI도 responseMimeType: application/json과 responseSchema로 생성 결과의 타입을 지정할 수 있게 한다.
개발자에게 중요한 변화는 “예쁜 JSON을 뽑는 법”이 아니다. 이제 LLM 출력은 다음 컴포넌트가 소비하는 typed boundary가 될 수 있고, 그만큼 schema 설계·검증·실패 처리 책임도 애플리케이션 쪽으로 선명해진다.
2. 핵심 개념
Structured Outputs를 이해하려면 세 층을 나눠야 한다.
첫째, JSON mode다. 이것은 대체로 “문법적으로 JSON인 텍스트”를 만들게 돕는다. 하지만 status가 반드시 "approved" | "rejected" 중 하나여야 한다거나, items 배열의 각 원소가 sku, quantity, reason을 모두 가져야 한다는 업무 규칙까지 보장하지는 않는다.
둘째, schema-constrained output이다. 개발자가 JSON Schema, OpenAPI schema subset, Pydantic 타입, enum, grammar 같은 구조를 주고 모델이 그 구조 안에서만 답하도록 제한한다. OpenAI는 JSON Schema를 context-free grammar로 바꿔 다음 토큰 후보를 마스킹하는 constrained decoding을 설명한다. Outlines도 생성 후 파싱·수정이 아니라 생성 중 구조를 제한한다는 관점을 취한다.
셋째, runtime validation이다. 구조화 출력 기능이 있어도 애플리케이션은 결과를 다시 검증해야 한다. 이유는 간단하다. 스키마는 “형태”를 강하게 만들지만 “진실성”이나 “업무상 안전성”을 자동으로 보장하지 않는다. 예를 들어 { "refund": true, "amount": 1000000 }이 스키마에는 맞아도 권한·재고·정책 검사는 별도다.
한 문장으로 정리하면 이렇다. Structured Outputs는 LLM을 결정론적 함수로 바꾸는 기능이 아니라, 출력 언어의 문법 공간을 줄여 downstream 시스템이 다룰 수 있는 계약으로 만드는 기능이다.
3. 최신 이슈와 연결
OpenAI 자료에서 중요한 포인트는 JSON mode와 Structured Outputs를 명확히 구분한 점이다. OpenAI는 JSON mode가 valid JSON을 돕지만 특정 schema 일치를 보장하지 않는다고 설명하고, Structured Outputs는 tool/function calling의 strict: true 또는 response_format의 JSON Schema로 제공된다고 밝힌다. 또한 constrained decoding을 위해 schema를 CFG로 전처리하고, 생성 중 매 토큰마다 유효 토큰만 남긴다고 설명한다.
Anthropic 쪽 변화도 같은 방향이다. Claude API 릴리스 노트에는 Structured Outputs가 Claude API에서 GA가 되었고, schema 지원 확대와 grammar compilation latency 개선, output_config.format 경로가 언급된다. 별도 consistency 문서는 출력 형식 지시, 예시, prefill 같은 프롬프트 기법을 소개하면서도 “항상 JSON schema conformant output이 필요하면 Structured Outputs를 쓰라”고 선을 긋는다. 즉 프롬프트 기법은 유연성에는 좋지만, 시스템 계약으로는 부족하다는 메시지다.
Google Vertex AI의 GenerationConfig는 responseMimeType과 responseSchema를 제공한다. 문서상 responseSchema는 OpenAPI 3.0 schema object의 subset이며, JSON 응답 schema로 쓰려면 application/json MIME type과 함께 설정해야 한다. 이 표현은 중요한 현실적 제약을 알려준다. 모든 provider가 JSON Schema 전체를 똑같이 지원하는 것은 아니다.
Outlines 같은 오픈소스 프로젝트는 provider API에만 의존하지 않는 선택지를 보여준다. GitHub README는 Pydantic, Literal, regex, grammar 등으로 생성 중 구조를 강제하는 접근을 내세운다. 최신 릴리스가 2026-05-13 기준 v1.3.0인 점도, structured generation이 일회성 유행이 아니라 로컬 모델·서빙 스택 쪽에서도 계속 다듬어지는 영역임을 보여준다.
4. 개발자 관점 해석
Structured Outputs를 도입할 때 가장 흔한 착각은 “이제 파싱 에러가 없어졌으니 신뢰성 문제가 끝났다”는 생각이다. 실제로는 문제가 이동한다.
첫 번째 이동은 프롬프트 디버깅에서 schema 디버깅으로다. 스키마가 너무 느슨하면 downstream에서 의미 오류가 난다. 반대로 너무 복잡하면 모델이 선택해야 하는 공간이 이상해지고, provider의 지원 범위를 벗어나거나 latency가 늘 수 있다. OpenAI도 schema를 전처리해 constrained decoding에 쓰는 구조를 설명하므로, 스키마는 단순한 문서가 아니라 inference path의 일부다.
두 번째 이동은 형식 안정성에서 의미 안정성으로다. enum 값이 맞고 required field가 있어도, 분류 근거가 틀릴 수 있다. 고객 불만을 low_priority로 분류한 JSON은 valid하지만 제품적으로는 위험하다. 따라서 structured output은 eval을 없애지 않는다. 오히려 “이 필드가 왜 이 값이어야 하는가”를 테스트 케이스로 만들기 쉽게 해준다.
세 번째 이동은 벤더 독립성 문제다. OpenAI의 JSON Schema, Anthropic의 output_config.format, Vertex AI의 OpenAPI subset, Outlines의 Pydantic·grammar 인터페이스는 비슷해 보이지만 동일하지 않다. schema keyword 지원, streaming 호환성, citations와의 충돌, grammar compilation 비용, tool call과 response body 중 어디에 붙일지 같은 차이가 생긴다. 그러므로 애플리케이션 내부 타입을 먼저 정하고 provider별 adapter를 두는 편이 장기적으로 안전하다.
네 번째 이동은 권한 경계다. tool call 입력을 strict schema로 받으면 quantity가 숫자인지는 보장할 수 있다. 하지만 “이 사용자가 이 수량을 변경할 권한이 있는가”, “이 tool을 자동 실행해도 되는가”는 schema가 아니라 runtime policy가 판단해야 한다. Structured Outputs는 권한 설계를 대체하지 않는다.
5. 내 프로젝트에 적용할 체크포인트
- 출력 종류를 분리하자. 사용자에게 보여줄 자연어, DB에 저장할 구조화 데이터, tool call 입력, 내부 평가 로그를 같은 응답 하나에 섞지 않는다.
- 스키마를 작게 시작하자. 처음부터 중첩 객체와 nullable union을 많이 쓰지 말고, enum·required field·짧은 배열부터 안정화한다.
- 업무 규칙은 schema 밖에 둔다. 금액 한도, 사용자 권한, 재시도 가능 여부, 승인 필요 여부는 애플리케이션 코드에서 다시 확인한다.
- provider 지원 범위를 기록하자. 같은
OrderDecision타입이라도 OpenAI, Anthropic, Vertex AI, 로컬 Outlines에서 지원되는 schema feature가 다를 수 있다. - 실패 모드를 명시하자. refusal, max token 중단, schema compilation 실패, validation 실패, 의미 eval 실패를 같은 “LLM 실패”로 묶지 않는다.
- eval 데이터를 필드 단위로 만들자. 전체 답변 점수보다
priority,category,requires_review,allowed_action같은 필드별 정답률을 보는 편이 수정하기 쉽다. - 재시도보다 축소를 먼저 고려하자. 출력이 자주 실패하면 retry loop를 늘리기 전에 schema를 더 작은 단계로 쪼개거나 enum을 줄인다.
6. 오늘 10분 액션
오늘은 내 프로젝트의 LLM 응답 하나를 골라 “프롬프트 요구사항”을 “스키마 계약”으로 바꿔보자.
- 최근에 “JSON으로 답해줘”라고 시킨 프롬프트를 하나 고른다.
- 그 JSON에서 downstream 코드가 실제로 읽는 필드만 표시한다.
- 각 필드에 대해 타입을 쓴다. 예:
priority: "low" | "medium" | "high",requires_human_review: boolean,reason: string. reason처럼 자유 텍스트가 필요한 필드와, enum처럼 닫힌 선택지가 필요한 필드를 나눈다.- 이 결과를 TypeScript
zodschema나 Pythonpydanticmodel로 적는다. - 마지막 줄에 이 질문을 남긴다. “이 스키마가 맞아도 아직 코드에서 검증해야 할 업무 규칙은 무엇인가?”
10분 안에 구현까지 못 해도 괜찮다. 목표는 LLM 출력이 “예상 텍스트”가 아니라 “다음 시스템과 맺는 계약”임을 눈으로 보는 것이다.
7. 더 볼 자료
- OpenAI Structured Outputs 글: JSON mode와 schema conformance, constrained decoding 차이를 이해하기 좋다.
- Anthropic consistency 문서: 프롬프트 기법과 Structured Outputs를 언제 구분해야 하는지 보여준다.
- Vertex AI
GenerationConfig: provider별 schema support가 완전히 같지 않다는 점을 확인하기 좋다. - Outlines GitHub: 로컬 모델이나 자체 inference stack에서 structured generation을 어떻게 생각하는지 살펴볼 수 있다.
중복 회피 메모
최근 ai_dev 글은 긴 컨텍스트 메모리, 에이전트 권한 계약, prefill/decode 지연, RAG 검색 품질, Tool Search, KV 캐시 양자화, 도구 선택 파인튜닝, LLM-as-judge, speculative decoding을 다뤘다. 이번 글은 서빙 속도나 eval 자체가 아니라 LLM 출력의 형식 신뢰성을 schema·constrained decoding·runtime validation의 경계로 나누는 structured outputs 설계 판단에 집중해 기존 주제와 관점을 분리했다.
핵심 출처
로그인하면 이 글을 북마크하고, 나만 보는 한 줄 메모를 남길 수 있어요.
댓글 0
최신순 ▾혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)