Kvmzen 블로그
← 기술 실전으로 돌아가기

OpenAI Structured Outputs 완전 가이드: GPT가 JSON Schema에 맞는 JSON을 안정적으로 내게 하려면?

기술 실전 ·약 12분 읽기

코드 에디터에서 JSON Schema와 구조화 데이터 파이프라인을 작성하는 화면

모델 출력을 데이터베이스, 폼, 하위 Agent에 바로 연결한다면 「JSON으로 반환해 주세요」라는 프롬프트에 운을 맡길 수 없습니다. OpenAI Structured Outputs는 제한된 디코딩으로 출력을 여러분이 제공한 JSON Schema에 고정합니다. 필드가 빠지지 않고, 열거값이 임의로 만들어지지 않으며, 문자열이 숫자로 바뀌는 일도 줄어듭니다. 이 글은 추출, 티켓 분류, 평가 채점, 다단계 워크플로를 다루는 엔지니어를 대상으로 JSON Mode에서 strict: true까지 올리는 경로를 정리합니다.

100%
지원 모델에서의 Schema 준수율
2
접속 경로: Chat Completions / Responses
10
객체 최대 중첩 깊이

JSON Mode만으로는 부족한 이유

JSON Mode(type: "json_object")는 「JSON 파서가 읽을 수 있다」는 점만 보장합니다. 키 이름, 필수 필드, 열거 집합, 숫자 타입까지는 보장하지 않습니다. 운영에서 자주 터지는 세 가지는 severity 누락, 42"42"로 쓰기, enum 밖에 urgent-plus를 만들어 내는 경우입니다. 하류가 강한 타입으로 파싱하면 무작위 샘플에서 파이프라인 전체가 실패합니다.

능력 JSON Mode Structured Outputs
유효한 JSON 출력
제공한 JSON Schema 준수 아니오(프롬프트에 의존) 예(제한된 디코딩)
켜는 방법 json_object json_schema + strict: true
대표 모델 비교적 이른 GPT-4o / GPT-3.5 등 gpt-4o-2024-08-06, gpt-4o-mini 및 이후 스냅샷
거절 처리 JSON처럼 보이는 거절 문구가 나올 수 있음 별도의 refusal 필드로 전달

공식 문서는 Structured Outputs를 JSON Mode의 다음 단계로 설명합니다. 새 프로젝트는 파싱이 실패한 뒤 세 번 재시도하기보다 Schema를 기본값으로 두는 편이 낫습니다. 모델별 토큰 요금을 함께 비교하신다면 Kimi K3와 GPT-5.5 API 비용 비교를 참고해 주세요. 구조화 출력 자체는 출력 크기를 거의 늘리지 않으며, 실제 청구를 키우는 것은 재시도와 과도하게 긴 추론입니다.

경로는 둘, Schema 규칙은 하나
Chat Completions는 Schema를 response_format에 두고, Responses API는 text.format에 둡니다. 필드 경로만 다를 뿐 strict, additionalProperties: false, 「모두 required」 같은 제약은 같습니다.

최소 실행 요청

아래 티켓 추출 Schema는 운영에서 필요한 요구의 약 80%를 덮습니다. 문자열, 열거, 정수, 그리고 null 유니온으로 선택 필드를 흉내 내는 방식입니다. 모델은 모든 키를 반환해야 하며, 계정 ID가 없으면 키를 생략하지 않고 명시적으로 null을 넣습니다.

Chat Completions · response_format
{
  "model": "gpt-4o-2024-08-06",
  "messages": [
    {"role": "system", "content": "사용자 설명을 티켓 객체로 추출하세요."},
    {"role": "user", "content": "결제 페이지에서 저장된 카드로 결제하면 500이 납니다. 오늘부터입니다. 계정 acct_8842."}
  ],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "support_ticket",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "summary": { "type": "string" },
          "category": {
            "type": "string",
            "enum": ["billing", "bug", "account", "other"]
          },
          "severity": { "type": "integer" },
          "account_id": { "type": ["string", "null"] }
        },
        "required": ["summary", "category", "severity", "account_id"],
        "additionalProperties": false
      }
    }
  }
}

