Techsy
문의하기
시작하기
블로그로 돌아가기
ai-machine-learning

어떤 LLM에서도 안정적인 JSON 추출: 2026년 Pydantic + Zod 패턴

작성자 Mert Batur Gürbüz
수정일 May 12, 2026
12 분 읽기
목차
어떤 LLM에서도 안정적인 JSON 추출: 2026년 Pydantic + Zod 패턴

**LLM 구조화된 출력(structured output)**은 언어 모델의 응답이 미리 정의된 스키마를 준수하도록 보장하는 메커니즘입니다. 단순히 유효한 JSON을 넘어, 사용자가 지정한 정확한 필드, 타입 및 제약 조건을 갖춘 스키마 유효(schema-valid) JSON을 보장합니다. 모든 주요 제공자가 이제 이를 네이티브로 지원하며, 이는 프로덕션 LLM 애플리케이션이 구축되는 방식을 변화시켰습니다.

빠른 요약: 한눈에 보는 구조화된 출력

시간이 부족하다면 2026년의 현황은 다음과 같습니다:

측면세부 사항
정의LLM으로부터 스키마가 강제된 응답, "최선의 노력"이 아닌 보장된 구조
지원 업체OpenAI, Anthropic, Gemini, Cohere, xAI(Grok), plus Ollama/vLLM을 통한 로컬 환경
핵심 메커니즘제약 디코딩(constrained decoding), 샘플링 전에 유효하지 않은 토큰 마스킹
JSON 모드 vs 엄격 모드JSON 모드 = 유효한 구문만 보장. 엄격 모드 = 전체 스키마 준수
Python 라이브러리스키마 정의를 위한 Pydantic (BaseModel + Field)
TypeScript 라이브러리스키마 정의를 위한 Zod (z.object + .describe)
추천 시작 방법네이티브 SDK를 통해 Pydantic 또는 Zod와 함께 OpenAI 사용
추천 프로덕션 라이브러리Instructor (Python) 또는 네이티브 SDK (TypeScript)
가장 흔한 함정답변 필드 뒤에 추론(reasoning) 필드를 배치하면, 모델이 생각하기 전에 결정함
지연 시간 오버헤드첫 호출 시 50-200ms (스키마 컴파일), 이후 캐싱됨

이제 각 부분을 자세히 살펴보겠습니다.

LLM 구조화된 출력이란 무엇인가?

구조화된 출력은 LLM이 유효한 JSON을 반환하기를 희망하는 것과 그것을 보장하는 것의 차이입니다. 구조화된 출력을 활성화하면 모델은 물리적으로 스키마를 위반하는 토큰을 생성할 수 없습니다. JSON 스키마(또는 Pydantic 모델, Zod 스키마)를 정의하여 API에 전달하면, 매번 일치하는 응답을 받게 됩니다.

왜 이것이 중요할까요? 구조화된 출력 이전에는 개발자들이 취약한 정규식 파서를 작성하고, 모든 LLM 호출을 try/catch JSON.parse 블록으로 감싸며, 여전히 "거의 맞는" 응답(필드가 누락되거나 타입이 잘못된 유효한 JSON)을 처리해야 했습니다. 이러한 버그 클래스는 이제 사라졌습니다.

구조 강제에는 세 가지 수준이 있으며, 이는 명확한 진화를 나타냅니다:

  1. 프롬프트 엔지니어링, "이 필드로 JSON을 반환해주세요." 신뢰할 수 없습니다. 모델은 80-90%의 확률로 따를 뿐입니다.
  2. JSON 모드, 구문적으로 유효한 JSON을 보장하지만 스키마는 강제하지 않습니다. {"name": string, "age": number}를 기대했을 때 {"foo": "bar"}를 받을 수 있습니다.
  3. 엄격 모드 / 제약 디코딩, 100% 스키마 준수를 보장합니다. 모델은 문자 그대로 유효하지 않은 토큰을 출력할 수 없습니다. 이것이 2026년에서의 "구조화된 출력"이 의미하는 바입니다.

2026년 초 기준, OpenAI, Anthropic, 그리고 Google Gemini 모두 네이티브 구조화된 출력을 지원합니다. 생태계가 수렴되었습니다.

결론: 프로덕션에서 정규식이나 JSON.parse로 LLM 응답을 파싱하고 있다면, 어려운 길을 가고 있는 것입니다. 네이티브 구조화된 출력은 이러한 실패 모드를 완전히 제거합니다.

JSON 모드 vs 엄격 모드: 실제로 무엇이 달라졌나?

이 구분은 이름이 비슷해 보이기 때문에 많은 개발자들을 혼란스럽게 합니다. 하지만 둘은 다릅니다.

기능JSON 모드엄격 모드 (구조화된 출력)
API 파라미터type: "json_object"strict: true인 type: "json_schema"
유효한 JSON 보장예예
스키마 준수 보장아니오예
메커니즘사후 토큰 바이어스제약 디코딩 (FSM)
예상치 못한 필드 반환 가능예아니오
필수 필드 누락 가능예아니오
타입 강제없음완전함 (string, number, array 등)
사용 시기사전에 스키마가 없을 때프로덕션의 모든 경우

