← 블로그로 돌아가기

OpenAI Structured Outputs 완벽 가이드: GPT가 JSON Schema를 준수하는 JSON 데이터를 안정적으로 출력하도록 하는 방법은?

OpenAI Structured Outputs 완벽 가이드: GPT가 JSON Schema를 준수하는 JSON 데이터를 안정적으로 출력하도록 하는 방법은?

GPT 결과를 데이터베이스, API, 큐, 에이전트 실행기에 연결하는 개발자를 위한 안내서입니다. Structured Outputs와 JSON Schema의 차이, strict mode 설정, 거부와 잘린 응답 처리, 의미 검증과 회귀 테스트 방법을 사용 목적별로 설명합니다.

공식 발표의 초기 평가에서 gpt-4o-2024-08-06은 복잡한 JSON Schema 일치 테스트에서 100%를 기록했습니다. 그러나 이 수치는 특정 모델과 평가 조건에 대한 결과입니다. OpenAI Structured Outputs 가이드의 핵심은 strict mode로 구조를 제한하고, 거부·잘림·업무 의미 오류를 애플리케이션에서 별도로 처리하는 것입니다. 일반적인 프롬프트 지시나 JSON mode만으로 데이터베이스와 도구 실행에 필요한 계약을 만들면 안 됩니다. Structured Outputs 공식 발표에서도 Schema 준수와 값의 정확성은 별개의 문제라고 설명합니다.

이 글은 GPT API 결과를 안정적으로 파싱해야 하는 백엔드 개발자, 모델 결과를 데이터베이스와 큐에 넣는 데이터 엔지니어, 최종 응답과 도구 인자를 함께 제한해야 하는 Agent 개발자를 위한 내용입니다. 단순한 호출 예제보다 소비 목적별 Schema 설계와 실패 처리에 초점을 둡니다.

먼저 구분해야 하는 세 가지 출력 방식

Structured Outputs를 도입할 때 가장 먼저 구분해야 하는 것은 일반 응답 형식, Function Calling의 도구 인자, 일반 JSON mode입니다. 세 방식은 모두 JSON과 관련되지만, 애플리케이션에서 기대할 수 있는 보장 수준이 다릅니다.

사용 방식 주된 목적 구조 보장 애플리케이션의 추가 책임
일반 텍스트 설명, 대화, 요약 없음 전체 파싱과 의미 해석
JSON mode JSON 문법으로 응답 올바른 JSON 중심 필드, 자료형, 누락 여부 검증
Structured Outputs Schema 계약이 필요한 응답 지원되는 Schema와 strict: true 기준 거부, 중단, 업무 규칙 검증
Function Calling + strict mode 도구 실행 인자 제한 함수 인자 Schema 기준 권한, 자원 상태, 실행 결과 검증

JSON mode는 유효한 JSON을 생성하도록 돕지만 특정 Schema와의 일치를 보장하지 않습니다. 반면 Function Calling에서 strict: true를 설정하면 함수 인자가 제공된 JSON Schema와 일치하도록 제한할 수 있습니다. Function Calling 공식 도움말에서 두 방식의 차이를 확인할 수 있습니다.

따라서 “JSON처럼 보이는 문자열”이 필요한지, “정해진 필드 계약”이 필요한지부터 정해야 합니다. 주문 추출, 고객 분류, 자동 라우팅, 도구 실행처럼 하류 시스템이 필드 이름과 자료형을 전제로 한다면 Structured Outputs가 기본 선택입니다.

데이터 추출은 작고 닫힌 Schema로 시작하기

회의 기록에서 할 일, 담당자, 기한을 추출한다고 가정합니다. 프롬프트에 “JSON으로만 답하라”고 쓰는 방식은 모델이 필드를 빼거나 이름을 바꾸거나 설명 문장을 덧붙이는 상황을 완전히 막지 못합니다. 최소 사례는 다음처럼 설계할 수 있습니다.

from openai import OpenAI

client = OpenAI()

schema = {
    "type": "object",
    "properties": {
        "items": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "task": {"type": "string"},
                    "owner": {"type": "string"},
                    "due_date": {"type": "string"}
                },
                "required": ["task", "owner", "due_date"],
                "additionalProperties": False
            }
        }
    },
    "required": ["items"],
    "additionalProperties": False
}

