Validatie van gestructureerde LLM-output in Python die stand houdt
Stop met het interpreteren van vibes. Valideer contracten.
De meeste tutorials over “gestructureerde output” van GPT-modellen (LLM’s) zijn niet serieus. Ze leren je beleefd om JSON te vragen en hopen daarna dat het model zich gedraagt. Dat is geen validatie. Dat is optimisme met accolades.
De eigen documentatie van OpenAI maakt dit onderscheid expliciet. JSON-modus geeft je geldige JSON, terwijl gestructureerde output (Structured Outputs) naleving van het schema afdwingt en OpenAI aanbeveelt om, indien mogelijk, gestructureerde output in plaats van JSON-modus te gebruiken.

Dat maakt de payload nog niet betrouwbaar. JSON Schema definieert structuur en toegestane waarden, Pydantic biedt getypeerde validatie in Python, en OpenAI merkt expliciet op dat een schema-geldige response nog steeds onjuiste waarden kan bevatten. Daarnaast kunnen weigeringen en onvolledige outputs de verwachte vorm omzeilen. In productieomgevingen is validatie van gestructureerde output een pipeline, geen aan/uit-schakelaar. Dezelfde grens moet ook passen binnen het bredere verhaal van doorvoer, opnieuw proberen en scheduler-limieten op de hub voor LLM-performance engineering.
Validatie van gestructureerde output is een contract
Validatie van gestructureerde output voor LLM’s betekent dat je de vorm van het antwoord van tevoren definieert, het model zodanig beperkt dat het die vorm produceert (indien mogelijk) en het resultaat vervolgens opnieuw valideert voordat je toepassing het vertrouwt. In de praktijk betekent dit het controleren van verplichte velden, types, enums, gesloten objectstructuren en domeinregels voordat de payload je database, UI, wachtrij of downstream-service bereikt. JSON Schema bestaat precies voor dit soort structurele validatie, Pydantic is ontworpen om onbetrouwbare data te valideren tegen Python-typehints, en de Python-bibliotheek jsonschema biedt een directe manier om een instantie te valideren tegen een schema.
Er is ook een schone scheiding tussen twee veelvoorkomende gebruiksscenario’s. Als het model de gebruiker in een gestructureerd formaat moet antwoorden, gebruik dan een gestructureerd responsformaat. Als het model de tools of functies van je applicatie moet aanroepen, gebruik dan function calling. De documentatie van OpenAI schetst dit onderscheid en voor function calling raden ze aan strict: true in te schakelen, zodat de argumenten betrouwbaar voldoen aan het functieschema.
Mijn sterke mening is simpel. Behandel elke gestructureerde LLM-response als een API-grens. Zodra je begint te denken in termen van contracten in plaats van prompts, wordt de architectuur schoner, worden bugs goedkoper en verdwijnt het probleem “waarom verzon het model een nieuw veld in productie” grotendeels. Dat is het echte antwoord op “wat is validatie van gestructureerde output voor LLM’s” en het is een veel beter antwoord dan “vraag het model beleefd om JSON.”
JSON-modus is geen validatie
Als je maar één ding uit dit artikel onthoudt, laat het dan dit zijn. JSON-modus is geen schemavalidatie. Het Help Center van OpenAI zegt dat JSON-modus niet garandeert dat de output aan een specifiek schema voldoet, alleen dat het geldige JSON is en zonder fouten wordt geparsed. De gids voor gestructureerde output zegt hetzelfde, maar op een schonerere manier. Zowel JSON-modus als gestructureerde output kunnen geldige JSON produceren, maar alleen gestructureerde output dwingt schemanaleving af.
Dat verschil is belangrijker dan mensen toegeven. In hun lancering van gestructureerde output meldde OpenAI dat gpt-4o-2024-08-06 met gestructureerde output 100 procent scoorde op zijn complexe JSON-schema-evaluaties, terwijl gpt-4-0613 onder de 40 procent scoorde. Je hoeft die cijfers niet als universele waarheid te beschouwen om het bredere punt te zien. Schemanaleving verandert het faaloppervlak van “alles kan gebeuren” naar “het contract is veel strakker.”
Er zijn nog steeds uitzonderingsgevallen, en alsof dat niet zo is, veranderen speelgoeddemo’s in pager-duty. OpenAI documenteert dat het model een onveilige aanvraag kan weigeren, en deze weigeringen worden gepresenteerd buiten je normale schemapad. Het documenteert ook onvolledige responses, inclusief gevallen zoals het bereiken van max_output_tokens of een onderbreking door een contentfilter. Dus de FAQ “is JSON-modus voldoende voor betrouwbare LLM-output” heeft een kort en een lang antwoord. Het korte antwoord is nee. Het langere antwoord is dat zelfs strikte gestructureerde output expliciete afhandeling van fouten nodig heeft.
Waar gestructureerde output nog steeds faalt
Schemanaleving verkleint het probleem. Het verwijdert het niet. In echt verkeer zie je nog steeds gebroken of verrassende payloads om redenen die weinig met je prompt-formulering te maken hebben.
Faalvormen waarvoor je moet ontwerpen
Modellen en clients verschillen van mening over details. Je kunt extra proza krijgen voor of na de JSON, Markdown-gecodeerde blokken rondom de payload, of een tool-oproep waarvan de naam geldig is, maar waarvan de argumenten JSON zijn die niet overeenkomt met je Pydantic-model. Streaming maakt het erger omdat je een halfafgeronde buffer kunt valideren. Defensieve code moet uitgaan van “string in, misschien JSON erin” in plaats van “bytes op de lijn die al met mijn model overeenkomen.”
Verschillen tussen providers en API’s
Niet elke host exposeert hetzelfde gestructureerde output-oppervlak. De ene stack geeft je een eersteklas schema-gebonden voltooiing, de andere garandeert alleen JSON-syntax, en lokale runtimes kunnen achterlopen bij gehoste API’s. Dat is een van de redenen waarom de FAQ “hoe valideer je LLM-JSON in Python” begint met provider-afdwinging wanneer die bestaat en toch eindigt met validatie aan de Python-kant. Voor een breder beeld van hoe vendors zich met elkaar vergelijken, zie de vergelijking van gestructureerde output tussen populaire LLM-providers. Als je modellen lokaal draait, geldt dezelfde validatiepipeline nadat je de lijnformaat hebt genormaliseerd, bijvoorbeeld na extractie met Ollama zoals in gestructureerde LLM-output met Ollama in Python en Go. Als een runtime JSON nog steeds omsluit met vreemde prefixes of redeneringssporen, verwacht dan dezelfde klasse van parser-fouten zoals beschreven in problemen met gestructureerde output van Ollama GPT-OSS.
De Python-stack die daadwerkelijk werkt
Mijn aanbeveling is opzettelijk saai. Laat eerst de modelprovider het structurele contract afdwingen wanneer dat kan. Valideer vervolgens de geretourneerde payload in Python met Pydantic. Gebruik derde expliciete validatie van bedrijfsregels voor feiten die een schema alleen niet kan bewijzen. Test vierde het contract met fixtures en adversariële voorbeelden in plaats van naar een screenshot van een playground te zwaaien en het af te ronden. De documentatie van OpenAI voor gestructureerde output, het validator-model van Pydantic, de jsonschema-tooling van Python en de eigen gestructureerde output-evaluatievoorbeelden van OpenAI wijzen allemaal in die richting.
Pydantic is het juiste zwaartepunt voor Python. Het stelt je in staat om de output te modelleren als normale Python-types, JSON Schema te genereren met model_json_schema() en ruwe JSON te valideren met model_validate_json(). De documentatie van Pydantic merkt ook op dat model_validate_json() over het algemeen de betere route is dan eerst json.loads(...) te doen en dan te valideren, omdat die twee-stappenroute extra parsewerk toevoegt in Python.
Als je standalone schema-bestanden in je repo bewaart, of als je wilt dat CI fixture-payloads onafhankelijk van modelcode valideert, geeft het jsonschema-pakket van Python de eenvoudig mogelijke contractcheck met jsonschema.validate(...). Als je dat in pre-commit wilt, bestaat check-jsonschema specifiek als CLI en pre-commit-hook gebouwd op jsonschema. Dat is een zeer goede fit voor teams die willen dat schema-wijzigingen worden beoordeeld als code-wijzigingen.
Frameworks kunnen de plumbings verkleinen, maar ze verwijderen de behoefte aan daadwerkelijke validatie niet. LangChain selecteert nu automatisch native gestructureerde output van de provider wanneer de provider dit ondersteunt en valt anders terug op een tool-strategie. Instructor legt Pydantic-responsemodellen, validatie, opnieuw proberen en multi-provider-ondersteuning bovenop modeloproepen. Guardrails richt zich op validators en input-output-beveiligingslagen. Nuttige tools, allemaal. Maar het schema en de bedrijfsregels blijven van jou. Als je kiest tussen higher-level libraries, is de vergelijking BAML vs Instructor voor Python een nuttige aanvulling op dit artikel.
Een minimaal OpenAI- en Pydantic-voorbeeld
Het kleinste productie-waardige voorbeeld heeft een paar onomstotelijke vereisten. Gebruik waar mogelijk een gesloten set van enum-achtige waarden. Verbied extra keys. Voeg veldbeschrijvingen toe zodat het schema begrijpelijk is voor mensen en leesbaarder voor het model. Houd het root-object expliciet en saai. OpenAI beveelt duidelijke namen plus titels en beschrijvingen voor belangrijke keys aan, JSON Schema gebruikt enum om waarden te beperken, en Pydantic kan de objectvorm sluiten met extra="forbid".
from typing import Literal
from openai import OpenAI
from pydantic import BaseModel, ConfigDict, Field
class TicketClassification(BaseModel):
model_config = ConfigDict(extra="forbid")
category: Literal["billing", "bug", "how_to", "abuse"] = Field(
description="Categorie van het supportticket."
)
priority: Literal["low", "medium", "high"] = Field(
description="Operationele urgentie."
)
needs_human: bool = Field(
description="Of een mens het geval moet beoordelen."
)
summary: str = Field(
description="Een zinsamenvatting van het probleem."
)
client = OpenAI()
response = client.responses.parse(
model="gpt-4o-2024-08-06",
input=[
{
"role": "system",
"content": "Classificatie van supporttickets. Retourneer alleen het gestructureerde resultaat.",
},
{
"role": "user",
"content": "Klant meldt dubbele kosten na het vernieuwen van de checkout.",
},
],
text_format=TicketClassification,
)
result = response.output_parsed
print(result.model_dump())
Twee details in dat voorbeeld zijn makkelijk te missen en absoluut de moeite waard om je zorgen over te maken. extra="forbid" aan de Pydantic-kant spiegelt het JSON Schema-concept van additionalProperties: false, wat ook een vereiste is voor strikte toolschemas in de function-calling-documentatie van OpenAI. En enums zijn niet decoratief. Ze zijn een van de eenvoudigste manieren om het model te stoppen met het verzinnen van een waarde die je code niet begrijpt.
De OpenAI Python SDK ondersteunt client.responses.parse(...) met een Pydantic-model geleverd als text_format, en het geparse object wordt geretourneerd op response.output_parsed. Dezelfde SDK ondersteunt ook client.chat.completions.parse(...), waar het geparse object zit op message.parsed. Als je directe gestructureerde data-extractie wilt met minimale glue, zijn die helpers het schoonste startpunt.
Parsen, normaliseren, dan valideren
Gestructureerde output en model_validate_json verwijderen veel parse-pijn als de stack eind-tot-eind is uitgelijnd. Het moment dat je een provider ondersteunt die platte chattekst retourneert, een model dat JSON in fences omsluit, of een logpad dat de ruwe voltooiingsstring opslaat, wil je één chokepoint dat tekst omzet in een dict voordat Pydantic draait.
import json
def parse_json_from_llm_text(text: str) -> dict:
cleaned = text.strip()
if cleaned.startswith("```"):
cleaned = cleaned.split("\n", 1)[1]
cleaned = cleaned.rsplit("```", 1)[0].strip()
# Veelvoorkomend "Natuurlijk, hier is de JSON:" prefix voor het object.
if not cleaned.startswith("{") and "{" in cleaned and "}" in cleaned:
start = cleaned.find("{")
end = cleaned.rfind("}")
if end > start:
cleaned = cleaned[start : end + 1]
return json.loads(cleaned)
ticket_dict = parse_json_from_llm_text(raw_completion_text)
ticket = TicketClassification.model_validate(ticket_dict)
Die helper is opzettelijk saai. Het handhaaft gefencede “json ... "-blokken en een leidende natuurlijke-taal-preambule wanneer de payload nog steeds een enkel top-level object is. Het is geen volledige JSON-extractor. Als het model accolades nestelt binnen string-waarden, kan naïef slicing breken, en de juiste fix is meestal striktere prompting, schema-gebonden voltooiingen, of een dedicated parser-bibliotheek.
Streaming-voltooiingen
Als je chat-tokens streamt, voer dan niet json.loads of model_validate_json uit op elke delta. Buffer tot de API een afgeronde message rapporteert (controleer je client voor stream-terminatie of finish_reason), concateneer de tekst, en parse dan één keer. Dezelfde regel geldt wanneer tool-call-argumenten in chunks aankomen. Je valideert pas nadat de argumentenstring compleet is.
chunks: list[str] = []
for chunk in completion_stream:
delta = chunk.choices[0].delta.content or ""
chunks.append(delta)
raw_completion_text = "".join(chunks)
ticket = TicketClassification.model_validate_json(raw_completion_text)
Je kunt raw_completion_text eerst door parse_json_from_llm_text sturen als je fences of praatjes verwacht rondom de JSON.
Zodra je plain-string-parsing eigenaarschap hebt, is de volgende beperking vaak niet Python, maar de JSON Schema-dialect van de provider en wat de remote API daadwerkelijk accepteert.
Provider-schemalimieten (voordat je slim doet in Python)
Dump niet blindelings elke schema-generator-output in een API en neem aan dat elk JSON Schema-feature wordt ondersteund. OpenAI ondersteunt een subset van JSON Schema, vereist dat alle velden vereist zijn voor gestructureerde output, vereist dat het root een object is in plaats van een top-level anyOf, en documenteert limieten op nestingsdiepte en totaal eigenschapenaantal. Houd het provider-facing schema simpel. Dat is geen compromis. Dat is goede engineering.
Als je een provider-agnostisch validatiepad nodig hebt, of als je opgeslagen fixtures en mocks wilt valideren, is Pydantic plus jsonschema nog steeds een geweldige combinatie.
from jsonschema import validate as validate_json
schema = TicketClassification.model_json_schema()
payload = {
"category": "bug",
"priority": "high",
"needs_human": True,
"summary": "Checkout dupliceert kosten na vernieuwen.",
}
validate_json(instance=payload, schema=schema)
ticket = TicketClassification.model_validate(payload)
print(ticket)
Dat patroon is vooral handig in tests, contract-fixtures en integraties waarbij de modelprovider geen native gestructureerde output-afdwinging biedt. Onthoud alleen dat een lokaal gegenereerd schema breder kan zijn dan het door een bepaalde provider ondersteunde subset, dus “lokaal geldig” betekent niet automatisch “geaccepteerd door elke LLM-API”. Merk ook op dat sommige providers schema-artefacten voorbewerken en cachen, zodat het eerste verzoek voor een nieuw schema langzamer kan zijn dan warme verzoeken.
Tool-oproepen zijn een tweede contract
Function- of tool-calling is de andere grote gestructureerde output-vorm. Het model kiest een naam en doorgeeft argumenten die moeten overeenkomen met een JSON Schema die jij beheert. OpenAI beveelt strict: true aan op tool-definitities zodat argumenten uitgelijnd blijven met dat schema. In agent-heavy stacks wordt slechte sampling snel ongeldige tool-JSON; houd sampler-instellingen uitgelijnd met multi-step werk met behulp van de referentie voor agentic inferentie-parameters voor Qwen en Gemma.
De snippets hieronder gaan ervan uit dat je het tool-call-object van de provider al hebt gemapped naar een name string en een arguments dict, bijvoorbeeld door tool_calls[].function te parsen op chat-completies (JSON-string-argumenten worden eerst json.loads). dispatch_tool is de stap na die normalisatie.
Twee praktische regels helpen in Python. Valideer eerst de tool-naam tegen een expliciete allowlist voordat je uitvoering routeert. Valideer tweede de argumenten-dict met hetzelfde Pydantic-model dat je in tests gebruikt, niet met ad hoc key-toegang. Het faalmode dat je vermijdt is “geldig JSON-argumenten, verkeerde vorm voor de tool die afvire”, wat langs string-checks glippt.
from typing import Any, Callable
from pydantic import BaseModel
ToolHandler = Callable[[dict[str, Any]], str]
def dispatch_tool(
*,
name: str,
arguments: dict[str, Any],
handlers: dict[str, tuple[type[BaseModel], ToolHandler]],
) -> str:
if name not in handlers:
raise ValueError(f"onondersteunde tool {name}")
model_cls, handler = handlers[name]
validated = model_cls.model_validate(arguments)
return handler(validated.model_dump())
handlers: dict[str, tuple[type[BaseModel], ToolHandler]] = {
"classify_ticket": (
TicketClassification,
lambda data: f"gequeueerd als {data['category']}",
),
}
Dat patroon houdt routing en validatie op één plek. Je echte handlers zullen rijker zijn, maar de scheiding moet hetzelfde blijven: toegestane namen, getypeerde argumenten, dan side effects.
Schemavalidatie heeft nog steeds bedrijfsregels nodig
Een geldig object is niet hetzelfde als een correct object. OpenAI zegt dit direct. Gestructureerde output voorkomt geen fouten binnen de waarden van het JSON-object. Dat is waarom de FAQ “waarom schemavalidatie én bedrijfsregels-validatie beide belangrijk zijn” een blunt antwoord heeft. Omdat een response het schema perfect kan volgen en toch op een manier verkeerd is die de business schaadt.
Hier is een realistisch voorbeeld. De structuur kan geldig zijn, maar de prijslogica kan nog steeds nonsens zijn.
from decimal import Decimal
from typing import Literal
from typing_extensions import Self
from pydantic import BaseModel, ConfigDict, Field, model_validator
class Offer(BaseModel):
model_config = ConfigDict(extra="forbid")
currency: Literal["USD", "EUR", "GBP"]
amount: Decimal = Field(gt=0)
original_amount: Decimal | None
discounted: bool
@model_validator(mode="after")
def check_discount_logic(self) -> Self:
if self.discounted:
if self.original_amount is None:
raise ValueError(
"original_amount is vereist wanneer discounted waar is"
)
if self.original_amount <= self.amount:
raise ValueError(
"original_amount moet groter zijn dan amount"
)
return self
Die validator doet iets wat schema’s alleen vaak slecht doen in echte systemen. Het controleert cross-field semantiek nadat het hele model is geparsed. Pydantic’s model_validator bestaat precies voor dit soort whole-object validatie. Merk op het Decimal | None veld zonder default. Dat houdt het veld aanwezig terwijl het toch null toestaat, wat overeenkomt met OpenAI’s gedocumenteerde patroon voor optionele waarden onder strikte gestructureerde output.
Als je wilt dat validatiefouten automatisch terugkoppelen naar het model, is Instructor een praktische laag boven Pydantic. De documentatie beschrijft een retry-lus waarbij validatiefouten worden vastgelegd, geformatteerd als feedback, en gebruikt om het model te vragen het opnieuw te proberen.
import instructor
retrying_client = instructor.from_provider("openai/gpt-4o", max_retries=2)
offer = retrying_client.create(
response_model=Offer,
messages=[
{
"role": "user",
"content": (
"Extract de aanbieding uit deze tekst. "
"Was 49.00 USD, nu 19.00 USD."
),
}
],
)
Dit is een van de weinige gemakken die ik graag aanbeveel. Automatische retries gekoppeld aan echte validatiefouten zijn nuttig. Stille coercie is dat niet. De modellayer van Instructor, retry-documentatie en validatiedocumentatie leunen allemaal in diezelfde idee, en ze hebben gelijk om dat te doen.
Je kunt hetzelfde idee implementeren zonder een framework. De lus is klein. Vraag het model, valideer met Pydantic, en als validatie faalt, stuur de foutdetails terug in een follow-up user message en vraag om gecorrigeerde JSON alleen. Cap pogingen, log de uiteindelijke fout, en presenteer een gecontroleerde fout aan callers. Als je al vertrouwt op responses.parse of andere schema-gebonden helpers, oefen je dit pad zelden. Het is nog steeds belangrijk voor JSON-modus, oudere chat-endpoints, of elke gateway die je een ruwe string geeft.
from openai import OpenAI
from pydantic import ValidationError
client = OpenAI()
messages = [
{"role": "system", "content": "Retourneer alleen JSON die voldoet aan het ticketschema."},
{"role": "user", "content": "Klant meldt dubbele kosten na het vernieuwen van de checkout."},
]
ticket: TicketClassification | None = None
for attempt in range(2):
completion = client.chat.completions.create(
model="gpt-4o-2024-08-06",
messages=messages,
response_format={"type": "json_object"},
)
raw_text = completion.choices[0].message.content or ""
try:
ticket = TicketClassification.model_validate_json(raw_text)
break
except ValidationError as exc:
messages.append(
{
"role": "user",
"content": f"Validatie mislukt met {exc.errors()}. Retourneer alleen gecorrigeerde JSON.",
}
)
else:
raise RuntimeError("uitgeputte gestructureerde output-retries")
assert ticket is not None
In echte services zou je tracing IDs koppelen, klanttekst in logs redacteren, en onderscheid maken tussen herstelbare validatiefouten en weigeringen of onvolledige responses. Het belangrijke deel is dat de retry wordt aangedreven door echte validator-output, niet door een generiek “probeer opnieuw” bericht.
Test, retry, en fail closed
Wat moet er gebeuren wanneer LLM-validatie faalt? Geen schouderophalen. Verwerp de payload, log de fout, probeer opnieuw met beperkte pogingen als de taak het waard is, en fail closed in plaats van rommel te normaliseren in iets dat er alleen acceptabel uitziet. Dit is ook waar veel teams vergeten om weigeringen en onvolledige outputs expliciet af te handelen, hoewel de provider-documentatie hen vertelt dat die paden bestaan.
Voor de Responses API van OpenAI moet afhandeling van fouten eersteklas code zijn, geen afterthought. De variabele is response van client.responses.create of parse, niet completion van chat-streaming elders in dit artikel.
if response.status == "incomplete":
raise RuntimeError(response.incomplete_details.reason)
content = response.output[0].content[0]
if content.type == "refusal":
raise RuntimeError(content.refusal)
Dat is geen defensief over-engineering. Het is direct uitgelijnd met de gedocumenteerde faalmodes. Als het model weigert, houd je geen schema-geldige payload vast. Als de response onvolledig is, houd je geen schema-geldige payload vast. Behandel beide als expliciete branches in je control flow.
Je moet het contract ook testen buiten de model-oproep zelf.
import pytest
from jsonschema import validate as validate_json
from pydantic import ValidationError
def test_ticket_fixture_matches_schema():
payload = {
"category": "bug",
"priority": "high",
"needs_human": True,
"summary": "Checkout dupliceert kosten na vernieuwen.",
}
validate_json(instance=payload, schema=TicketClassification.model_json_schema())
def test_discount_logic_rejects_broken_offer():
with pytest.raises(ValidationError):
Offer.model_validate(
{
"currency": "USD",
"amount": "19.00",
"original_amount": "10.00",
"discounted": True,
}
)
def test_ticket_rejects_unknown_category_string():
with pytest.raises(ValidationError):
TicketClassification.model_validate(
{
"category": "refund",
"priority": "high",
"needs_human": True,
"summary": "Klant wil een restitutie.",
}
)
def test_ticket_rejects_extra_keys():
with pytest.raises(ValidationError):
TicketClassification.model_validate(
{
"category": "bug",
"priority": "high",
"needs_human": True,
"summary": "Gebroken flow.",
"severity": "critical",
}
)
Dit is de juiste vorm van teststrategie voor LLM-outputvalidatie in Python. Valideer golden fixtures met jsonschema zodat elk veld in het contract wordt getest. Valideer semantiek met Pydantic, voeg dan adversariële gevallen toe zoals illegale enum-strings, verboden extra keys, en cross-field contradicties die je belangrijk vindt. Als je echte model-outputs snapshot, scrub PII en behandel ze als regressie-fixtures.
Als je team leeft in de OpenAI-stack, bevat de Evals API ook gestructureerde output-evaluatie-recepten specifiek voor het testen en itereren van taken die afhankelijk zijn van machine-leesbare formaten. En als je ruwe schema-bestanden in de repo bewaart, wire check-jsonschema in CI of pre-commit. Ship contracten, niet vibes.
Productiechecks die je later redden
Wanneer validatie faalt, is het FAQ-antwoord blunt. Verwerp de payload, log waarom, probeer opnieuw met gerichte feedback als de taak een nieuwe poging waard is, en fail closed in plaats van slechte data te forceren in een wachtrij.
Een korte operations checklist helpt teams om herhalende incidenten te vermijden.
- Log de schema-versie of een hash van het JSON Schema dat je naar de provider stuurde, zodat je fouten nauwkeurig kunt herhalen.
- Redacteer model-inputs en outputs in logs. Gestrucreerde logs zijn nutteloos als ze klanttekst lekken.
- Emit counters of metrics voor weigeringspercentage, onvolledige response-percentage, validatiefoutpercentage en reparatiesuccespercentage. Pieken daar verslaan raden wanneer een model of prompt-wijziging is uitgebracht.
Brede observability voor LLM-systemen richtlijn helpt om die signalen te wiren in dashboards, traces en SLO reviews zodra de counters bestaan.
De best practice is niet ingewikkeld. Gebruik provider-side gestructureerde output of strikte toolschemas wanneer je kunt. Normaliseer ruwe tekst wanneer je moet. Spiegels het contract in Python met Pydantic. Voeg bedrijfsregels-validatie toe voor wat het schema niet kan bewijzen. Handeer weigeringen en onvolledige responses af als normale branches. Test het contract tot het stopt met een demo te zijn en begint als software. Minder is gewoon prompt engineering cosplay.