타임라인: OpenAI는 2023년 말에 JSON 모드를 도입했습니다. 이는 진전이었지만, 개발자들은 곧 "유효한 JSON"만으로는 충분하지 않으며 스키마 유효 JSON이 필요하다는 것을 깨달았습니다. 2024년 8월, OpenAI는 스키마 준수를 보장하기 위해 제약 디코딩을 사용하는 Strict Mode와 함께 구조화된 출력을 출시했습니다. 2025-2026년까지 모든 주요 제공자가 동일한 접근 방식을 채택했습니다.

JSON 모드는 여전히 좁은 용도가 있습니다: 응답의 형태를 미리 알 수 없고 비구조화된 탐색을 위해 어떤 유효한 JSON이든 원할 때입니다. 하지만 프로덕션에서는 드문 경우입니다.

결론: 프로덕션의 모든 것에 엄격 모드를 사용하세요. 스키마 기반 사용 사례에서 JSON 모드는 사실상 폐기되었습니다. 스키마가 있다면(그리고 있어야 합니다), strict: true와 함께 type: "json_schema"를 사용하세요.

제약 디코딩은 실제로 어떻게 작동하는가?

99.9%가 아니라 문자 그대로 100% 스키마 준수를 가능하게 하는 메커니즘은 다음과 같습니다.

Strict Mode가 활성화된 상태에서 제공자에게 JSON 스키마를 보내면, 스키마는 **유한 상태 기계(FSM, finite state machine)**로 컴파일됩니다. 이 FSM은 스키마를 통한 모든 유효한 경로를 나타냅니다. 각 토큰 생성 단계에서 추론 엔진은 어떤 토큰이 출력을 유효한 경로로 유지하고 어떤 토큰이 그렇지 않은지 확인합니다. 유효하지 않은 토큰은 샘플링 전에 로짓(logit)이 음의 무한대로 설정되므로, 선택될 확률이 0이 됩니다.

<!-- IMAGE: constrained-decoding diagram showing FSM token masking during structured output generation -->

강력해진 자동 완성이라고 생각하면 됩니다. 모델이 방금 {"rating":을 출력했고 스키마에서 rating이 정수라고 명시했다면, 다음에 허용되는 토큰은 숫자 토큰뿐입니다. 따옴표, 문자, 괄호 등은 모두 마스킹됩니다. 모델이 "원한다" 해도 "five"를 출력할 수 없습니다.

이는 XGrammar(vLLM, SGLang 및 대부분의 로컬 추론 서버 뒤의 엔진)와 Outlines(제약 생성을 위한 오픈 소스 Python 라이브러리)에서 사용되는 동일한 핵심 메커니즘입니다. API 제공자는 이를 추론 인프라에 통합했을 뿐입니다.

알아두어야 할 trade-off가 하나 있습니다: 새로운 스키마를 사용한 첫 요청은 FSM이 빌드되는 동안 컴파일 지연 시간 손실(일반적으로 50-200ms)이 발생합니다. 동일한 스키마를 사용한 후속 요청은 캐시된 FSM을 사용하며 오버헤드가 거의 0에 가깝습니다. 또한 미묘한 품질 고려 사항이 있는데, 토큰 어휘를 제한하면 창의적이거나 자유 형식의 필드에서 출력 품질이 가끔 감소할 수 있으므로, 스키마는 진정한 구조화된 데이터에 집중하세요.

결론: 제약 디코딩은 "대부분 작동"과 "항상 작동"을 구분합니다. 이것이 구조화된 출력을 프로덕션 준비 상태로 만드는 엔지니어링입니다.

다중 제공자 구현: OpenAI, Anthropic 및 Gemini

다른 가이드에서는 보여주지 않는 내용이 있습니다: 세 가지 주요 제공자 전반에 걸쳐 구현된 동일한 추출 작업입니다. 비구조화된 텍스트에서 구조화된 제품 리뷰를 추출해 보겠습니다.

모든 제공자에서 공유되는 Pydantic 스키마:

python
from pydantic import BaseModel, Field
from typing import Literal

class ProductReview(BaseModel):
    reasoning: str = Field(description="Think through the review before scoring")
    rating: int = Field(description="Rating from 1-5", ge=1, le=5)
    sentiment: Literal["positive", "negative", "neutral"]
    pros: list[str] = Field(description="Key positive points")
    cons: list[str] = Field(description="Key negative points")
    summary: str = Field(description="One-sentence summary")

OpenAI 구현

python
from openai import OpenAI

client = OpenAI()

response = client.beta.chat.completions.parse(
    model="gpt-4o-2024-08-06",
    messages=[
        {"role": "system", "content": "Extract a structured review from the text."},
        {"role": "user", "content": review_text}
    ],
    response_format=ProductReview,  # Pydantic model directly
)

review = response.choices[0].message.parsed  # Typed ProductReview object

OpenAI의 구현은 가장 성숙합니다. parse() 메서드는 Pydantic 모델을 직접 받아 타입이 지정된 객체를 반환합니다. 한 가지 제약 사항: OpenAI의 Strict Mode는 JSON 스키마의 하위 집합만 지원합니다. $ref 없음, 제한된 anyOf, 그리고 모든 필드는 additionalProperties: false와 함께 필수여야 합니다.

Anthropic 구현

python
from anthropic import Anthropic

client = Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": f"Extract a structured review:\n\n{review_text}"}
    ],
    output_config={
        "format": {
            "type": "json_schema",
            "json_schema": ProductReview.model_json_schema()
        }
    }
)

import json
review_data = json.loads(response.content[0].text)
review = ProductReview(**review_data)

