본문으로 이동
메뉴 여닫기
환경 설정 메뉴 여닫기
개인 메뉴 여닫기
로그인하지 않음
지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.

구조화된 출력 (언어 모델)

IT 위키
Structured outputs; 구조화된 출력
언어 모델의 출력을 JSON 스키마, 정규 표현식, 문법 같은 형식 명세에 반드시 맞게 생성하도록 하는 기능과 그 바탕의 제약 디코딩 기술

구조화된 출력(structured outputs)은 대형 언어 모델이 프로그램이 바로 읽을 수 있는 형식, 대표적으로 지정한 JSON 스키마에 맞는 응답을 내도록 보장하는 기능이다. 핵심 기술은 제약 디코딩(constrained decoding)으로, 토큰을 하나 고를 때마다 형식상 올 수 없는 토큰의 확률을 0으로 만든다. 형식을 정규 표현식이나 문맥 자유 문법으로 적으면 문법 기반 디코딩(grammar-constrained decoding)이라고도 한다. 국내 공식 문서에서는 앤트로픽과 구글이 "구조화된 출력", 마이크로소프트가 "구조적 출력"이라고 쓴다.[1][2]

데이터 추출, 분류, 도구 호출 인자 생성, 에이전트가 다음 행동을 고르는 명령 형식처럼, 사람이 아니라 코드가 출력을 받는 곳이면 어디서나 쓴다. 디코딩 일반은 언어 모델 디코딩 문서에서 다룬다.

형식을 맞추는 방법들

편집 원본 편집
방법 하는 일 형식 보장 비고
프롬프트 지시 "다음 JSON 형식으로만 답하라"와 예시를 준다 없음 괄호 누락, 필드 이름 오타, 설명 문장 섞임이 생긴다
사후 검증·재시도 출력을 파싱·검증해 실패하면 오류를 붙여 다시 요청 재시도 횟수만큼 지연과 비용이 늘어난다
JSON 모드 유효한 JSON만 나오게 제약 문법만 보장 스키마(필드 이름, 타입)는 보장하지 않는다. OpenAI가 2023년 11월 도입[3]
스키마 기반 제약 디코딩 JSON 스키마를 문법으로 바꿔 디코딩 단계마다 강제 스키마까지 보장 상용 API의 "구조화된 출력"이 이것이다
정규식·문법 기반 제약 디코딩 정규 표현식, EBNF, GBNF 같은 임의 문법 문법까지 보장 SQL, 코드, 도메인 전용 언어에도 쓸 수 있다

원리: 로짓 마스킹

편집 원본 편집

형식을 받아들이는 오토마타가 지금까지 생성한 토큰을 읽고 상태 st에 있다고 하자. 그 상태에서 이어서 나와도 형식을 깨지 않는 토큰 집합을 𝒜(st)라 하면, 다음 토큰 분포를 다음처럼 바꾼다.

p′(v∣x<t)=p(v∣x<t)𝟏[v∈𝒜(st)]∑u∈𝒜(st)p(u∣x<t)

구현에서는 허용되지 않는 토큰의 로짓을 −∞로 바꾸는 비트 마스크를 만든다. 그다음은 보통의 샘플링이나 탐욕 디코딩과 같다. 어려운 점은 두 가지다.

  • 어휘가 10만 개를 넘으므로 매 스텝 모든 토큰을 오토마타에 넣어 보면 느리다. 단순한 방법은 토큰마다 O(N)(N은 어휘 크기)의 비용이 든다.
  • 토큰 경계와 문법 기호가 맞지 않는다. 토큰 하나가 ", "나 "}]처럼 여러 문법 기호에 걸치거나, 문자열 중간에서 끝날 수 있다.

정규 표현식: 유한 상태 기계 색인

편집 원본 편집

Outlines 논문(2023)은 정규 표현식을 유한 상태 기계(FSM)로 바꾸고, 미리 "FSM의 각 상태에서 출발해 끝까지 읽을 수 있는 어휘 토큰 목록"을 계산해 색인으로 만들어 두었다. 생성 중에는 현재 상태로 색인을 찾기만 하면 되므로 스텝당 비용이 평균 O(1)이다.[4] 토큰 하나가 FSM 전이를 여러 번 일으켜도 색인을 만들 때 다 따라가 보므로 토큰 경계 문제가 해결된다. JSON 스키마도 깊이가 정해져 있으면 정규 표현식으로 펼칠 수 있다.

문맥 자유 문법: 푸시다운 오토마타

편집 원본 편집

