Structured outputs(구조화된 출력)는 모델의 응답이 사용자가 제공한 JSON Schema를 따르도록 강제하는 LLM API 기능입니다. API는 제약 디코딩을 사용해 스키마를 토큰 생성을 제한하는 문법이나 상태 머신으로 컴파일하므로 OpenAI, Anthropic, Google API에서 구문 오류와 필드 누락을 방지합니다.
LLM API의 Structured Outputs란?
LLM API의 structured outputs는 모델이 생성하는 토큰을 제한해 완성된 응답이 개발자가 제공한 JSON Schema와 일치하도록 합니다. Anthropic과 Google은 모두 이 기반 메커니즘을 제약 디코딩이라고 설명합니다. 샘플링이 시작되기 전에 API가 스키마를 문법이나 유한 상태 머신으로 컴파일하고, 스키마의 구조, 키 이름 또는 값 유형을 위반할 다음 토큰을 제외합니다.
structured outputs 없이 프롬프트에서 언어 모델에 JSON으로 답하도록 요청하면 프로덕션 환경에서 일관되게 작동하지 않습니다. Anthropic 문서는 프롬프트 지시만 사용할 때 흔히 발생하는 네 가지 실패 유형으로 잘못된 JSON 구문으로 인한 파싱 오류, 필수 필드 누락, 일관되지 않은 데이터 유형, 오류 처리와 재시도가 필요한 스키마 위반을 강조합니다. 실제로 이러한 실패는 다음과 같은 일반적인 예로 나타납니다.
- 잘못된 JSON 구문으로 인한 파싱 오류: 예를 들어 닫히지 않은 중괄호, 끝에 붙은 쉼표 또는 데이터 주변의 Markdown 문장 때문에 JSON 파서가 실패할 수 있습니다.
- 필수 필드 누락: 예를 들어 모델이 후속 애플리케이션 코드에서 기대하는 키를 생략할 수 있습니다.
- 일관되지 않은 데이터 유형: 예를 들어 숫자 필드가 문자열로 도착하거나 배열 대신 단일 값이 도착할 수 있습니다.
- 오류 처리와 재시도가 필요한 스키마 위반: 예를 들어 요청하지 않은 키나 범위를 벗어난 값 때문에 재시도 루프와 방어적인 검증 코드가 필요합니다.
제약 디코딩을 사용하면 프롬프트에 강한 표현의 형식 지시를 넣을 필요가 없습니다. 애플리케이션 유형을 API에 전달하는 스키마와 일치시키기 위해 OpenAI와 Google은 Python의 Pydantic BaseModel 클래스와 JavaScript의 Zod 객체를 받는 네이티브 SDK 도우미를 제공합니다. Anthropic의 Python SDK는 client.messages.parse()를 통해 Pydantic 모델을 받아 응답을 해당 유형으로 파싱합니다.
Structured Outputs는 JSON mode 및 프롬프트만 사용한 JSON과 어떻게 다를까?
Structured outputs는 정확한 JSON Schema 준수를 보장하지만, JSON mode와 프롬프트 지시만으로는 특정 속성 이름, 중첩 구조 또는 필수 필드를 강제할 수 없습니다.
JSON mode는 완전한 스키마 제약이 생기기 전의 중간 단계였습니다. OpenAI에서 { "type": "json_object" }로 JSON mode를 활성화하면 객체 중간에 출력 토큰이 소진되는 등의 예외 상황이 없는 한 모델이 구문상 유효한 JSON을 생성합니다. 하지만 OpenAI 문서에는 명시적인 주의사항이 있습니다. 대화 컨텍스트 어딘가에 JSON 출력 지시를 포함해야 하며, JSON이라는 단어가 없으면 API가 오류를 반환하고, 그렇지 않으면 모델이 토큰 한도에 도달할 때까지 공백을 끝없이 생성할 수도 있습니다.
OpenAI는 structured outputs를 JSON mode의 발전형으로 설명하며, 지원되는 경우 언제나 structured outputs를 사용하고 JSON mode는 gpt-4-turbo 및 그 이전의 오래된 모델에 사용하도록 권장합니다.
| 기능 | 프롬프트만 사용한 JSON | JSON mode | Structured Outputs |
|---|---|---|---|
| 유효한 JSON 구문 보장 | 아니요 | 예, 잘리지 않는 한 | 예, 거부되거나 잘리지 않는 한 |
| 정확한 스키마와 유형 강제 | 아니요 | 아니요 | 예 |
| 프롬프트에 JSON 지시 필요 | 예 | 예, OpenAI에서 필수 | 아니요 |
| 형식 오류에 따른 재시도 제거 | 아니요 | 일부 | 예, 스키마 구조에 대해 |
| 네이티브 Pydantic 및 Zod 파싱 | 수동 | 수동 | 공급업체 SDK에서 지원 |
Structured Outputs와 Function Calling: 각각 언제 사용해야 할까?
Structured outputs는 애플리케이션이나 최종 사용자에게 전달하는 모델의 최종 응답을 포맷하고, function calling은 실행 중 모델을 외부 도구, API, 데이터 소스에 연결합니다.
두 기능 모두 JSON Schema 정의에 의존하기 때문에 개발자들은 흔히 둘을 혼동합니다. OpenAI와 Google은 같은 아키텍처 경계를 구분합니다.
- structured outputs 사용: 데이터 추출이나 구조화된 분류처럼 최종 어시스턴트 메시지를 UI, 데이터베이스 또는 후속 파이프라인에 맞게 깔끔하게 포맷하려는 경우입니다.
- function calling(tool use) 사용: 모델이 답변을 마치기 전에 작업을 요청하거나 실시간 레코드를 조회하거나 백엔드 루틴을 호출해야 하는 경우입니다. 도구 정의가 현대적인 프로토콜에 어떻게 들어맞는지 더 자세히 알아보려면 tool calling과 MCP 가이드를 참조하세요.
OpenAI는 엄격한 스키마를 두 가지 형태로 제공합니다. 모델을 도구나 데이터에 연결할 때 쓰는 엄격한 스키마의 function calling과 사용자 응답을 구조화할 때 쓰는 json_schema 응답 형식입니다. Anthropic은 Claude의 최종 응답을 위한 output_config.format과 유효한 도구 이름 및 입력 인수를 보장하기 위해 개별 도구에 적용하는 strict: true로 기능을 구분하며, 한 요청에서 둘을 함께 사용하는 것도 명시적으로 지원합니다. 두 기능의 조합은 중간 도구 호출과 최종 요약이 엄격한 계약을 따라야 하는 AI 에이전트 워크플로에서 특히 실용적입니다.
OpenAI, Anthropic, Google의 Structured Outputs 구현 방식
2026년 11월 기준 OpenAI, Anthropic, Google은 각각 스키마 컴파일을 통해 structured outputs를 구현하지만, 요청 매개변수, 지원하는 JSON Schema 키워드, 복잡도 상한은 다릅니다.
| 공급업체 | 활성화 방법 | SDK 스키마 도우미 | 주요 스키마 규칙 | 주요 제한 |
|---|---|---|---|---|
| OpenAI | Responses API의 text.format 또는 Chat Completions의 response_format에 type: "json_schema"와 strict: true 사용 |
Pydantic BaseModel(Python), Zod(JS) |
루트는 anyOf가 아닌 객체여야 함; 모든 필드는 required에 포함; additionalProperties: false 필수; 키는 스키마 순서로 출력 |
최대 5,000개 속성, 10단계 중첩, 이름과 enum 합계 120,000자, enum 값 1,000개 |
| Anthropic Claude | output_config.format에 type: "json_schema" 사용, 도구에는 strict: true 추가 |
Pydantic과 함께 client.messages.parse() 사용(Python) |
$ref/$defs, anyOf, allOf 지원(제한 있음); additionalProperties: false 필수; 숫자 또는 문자열 길이 경계 없음 |
최대 엄격한 도구 20개, 선택적 매개변수 24개, 유니온 매개변수 16개, 컴파일 제한 시간 180s; 문법 캐시 24시간 |
| Google Gemini API | 텍스트 유형, mime_type: "application/json", 스키마를 포함한 응답 설정 |
Pydantic(Python), Zod(JS) | minimum/maximum, minItems/maxItems, prefixItems 지원; 지원하지 않는 경계는 알림 없이 제거 |
크거나 깊이 중첩된 스키마는 400 INVALID_ARGUMENT로 실패 |
OpenAI
OpenAI는 GPT-4o부터 structured outputs를 지원하며 새 프로젝트에는 gpt-6-astra를 권장합니다. strict: true와 allOf, not, if/then/else, dependentRequired, dependentSchemas 같은 지원하지 않는 키워드로 API를 호출하면 알림 없이 실패하는 대신 오류가 반환됩니다. OpenAI는 date-time, time, date, duration, email, hostname, ipv4, ipv6, uuid를 포함한 문자열 형식과 숫자 경계(minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf), 배열 경계(minItems, maxItems)를 지원합니다.
Anthropic Claude
Anthropic은 Messages API의 모든 모델에서 structured outputs를 지원하지만 Managed Agents 내에서는 지원하지 않습니다. Python SDK에서 client.messages.parse()를 사용하면 minimum, maximum, minLength, maxLength 같은 지원되지 않는 제약이 Claude가 의도를 볼 수 있도록 필드 설명으로 자동 이동되며, SDK는 반환된 데이터를 원래 Pydantic 모델에 대해 로컬에서 검증합니다.
Claude는 컴파일된 문법을 마지막 사용 시점부터 24시간 캐시합니다. 스키마 구조나 도구 집합을 변경하면 문법 캐시가 무효화되지만 필드 이름이나 설명을 수정해도 무효화되지 않습니다. Claude는 JSON 형식을 설명하는 자동 시스템 프롬프트도 받으므로 입력 토큰 수가 약간 증가하며, output_config.format을 변경하면 대화의 프롬프트 캐시가 무효화됩니다. 필수 속성이 먼저 출력되고 선택적 속성이 뒤따릅니다. Structured outputs는 스트리밍, 토큰 계산, Batch processing(Anthropic의 50퍼센트 할인 적용)과 함께 작동합니다. 그러나 JSON 출력과 Citations를 결합하면 400 오류가 반환되며 structured outputs는 메시지 미리 채우기와 호환되지 않습니다.
Google Gemini API
Google은 Gemini structured outputs의 주요 사용 사례로 데이터 추출, 구조화된 분류, 에이전트 워크플로를 강조합니다. OpenAI 및 Anthropic과 달리 Gemini는 minLength, maxLength, minProperties, maxProperties, uniqueItems, contains, minContains, maxContains를 비롯한 여러 키워드를 오류 없이 조용히 제거하고 무시합니다. 애플리케이션 코드에서 이러한 제약을 강제하려면 Gemini 응답에 항상 Pydantic model_validate_json() 또는 Zod .parse()를 실행해야 합니다. 지나치게 복잡한 스키마에 대해 Gemini가 400 INVALID_ARGUMENT를 반환하면 Google은 중첩을 평탄화하고, 선택적 필드나 enum을 줄이고, 속성 이름을 짧게 하고, 구조와 무관한 규칙을 필드 설명으로 옮기도록 권장합니다.
LLM을 위한 신뢰할 수 있는 JSON Schema 설계 방법
LLM structured outputs를 위한 신뢰할 수 있는 JSON Schema는 평탄한 구조를 유지하고, 필드를 필수로 지정하며, 선택적 키 대신 null을 허용하는 유형을 사용합니다. 공급업체 간 공통 규칙을 중심으로 스키마를 설계하면 컴파일 오류와 실행 시 예상치 못한 거부를 방지할 수 있습니다.
OpenAI, Claude 또는 Gemini에서 사용할 스키마를 배포하기 전에 다음 체크리스트를 사용하세요.
- 중첩을 얕게 유지: 가능하면 깊이 중첩된 객체를 평탄화하여 OpenAI의 10단계 상한보다 충분히 낮게 유지하고 Gemini 또는 Claude의 컴파일 거부를 피합니다.
- 모든 필드를 필수로 지정: 모든 객체 키를
required에 나열합니다. OpenAI는 이 규칙을 엄격히 적용하며 Claude는 요청당 선택적 매개변수의 총수를 24개로 제한합니다. - 누락된 데이터에는 null을 허용하는 유니온 사용:
required에서 키를 생략하는 대신"type": ["string", "null"]으로 선택적 값을 표현합니다. - 객체 제한: OpenAI와 Anthropic이 모두 요구하는 대로 모든 객체 정의에
"additionalProperties": false를 설정합니다. - 고정된 선택지를 enum으로 제한: 알려진 레이블 집합만 유효한 경우 자유로운 문자열 범주를 명시적인
enum배열로 바꿉니다. - 명확한 설명 작성: 글자 수 목표나 대체 지시 등 도메인 규칙과 무시되는 제약을 각 속성의
description에 넣습니다.
다음은 고객 지원 티켓을 분류하는 예시 스키마입니다. 객체 루트, 모든 필드 필수, 선택적 메타데이터를 위한 null 허용 필드, additionalProperties: false, 라우팅을 위한 enum 등 위의 공급업체 공통 규칙을 따릅니다.
{
"type": "object",
"additionalProperties": false,
"required": ["category", "urgency", "summary", "order_id"],
"properties": {
"category": {
"type": "string",
"enum": ["billing", "bug_report", "account_access", "other"],
"description": "Primary topic of the support message."
},
"urgency": {
"type": "string",
"enum": ["low", "normal", "high"],
"description": "Escalation priority based on user impact."
},
"summary": {
"type": "string",
"description": "One-sentence summary of the issue in 25 words or fewer."
},
"order_id": {
"type": ["string", "null"],
"description": "Order identifier if present in the message, otherwise null."
}
}
}
Structured Outputs에서도 무엇이 잘못될 수 있을까?
Structured outputs는 스키마에 맞는 유효한 구문을 보장하지만, 프로덕션 애플리케이션에서는 여전히 거부, 토큰 잘림, 환각으로 생성된 값, enum 대소문자 변동, 첫 요청 지연, 신뢰할 수 없는 사용자 입력이 발생합니다. 스키마 준수를 진실성이나 안전성 검증이 아닌 구문 검증으로 취급하면 프로덕션에서 알아차리기 어려운 실패를 방지할 수 있습니다.
안전상 거부와 최대 토큰 수에 따른 잘림
모델은 여전히 안전하지 않은 사용자 요청을 거부하거나 출력 토큰 상한에 도달해 생성 도중 멈출 수 있습니다. OpenAI에서는 안전상 거부 시 JSON Schema를 따르지 않는 별도의 refusal 필드가 채워집니다. Anthropic Claude에서는 거부 시 stop_reason이 "refusal"로 설정된 HTTP 200이 반환되고(요금은 계속 부과됨) 스키마보다 우선합니다. 마찬가지로 출력 상한에 도달하면 stop_reason "max_tokens"와 불완전한 JSON이 반환됩니다. JSON.parse()나 검증기를 호출하기 전에 항상 거부 표시와 중지 이유를 확인하세요.
스키마에는 유효하지만 사실과 다른 값
제약 디코딩은 원본 텍스트에 답이 없거나 스키마의 가정과 모순되어도 모델이 스키마를 채우도록 강제합니다. 응답이 모든 유형 및 enum 검사를 통과하면서도 지어낸 날짜, 잘못된 범주 또는 그럴듯한 추측을 포함할 수 있습니다. OpenAI는 입력이 스키마와 호환되지 않을 때 null이나 빈 필드를 반환하는 등 무엇을 반환할지 프롬프트에서 모델에 지시하도록 권장하며, Google은 개발자에게 코드에서 비즈니스 규칙에 따라 값을 검증하라고 경고합니다. 확신에 찬 출력이 여전히 틀릴 수 있는 이유는 AI 환각률 가이드에서 확인하세요.
Claude의 enum 대소문자와 컴파일 지연
배포 중 팀을 당황하게 하는 두 가지 운영상 특징이 있습니다.
- Claude의 enum 대소문자: Anthropic은 제약 디코딩 중 enum의 대소문자가 보장되지 않는다고 설명하므로 애플리케이션 로직에서 반환된 enum 문자열을 대소문자를 구분하지 않고 비교하세요.
- 첫 요청의 컴파일 지연: Claude는 새 스키마를 처음 문법으로 컴파일할 때 추가 지연이 발생하며 이후 24시간 캐시합니다. OpenAI도 파인튜닝된 모델에서 유사한 첫 요청 스키마 처리 지연을 언급합니다.
필드 값 안의 프롬프트 인젝션
출력 구조를 제한해도 JSON 문자열 필드로 요약하거나 추출하는 신뢰할 수 없는 텍스트 안에 숨겨진 악의적인 지시가 무력화되지는 않습니다. 모델은 유효한 JSON을 충실히 반환하면서도 summary 또는 notes 필드에 후속 도구나 인간 검토자를 노린 주입된 지시나 기만적인 내용을 담을 수 있습니다. 파싱된 모든 필드 값을 신뢰할 수 없는 입력으로 취급하고, 추출된 필드를 자동 작업에 전달하기 전에 프롬프트 인젝션이란 무엇인가 입문 글을 확인하세요.
자주 묻는 질문
structured outputs를 사용하면 LLM 응답이 느려지나요?
Structured outputs는 새 스키마를 사용하는 첫 요청에 지연을 추가할 수 있지만 이후 캐시된 요청은 컴파일 오버헤드 없이 실행됩니다. Anthropic은 스키마 구조나 도구 정의를 변경하지 않는 한 컴파일된 Claude 문법을 마지막 사용부터 24시간 캐시합니다. OpenAI는 파인튜닝된 모델을 사용할 때만 첫 요청의 추가 스키마 처리 지연을 언급합니다.
structured outputs를 사용해도 LLM이 환각을 일으킬 수 있나요?
네. LLM은 스키마에 100퍼센트 유효한 JSON을 반환하면서도 값을 환각으로 만들어낼 수 있습니다. 제약 디코딩은 토큰 구문과 필드 유형을 제한할 뿐 사실의 정확성을 제한하지 않습니다. 입력에 필수 정보가 없으면 null을 허용하는 필드를 제공하고 언제 null을 반환할지 지시하지 않는 한 모델이 필수 키를 채우기 위해 값을 지어낼 수 있습니다.
structured output API에 Pydantic이나 Zod를 사용해야 하나요?
네이티브 SDK 스키마 도우미를 사용하면 애플리케이션 유형과 API 스키마를 일치시키는 데 도움이 됩니다. OpenAI와 Google은 Python의 Pydantic과 JavaScript의 Zod를 모두 지원하며, Anthropic의 Python SDK는 client.messages.parse()를 통해 Pydantic을 지원합니다. 이 도우미는 모델을 JSON Schema로 변환하고, 응답을 유형이 지정된 객체로 파싱하며, Gemini가 제거하거나 Anthropic이 필드 설명으로 옮기는 문자열 길이 같은 제약을 검증합니다.
structured outputs를 활성화했을 때 모델이 요청을 거부하면 어떻게 되나요?
모델이 안전상의 이유로 요청을 거부하면 거부가 JSON Schema보다 우선합니다. OpenAI는 스키마를 따르는 대신 전용 refusal 필드에 설명을 넣고, Anthropic은 stop_reason을 "refusal"로 설정한 HTTP 200을 반환합니다. 애플리케이션은 JSON 파싱을 시도하기 전에 해당 필드를 확인해야 합니다.
Claude나 Gemini가 JSON Schema를 400 오류로 거부하는 이유는 무엇인가요?
Claude와 Gemini는 모두 문법이나 상태 머신의 내부 복잡도 제한을 초과하는 스키마를 400 오류로 거부합니다. Claude는 재귀 스키마나 숫자 경계처럼 지원하지 않는 스키마 기능을 포함하거나 JSON 출력과 Citations를 결합해도 400 오류를 반환합니다. 중첩 객체를 평탄화하고 이름을 줄이며 선택적 또는 유니온 매개변수를 줄이면 보통 컴파일 거부가 해결됩니다.