Anthropic의 네이티브 구조화된 출력은 JSON 스키마와 함께 output_config.format을 사용합니다. 2026년 초 GA(일반 공개)에 도달했습니다. Anthropic은 또한 "가짜" 도구를 정의하고 tool_use를 통해 추출하는 이전 패턴도 지원하는데, 이는 여전히 작동하지만 순수 추출을 위해서는 네이티브 구조화된 출력이 더 깔끔합니다.

Gemini 구현

python
from google import genai

client = genai.Client()

response = client.models.generate_content(
    model="gemini-2.0-flash",
    contents=f"Extract a structured review:\n\n{review_text}",
    config={
        "response_mime_type": "application/json",
        "response_schema": ProductReview,  # Pydantic model directly
    }
)

import json
review = ProductReview(**json.loads(response.text))

Gemini는 Python SDK에서 response_schema를 통해 Pydantic 모델을 직접 지원합니다. 독특한 기능: Gemini는 스키마의 propertyOrdering을 존중하므로, 필드 출력 순서를 제어할 수 있습니다(추론 우선 패턴에 유용).

제공자 비교

기능OpenAIAnthropicGemini
API 파라미터response_formatoutput_config.formatresponse_schema
스키마 입력Pydantic 또는 JSON 스키마JSON 스키마Pydantic 또는 JSON 스키마
엄격 모드strict: truejson_schema와 함께 암시적암시적
스트리밍예 (부분 JSON)예예
거부 처리message.refusal 필드오류 응답오류 응답
도구 사용 대안예예 (원래 방법)예
스키마 컴파일 캐시예 (서버 측)예예
속성 순서네이티브 지원 안 함아니오예 (propertyOrdering)

결론: OpenAI는 parse() 메서드로 가장 세련된 개발자 경험(DX)을 제공합니다. Anthropic은 가장 유능한 기본 모델을 제공합니다. Gemini의 속성 순서 지정은 독특하게 유용합니다. 세 곳 모두 작업을 완료하므로, 기존 제공자 관계에 따라 선택하세요.

Python 개발자를 위한 Pydantic 패턴

Pydantic은 Python에서 구조화된 출력 스키마를 정의하는 사실상의 표준입니다. 중요한 패턴들은 다음과 같습니다.

설명이 포함된 기본 스키마

python
from pydantic import BaseModel, Field
from typing import Literal, Optional

class ExtractedEntity(BaseModel):
    reasoning: str = Field(description="Think step by step about the entity")
    name: str = Field(description="Full name of the entity")
    entity_type: Literal["person", "company", "location"]
    confidence: float = Field(description="Confidence score 0.0-1.0", ge=0.0, le=1.0)
    context: Optional[str] = Field(description="Surrounding context, if relevant")

那些 description 문자열은 문서화용만이 아닙니다. 이들은 모델에 전송되는 JSON 스키마의 일부가 되어 모델이 생성하는 내용에 직접적인 영향을 미칩니다. 이를 스키마 내부의 프롬프트 엔지니어링이라고 생각하세요.

중첩 모델

python
class Address(BaseModel):
    street: str
    city: str
    country: str
    postal_code: Optional[str] = None

class Company(BaseModel):
    reasoning: str = Field(description="Analysis of the company details")
    name: str
    industry: Literal["tech", "finance", "healthcare", "retail", "other"]
    headquarters: Address  # Nested model
    key_products: list[str] = Field(description="Top 3 products or services")

중첩은 최대 2-3단계로 유지하세요. 깊게 중첩된 스키마는 오류율을 높이고 스키마 컴파일을 느리게 합니다.

추론 우선(Reasoning-First) 패턴

これは単一の 가장 영향력 있는 스키마 설계 패턴입니다. 답변 필드 앞에 reasoning 필드를 배치하세요:

python
# Reliable JSON from Any LLM: Pydantic + Zod Patterns for 2026
class ClassificationBad(BaseModel):
    category: Literal["spam", "ham"]
    confidence: float

# Good -- model reasons through the problem first
class ClassificationGood(BaseModel):
    reasoning: str = Field(description="Analyze the text before classifying")
    category: Literal["spam", "ham"]
    confidence: float = Field(ge=0.0, le=1.0)

LLM은 토큰을 왼쪽에서 오른쪽으로 생성합니다. category가 먼저 오면 모델은 카테고리를 선택한 후 이를 합리화합니다. reasoning이 먼저 오면 모델은 문제를 해결한 후 카테고리에commit합니다. 이는 스키마에 내장된 연쇄 사고(chain-of-thought)입니다.

JSON 스키마 export

python
# Generate the JSON Schema for any Pydantic model
schema = ProductReview.model_json_schema()
# Pass this to any provider that accepts raw JSON Schema

결론: Pydantic + 설명 필드 + 추론 우선 순서는 Python 구조화된 출력의 삼각편대입니다. 이 세 가지 패턴을 마스터하면 사용 사례의 90%를 처리할 수 있습니다.

TypeScript 개발자를 위한 Zod 패턴

Zod는 Pydantic의 TypeScript 버전이며, 구조화된 출력 워크플로우에서 마찬가지로 중심적입니다.

설명이 포함된 기본 스키마

typescript
import { z } from "zod";

