Porównanie strukturalnych danych wyjściowych w popularnych dostawcach LLM – OpenAI, Gemini, Anthropic, Mistral i AWS Bedrock

Nieco odmienne interfejsy API wymagają specjalnego podejścia.

Page content

Oto porównanie obsługi strukturyzowanej odpowiedzi (uzyskiwanie niezawodnego JSONa) u popularnych dostawców modeli LLM, wraz z minimalnymi przykładami w języku Python

colorised bars standing

Już wcześniej przyglądaliśmy się temu, jak żądać strukturyzowanej odpowiedzi od modelu LLM hostowanego na Ollama. Gdy JSON wraca po sieci, artykuł o walidacji strukturyzowanej odpowiedzi LLM w Pythonie, która trzyma się schematu omija kwestie parsowania, sprawdzeń z Pydantic, ponownych prób i testów w Twojej usłudze. Tutaj przeglądamy, jak żądać tego samego od innych dostawców.

Macierz TL;DR

Dostawca Nativny „tryb JSON” Egzekucja Schematu JSON Typowa konfiguracja Uwagi
OpenAI Tak Tak (pierwszoklasowa) response_format={"type":"json_schema", ...} Działa poprzez Responses API lub Chat Completions; można też używać function calling.
Google Gemini Tak Tak (pierwszoklasowa) response_schema= + response_mime_type="application/json" Zwraca rygorystycznie zwalidowany JSON, gdy schemat jest ustawiony.
Anthropic (Claude) Niebezpośrednio Tak (poprzez Tool Use ze schematem JSON) tools=[{input_schema=...}] + tool_choice Wymusza na modelu „wywołanie” narzędzia zdefiniowanego schematem; zwraca argumenty zgodne ze schematem.
Mistral Tak Częściowa (tylko JSON; brak schematu po stronie serwera) response_format={"type":"json_object"} Zapewnia JSON, ale walidujesz go pod kątem schematu po stronie klienta.
AWS Bedrock (platforma) Zależy od modelu Tak (poprzez schemat Tool/Converse) toolConfig.tools[].toolSpec.inputSchema API Converse w Bedrock waliduje dane wejściowe narzędzia pod kątem schematu JSON.

Strukturyzowana odpowiedź LLM - Informacje ogólne

Strukturyzowana odpowiedź LLM odnosi się do możliwości dużych modeli językowych (LLMs) generowania odpowiedzi, które ściśle przestrzegają zdefiniowanego z góry formatu lub struktury, zamiast produkować tekst w formie swobodnej. Ta strukturyzowana odpowiedź może przybierać formaty takie jak JSON, XML, tabele lub szablony, co sprawia, że dane są czytelne dla maszyny, spójne i łatwo parsowalne przez oprogramowanie do użytku w różnych aplikacjach.

Strukturyzowane odpowiedzi różnią się od tradycyjnych odpowiedzi LLM, które typowo generują otwarty, naturalny tekst. Zamiast tego, strukturyzowane odpowiedzi egzekwują schemat lub format, taki jak obiekty JSON z zdefiniowanymi kluczami i typami wartości, lub konkretne klasy w odpowiedzi (np. odpowiedzi wielokrotnego wyboru, klasy sentymentu lub formaty wierszy bazy danych). To podejście poprawia niezawodność, redukuje błędy i halucynacje oraz upraszcza integrację z systemami takimi jak bazy danych, API lub przepływy pracy.

Generowanie strukturyzowanych odpowiedzi w LLM często obejmuje techniki takie jak:

  • Określanie szczegółowych instrukcji w prompcie, aby prowadzić model do wygenerowania odpowiedzi w pożądanym formacie.
  • Używanie narzędzi walidacji i parsowania, takich jak Pydantic w Python, aby zapewnić zgodność odpowiedzi ze schematem.
  • Czasem wymuszanie ograniczeń dekodowania opartych na gramatyce lub automatach skończonych, aby zapewnić zgodność na poziomie tokenów z formatem.