Responses API의 동등한 작성법은 같은 Schema를 text.format에 넣고 type, name, schema, strict를 형제 필드로 두는 것입니다. 어떤 Schema를 처음 쓰면 서버가 제약을 컴파일하므로 지연이 조금 늘 수 있습니다. 이후 같은 Schema는 캐시되어 요청이 다시 정상 수준으로 돌아옵니다.

노트북 위 코드와 구조화 API를 디버깅하는 환경
Schema를 인터페이스 계약으로 두세요. 같은 정의로 로컬 단위 테스트를 먼저 돌린 뒤 모델에 생성을 맡기는 편이 안전합니다

strict Schema가 지켜야 할 규칙

strict: true를 켜면 제출하는 것은 「느슨한 JSON Schema」가 아니라 공식 지원 부분집합입니다. 규칙을 어기면 「가능한 한 지키라」가 아니라 바로 400이 납니다.

  • 루트는 object여야 합니다: 루트 노드에 anyOf나 배열을 둘 수 없습니다. Zod의 discriminatedUnion이 최상위 anyOf를 만들면 { "result": ... }처럼 객체로 한 겹 감싸야 합니다.
  • 모든 object에 additionalProperties: false: 중첩 객체도 포함합니다. 하나라도 빠지면 거절됩니다.
  • properties에 나온 키는 모두 required에 넣습니다: 선택 의미는 "type": ["string", "null"] 또는 anyOfnull을 더해 표현합니다.
  • 지원 타입: string, number, integer, boolean, object, array, enum, anyOf.
  • 문자열 제약: pattern, format(email, date-time, uuid, ipv4 등).
  • 숫자와 배열: minimum / maximum / multipleOf; minItems / maxItems.
  • 명시적으로 미지원: allOf, not, if/then/else, dependentRequired 같은 조합 키워드.
  • 규모 상한: 객체 속성은 합산 약 5,000개, 중첩 10층까지. 속성 이름, 정의 이름, 열거값 문자열 총길이는 12만 자를 넘지 않으며, 열거값 개수는 최대 1,000개입니다.
미세 조정 모델은 더 엄격합니다
미세 조정 모델에서는 pattern, format, minLength, minimum, minItems 같은 제약이 아직 지원되지 않을 수 있습니다. 기본 스냅샷에서 Schema를 먼저 검증한 뒤 미세 조정 여부를 결정하는 것이 안전합니다.

도구 호출과 응답 본문

같은 strict는 함수·도구 인수에도 쓸 수 있습니다. 차이는 의도입니다. 도구 Schema는 「모델이 무엇을 호출할지」를 설명하고, response_format / text.format은 「사용자에게 어떤 모양으로 답할지」를 설명합니다. 추출, 채점, UI 상태 생성은 후자이고, 날씨 조회, 파일 쓰기, 명령 실행은 전자입니다. 터미널 Agent와 GUI 조작을 비교하신다면 Claude Code와 OpenAI Computer Use의 관계를 참고해 주세요. 그쪽은 「어떻게 행동할지」의 층이고, 이 글은 행동 전후의 데이터 계약을 다룹니다.

SDK로 객체까지 바로 파싱하기

JSON Schema를 손으로 쓰면 required를 빠뜨리기 쉽습니다. 공식 SDK는 Pydantic / Zod 도우미를 제공해 타입에서 Schema를 만들고, 응답 쪽에서는 파싱된 객체를 바로 돌려줍니다. 아래는 Python의 전형적인 작성법입니다.

Python · Pydantic
from typing import Literal, Optional
from pydantic import BaseModel
from openai import OpenAI

class SupportTicket(BaseModel):
    summary: str
    category: Literal["billing", "bug", "account", "other"]
    severity: int
    account_id: Optional[str]