const ProductReview = z.object({
  reasoning: z.string().describe("Think through the review before scoring"),
  rating: z.number().int().min(1).max(5),
  sentiment: z.enum(["positive", "negative", "neutral"]),
  pros: z.array(z.string()).describe("Key positive points"),
  cons: z.array(z.string()).describe("Key negative points"),
  summary: z.string().describe("One-sentence summary"),
});

// Infer the TypeScript type automatically
type ProductReview = z.infer<typeof ProductReview>;

Pydantic의 Field(description=...)와 마찬가지로, Zod의 .describe()는 JSON 스키마의 일부가 되어 모델의 출력을 안내합니다.

OpenAI Node SDK와의 통합

typescript
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";

const client = new OpenAI();

const response = await client.beta.chat.completions.parse({
  model: "gpt-4o-2024-08-06",
  messages: [
    { role: "system", content: "Extract a structured review." },
    { role: "user", content: reviewText },
  ],
  response_format: zodResponseFormat(ProductReview, "product_review"),
});

const review = response.choices[0].message.parsed; // Typed!

Vercel AI SDK와의 통합

typescript
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";

const { object: review } = await generateObject({
  model: openai("gpt-4o"),
  schema: ProductReview,
  prompt: `Extract a structured review:\n\n${reviewText}`,
});
// review is fully typed as ProductReview

Vercel AI SDK는 generateObject()와 함께 Zod를 네이티브로 사용하여 가장 깔끔한 TypeScript 통합을 제공합니다. 통합 API를 통해 OpenAI, Anthropic, Gemini 및 기타 제공자와 함께 작동합니다.

JSON 스키마 변환

typescript
import { zodToJsonSchema } from "zod-to-json-schema";

const jsonSchema = zodToJsonSchema(ProductReview);
// Use with any provider that accepts raw JSON Schema

결론: Zod + .describe() + Vercel AI SDK는 TypeScript 구조화된 출력 스택입니다. Node/Next.js 생태계에 있다면 이것이 최소 저항의 경로입니다.

구조화된 출력 vs 함수 호출: 언제 각각 사용하는가?

이는 가장 흔한 혼란의 원인 중 하나입니다. 둘 다 스키마를 포함하고 구조화된 데이터를 반환하지만, 서로 다른 문제를 해결합니다.

구조화된 출력은 말합니다: "데이터를 이 정확한 형태로 주세요." 이는 추출, 분류 및 포맷팅을 위한 것입니다. 비구조화된 텍스트에서 구조화된 정보를 끌어내는 것입니다.

함수 호출(도구 사용)은 말합니다: "다음은 수행할 수 있는 작업들입니다. 어떤 것을 실행할지 결정하고 인수를 제공하세요." 이는 모델이 여러 도구 중에서 선택하고 작업을 트리거하는 에이전트 워크플로우를 위한 것입니다.

역사적으로 혼란이 생긴 것은 이해갑니다. Anthropic의 원래 "구조화된 출력"은 문자 그대로 함수 호출이었습니다. extract_review라는 가짜 도구를 정의하고 인수를 가져오는 방식이었습니다. 이는 여전히 작동하지만, 순수 추출을 위해서는 네이티브 구조화된 출력이 더 간단합니다.

시나리오최선의 접근법이유
텍스트에서 데이터 추출구조화된 출력직접적, 낮은 지연 시간, 단일 스키마
카테고리 분류구조화된 출력하나의 응답, 하나의 스키마
에이전트가 호출할 도구 결정함수 호출모델이 여러 도구 중 선택
다단계 오케스트레이션함수 호출순차적 도구 호출
데이터 추출 AND 다음 작업 결정둘 다추출에는 구조화된 출력, 오케스트레이션에는 함수 호출

구조화된 출력은 AI 에이전트 시스템의 도구 호출 파이프라인을 구동합니다. 이러한 것이 프로덕션 워크플로우에 어떻게 적합하는지 알아보려면 비즈니스용 AI 에이전트 가이드를 참조하세요.

결론: 데이터의 형태를 알고 있을 때는 구조화된 출력을 사용하세요. 모델이 작업을 선택해야 할 때는 함수 호출을 사용하세요. 실제로 대부분의 애플리케이션은 둘 다 사용합니다. 데이터 추출에는 구조화된 출력, 에이전트 오케스트레이션에는 함수 호출을 사용합니다.

프로덕션 패턴: 오류, 재시도 및 스트리밍

데모에서 구조화된 출력을 작동시키는 것은 쉽습니다. 프로덕션에서 신뢰성을 유지하려면 거부, 검증 실패 및 스트리밍이라는 세 가지 사항을 처리해야 합니다.

거부 처리

때로는 모델이 요청한 출력을 생성하는 것을 거부합니다. 일반적으로 안전 필터가 입력을 플래그했기 때문입니다. 이때 구조화된 출력 API는 스키마를 반환하지 않습니다. 거부를 반환합니다.

python
response = client.beta.chat.completions.parse(
    model="gpt-4o-2024-08-06",
    messages=messages,
    response_format=ProductReview,
)

# ALWAYS check for refusal before accessing parsed content
if response.choices[0].message.refusal:
    print(f"Model refused: {response.choices[0].message.refusal}")
else:
    review = response.choices[0].message.parsed

거부 확인을 건너뛰고 거부 상태에서 .parsed에 액세스하려고 하면 None과 혼란스러운 하류 오류가 발생합니다. 항상 먼저 확인하세요.

검증 피드백을 통한 재시도 패턴