Korzyści ze strukturyzowanych odpowiedzi LLM obejmują:

  • Czytelność dla maszyny i łatwość integracji.
  • Zredukowaną zmienność i błędy.
  • Poprawioną przewidywalność i weryfikowalność w zadaniach wymagających spójnych formatów danych.

Wyzwania obejmują projektowanie skutecznych schematów, obsługę złożonych, zagnieżdżonych danych oraz potencjalne ograniczenia w zdolnościach rozumowania w porównaniu do generowania tekstu w formie swobodnej.

Ogólnie rzecz biorąc, strukturyzowana odpowiedź umożliwia LLMom bycie bardziej przydatnymi w aplikacjach wymagających precyzyjnych, sformatowanych danych, a nie tylko tekstu czytelnego dla człowieka.

Przykłady strukturyzowanej odpowiedzi w Pythonie

Wszystkie fragmenty kodu wydobywają informacje o wydarzeniu jako JSON: {title, date, location}. Zmień klucze/modeli według uznania.

1) OpenAI — Schemat JSON (rygorystyczny)

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)

Funkcja Structured Outputs OpenAI egzekwuje ten schemat po stronie serwera.


2) Google Gemini — schemat odpowiedzi + MIME JSON

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

# Configure with your API key
# 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)  # already valid JSON per schema

Gemini zwróci rygorystyczny JSON, który zgodny jest z response_schema.


3) Anthropic (Claude) — Używanie narzędzi (Tool Use) ze schematem JSON

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"},  # force schema
    messages=[{"role": "user", "content":
        "Extract the event from: 'PyData Sydney is on 2025-11-03 at Darling Harbour.'"}],
)

# Claude will "call" the tool; grab the args (which match your schema)
tool_use = next(b for b in msg.content if b.type == "tool_use")
print(json.dumps(tool_use.input, indent=2))

Claude nie ma generycznego przełącznika „trybu JSON”; zamiast tego Tool Use ze schematem input_schema daje Ci zwalidowane argumenty zgodne ze schematem (i możesz wymusić jego użycie).


4) Mistral — Tryb JSON (walidacja po stronie klienta)

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"}  # guarantees valid JSON
)

data = json.loads(resp.choices[0].message.content)
print(data)
# Tip: validate `data` against your Pydantic/JSON Schema locally.

json_object w Mistralu egzekwuje kształt JSONa (nie Twój dokładny schemat) — waliduj po stronie klienta.


5) AWS Bedrock — Schemat narzędzia w API Converse (niezależny od modelu)

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"}},  # force schema
    messages=[{"role":"user","content":[{"text":
        "Extract the event from: 'PyData Sydney is on 2025-11-03 at Darling Harbour.'"}]}],
)

# Pull toolUse content
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 waliduje dane wejściowe narzędzia pod kątem Twojego schematu JSON, a wiele hostowanych modeli (np. Claude) obsługuje je poprzez Converse.


Praktyczne wskazówki i walidacja

  • Jeśli chcesz najsilniejszych gwarancji po stronie serwera: strukturyzowane odpowiedzi OpenAI lub schemat odpowiedzi Gemini.
  • Jeśli korzystasz już z Claude/Bedrock: zdefiniuj Narzędzie ze schematem JSON i wymuś jego użycie; odczytaj argumenty narzędzia jako swój typowany obiekt.
  • Jeśli używasz Mistral: włącz json_object i waliduj lokalnie (np. z Pydantic).

Wzorzec walidacji (działa dla wszystkich)

from pydantic import BaseModel, ValidationError

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

try:
    event = Event.model_validate(data)  # `data` from any provider
except ValidationError as e:
    # handle / retry / ask model to fix with e.errors()
    print(e)

Przydatne linki

Subskrybuj

Otrzymuj nowe wpisy o systemach, infrastrukturze i inżynierii AI.