client = OpenAI()
completion = client.beta.chat.completions.parse(
    model="gpt-4o-2024-08-06",
    messages=[
        {"role": "system", "content": "티켓을 추출하세요."},
        {"role": "user", "content": "결제 페이지에서 500이 납니다. 계정 acct_8842."},
    ],
    response_format=SupportTicket,
)
ticket = completion.choices[0].message.parsed
if completion.choices[0].message.refusal:
    raise RuntimeError(completion.choices[0].message.refusal)
print(ticket.category, ticket.severity)
Schema를 단일 사실 원천으로 두세요
같은 Pydantic / Zod 모델을 API 요청, 단위 테스트, 하류 검증에 함께 씁니다. 프롬프트에 필드 목록을 다시 적지 마세요. 문서가 코드보다 먼저 낡아집니다.

오류, 거절 응답, 엔지니어링 점검표

배포 전에 이 목록을 한 번 돌리면 「로컬 curl은 되는데 파이프라인만 400」인 문제를 대부분 줄일 수 있습니다.

  • 400 Unsupported schema: additionalProperties: false 누락, required가 아닌 필드, 루트가 anyOf, 또는 allOf 사용.
  • 모델 거절: 안전 정책이 걸리면 내용이 Schema에 채워지지 않습니다. refusal을 읽어 UI에 보여 주고, JSON 파싱 실패로 재시도하지 마세요.
  • 선택 필드가 빈 문자열이 됨: 계약에 null을 썼다면 null을 받아들여야 합니다. 적재 전에 null을 SQL NULL로 매핑하고 다시 추측하지 마세요.
  • 열거가 너무 넓음: 업무 상태는 5–8개로 줄이세요. 수백 개 열거는 컨텍스트를 태우고 1,000개 상한에도 가까워집니다.
  • 키 순서: 출력 키 순서는 Schema에 선언한 순서와 같습니다. 스트리밍 소비 시 필드 단위로 증분 파싱할 수 있습니다.
  • 프롬프트는 여전히 필요합니다: Schema는 모양을, 프롬프트는 의미를 담당합니다. 예를 들어 「severity는 1–5, 5는 전면 장애」는 system에 쓰고, 정수 범위는 minimum / maximum으로 한 겹 더 잠글 수 있습니다.

운영에 올릴 때는 세 가지를 고정하는 것을 권합니다. Schema를 Git에 넣고, 거절·400·타임아웃을 각각 계측하며, 「Hello를 JSON으로 반환」 같은 장난감 입력이 아니라 실제 티켓으로 회귀 샘플을 만듭니다. 파싱 층이 안정되면 매주 정규식을 고치는 대신 분류 정확도와 지연에 시간을 쓸 수 있습니다.

Mac mini에서 추출 파이프라인을 돌리면 환경이 한결 수월합니다

Structured Outputs의 디버그 주기는 짧습니다. Schema를 고치고, 작은 샘플을 돌리고, 파싱된 객체를 확인하면 됩니다. macOS에서는 Python, Node.js, Docker, Homebrew가 바로 쓰이며 WSL을 먼저 꾸릴 필요가 없습니다. Apple Silicon 통합 메모리 덕분에 에디터, 로컬 검증 스크립트, 긴 컨텍스트 평가를 함께 열어 두어도 타입 검사 중에 기기가 swap으로 끌려가지 않습니다.

Mac mini M4의 대기는 약 4W라 회귀 샘플을 밤새 걸어 두기에 알맞고, macOS 충돌률이 낮으며 Gatekeeper와 SIP가 의존성 스크립트 오염 위험도 낮춥니다. 같은 가격대의 Windows 상자보다 장시간 무인 Agent 평가에서 안정성과 전기 요금 모두 유리한 편입니다.

JSON 회귀와 모델 비교를 전담하는 상시 가동 노드가 필요하시다면 Kvmzen 요금제를 확인해 보세요. Schema 계약을 노트북에서 고정 사양의 클라우드 Mac으로 옮길 수 있습니다.

한정 특가

단순한 Mac이 아닌, 클라우드의 개발 기지

전용 컴퓨팅 · 글로벌 노드 · 월간 구독 · 하드웨어 불필요

홈으로 돌아가기
한정 특가 플랜 보기