response = client.chat.completions.create(
    model="지원되는 모델",
    messages=[
        {"role": "system", "content": "회의 기록에서 할 일을 추출합니다."},
        {"role": "user", "content": "회의 내용 원문"}
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "action_items",
            "strict": True,
            "schema": schema
        }
    }
)

message = response.choices[0].message

중요한 부분은 네 가지입니다.

  • 최상위 결과를 객체로 고정합니다.
  • 반복 데이터는 배열 안의 객체로 정의합니다.
  • 데이터베이스에 넣을 필드는 required에 넣습니다.
  • additionalProperties: false로 예기치 않은 필드 확장을 차단합니다.

다만 모든 JSON Schema 기능이 같은 방식으로 지원되는 것은 아닙니다. Structured Outputs는 지원되는 JSON Schema 하위 집합을 사용하므로, 복잡한 조건부 Schema나 지원되지 않는 키워드를 먼저 공식 문서에서 확인해야 합니다. Structured Outputs 개발자 문서의 지원 범위와 제한을 기준으로 Schema를 줄여야 합니다.

첫 번째 요청에서 새 Schema를 처리하는 추가 지연이 발생할 수 있습니다. 공식 발표는 일반적인 Schema가 처음 처리될 때 10초 이내인 경우가 많고, 복잡한 Schema는 1분까지 걸릴 수 있다고 안내합니다. 이후에는 처리된 구조가 재사용될 수 있습니다. 이 때문에 매 요청마다 필드 순서와 이름을 바꾸기보다 작업별로 안정된 Schema를 유지하는 편이 운영에 유리합니다.

분류와 라우팅은 자유 텍스트보다 enum을 우선하기

고객 문의를 billing, technical, account 중 하나로 보내는 시스템이라면 모델에게 분류명을 자유롭게 작성하게 하지 않는 것이 좋습니다. technical_issue, tech, technical-support처럼 의미가 비슷한 값이 늘어나면 라우터와 분석 테이블이 불안정해집니다.

필드 설계 권장 예시 피해야 할 예시
분류값 "enum": ["billing", "technical", "account", "unknown"] 자유 형식 문자열
확신이 낮은 상태 unknown, needs_review 임의의 설명 문장
설명 필드 분류 근거를 짧은 문자열로 저장 근거를 라우팅 조건으로 직접 사용
버전 관리 schema_version을 별도 기록 기존 필드 의미를 조용히 변경

unknown이나 needs_review는 실패를 뜻하는 값이 아니라 모델이 판단할 수 없는 입력을 보존하는 안전장치입니다. 분류 결과를 반드시 세부 카테고리 중 하나로 만들도록 강제하면 데이터가 깨끗해 보일 뿐, 실제로는 잘못된 라우팅이 누적됩니다.

분류 뒤에는 별도의 업무 규칙도 필요합니다. 예를 들어 billing으로 분류된 문의라도 결제 식별자가 존재하는지, 해당 사용자가 접근 권한을 갖는지, 환불 요청 기간이 유효한지는 모델이 아니라 서버가 판단해야 합니다. Schema는 허용된 값의 모양을 제한하지만 사실 여부를 증명하지는 않습니다.

도구 인자는 엄격하게, 실행은 보수적으로 분리하기

Agent 도구 호출에서는 최종 답변 형식과 함수 인자 형식을 분리해야 합니다. 예를 들어 create_ticket 도구의 인자는 다음처럼 고정할 수 있습니다.

{
  "type": "function",
  "function": {
    "name": "create_ticket",
    "description": "지원 티켓을 생성합니다.",
    "strict": true,
    "parameters": {
      "type": "object",
      "properties": {
        "title": {"type": "string"},
        "priority": {
          "type": "string",
          "enum": ["low", "normal", "high"]
        },
        "project_id": {"type": "string"}
      },
      "required": ["title", "priority", "project_id"],
      "additionalProperties": false
    }
  }
}

이 정의는 priority가 허용된 값인지, project_id가 빠지지 않았는지를 제한합니다. 그러나 다음 검사는 여전히 서버가 수행해야 합니다.

  • 호출한 사용자가 해당 프로젝트에 티켓을 만들 권한이 있는지 확인합니다.
  • project_id가 실제로 존재하고 현재 활성 상태인지 확인합니다.
  • 우선순위에 따라 자동 승인이나 알림이 실행되는지 확인합니다.
  • 동일 요청의 재전송으로 중복 티켓이 만들어지지 않는지 확인합니다.
  • 도구 실행 결과를 모델의 주장과 분리해 저장합니다.

