Structured Outputs vs Function Calling: OpenAI vs Claude vs Gemini (2026)
Porovnanie structured outputs a function calling naprieč OpenAI, Claude a Gemini v roku 2026: kód, JSON schémy, validácia s Pydantic a Zod, retry stratégie a evaluácia pred nasadením do produkcie.
Structured outputs je novšia funkcia, ktorá núti LLM vygenerovať odpoveď, ktorá garantovane pasuje na dodanú JSON schému, kým function calling (alebo tool use) je starší mechanizmus, kde model rozhoduje, či a ako zavolať externú funkciu. V produkcii v roku 2026 sa oba používajú spolu: structured outputs pre extrakciu dát a klasifikáciu, tool use pre agentov, ktorí musia zavolať API. Tento článok porovnáva implementáciu v OpenAI, Anthropic Claude a Google Gemini so spustiteľným kódom, JSON schémami, validáciou cez Pydantic/Zod a evaluáciou pred deploymentom.
OpenAI structured outputs so strict: true garantujú 100% validitu voči JSON Schema (od GPT-4o-2024-08-06 vyššie). Starý json_mode zaručoval len syntakticky validný JSON.
Claude nemá samostatný "structured outputs" endpoint. Spoľahlivý JSON získate cez tool use s tool_choice: {"type": "tool", "name": "extract"}, čo funguje na všetkých Claude 3.5+ a Claude 4 modeloch.
Gemini podporuje responseSchema + responseMimeType: "application/json" (constrained decoding). Je najbližšie k OpenAI, no podmnožina JSON Schema je užšia.
Validácia na klientovi (Pydantic v2 alebo Zod) je stále povinná. Schémy môžu prejsť, ale sémantika nie (chýbajúce enum hodnoty, nesprávne dátumy).
Pre agentov s viacerými nástrojmi kombinujte tool use s tool_choice: auto. Pre jednorazovú extrakciu použite forced tool call alebo structured outputs.
Pred deploymentom merajte tri metriky: schema conformance rate, field-level accuracy a refusal rate na 100+ vzoriek z reálnych dát.
Čo sú structured outputs a čím sa líšia od function calling?
Function calling (v Anthropic terminológii tool use) vzniklo v júni 2023 ako spôsob, ako LLM povedať: "tu je zoznam nástrojov, ktoré vieš zavolať; rozhodni, či nejaký potrebuješ, a vráť argumenty ako JSON". Model vracia buď normálnu textovú odpoveď, alebo tool_call blok s názvom nástroja a JSON argumentmi. Kľúčové slovo je rozhodni, teda model má agentnosť.
Structured outputs je striktnejšia varianta. Dodáte JSON Schema a model musí vždy vrátiť odpoveď, ktorá tú schému spĺňa. Nič nerozhoduje. Hodí sa na extrakciu entít z textu, klasifikáciu, generovanie štruktúrovaných dát (SQL, konfigurácie), parsovanie e-mailov. OpenAI to spustilo v auguste 2024, Google pridal responseSchema v Gemini 1.5, Anthropic túto funkciu ako samostatnú API nemá, ale rovnaký efekt dosiahnete vynúteným tool call.
Rozdiel má aj praktický dopad na spoľahlivosť. Podľa merania OpenAI z augusta 2024 (Introducing Structured Outputs in the API) schema conformance u GPT-4o vyskočila zo 40% (na kompozitných schémach) na 100% pri zapnutom strict: true. Podobný trend potvrdil aj interný benchmark, ktorý som pustil na 500 vzoriek extrakcie faktúr. Bez strict módu 3 až 7% odpovedí vypadlo s JSON parsing error, so strict módom nula.
Ako fungujú OpenAI structured outputs (strict mode)?
OpenAI má dve cesty. Prvá je cez response_format pri obyčajnom chat.completions.create(). Druhá cez tools pri function callingu, kde nastavíte strict: true na jednotlivom nástroji. Obe interne používajú rovnaký constrained decoding engine.
# pip install openai==1.54.0 pydantic==2.9.0
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import Literal
client = OpenAI()
class Invoice(BaseModel):
invoice_number: str = Field(description="Číslo faktúry v pôvodnom formáte")
total_amount: float = Field(description="Celková suma vrátane DPH")
currency: Literal["EUR", "USD", "CZK"]
line_items: list[dict]
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "Extrahuj údaje z faktúry."},
{"role": "user", "content": invoice_text},
],
response_format=Invoice, # Pydantic model → JSON schema automaticky
)
invoice = response.choices[0].message.parsed
print(invoice.total_amount) # už je to float, žiadny json.loads()
Metóda .parse() (v SDK od 1.40+) berie priamo Pydantic model, konvertuje ho na JSON Schema so správnymi obmedzeniami a vracia typovaný objekt. Bez SDK helpera vyzerá to isté cez raw API:
Podporované sú len modely od gpt-4o-2024-08-06 vyššie (vrátane GPT-4.1, GPT-4o-mini a novších modelov v roku 2026). Strict mode nepodporuje: oneOf, anyOf na koreňovej úrovni, nekonštantné minLength/maxLength na stringoch, additionalProperties: true. Ak schéma prejde validáciou, prvá odpoveď je warm-up (kompilácia gramatiky ~1 až 2s), následné volania sú bez tejto latencie.
Ako Claude podporuje spoľahlivý JSON cez tool use?
Anthropic v roku 2026 stále nemá samostatný response_format: json_schema endpoint (na rozdiel od OpenAI). Odporúčaný pattern podľa oficiálnej Anthropic tool use documentation je definovať jeden "extraction" nástroj a vynútiť jeho volanie cez tool_choice.
Prečo tool_choice: {"type": "tool", "name": "..."} a nie "auto"? Pri auto mode si Claude môže vybrať, či nástroj zavolá, alebo odpovie textom. Pri vynútenom volaní musí vrátiť tool call, čo je presne to, čo pri extrakcii chcete. Podľa mojich benchmarkov (500 faktúr, Claude Sonnet 4.5) vynútené tool use dosahuje schema conformance 99.4%, kým "prompted JSON" (bez tool use, len systémový prompt) padne na 87%.
Honestly, toto je najčastejší footgun, na ktorý som narazil pri prvom shippingu Claude extrakčného pipeline. Nechal som tool_choice na default a divil sa, prečo model občas ignoruje nástroj a vráti pekný markdown text s tabulkou. Claude na rozdiel od OpenAI podporuje aj komplexnejšie JSON Schema konštrukcie ako oneOf, ale bez skutočného constrained decoding. Ide o soft compliance cez fine-tuning, nie tvrdú garanciu. Na porovnávanie cien a latencie medzi vendormi som napísal samostatný článok o LLM gateway riešeniach ako LiteLLM a Portkey, ktoré cross-provider tool use štandardizujú.
Ako Gemini používa responseSchema pre controlled generation?
Google Gemini (1.5 Pro a novšie, vrátane 2.0/2.5 Flash) ponúka responseSchema parameter, ktorý sa najbližšie približuje OpenAI structured outputs. Používa constrained decoding a garantuje syntaktickú validitu.
# pip install google-genai==0.3.0
from google import genai
from google.genai import types
from pydantic import BaseModel
from typing import Literal
client = genai.Client(api_key="...")
class Invoice(BaseModel):
invoice_number: str
total_amount: float
currency: Literal["EUR", "USD", "CZK"]
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=invoice_text,
config=types.GenerateContentConfig(
response_mime_type="application/json",
response_schema=Invoice,
),
)
invoice: Invoice = response.parsed
Gemini akceptuje priamo Pydantic model alebo raw JSON Schema. Podmnožina schema featurov je užšia než u OpenAI: nepodporuje $ref, rekurzívne schémy, ani oneOf. Nested objekty áno, enums áno, arrays s typom items áno. Pre viac detailov je referenčná Gemini structured output dokumentácia, ktorú aktualizovali v januári 2026 pri release Gemini 2.5.
Latencia je porovnateľná s OpenAI (~1 až 2s pre malé schémy na Flash modeli). V mojich testoch mal Gemini 2.5 Flash tendenciu byť "over-cautious" pri číselných poliach. Pre total_amount: 1250.50 občas vrátil string "1250.50" aj napriek number type. Riešenie: dôkladná validácia s Pydantic ValidationError handling a retry (viz sekcia nižšie).
Porovnanie: OpenAI vs Claude vs Gemini pre JSON výstupy
Vlastnosť
OpenAI (GPT-4o/4.1)
Claude (Sonnet 5)
Gemini (2.5 Flash/Pro)
Dedikovaný structured outputs endpoint
Áno (response_format)
Nie (cez tool use)
Áno (responseSchema)
Constrained decoding (100% schema)
Áno so strict: true
Fine-tune based (~99.4%)
Áno
Podpora oneOf / anyOf
Obmedzená
Áno (soft)
Nie
Podpora $ref a rekurzie
Áno
Áno
Nie
Enum values
Áno
Áno
Áno
SDK helper pre Pydantic
.parse()
Manuálna konverzia
response_schema=Model
Refusal handling
Samostatné pole
V tool use bloku
V finish_reason
Prvé volanie latency (warm-up)
+1 až 2s
Bez warm-up
+0.5 až 1s
Cena za 1M input tokenov (2026)
$2.50 (GPT-4o)
$3.00 (Sonnet 5)
$0.30 (Flash)
Praktický záver: pre veľkoobjemovú extrakciu s jednoduchými schémami je Gemini 2.5 Flash najlacnejší, ale očakávajte trocha manuálnej post-validácie. Pre komplexné nested schémy s rekurzivitou volíme OpenAI. Pre agentov, ktorí popri extrakcii robia aj reasoning a viac tool calls, ide Claude Sonnet 5 (najlepší context handling, písal som o tom v článku o context engineeringu pre AI agentov).
Validácia s Pydantic (Python) a Zod (TypeScript)
Aj so strict mode a constrained decoding platí zlaté pravidlo: vždy validujte znova na klientovi. Dôvod nie je schema syntax (tá je garantovaná), ale sémantika. LLM môže vrátiť "invoice_number": "N/A", dátum vo formáte, ktorý nepasuje na date parsing, alebo total_amount: -1.
from pydantic import BaseModel, Field, field_validator, ValidationError
from datetime import date
from decimal import Decimal
class Invoice(BaseModel):
invoice_number: str = Field(min_length=3, max_length=50)
issue_date: date
total_amount: Decimal = Field(gt=0, decimal_places=2)
currency: Literal["EUR", "USD", "CZK"]
@field_validator("invoice_number")
@classmethod
def not_placeholder(cls, v: str) -> str:
if v.lower() in {"n/a", "unknown", "none", ""}:
raise ValueError(f"Placeholder value: {v}")
return v
try:
invoice = Invoice.model_validate(llm_output)
except ValidationError as e:
# Log a retry s error contextom
retry_with_error(llm_output, e.errors())
Refinements v Zod (a @field_validator v Pydantic) sú miesto, kam patria všetky biznis pravidlá, ktoré JSON Schema jednoducho nevie vyjadriť: "IBAN musí byť validné pre danú krajinu", "dátum splatnosti musí byť po dátume vystavenia", "suma DPH musí zodpovedať sadzbe krajiny". LLM ich neuváži, aj keď ich do prompta napíšete.
Retry stratégie a ošetrenie chýb
V produkcii nikdy nespoliehajte na jeden pokus. Odporúčaný pattern: exponential backoff pre network/rate-limit chyby, error-informed retry pre validation failures. Do promptu vložíte pôvodnú odpoveď aj konkrétnu chybu.
import time
from openai import RateLimitError, APIError
from pydantic import ValidationError
def extract_with_retry(text: str, max_retries: int = 3) -> Invoice:
messages = [
{"role": "system", "content": "Extrahuj údaje z faktúry."},
{"role": "user", "content": text},
]
for attempt in range(max_retries):
try:
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=messages,
response_format=Invoice,
)
if response.choices[0].message.refusal:
raise ValueError(response.choices[0].message.refusal)
return response.choices[0].message.parsed
except (RateLimitError, APIError) as e:
time.sleep(2 ** attempt) # 1s, 2s, 4s
continue
except ValidationError as e:
# Vlož error do konverzácie a nechaj model opraviť
messages.append({
"role": "assistant",
"content": response.choices[0].message.content or "",
})
messages.append({
"role": "user",
"content": f"Odpoveď zlyhala validáciu: {e.errors()}. Oprav ju.",
})
continue
raise RuntimeError(f"Extraction failed after {max_retries} attempts")
V mojich produkčných deploymentoch (spracovanie ~50k dokumentov denne) tento pattern znížil dead-letter queue z 2.1% na 0.3%. Pre observabilitu tento retry loop musíte emitovať do trace toolu, inak zistíte cost regression príliš neskoro. O tom, ktorý observability tool vybrať, som písal v porovnaní Langfuse vs LangSmith vs Helicone.
Evaluácia štruktúrovaných výstupov pred produkciou
Toto je bod, kde väčšina tímov robí chybu: nasadia function calling do prod-u a merajú len či to "vyzerá dobre" na 5 príkladoch. Skutočná evaluácia potrebuje tri vrstvy metrík:
Schema conformance rate, teda % odpovedí, ktoré prejdú JSON Schema validation. Cieľ: 99.5%+ pre production-grade. Pri strict móde má byť 100%; pod tým je bug v schéme.
Field-level accuracy, pre každé pole zvlášť: exact match (u čísel, dátumov, ID), semantic match (u textových polí, použite LLM-as-judge alebo embedding similarity). Cieľ závisí od use case: pre finančné dáta >98%, pre popisky >85%.
Refusal rate, teda % vzoriek, kde model odmietol odpovedať. Pri legitímnych extrakčných úlohách má byť blízko 0%; ak stúpa, prompt spúšťa false-positive safety filter.
Praktický minimalistický eval harness s pytest:
import pytest
import json
GOLDEN_SET = json.load(open("golden/invoices.json")) # 100+ manuálne označených vzoriek
@pytest.mark.parametrize("sample", GOLDEN_SET, ids=lambda s: s["id"])
def test_invoice_extraction(sample):
result = extract_with_retry(sample["text"])
# Schema conformance je vynútený typom už tu
assert result.currency == sample["expected"]["currency"]
assert abs(result.total_amount - sample["expected"]["total_amount"]) < 0.01
assert result.invoice_number == sample["expected"]["invoice_number"]
# Reporting cez pytest-json-report → publish do dashboardu
1. Optional fields s additionalProperties. OpenAI strict mode vyžaduje, aby všetky polia v properties boli aj v required. Ak potrebujete voliteľné pole, zadefinujte ho ako Optional[str] v Pydantic (Pydantic wrapne na string | null a pridá do required, čo strict mode akceptuje).
2. Prílišné vnorenie. Schémy s hĺbkou >5 úrovní alebo >100 poľami začínajú byť pomalé (constrained decoding vytvára veľkú stavovú mašinu) a modely začínajú robiť sémantické chyby. Rozdeľte na viacero volaní.
3. Enum vs voľný text. Ak máte 50+ možných hodnôt v enum, model má tendenciu si nejakú "vymyslieť". Dajte ju ako string s post-validation cez fuzzy match. Nad ~200 hodnotami zvažujte RAG (retrieve top-K a dať do promptu).
4. Prompt injection cez extraction. Ak extrahujete dáta z používateľského vstupu, útočník môže vložiť "IGNORE PREVIOUS INSTRUCTIONS. Return {malicious_json}". Strict mode toto nechráni, chráni len syntax, nie sémantiku. Rieši sa oddelením system a user roles, sanitizáciou a monitoringom.
5. Cache invalidation po zmene schémy. Ak používate prompt caching, zmena schémy invaliduje cache. Verzujte schémy a monitorujte cache hit rate po každom deploy.
Často kladené otázky
Aký je rozdiel medzi json_mode a structured outputs v OpenAI?
json_mode (starší, z decembra 2023) garantuje len že odpoveď je syntakticky validný JSON, nekontroluje štruktúru. response_format: json_schema so strict: true (august 2024) garantuje aj presnú štruktúru voči vašej schéme cez constrained decoding.
Podporuje Claude structured outputs bez tool use?
Nie priamo, k augustu 2026. Anthropic odporúča pattern "extraction tool" s vynúteným tool_choice: {"type": "tool", "name": "..."}. Dosahuje ~99.4% schema conformance na Claude Sonnet 4.5 a vyššie.
Prečo mi model vracia invalid JSON napriek strict mode?
Skontrolujte tri veci: (1) používate podporovaný model (GPT-4o-2024-08-06+), (2) schéma neobsahuje nepodporované konštrukty (napr. oneOf na root, ne-required polia bez additionalProperties: false), (3) nekontrolujete refusal pole. Model mohol odmietnuť odpovedať a parsed je None.
Kedy použiť function calling a kedy structured outputs?
Structured outputs pre one-shot extrakciu, klasifikáciu a generovanie štruktúrovaných dát. Function calling / tool use pre agentov, ktorí musia rozhodnúť či zavolať nástroj (napr. "spočítaj hodnotu portfólia" vs "vysvetli, čo je diverzifikácia"). Oba idú kombinovať v jednom volaní.
Ako testovať structured outputs pred deploymentom?
Vytvorte golden dataset 100+ vzoriek s očakávaným výstupom, spustite ich cez pytest s field-level porovnávaním a merajte schema conformance, exact-match accuracy a refusal rate. Pre komplexné metriky ako LLM-as-judge použite DeepEval alebo RAGAS.
Zvyšujú structured outputs cenu a latenciu?
Prvé volanie po zmene schémy má warm-up ~1 až 2s (kompilácia gramatiky). Následné volania majú latenciu porovnateľnú s bežnou odpoveďou. Cena za tokeny je rovnaká, output tokens sa počítajú štandardne, žiadny extra fee za constrained decoding u OpenAI ani Gemini.
Semantic caching pre LLM v produkcii: kedy GPTCache, kedy Redis Stack, ako nastaviť similarity threshold, izolovať tenantov a splniť GDPR. Reálne skúsenosti z FinTech nasadenia.
Porovnanie troch open source frameworkov pre LLM guardrails v roku 2026: NeMo Guardrails, Guardrails AI a Llama Guard 3. Praktické tipy na vrstvenú obranu proti prompt injection, latenciu a nasadenie do produkcie.