주요 LLM 제공사의 구조화된 출력 비교 - OpenAI, Gemini, Anthropic, Mistral 및 AWS Bedrock

약간 다른 API에는 특별한 접근 방식이 필요합니다.

Page content

다음은 인기 있는 LLM 제공업체 간의 구조화된 출력 (신뢰할 수 있는 JSON 응답) 지원 비교와 최소한의 Python 예제입니다.

colorised bars standing

이전에 Ollama에 호스팅된 LLM에서 구조화된 출력을 요청하는 방법을 살펴보았습니다. JSON이 전송되면, 서비스에서 작동하는 Python 기반 LLM 구조화된 출력 유효성 검사는 파싱, Pydantic 검사, 재시도 및 테스트 과정을 안내합니다. 여기서는 다른 제공업체들로부터 동일한 결과를 요청하는 방법을 검토해 보겠습니다.

TL;DR 비교 표

제공업체 네이티브 “JSON 모드” JSON 스키마 강제 적용 일반적인 설정 비고
OpenAI 지원 지원 (최상위 클래스) response_format={"type":"json_schema", ...} Responses API 또는 Chat Completions를 통해 작동하며, 함수 호출도 가능합니다.
Google Gemini 지원 지원 (최상위 클래스) response_schema= + response_mime_type="application/json" 스키마가 설정되면 엄격하게 검증된 JSON을 반환합니다.
Anthropic (Claude) 간접적 지원 (Tool Use를 통한 JSON 스키마) tools=[{input_schema=...}] + tool_choice 모델이 스키마로 정의된 도구를 “호출"하도록 강제하여 스키마 형식의 인수를 반환합니다.
Mistral 지원 부분적 (JSON만, 서버 측 스키마 없음) response_format={"type":"json_object"} JSON을 보장하지만, 클라이언트 측에서 스키마에 대해 유효성을 검사해야 합니다.
AWS Bedrock (플랫폼) 모델에 따라 다름 지원 (Tool/Converse 스키마를 통해) toolConfig.tools[].toolSpec.inputSchema Bedrock의 Converse API는 JSON 스키마에 대해 도구 입력을 검증합니다.

LLM 구조화된 출력 - 일반 정보

LLM 구조화된 출력은 대형 언어 모델(LLMs)이 자유 형식 텍스트를 생성하는 대신 미리 정의된 특정 형식 또는 구조를 엄격히 따르는 응답을 생성할 수 있는 능력을 의미합니다. 이러한 구조화된 출력은 JSON, XML, 표, 또는 템플릿과 같은 형식을 사용할 수 있으며, 데이터를 기계가 읽을 수 있고 일관되며 다양한 애플리케이션에서 소프트웨어가 쉽게 파싱할 수 있도록 합니다.

구조화된 출력은 일반적으로 개방형 자연어 텍스트를 생성하는 전통적인 LLM 출력과 다릅니다. 대신, 구조화된 출력은 정의된 키와 값 유형을 가진 JSON 객체 또는 출력 내 특정 클래스(예: 다중 선택 답변, 감정 클래스, 또는 데이터베이스 행 형식)와 같은 스키마나 형식을 강제 적용합니다. 이 접근법은 신뢰성을 향상시키고 오류 및 환상을 줄이며, 데이터베이스, API 또는 워크플로우와 같은 시스템과의 통합을 단순화합니다.

LLM에서 구조화된 출력을 생성하는 데에는 일반적으로 다음과 같은 기법들이 포함됩니다:

  • 모델이 원하는 형식으로 출력을 생성하도록 안내하기 위한 자세한 프롬프트 지침 지정.
  • Python의 Pydantic과 같은 유효성 검사 및 파싱 도구를 사용하여 출력이 스키마와 일치하도록 보장.
  • 문법 또는 유한 상태 기계를 기반으로 한 디코딩 제약 조건을 적용하여 형식에 대한 토큰 수준의 준수를 보장.

구조화된 LLM 출력의 이점은 다음과 같습니다:

  • 기계 가독성 및 통합 용이성.
  • 변동성 및 오류 감소.
  • 일관된 데이터 형식이 필요한 작업에 대한 예측 가능성 및 검증 가능성 향상.

도전 과제로는 효과적인 스키마 설계, 복잡한 중첩 데이터 처리, 그리고 자유 형식 텍스트 생성에 비해 추론 기능의 잠재적 제한 사항 등이 있습니다.

전반적으로, 구조화된 출력은 LLM이 인간이 읽기 쉬운 텍스트가 아닌 정확하고 형식화된 데이터를 필요로 하는 애플리케이션에서 더 유용하게 사용될 수 있게 합니다.

구조화된 출력 Python 예제

아래 모든 스니펫은 이벤트 정보를 JSON으로 추출합니다: {title, date, location}. 키와 모델은 필요에 따라 교체하세요.

1) OpenAI — JSON Schema (엄격한 방식)

from openai import OpenAI
import json

client = OpenAI()

schema = {
    "name": "Event",
    "schema": {
        "type": "object",
        "properties": {
            "title": {"type": "string"},
            "date":  {"type": "string", "description": "YYYY-MM-DD"},
            "location": {"type": "string"}
        },
        "required": ["title", "date", "location"],
        "additionalProperties": False
    },
    "strict": True
}

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "user", "content": "Extract the event from: 'PyData Sydney is on 2025-11-03 at Darling Harbour.'"}
    ],
    response_format={"type": "json_schema", "json_schema": schema},
)