스키마 준수는 제약 디코딩에 의해 보장되지만, semantic 정확성은 보장되지 않습니다. 모델은 {"rating": 1, "sentiment": "positive"}를 반환할 수 있습니다. 유효한 스키마이지만 내용이 모순됩니다. 이때 검증 + 재시도가 필요합니다.

python
import instructor

client = instructor.from_openai(OpenAI())

# Instructor handles retries automatically
review = client.chat.completions.create(
    model="gpt-4o",
    response_model=ProductReview,
    max_retries=3,  # Retries with validation error feedback
    messages=[
        {"role": "user", "content": review_text}
    ],
)

Instructor는 재시도 시 검증 오류를 모델에 다시 피드백하여 자체 수정할 수 있게 합니다. Instructor 없이.manual 재시도 패턴을 위한 코드:

python
from pydantic import ValidationError

for attempt in range(3):
    try:
        response = client.beta.chat.completions.parse(
            model="gpt-4o-2024-08-06",
            messages=messages,
            response_format=ProductReview,
        )
        review = response.choices[0].message.parsed
        # Run additional semantic validation here
        break
    except ValidationError as e:
        messages.append({"role": "assistant", "content": str(response)})
        messages.append({"role": "user", "content": f"Validation error: {e}. Fix it."})

구조화된 출력 스트리밍

큰 구조화된 응답, 긴 배열, 많은 필드, 복잡한 중첩 객체의 경우, 스트리밍을 통해 부분 결과를 점진적으로 렌더링할 수 있습니다.

python
import instructor

client = instructor.from_openai(OpenAI())

# Stream partial results as fields populate
review_stream = client.chat.completions.create_partial(
    model="gpt-4o",
    response_model=ProductReview,
    messages=[{"role": "user", "content": review_text}],
)

for partial_review in review_stream:
    # Fields populate one by one as tokens stream in
    if partial_review.summary:
        print(f"Summary so far: {partial_review.summary}")

주의할 점: 개별 스트리밍 청크는 자체적으로 스키마 유효하지 않습니다. rating이 아직 None인 동안 reasoning 필드가 채워질 수 있습니다. 이에 맞춰 UI를 계획하고, 채워지지 않은 필드에 대해 로딩 상태를 표시하세요.

결론: 거부 확인은 필수입니다. 검증 피드백을 통한 재시도는 semantic 오류를 catch합니다. 몇 초 이상かかる 응답에는 스트리밍이 가치 있습니다.

구조화된 출력 라이브러리 비교

네이티브 API를 통해 구조화된 출력을 사용할 수 있지만, 라이브러리는 검증, 재시도, 스트리밍 및 다중 제공자 지원을 추가합니다. 현황은 다음과 같습니다.

**Instructor**는 11K+ GitHub 스타와 월 3M+ 다운로드로 가장 인기 있는 옵션입니다. OpenAI, Anthropic, Gemini, Cohere, Ollama 등을 통합된 Pydantic 기반 인터페이스로 감쌉니다. 주요 기능: 검증 피드백을 통한 자동 재시도, create_partial()을 통한 스트리밍, 그리고 매우 간단한 설정(instructor.from_openai(client)). Python 팀이라면 여기서 시작하세요.

**BAML**은 다른 접근 방식을 취합니다: custom DSL을 통한 스키마 우선. .baml 파일에서 스키마를 정의하고 Python, TypeScript, Ruby 등에 대한 클라이언트를 자동 생성합니다. SAP(스키마 정렬 파싱) 알고리즘은 지저분한 모델 출력을 우아하게 처리합니다. 크로스 언어 팀이나 LLM 레이어와 애플리케이션 레이어 간의 계약이 필요할 때 가장 좋습니다. Trade-off: 추가 빌드 단계와 학습해야 할 새로운 문법.

LangChain은 제공자 독립적인 구조화된 출력을 위해 .with_structured_output(schema)를 제공합니다. 이미 LangChain 생태계에 있다면 편리합니다. Trade-off: 무거운 의존성이며, 추상화가 필요한 제공자별 기능을 숨길 수 있습니다.

네이티브 API, response_format / output_config를 사용한 직접 호출은 제공자 SDK 외에는 제로 의존성이 필요합니다. 완전한 제어와 가시성을 얻습니다. 간단한 사용 사례나 최소한의 추상화를 선호하는 팀에 가장 적합합니다.

라이브러리언어제공자자동 재시도스트리밍GitHub 스타학습 곡선
InstructorPython, TS15+예예11K+낮음
BAMLPython, TS, Ruby, GoAll (DSL-agnostic)예예7K+중간
LangChainPython, TS20+부분예100K+중간-높음
네이티브 APIAnySDK당 1개아니오예N/A낮음

올바른 구조화된 출력 라이브러리 선택은 더 넓은 AI 스택 결정의 일부입니다. 전체 스택을 분석한 내용은 SaaS를 위한 최고의 AI 스택 가이드에서 확인할 수 있습니다.

Instructor, BAML, Mirascope 등의 심층 비교를 담은 LLM 구조화된 출력을 위한 최고의 라이브러리 [곧 공개]를 참조하세요.

결론: Python에는 Instructor, TypeScript에는 네이티브 API로 시작하세요. 크로스 언어 스키마 계약이 필요하면 BAML로 이동하세요. 구조화된 출력만을 위해 LangChain은 사용하지 마세요. 과잉입니다.