Structured Outputs는 도구 인자의 구조를 제한하는 기능이지, 권한 시스템이나 거래 원장을 대신하는 기능이 아닙니다. 또한 일부 도구 호출 흐름은 병렬 함수 호출과 함께 사용할 때 별도 검증이 필요하므로, 여러 도구를 동시에 허용하는 설정은 실제 SDK와 모델 조합으로 테스트해야 합니다. Function Calling 개발자 문서를 기준으로 실행 경로를 확인하는 편이 안전합니다.

화면과 하류 API에서는 자료형 다음에 의미를 검증하기

날짜가 문자열이고 금액이 숫자라는 사실만으로는 충분하지 않습니다. 다음과 같은 오류는 Schema 검증을 통과할 수 있습니다.

  • 2026-02-30처럼 달력에 존재하지 않는 날짜입니다.
  • 통화가 USD인데 실제 결제 계정은 다른 통화를 사용합니다.
  • 시작일보다 종료일이 빠릅니다.
  • 주문 식별자는 형식이 맞지만 데이터베이스에 존재하지 않습니다.
  • 사용자 식별자와 주문 식별자가 서로 다른 고객을 가리킵니다.
  • 금액이 숫자지만 음수이거나 허용 한도를 초과합니다.

권장 흐름은 Schema 검증 → 자료형 변환 → 업무 규칙 검증 → 데이터베이스 제약 → 필요 시 사람 검토입니다. 화면을 자동 생성하는 경우에도 같은 흐름을 적용해야 합니다. 화면 구성 객체가 Schema에 맞더라도 버튼이 허용되지 않은 작업을 실행하거나 사용자에게 노출하면 안 되는 내부 필드를 포함할 수 있기 때문입니다.

Structured Outputs가 객체 내부 값의 사실성이나 계산 정확성까지 보장하는 것은 아닙니다. 따라서 “파싱에 성공했다”를 “업무적으로 올바르다”로 기록하면 안 됩니다.

실패 응답은 유형별로 저장하고 처리하기

구조화된 응답을 파싱하기 전에 정상 완료 여부를 확인해야 합니다. 특히 다음 세 가지를 별도로 구분해야 합니다.

실패 유형 확인할 신호 기본 처리
안전 거부 메시지의 refusal 재시도보다 대체 안내 또는 사람 검토
출력 중단 finish_reason 또는 응답 상태가 완료가 아님 길이와 입력을 조정한 뒤 제한적으로 재실행
애플리케이션 오류 SDK 예외, 네트워크 오류, 로컬 파서 오류 원인별 재시도 정책과 알림 적용

공식 발표는 거부 응답을 감지할 수 있도록 refusal 필드를 제공하며, 최대 출력 길이에 도달해 응답이 중간에 끝나는 경우에는 Schema 일치를 기대할 수 없다고 설명합니다. Structured Outputs 안전 처리 안내를 기준으로 예외 경로를 구현해야 합니다.

실패 시 원본 입력, 원본 응답, 모델 이름, 요청 식별자, 종료 이유, 거부 내용, Schema 버전을 함께 저장해야 합니다. 모든 실패를 같은 프롬프트로 재시도하면 안전 거부를 반복하거나 잘린 응답을 정상 데이터로 오인할 수 있습니다. 스트리밍을 사용한다면 완료 이벤트와 중단 상태를 확인한 뒤에만 최종 객체를 큐나 데이터베이스로 넘겨야 합니다. Responses API 스트리밍 참고 문서를 통해 완료와 불완전 상태를 구분할 수 있습니다.

중간 점검: 생산 환경용 확인 목록

