OpenAI 호환 API의 구조화 출력: JSON Schema 가이드
Responses와 Chat Completions에서 엄격한 JSON Schema를 적용하고 결과 검증, 예외 처리, 경로 호환성을 점검하는 실전 가이드입니다.

Structured Outputs를 사용하면 모델에 단순히 “JSON으로 응답하라”고 요청하는 대신 정의된 스키마를 따르는 JSON을 요구할 수 있습니다. OpenAI 호환 API에서는 같은 JSON Schema를 Responses와 Chat Completions에서 사용할 수 있지만 요청 형식이 서로 다르므로, 선택한 모델과 경로에서 실제 지원 여부를 검증해야 합니다.
안전한 운영 방식은 세 단계로 구성됩니다. 지원 모델에서는 strict 스키마를 사용하고, 애플리케이션에서 반환된 JSON을 다시 파싱하고 검증한 뒤, 값에 결정론적인 비즈니스 규칙을 적용합니다. 스키마 준수는 형식 오류를 줄이지만 사실성과 의미의 정확성을 보장하지는 않습니다.
Structured Outputs와 JSON 모드의 차이
| 방법 | 유효한 JSON | 스키마 강제 | 일반적인 용도 |
|---|---|---|---|
| 프롬프트만 사용 | 보장되지 않음 | 아니요 | 파싱 실패를 허용하는 프로토타입 |
| JSON 모드 | 지원되고 생성이 완료되면 예 | 아니요 | 애플리케이션이 구조를 검증하는 유연한 JSON |
strict: true Structured Outputs |
완료와 거부 응답 처리가 필요 | 지원되는 JSON Schema 범위 내에서 예 | 형식화된 추출과 애플리케이션 작업 흐름 |
OpenAI Structured Outputs 가이드는 모델과 엔드포인트가 지원한다면 JSON 모드보다 Structured Outputs 사용을 권장합니다. 모델이 예측 가능한 데이터 객체로 답해야 할 때는 구조화 응답 형식을, 애플리케이션 동작을 요청해야 할 때는 Function Calling을 사용합니다. 두 방식은 엄격한 인수 스키마를 공유할 수 있지만 호출 ID와 결과 메시지 규약은 다릅니다.
엔드포인트보다 스키마를 먼저 정의하기
고객 지원 요청을 분류하는 데 다음 네 필드가 필요하다고 가정합니다.
{
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": ["billing", "technical", "account"]
},
"priority": {
"type": "integer",
"minimum": 1,
"maximum": 3
},
"requires_human": {
"type": "boolean"
},
"summary": {
"type": "string"
}
},
"required": ["category", "priority", "requires_human", "summary"],
"additionalProperties": false
}
모든 속성을 필수로 지정하고 additionalProperties: false를 설정하면 후속 코드가 안정된 객체를 받을 수 있습니다. 그렇다고 모든 JSON Schema 키워드가 다른 제공자에서도 그대로 동작하는 것은 아닙니다. OpenAI는 문서에 명시된 범위를 지원하며, 다른 OpenAI 호환 제공자는 지원 범위가 다르거나 엄격 모드 자체를 지원하지 않을 수 있습니다.
Responses API로 스키마 보내기
Responses API는 스키마를 text.format 아래에 둡니다.
export MODELFLARE_API_KEY="<YOUR_API_KEY>"
export MODEL_ID="<MODEL_WITH_VERIFIED_STRUCTURED_OUTPUT_SUPPORT>"
curl -sS https://modelflare.dev/v1/responses \
-H "Authorization: Bearer $MODELFLARE_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"$MODEL_ID\",
\"input\": \"고객이 요금을 두 번 결제했으며 환불을 요청합니다.\",
\"text\": {
\"format\": {
\"type\": \"json_schema\",
\"name\": \"support_ticket\",
\"schema\": {
\"type\": \"object\",
\"properties\": {
\"category\": {
\"type\": \"string\",
\"enum\": [\"billing\", \"technical\", \"account\"]
},
\"priority\": {
\"type\": \"integer\",
\"minimum\": 1,
\"maximum\": 3
},
\"requires_human\": { \"type\": \"boolean\" },
\"summary\": { \"type\": \"string\" }
},
\"required\": [\"category\", \"priority\", \"requires_human\", \"summary\"],
\"additionalProperties\": false
},
\"strict\": true
}
}
}"
Authorization: Bearer <YOUR_API_KEY> 헤더와 함께 POST https://modelflare.dev/v1/responses로 전송합니다. Responses의 생성 결과는 형식이 지정된 출력 항목에 있습니다. SDK가 합쳐진 텍스트나 분석된 결과를 제공할 수 있지만, HTTP 클라이언트는 최상위 형식 객체를 가정하지 말고 완료된 응답에서 텍스트 출력을 찾아 JSON으로 파싱해야 합니다.
Chat Completions로 같은 스키마 보내기
Chat Completions는 response_format.json_schema를 사용합니다.
curl -sS https://modelflare.dev/v1/chat/completions \
-H "Authorization: Bearer $MODELFLARE_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"$MODEL_ID\",
\"messages\": [
{
\"role\": \"user\",
\"content\": \"고객이 요금을 두 번 결제했으며 환불을 요청합니다.\"
}
],
\"response_format\": {
\"type\": \"json_schema\",
\"json_schema\": {
\"name\": \"support_ticket\",
\"schema\": {
\"type\": \"object\",
\"properties\": {
\"category\": {
\"type\": \"string\",
\"enum\": [\"billing\", \"technical\", \"account\"]
},
\"priority\": {
\"type\": \"integer\",
\"minimum\": 1,
\"maximum\": 3
},
\"requires_human\": { \"type\": \"boolean\" },
\"summary\": { \"type\": \"string\" }
},
\"required\": [\"category\", \"priority\", \"requires_human\", \"summary\"],
\"additionalProperties\": false
},
\"strict\": true
}
}
}"
JSON 텍스트는 일반적으로 choices[0].message.content에 문자열로 들어 있으므로 별도로 파싱해야 합니다.
| 목적 | Responses API | Chat Completions |
|---|---|---|
| 스키마 위치 | text.format |
response_format.json_schema |
| 형식 유형 | text.format.type |
response_format.type |
| 스키마 이름 | text.format.name |
response_format.json_schema.name |
| 스키마 본문 | text.format.schema |
response_format.json_schema.schema |
| 엄격 모드 | text.format.strict |
response_format.json_schema.strict |
| 결과 | 형식화된 출력 항목 / SDK 도구 | choices[0].message.content |
Modelflare의 OpenAI 호환 계층은 지원되는 OpenAI/Codex 경로에서 두 요청 형식을 변환하지만, 상위 모델에 없는 Structured Outputs 기능을 새로 만들 수는 없습니다. 다른 모델 계열의 Chat Completions를 변환 없이 전달한다면 제공자의 정확한 요청 규약이 기준입니다.
구조와 의미를 별도로 검증하기
{
"category": "billing",
"priority": 2,
"requires_human": true,
"summary": "고객이 중복 결제를 신고하고 환불을 요청합니다."
}
구조 검증
JSON Schema 검증기나 SDK의 형식 검사 기능으로 필수 속성, 예상하지 못한 속성, 자료형, 열거형, 값의 범위를 확인합니다. 제공자가 엄격한 준수를 약속했더라도 애플리케이션 검증은 미지원 경로, 통합 오류, 잘린 출력, 규약 변경으로부터 시스템을 보호합니다.
의미와 비즈니스 규칙 검증
스키마는 실제 중복 결제가 있었는지, priority: 2가 적절한지, 누가 환불을 승인할 수 있는지 판단하지 못합니다. 영향이 큰 작업 흐름에서는 모델의 객체를 제안된 분류로 취급하고 신뢰할 수 있는 기록과 비교해야 합니다. 권한과 재무 한도는 결정론적인 코드에서 강제하며, 구조적으로 유효한 객체가 인증·과금·보안 경계를 우회하게 해서는 안 됩니다.
불완전하거나 예외적인 출력 처리하기
거부 응답
JSON을 찾기 전에 응답 상태와 거부 응답의 표현 방식을 확인합니다. 안전상의 거부를 스키마 파싱 오류로 보고하면 안 됩니다.
출력 잘림과 출력 한도
객체가 끝나기 전에 생성이 중단되면 스키마가 누락된 부분을 복원할 수 없습니다. 완료 상태와 중단 사유를 확인하고, 가장 큰 정상 객체를 수용할 수 있도록 충분하지만 제한된 출력 한도를 설정합니다.
지원되지 않는 스키마 키워드
엄격 출력은 대개 JSON Schema의 일부 기능만 지원합니다. 객체, 배열, 원시 자료형, 열거형, 필수 속성, 명시적인 추가 속성부터 시작하고 참조, 재귀, 복잡한 합집합 또는 고급 키워드는 제공자 문서를 확인한 뒤 사용합니다.
첫 스키마 사용 지연
일부 제공자는 새 스키마를 사전 처리하고 캐시합니다. 첫 요청이 이후 요청보다 느릴 수 있으므로 안정적이고 버전이 지정된 스키마를 재사용하고, 첫 사용 지연과 정상 상태 지연을 나누어 측정합니다.
모델 또는 경로 비호환
OpenAI 호환 엔드포인트가 일반 Chat Completions 요청은 받으면서 json_schema를 거절하거나 strict를 무시할 수 있습니다. JSON이 포함된 200 응답도 엄격한 준수의 증거가 아닙니다. 잘못된 입력과 경계 사례를 포함해 검증해야 합니다.
운영 투입 전에 호환성 매트릭스 실행하기
| 테스트 | 기록할 증거 |
|---|---|
| 최소 필수 객체 | 상태, 모델, 경로, 파싱된 객체, 검증 결과 |
| 모든 열거형 값 | 각 값의 생성 및 파싱 여부 |
| 정보 부족 | 제한된 대체 값 또는 추가 설명 요청 |
| 안전 관련 입력 | 거부 형식과 애플리케이션 처리 |
| 낮은 출력 한도 | 완료 상태와 출력 잘림 처리 |
| 미지원 스키마 기능 | 명시적 오류 또는 무시 여부 |
| Responses와 Chat 형식 | 애플리케이션 객체의 동등성 |
| 안정된 스키마 반복 | 첫 사용과 정상 상태의 소요 시간 |
스키마, 프롬프트, 리전, 스트리밍 모드, 출력 한도가 다른 상태에서 모델 품질만 비교하면 안 됩니다. 제공자의 동작과 모델 제공 여부가 바뀔 수 있으므로 검증 날짜도 기록합니다.
작업 흐름을 검증한 뒤 엔드포인트 선택하기
형식화된 출력 항목, Responses 스트리밍 이벤트 또는 더 넓은 도구 작업 흐름을 사용한다면 Responses가 적합합니다. 메시지 기반 통합이 안정적이고 경로가 response_format을 지원하면 Chat Completions를 사용할 수 있습니다. 자세한 비교는 Responses API와 Chat Completions를 참고하십시오.
어느 엔드포인트를 선택해도 다음 원칙은 같습니다.
- 버전이 지정된 JSON Schema 기준본을 하나만 유지합니다.
- 엔드포인트의 요청 형식만 바꾸고 의미는 바꾸지 않습니다.
- 완료된 응답을 애플리케이션에서 검증합니다.
- 구조 검증 후 결정론적인 비즈니스 규칙을 적용합니다.
- 파싱 오류, 거부, 출력 잘림, 호환성 실패를 별도로 관찰합니다.
클라이언트 이전은 OpenAI 호환 API 가이드에서 시작하고, 불완전한 이벤트 처리는 AI API 스트리밍 가이드를 참고하십시오. 모델 및 가격에서 현재 제공 범위를 확인한 뒤 API 키가 실제로 사용할 경로에서 Structured Outputs를 검증해야 합니다.
Structured Outputs는 모델 응답을 더 신뢰할 수 있는 애플리케이션 인터페이스로 제한합니다. 그러나 사실 검증, 인증·인가, 과금 로직을 대체하지는 않습니다. “OpenAI 호환”은 기본 URL만 보고 추정할 보장이 아니라 기능별로 시험해야 하는 주장입니다.