스키마 설계 모범 사례 (및 일반적인 실수)

스키마 설계는 출력 품질에 직접적인 영향을 미칩니다. 중요한 패턴과 정확도를 떨어뜨리는 실수들은 다음과 같습니다.

답변 앞에 추론 배치

Pydantic 섹션에서 다루었지만, 가장 영향력 있는 설계 결정이므로 반복합니다:

python
# Before: model guesses the answer, then rationalizes
class Bad(BaseModel):
    answer: str
    reasoning: str

# After: model thinks first, then commits
class Good(BaseModel):
    reasoning: str = Field(description="Think step by step")
    answer: str

LLM은 왼쪽에서 오른쪽으로 생성합니다. 필드 순서는 프롬프트 순서입니다. 추론이 먼저이면 모델은 답변에 commit하기 전에 문제를 해결해야 합니다.

안티패턴 테이블

실수문제점해결책
답변 뒤에 추론 필드 배치모델이 생각하기 전에 결정추론을 답변 앞으로 이동
깊은 중첩 (4+ 레벨)높은 오류율, 느린 컴파일2-3 레벨로 평탄화
필드 설명 없음모델이 의도를 추측.describe() / Field(description=...) 추가
null 처리 누락모델이 필드를 채우기 위해 환각 발생Optional / .nullable() 사용
과도하게 큰 스키마 (50+ 필드)컴파일 타임아웃, 품질 저하여러 호출로 분할
모호한 enum 옵션모델이 잘못된 카테고리 선택구체적이고 중복되지 않는 옵션 사용

Null을 명시적으로 처리

소스 텍스트에 데이터가 없을 수 있는 필드는 선택적으로 만드세요. 데이터가 없을 때 필수 필드를 강요하면 환각(hallucination)으로 이어집니다:

python
class PersonInfo(BaseModel):
    name: str  # Always present
    email: Optional[str] = Field(None, description="Email if mentioned, null otherwise")
    phone: Optional[str] = Field(None, description="Phone if mentioned, null otherwise")

스키마를 집중적으로 유지

작업당 하나의 스키마. 하나의 거대한 스키마로 모든 것을 추출하려고 하지 마세요. 50개 이상의 필드가 필요하면 여러 추출 호출로 분할하세요. OpenAI의 Strict Mode에는 스키마 복잡성에 대한 실제 제한이 있으며, 작동하더라도 매우 큰 스키마는 출력 품질을 저하시킵니다.

결론: 추론 우선, 설명 필드, 명시적 nulls, 그리고 집중된 스키마. 이 네 가지를 올바르게 수행하면 구조화된 출력 정확도가 측정 가능하게 향상됩니다.

로컬 LLM과의 구조화된 출력

구조화된 출력을 위해 API 제공자가 필요하지 않습니다. 로컬 추론 엔진은 자체 하드웨어에서 실행되는 동일한 근본 메커니즘인 문법 기반 제약 디코딩을 통해 이를 지원합니다.

Ollama

로컬 구조화된 출력을 위한 가장 쉬운 경로입니다. Ollama는 format 파라미터를 통해 JSON 스키마를 허용합니다:

python
import ollama
from pydantic import BaseModel

class Country(BaseModel):
    name: str
    capital: str
    languages: list[str]

response = ollama.chat(
    model="llama3.2",
    messages=[{"role": "user", "content": "Tell me about Japan."}],
    format=Country.model_json_schema(),
)

import json
country = Country(**json.loads(response.message.content))

Ollama는 내부적으로 제약 디코딩을 위해 XGrammar를 사용합니다. API 제공자와 동일한 보장: 100% 스키마 준수.

vLLM 및 SGLang

프로덕션 등급 로컬 추론을 위해, vLLM과 SGLang은 모두 guided_json 및 guided_regex 파라미터를 통해 구조화된 출력을 지원합니다. XGrammar는 기본 백엔드로, JSON 생성에서 거의 제로 오버헤드를 제공하며 대체 문법 엔진보다 최대 3.5배 빠릅니다.

Outlines

Outlines는 문법 기반 제약 생성을 개척한 오픈 소스 Python 라이브러리입니다. 모든 Hugging Face 모델과 함께 작동하며 JSON 스키마, 정규식 및 전체 문맥 자유 문법(CFG/EBNF) 제약을 지원합니다. 또한 vLLM 및 SGLang에 문법 백엔드 옵션으로 통합되어 있습니다.

API 제공자와의 주요 차이점: 로컬 구조화된 출력에는 스키마 하위 집합 제한이 없습니다. 문법을 완전히 제어합니다. 하지만 모델 품질은 더 다양합니다. 7B 파라미터 로컬 모델은 복잡한 추출 작업에서 GPT-4o나 Claude와 일치하지 않습니다. 스키마는 항상 유효하지만; 콘텐츠 품질은 모델에 따라 다릅니다.

결론: 개발에는 Ollama, 프로덕션에는 XGrammar와 함께 vLLM/SGLang. 로컬 구조화된 출력은 대부분의 사용 사례에 충분히 성숙했지만, 더 작은 모델은 스키마 내에서 더 낮은 품질의 콘텐츠를 생성한다는 점은 유의하세요.

FAQ

LLM에서 구조화된 출력이란 무엇인가?