FSM은 중첩 깊이에 제한이 없는 괄호나 재귀 스키마를 표현하지 못한다. OpenAI는 구조화된 출력을 발표하며 JSON 스키마를 문맥 자유 문법(CFG)으로 바꿔 쓴다고 밝혔고, 자식 요소가 루트 스키마를 다시 참조하는 재귀 UI 스키마를 예로 들었다.[5] CFG는 스택을 가진 푸시다운 오토마타(PDA)로 실행하는데, 스택 상태가 무한히 많을 수 있어 FSM처럼 상태별 마스크를 모두 미리 계산할 수 없다.

XGrammar(2024)는 어휘를 두 부류로 나눠 이 문제를 줄였다.[6]

  • 문맥 독립 토큰: 현재 문법 위치(스택 꼭대기)만 보고 허용 여부가 정해지는 토큰. 대부분이 여기 속하며, 위치별로 미리 계산해 적응형 토큰 마스크 캐시(adaptive token mask cache)에 저장한다.
  • 문맥 의존 토큰: 스택 아래쪽까지 봐야 하는 토큰. 실행 중에만 검사하며, 영속(persistent) 스택 자료 구조로 분기·되돌리기를 빠르게 한다.
  • 문법 마스크 계산을 GPU의 모델 실행과 겹쳐(overlap) 돌린다. 기존 구현 대비 최대 100배 빠르고, 서빙 엔진에 붙였을 때 구조화 생성의 추가 비용이 거의 없다고 보고했다.

문법 표기 예: GBNF

편집 원본 편집

llama.cpp는 BNF를 확장한 GBNF로 문법을 받는다.[7] 다음은 "예" 또는 "아니오"와 1~5 사이 점수만 허용하는 문법이다.

root   ::= answer ", 점수: " score
answer ::= "예" | "아니오"
score  ::= [1-5]
공급자 지정 방법 특징·제한
OpenAI 응답 형식은 response_format에 {"type": "json_schema", "json_schema": {..., "strict": true}}, 도구는 함수 정의에 "strict": true 2024년 8월 6일 발표. 복잡한 스키마 평가에서 gpt-4o-2024-08-06+구조화된 출력이 100%, gpt-4-0613이 40% 미만. 스키마를 CFG로 전처리하므로 새 스키마의 첫 요청은 보통 10초 이내, 복잡하면 1분까지 걸린다. 모델이 안전상 거절하면 refusal 필드로 알려 준다. 병렬 함수 호출과는 함께 쓸 수 없다[5]
앤트로픽 응답은 output_config.format에 {"type": "json_schema", "schema": ...}, 도구는 "strict": true 2025년 11월 베타(베타 헤더 structured-outputs-2025-11-13)를 거쳐 정식 기능이 되었다. 컴파일한 문법은 마지막 사용 후 24시간 캐시된다. 재귀 스키마, minimum·maxLength 같은 수치·길이 제약은 지원하지 않고, 객체는 additionalProperties: false여야 한다. 형식 안내용 시스템 프롬프트가 자동으로 붙어 입력 토큰이 약간 늘어난다[8]
구글 응답 형식에 MIME 타입 application/json과 JSON 스키마 지정. SDK에서는 Pydantic(파이썬), Zod(자바스크립트)로 스키마 정의 추출, 분류, 에이전트 도구 입력 생성 용도를 안내[9]

세 공급자 모두 JSON 스키마의 일부만 지원하고, 지원 범위는 공급자마다 다르다. 같은 스키마를 여러 공급자에 쓰려면 가장 좁은 공통 부분집합으로 맞추는 편이 안전하다.

예시: OpenAI 호환 API(vLLM 서버)

편집 원본 편집

vLLM은 OpenAI와 같은 response_format을 받는다. 그 밖의 형식(정규식, 선택지, 문법)은 extra_body의 structured_outputs로 준다. 옛 guided_json 등의 인자는 v0.12.0에서 제거되었다.[10]

from openai import OpenAI
from pydantic import BaseModel

class Review(BaseModel):
    sentiment: str      # "긍정" | "부정" | "중립"
    score: int
    keywords: list[str]

client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
resp = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=[{"role": "user", "content":
               "다음 리뷰를 분석해 JSON으로 답하라: 배송은 느렸지만 품질은 훌륭함"}],
    response_format={
        "type": "json_schema",
        "json_schema": {"name": "review", "schema": Review.model_json_schema()},
    },
)
review = Review.model_validate_json(resp.choices[0].message.content)

# 정규 표현식이나 선택지 제약은 extra_body로
resp2 = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=[{"role": "user", "content": "이 리뷰의 감성은?"}],
    extra_body={"structured_outputs": {"choice": ["긍정", "부정", "중립"]}},
)

예시: 앤트로픽 메시지 API