data = json.loads(resp.choices[0].message.content)
print(data)

OpenAI의 Structured Outputs 기능은 서버 측에서 이 스키마를 강제 적용합니다.


2) Google Gemini — response schema + JSON MIME

import google.generativeai as genai
from google.genai import types

# API 키로 구성
# genai.configure(api_key="your-api-key")

schema = types.Schema(
    type=types.Type.OBJECT,
    properties={
        "title": types.Schema(type=types.Type.STRING),
        "date": types.Schema(type=types.Type.STRING),
        "location": types.Schema(type=types.Type.STRING),
    },
    required=["title", "date", "location"],
    additional_properties=False,
)

resp = genai.generate_content(
    model="gemini-2.0-flash",
    contents="Extract the event from: 'PyData Sydney is on 2025-11-03 at Darling Harbour.'",
    generation_config=genai.GenerationConfig(
        response_mime_type="application/json",
        response_schema=schema,
    ),
)

print(resp.text)  # 이미 스키마에 맞는 유효한 JSON

Gemini는 response_schema에 부합하는 엄격한 JSON을 반환합니다.


3) Anthropic (Claude) — JSON 스키마를 사용한 Tool Use

from anthropic import Anthropic
import json

client = Anthropic()

tool = {
    "name": "extract_event",
    "description": "Return event details.",
    "input_schema": {
        "type": "object",
        "properties": {
            "title": {"type": "string"},
            "date": {"type": "string"},
            "location": {"type": "string"}
        },
        "required": ["title", "date", "location"],
        "additionalProperties": False
    }
}

msg = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=256,
    tools=[tool],
    tool_choice={"type": "tool", "name": "extract_event"},  # 스키마 강제 적용
    messages=[{"role": "user", "content":
        "Extract the event from: 'PyData Sydney is on 2025-11-03 at Darling Harbour.'"}],
)

# Claude는 도구를 "호출"합니다. 인수를 가져오면 스키마와 일치합니다.
tool_use = next(b for b in msg.content if b.type == "tool_use")
print(json.dumps(tool_use.input, indent=2))

Claude에는 범용적인 “JSON 모드” 스위치가 없습니다. 대신, **input_schema**와 함께 Tool Use를 사용하면 유효성 검사된 스키마 형식의 인수를 얻을 수 있습니다(사용을 강제할 수도 있음).


4) Mistral — JSON 모드 (클라이언트 측 유효성 검사)

from mistralai import Mistral
import json

client = Mistral()

resp = client.chat.complete(
    model="mistral-large-latest",
    messages=[{"role":"user","content":
        "Return JSON with keys title, date (YYYY-MM-DD), location for: "
        "'PyData Sydney is on 2025-11-03 at Darling Harbour.'"}],
    response_format={"type": "json_object"}  # 유효한 JSON 보장
)

data = json.loads(resp.choices[0].message.content)
print(data)
# 팁: `data`를 로컬의 Pydantic/JSON 스키마에 대해 유효성 검사하세요.

Mistral의 json_objectJSON 형식(정확한 스키마가 아님)을 강제 적용합니다 — 클라이언트 측에서 유효성 검사하세요.


5) AWS Bedrock — Converse API Tool 스키마 (모델 독립적)

import boto3, json

bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")
model_id = "anthropic.claude-3-5-sonnet-20240620-v1:0"

tools = [{
    "toolSpec": {
        "name": "extract_event",
        "inputSchema": {
            "json": {
                "type": "object",
                "properties": {
                    "title": {"type": "string"},
                    "date": {"type": "string"},
                    "location": {"type": "string"}
                },
                "required": ["title","date","location"],
                "additionalProperties": False
            }
        }
    }
}]

resp = bedrock.converse(
    modelId=model_id,
    toolConfig={"tools": tools},
    toolChoice={"tool": {"name": "extract_event"}},  # 스키마 강제 적용
    messages=[{"role":"user","content":[{"text":
        "Extract the event from: 'PyData Sydney is on 2025-11-03 at Darling Harbour.'"}]}],
)

# toolUse 콘텐츠 추출
tool_use = next(
    c["toolUse"] for c in resp["output"]["message"]["content"] if "toolUse" in c
)
print(json.dumps(tool_use["input"], indent=2))

Bedrock은 JSON 스키마에 대해 도구 입력을 검증하며, 많은 호스팅 모델(예: Claude)이 Converse를 통해 이를 지원합니다.


실용적인 가이드 및 유효성 검사

  • 가장 강력한 서버 측 보장을 원한다면: OpenAI 구조화된 출력 또는 Gemini 응답 스키마 사용.
  • 이미 Claude/Bedrock을 사용하고 있다면: JSON 스키마가 포함된 Tool을 정의하고 사용을 강제하며, 도구의 arguments를 타입 지정된 객체로 읽으세요.
  • Mistral을 사용한다면: json_object를 활성화하고 로컬에서 유효성 검사(예: Pydantic 사용) 수행.

유효성 검사 패턴 (모든 제공업체에 적용 가능)

from pydantic import BaseModel, ValidationError

class Event(BaseModel):
    title: str
    date: str
    location: str

try:
    event = Event.model_validate(data)  # 어떤 제공업체에서든 `data` 수신
except ValidationError as e:
    # 처리 / 재시도 / e.errors()를 사용하여 모델 수정 요청
    print(e)

유용한 링크

구독하기

시스템, 인프라, AI 엔지니어링에 관한 새 글을 받아보세요.