구조화된 출력은 LLM의 응답이 미리 정의된 JSON 스키마를 준수하도록 보장하는 메커니즘입니다. 일반 텍스트나 심지어 JSON 모드와 달리, 구조화된 출력은 제약 디코딩을 사용하여 스키마의 모든 필드, 타입 및 제약 조건이 충족되도록 합니다 -- "대부분"이 아닌 100%의 시간 동안.

JSON 모드와 구조화된 출력의 차이점은 무엇인가?

JSON 모드는 구문적으로 유효한 JSON을 보장하지만 스키마는 강제하지 않습니다. 어떤 유효한 JSON 객체라도 받을 수 있습니다. 구조화된 출력(Strict Mode)은 제약 디코딩을 통해 전체 스키마 준수를 보장합니다. 프로덕션에는 Strict Mode를 사용하세요; JSON 모드는 사전에 스키마가 없을 때만 관련이 있습니다.

어떤 LLM 제공자가 네이티브로 구조화된 출력을 지원하는가?

OpenAI(2024년 8월 이후), Google Gemini(2024년, 2026년 확장), Anthropic(2025년 11월 베타, 2026년 초 GA), Cohere 및 xAI(Grok)는 모두 네이티브 구조화된 출력을 지원합니다. 로컬 측면에서는 Ollama, vLLM 및 SGLang이 문법 기반 제약 디코딩을 통해 이를 지원합니다.

제약 디코딩은 어떻게 스키마 준수를 보장하는가?

JSON 스키마는 유한 상태 기계(FSM)로 컴파일됩니다. 각 토큰 생성 단계에서 FSM을 통한 유효한 경로로 출력을 유지하는 토큰만 허용되며, 유효하지 않은 토큰의 로짓은 음의 무한대로 설정됩니다. 이는 유효하지 않은 토큰이 생성될 확률이 0임을 의미하며, 통계적 보장이 아닌 수학적 보장을 제공합니다.

구조화된 출력과 함수 호출 중 무엇을 사용해야 하는가?

특정 형태의 데이터를 원할 때 추출 및 분류에는 구조화된 출력을 사용하세요. 모델이 수행할 작업을 결정해야 할 때 에이전트 워크플로우에는 함수 호출을 사용하세요. 많은 프로덕션 애플리케이션은 둘 다 사용합니다. 데이터 추출에는 구조화된 출력, 오케스트레이션에는 함수 호출.

구조화된 출력을 스트리밍할 수 있는가?

예. OpenAI는 parse() 메서드로 스트리밍을 지원하며, Instructor는 필드별로 채워지는 Pydantic 모델을 스트리밍하기 위해 create_partial()을 제공합니다. 개별 스트리밍 청크는 개별적으로 스키마 유효하지 않으며, 필드는 점진적으로 채워진다는 점을 명심하세요.

Instructor 라이브러리는 무엇인가?

Instructor는 가장 인기 있는 구조화된 출력 라이브러리입니다(11K+ GitHub 스타, 월 3M+ 다운로드). Pydantic 기반 검증, 검증 피드백을 통한 자동 재시도 및 스트리밍 지원으로 제공자 SDK를 감쌉니다. OpenAI, Anthropic, Gemini, Cohere, Ollama 및 10개 이상의 다른 제공자와 함께 작동합니다.

구조화된 출력이 로컬 LLM에서 작동하는가?

예. Ollama는 JSON 스키마와 함께 format 파라미터를 통해 구조화된 출력을 지원합니다. vLLM과 SGLang은 guided_json 파라미터를 통해 이를 지원합니다. 세 곳 모두 제약 디코딩을 위해 XGrammar 또는 Outlines를 사용합니다. 스키마 준수 보장은 API 제공자와 동일합니다; 콘텐츠 품질은 모델에 따라 다릅니다.

일반적인 스키마 설계 실수는 무엇인가?

주요 실수: 추론 필드를 답변 필드 뒤에 배치(모델이 생각하기 전에 결정), 깊게 중첩된 스키마(4+ 레벨은 오류 증가), 필드 설명 누락(모델이 의도 추측), 선택적 데이터에 대한 null 처리 없음(환각 강제), 과도하게 큰 스키마(50+ 필드는 품질 저하).

구조화된 출력이 지연 시간을 추가하는가?

첫 요청에는 스키마 컴파일 오버헤드가 있으며, 일반적으로 FSM이 빌드되는 동안 50-200ms입니다. 동일한 스키마를 사용한 후속 요청은 캐시된 FSM을 사용하며 지연 시간이 거의 0에 가깝습니다. 대부분의 애플리케이션에서 이는 전체 모델 추론 시간에 비해 무시할 수 있습니다.

구조화된 출력을 이미지或多模态 입력과 함께 사용할 수 있는가?

예. 구조화된 출력은 입력이 아닌 응답 형식에 적용됩니다. 구조화된 출력 스키마와 함께 GPT-4o나 Gemini에 이미지를 보내면 이미지의 스키마 준수 분석을 받을 수 있습니다. 이는 영수증, 양식 또는 제품 이미지에서 구조화된 데이터를 추출하는 시각적 추출 워크플로우에 강력합니다.

출처

  • OpenAI 구조화된 출력 가이드
  • Anthropic 도구 사용 문서
  • Google Gemini 구조화된 출력
  • Instructor 라이브러리 문서
  • BAML 문서
  • Pydantic 문서
  • Zod 문서
  • Outlines 라이브러리
  • XGrammar GitHub
  • Ollama 구조화된 출력
  • Vercel AI SDK