편집 원본 편집
{
  "model": "모델 ID",
  "max_tokens": 1024,
  "messages": [{"role": "user", "content": "이 이메일에서 이름과 이메일 주소를 추출하라: ..."}],
  "output_config": {
    "format": {
      "type": "json_schema",
      "schema": {
        "type": "object",
        "properties": {
          "name": {"type": "string"},
          "email": {"type": "string"}
        },
        "required": ["name", "email"],
        "additionalProperties": false
      }
    }
  }
}

오픈소스 엔진

편집 원본 편집
엔진 방식 쓰이는 곳
Outlines 정규식·JSON 스키마를 FSM 색인으로. 문법도 지원 독립 파이썬 라이브러리. vLLM에서도 백엔드로 고를 수 있다
XGrammar CFG를 푸시다운 오토마타로 실행, 문맥 독립 토큰 마스크 캐시 2024년 11~12월 MLC-LLM, SGLang, vLLM에, 2025년 1월 TensorRT-LLM에 통합[11]
llguidance(guidance) 토큰 단위로 문법을 빠르게 검사하는 엔진 vLLM의 guidance 백엔드
lm-format-enforcer 파이썬 정규식 기반 문자 단위 검사 vLLM 백엔드 중 하나
llama.cpp GBNF BNF 확장 문법, JSON 스키마를 GBNF로 변환 llama.cpp 서버·CLI, 이를 쓰는 로컬 도구

vLLM은 백엔드를 auto로 두면 요청 내용에 맞는 것을 고른다.[10]

  • 형식은 보장해도 내용은 보장하지 않는다. 스키마에 맞는 거짓 값(환각)이 얼마든지 나올 수 있다. 값 검증은 따로 해야 한다.
  • 분포 왜곡: 마스킹은 모델이 원래 쓰려던 토큰 대신 형식에 맞는 차선 토큰을 강요한다. 모델이 형식에 익숙하지 않으면 이상한 값으로 채우거나 문자열을 끝없이 늘이기도 한다. 그래서 공급자들은 형식을 지시하는 프롬프트를 함께 넣고(앤트로픽은 자동 삽입), vLLM 문서도 프롬프트에 스키마와 필드 작성 방법을 적으면 결과가 좋아진다고 권한다.[10]
  • 추론 품질: 2024년 연구는 JSON 같은 형식 제약을 걸면 LLM의 추론 성능이 떨어지고, 제약이 엄격할수록 하락이 크다고 보고했다.[12] 추론이 필요한 작업은 자유 형식으로 먼저 생각하게 한 뒤 결과만 구조화하거나, 스키마에서 근거 필드를 답 필드보다 앞에 두는 식으로 보완한다. 다만 앤트로픽은 단계별 추론을 담는 필드가 거절을 부를 수 있으니 짧은 설명만 요청하라고 안내한다.[8]
  • 잘림: 최대 토큰에 걸려 생성이 끊기면 JSON도 끊긴다. 종료 이유(finish_reason, stop_reason)를 확인해야 한다.
  • 지연: 새 스키마는 문법 컴파일 시간이 들고, 스키마가 복잡할수록(선택 필드, 합집합 타입이 많을수록) 컴파일 비용과 실패 가능성이 커진다. 앤트로픽은 컴파일 시간 제한을 180초로 둔다.[8]
  • 캐시: 응답 형식 지정을 바꾸면 그 앞의 프롬프트 캐시가 무효가 될 수 있다(형식 안내가 프롬프트 앞부분에 들어가기 때문).[8]
  1. ↑ 구조화된 출력, Claude API 문서(한국어).
  2. ↑ 구조화된 출력, Gemini API 문서(한국어).
  3. ↑ New models and developer products announced at DevDay, OpenAI, 2023-11-06.
  4. ↑ Brandon T. Willard, Rémi Louf, 〈Efficient Guided Generation for Large Language Models〉, 2023. arXiv:2307.09702
  5. ↑ 5.0 5.1 Introducing Structured Outputs in the API, OpenAI, 2024-08-06.
  6. ↑ Yixin Dong 외, 〈XGrammar: Flexible and Efficient Structured Generation Engine for Large Language Models〉, MLSys 2025. arXiv:2411.15100
  7. ↑ GBNF Guide, llama.cpp, GitHub.
  8. ↑ 8.0 8.1 8.2 8.3 Structured outputs, Claude API Docs.
  9. ↑ Structured outputs, Gemini API Docs.
  10. ↑ 10.0 10.1 10.2 Structured Outputs, vLLM Documentation.
  11. ↑ mlc-ai/xgrammar, GitHub.
  12. ↑ Zhi Rui Tam 외, 〈Let Me Speak Freely? A Study on the Impact of Format Restrictions on Performance of Large Language Models〉, 2024. arXiv:2408.02442