다음 목록은 배포 전 단일 테스트가 아니라 Schema와 모델을 변경할 때마다 실행해야 하는 승인 기준입니다.

  • [ ] 최상위 객체와 모든 하위 객체에 필요한 required 필드를 정의했습니다.
  • [ ] 각 객체에 additionalProperties: false를 검토하고 설정했습니다.
  • [ ] 분류와 라우팅 값에 enum을 사용했습니다.
  • [ ] 모델이 판단하지 못하는 입력을 위한 unknown 또는 사람 검토 상태를 마련했습니다.
  • [ ] 응답 형식과 Function Calling 도구 인자를 서로 다른 Schema로 관리합니다.
  • [ ] refusal과 비정상 종료를 일반 파싱 오류와 구분합니다.
  • [ ] Schema 검증 뒤에 권한, 날짜, 금액, 식별자, 연관 데이터 검사를 둡니다.
  • [ ] 정상·경계·악성·누락 입력을 회귀 테스트에 포함했습니다.
  • [ ] 원본 응답과 Schema 버전을 장애 기록에 저장합니다.
  • [ ] Schema 변경 시 버전과 하류 소비자의 호환성을 확인합니다.

회귀 테스트와 Schema 버전을 운영 규칙으로 만들기

생산 파이프라인에서는 정상 입력만 테스트하면 안 됩니다. 최소한 다음 네 묶음을 유지해야 합니다.

  1. 정상 사례: 모든 필드가 있고 값이 업무 규칙을 만족하는 입력입니다.
  2. 경계 사례: 빈 배열, 긴 문자열, 날짜 경계, 최대 허용 금액처럼 규칙의 끝에 있는 입력입니다.
  3. 악성 사례: 프롬프트 주입, 허위 식별자, 권한 없는 작업 요청을 포함한 입력입니다.
  4. 변경 사례: 필드를 추가하거나 enum 값을 바꾼 뒤 기존 소비자가 계속 동작하는지 확인하는 입력입니다.

Schema를 수정할 때는 기존 버전을 즉시 덮어쓰지 말고, 생산 데이터가 어느 버전으로 생성되었는지 추적할 수 있게 해야 합니다. 데이터베이스 테이블이나 큐 메시지에 schema_version을 저장하면 재처리와 롤백이 쉬워집니다.

지속적인 배치 작업이나 Apple 개발 파이프라인에서 이 검증을 돌린다면 실행 환경도 함께 관리해야 합니다. 짧은 테스트는 로컬에서 처리할 수 있지만, 반복적인 배치와 CI 작업은 실행 시간, 네트워크, SDK 버전, 로그 보존 정책을 기준으로 임시 원격 Mac 환경과 상시 운영 환경을 나누는 편이 낫습니다. 필요한 경우 한국 리전 Mac 실행 환경이나 미국 동부 Mac 실행 환경을 비교하면서 작업 주기와 접속 위치를 맞출 수 있습니다.

현재 운영 방식이 개인 개발자의 로컬 Mac에 의존한다면 장시간 실행 중 절전이나 네트워크 변경으로 배치가 끊기고, 동일 SDK와 환경을 팀원이 재현하기 어렵다는 문제가 생깁니다. 반대로 장기간 고정된 고부하 작업이나 물리 장치 연결이 핵심이라면 Mac 임대보다 자체 장비가 적합할 수 있습니다. 다만 임시 검증, 릴리스 전 회귀 테스트, 여러 개발자가 공유하는 Apple 개발 흐름에는 필요한 기간만 원격 Mac을 빌리는 방식이 운영 부담과 초기 장비 구매를 줄이는 선택지가 될 수 있습니다.

OpenAI Structured Outputs는 항상 완벽한 데이터를 만드는 기능이 아닙니다. strict mode는 JSON Schema에 맞는 구조를 만드는 층이고, refusal과 중단 처리는 응답 제어 층이며, 날짜·권한·금액·식별자 검증은 업무 시스템의 책임입니다. 세 층을 분리해 설계하면 GPT API의 파싱 실패를 줄이면서도 잘못된 값이 데이터베이스와 Agent 실행기로 바로 흘러가는 사고를 막을 수 있습니다. 개발팀은 먼저 위 확인 목록을 테스트 기준으로 고정한 뒤, 작업 주기에 따라 임시 또는 상시 Mac 실행 환경을 평가하는 순서로 진행하는 것이 안전합니다.

안정적인 개발 환경이 필요하다면 nuvcloud를 시작해 보세요

필요한 기간만 원격 맥을 이용해 인공지능 서비스 개발과 검증 환경을 유연하게 운영할 수 있습니다.

안정적인 원격 접속 환경에서 데이터 처리와 자동화 작업을 편리하게 점검할 수 있습니다.

한정 특가 →