태그

llm 구조화된 출력structured outputsjson schemapydanticzodopenaianthropicgemini

이 기사 공유하기

관련 글

더 많은 글 보기 ai-machine-learning

ai-machine-learning
Jul 24, 2026

Claude Opus 5 출시: Fable 5에 근접한 지능, 가격은 절반

Anthropic이 2026년 7월 24일 Claude Opus 5를 출시했습니다. Frontier-Bench에서 Opus 4.8을 두 배 이상 앞서면서도 Opus 가격을 유지하지만, 일부 테스트에서는 Fable 5와 Mythos 5에 뒤처집니다. 벤치마크 표, 가격, 전환/대기/유지 판단을 정리했습니다.

10 min read 분 읽기
읽어보기
ai-machine-learning
Jul 20, 2026

2026년 최고의 AI 웹 스크래핑 API 8선 (자체 에이전트 스택으로 직접 테스트)

자체 에이전트 스택으로 실제 2026년 요금을 확인하며 AI 웹 스크래핑 API 8종을 테스트했습니다. Firecrawl, Bright Data, ScrapingBee 외 5종을 LLM 최적화 출력, 안티봇, MCP 지원 기준으로 순위를 매겼습니다.

9 min read 분 읽기
읽어보기
ai-machine-learning
Jul 20, 2026

코딩을 위한 프롬프트 엔지니어링: Claude Code와 Cursor에서 매일 사용하는 7가지 패턴 (2026)

대부분의 'AI 코딩 프롬프트' 글은 복사해서 쓸 수 있는 템플릿 50개를 제시합니다. 이 글은 우리가 16개 에이전트 Claude Code 파이프라인을 운영하는 데 매일 사용하는 7가지 패턴을 가르쳐주며, 각 패턴별 실제 Before/After 예시와 2026년 기준 Claude Code, Cursor, Copilot에서 각 패턴이 어떻게 적용되는지 설명합니다.

11 min read 분 읽기
읽어보기
모든 글 보기
프로젝트 시작하기

새로운 것을 만들 준비가 되었다면 특별함은?

여러분의 비전을 현실로 만들어 보세요. 차이를 만드는 소프트웨어, 우리 팀이 함께 만들겠습니다.

30분 스코핑 미팅 예약프로젝트 보기

라이브러리에서 인기 있는 도구

Claude 스킬

전체 보기
  • New Post

    Full SEO blog pipeline: research, brief, write, validate, image, translate, publish to Sanity. Autonomous from start to finish.

  • Content Refresh

    Audit a stale post, find decay drivers, and ship a SERP-aligned refresh without losing existing rankings.

  • SEO Audit

    Site-wide SEO audit with prioritized fix list: technical, on-page, and EEAT signals.

AI 자동화

전체 보기
  • Security Auditor

    Weekly SCA + IaC scan with prioritized fix PRs.

  • Cold Email Writer

    Generates first-touch emails grounded in one specific public detail.

  • Lead Research Agent

    Enrich an email into a profile, score fit, alert in Slack.

라이브러리에서 인기 있는 도구

Claude 스킬

전체 보기
  • New Post

    Full SEO blog pipeline: research, brief, write, validate, image, translate, publish to Sanity. Autonomous from start to finish.

  • Content Refresh

    Audit a stale post, find decay drivers, and ship a SERP-aligned refresh without losing existing rankings.

  • SEO Audit

    Site-wide SEO audit with prioritized fix list: technical, on-page, and EEAT signals.

AI 자동화

전체 보기
  • Security Auditor

    Weekly SCA + IaC scan with prioritized fix PRs.

  • Cold Email Writer

    Generates first-touch emails grounded in one specific public detail.

  • Lead Research Agent

    Enrich an email into a profile, score fit, alert in Slack.

서비스

  • 엔터프라이즈 솔루션
  • 모바일 앱
  • 웹 애플리케이션

솔루션

  • CRM 시스템
  • AI 통합
  • ERP 솔루션
  • 음성 에이전트
  • 프로세스 자동화
  • 사이버 보안

라이브러리

  • 블로그
  • 포트폴리오

커뮤니티

  • AI 자동화
  • Claude 스킬

도구

  • 모바일 앱 비용 계산기
  • OpenAI / LLM API 비용 계산기
  • MVP 비용 계산기
  • 음성 AI 에이전트 비용 계산기

회사 소개

  • 소개
  • 파트너
  • 문의하기

법적 고지사항

  • 개인정보 처리방침
  • 서비스 약관
  • 쿠키 정책

서비스

  • 엔터프라이즈 솔루션
  • 모바일 앱
  • 웹 애플리케이션

솔루션

  • CRM 시스템
  • AI 통합
  • ERP 솔루션
  • 음성 에이전트
  • 프로세스 자동화
  • 사이버 보안

라이브러리

  • 블로그
  • 포트폴리오

커뮤니티

  • AI 자동화
  • Claude 스킬

도구

  • 모바일 앱 비용 계산기
  • OpenAI / LLM API 비용 계산기
  • MVP 비용 계산기
  • 음성 AI 에이전트 비용 계산기

회사 소개

  • 소개
  • 파트너
  • 문의하기
법적 고지사항개인정보 처리방침서비스 약관쿠키 정책
TECHSY
© 2026 Techsy. 무단전재 및 재배포 금지.