Validazione dell'output strutturato degli LLM in Python che regge
Smetti di interpretare le vibrazioni. Convalida i contratti.
La maggior parte dei tutorial sull’output strutturato degli LLM è superficiale. Ti insegnano a chiedere JSON gentilmente e poi sperare che il modello si comporti correttamente. Quello non è convalida. È ottimismo con le parentesi graffe.
La documentazione stessa di OpenAI fa questa distinzione in modo esplicito. La modalità JSON ti fornisce JSON valido, mentre l’Output Strutturato (Structured Outputs) applica l’aderenza allo schema, e OpenAI consiglia di utilizzare l’Output Strutturato invece della modalità JSON quando possibile.

Ciò non rende comunque il payload affidabile. JSON Schema definisce la struttura e i valori consentiti, Pydantic offre la convalida tipizzata in Python, e OpenAI nota esplicitamente che una risposta valida secondo lo schema può comunque contenere valori errati. Inoltre, i rifiuti (refusals) e gli output incompleti possono bypassare la struttura che ti aspettavi. In produzione, la convalida dell’output strutturato è un pipeline, non un interruttore. Lo stesso confine deve esistere anche nel contesto più ampio di throughput, retry e limiti dello scheduler sul hub di ingegneria delle prestazioni degli LLM.
La convalida dell’output strutturato è un contratto
La convalida dell’output strutturato per gli LLM significa che definisci la forma della risposta in anticipo, vincoli il modello a produrre quella forma quando possibile e poi convalidi nuovamente il risultato prima che la tua applicazione lo consideri affidabile. In termini pratici, ciò significa verificare i campi obbligatori, i tipi, gli enum, le forme degli oggetti chiuse e le regole di dominio prima che il payload tocchi il tuo database, UI, coda o servizio a valle. JSON Schema esiste esattamente per questo tipo di convalida strutturale, Pydantic è progettato per convalidare dati non fidati rispetto agli hint di tipo di Python, e la libreria jsonschema di Python offre un modo diretto per convalidare un’istanza rispetto a uno schema.
Esiste anche una netta distinzione tra due casi d’uso comuni. Se il modello deve rispondere all’utente in un formato strutturato, usa un formato di risposta strutturata. Se il modello deve chiamare gli strumenti o le funzioni della tua applicazione, usa il function calling. La documentazione di OpenAI espone chiaramente questa distinzione e, per il function calling, consiglia di abilitare strict: true in modo che gli argument aderiscano in modo affidabile allo schema della funzione.
La mia opinione forte è semplice. Tratta ogni risposta strutturata di un LLM come un confine di API. Una volta che inizi a pensare in termini di contratti invece che di prompt, l’architettura diventa più pulita, i bug costano meno e il problema del “perché il modello ha inventato un nuovo campo in produzione” scompare per lo più. Questa è la vera risposta a “cos’è la convalida dell’output strutturato per gli LLM” ed è una risposta molto migliore rispetto a “chiedere gentilmente al modello il JSON”.
La modalità JSON non è convalida
Se ricordi solo una cosa da questo articolo, che sia questa. La modalità JSON non è convalida dello schema. Il Centro Assistenza di OpenAI afferma che la modalità JSON non garantisce che l’output corrisponda a uno schema specifico, solo che è JSON valido e si analizza senza errori. La guida agli Output Strutturati dice la stessa cosa in modo più pulito. Sia la modalità JSON che gli Output Strutturati possono produrre JSON valido, ma solo gli Output Strutturati applicano l’aderenza allo schema.
Questa differenza è più importante di quanto许多人 ammettano. Nel suo post di lancio sugli Output Strutturati, OpenAI ha riferito che gpt-4o-2024-08-06 con gli Output Strutturati ha ottenuto il 100% nei suoi eval di schema JSON complessi, mentre gpt-4-0613 ha ottenuto meno del 40%. Non devi considerare quei numeri come verità universali per vedere il punto più ampio. L’applicazione dello schema cambia la superficie di fallimento da “può succedere qualsiasi cosa” a “il contratto è molto più rigoroso”.
Ci sono ancora casi limite, e fingere il contrario è il modo in cui le demo giocattolo diventano dover di pager. OpenAI documenta che il modello può rifiutare una richiesta non sicura, e questi rifiuti vengono visualizzati al di fuori del tuo normale percorso dello schema. Documenta anche risposte incomplete, inclusi casi come il raggiungimento di max_output_tokens o l’interruzione di un filtro di contenuto. Quindi la FAQ “la modalità JSON è sufficiente per un output LLM affidabile” ha una risposta breve e una più lunga. La risposta breve è no. La risposta più lunga è che anche l’output strutturato rigoroso ha bisogno di una gestione esplicita dei fallimenti.
Dove l’output strutturato si rompe ancora
L’applicazione dello schema riduce il problema. Non lo elimina. Nel traffico reale vedi ancora payload rotti o sorprendenti per ragioni che hanno poco a che fare con la formulazione del tuo prompt.
Forme di fallimento per cui progettare
Modelli e client non sono d’accordo sui dettagli. Puoi ottenere prosa extra prima o dopo il JSON, blocchi Markdown recintati intorno al payload, o una chiamata a strumento il cui nome è valido ma i cui argument sono JSON che non corrispondono al tuo modello Pydantic. Lo streaming peggiora la situazione perché potresti convalidare un buffer a metà. Il codice difensivo dovrebbe assumere “stringa in ingresso, forse JSON dentro” piuttosto che “byte sulla rete che già corrispondono al mio modello”.
Differenze tra provider e API
Non ogni host espone la stessa superficie di output strutturato. Uno stack potrebbe darti un completamento vincolato allo schema di prima classe, un altro potrebbe garantire solo la sintassi JSON, e i runtime locali potrebbero rimanere indietro rispetto alle API ospitate. Questo è uno dei motivi per cui la FAQ “come si convalida il JSON LLM in Python” inizia con l’applicazione del provider quando esiste e termina comunque con la convalida lato Python. Per una visione più ampia di come i vendor si confrontano, vedi il confronto dell’output strutturato tra i principali provider LLM. Se esegui modelli localmente, lo stesso pipeline di convalida si applica dopo aver normalizzato il formato della rete, ad esempio dopo l’estrazione con Ollama come in output LLM strutturato con Ollama in Python e Go. Quando un runtime ancora avvolge il JSON con prefissi strani o tracce di ragionamento, aspettati la stessa classe di errori di parser descritti in problemi di output strutturato Ollama GPT-OSS.
Lo stack Python che funziona davvero
Il mio consiglio è noioso appositamente. Prima, lascia che il provider del modello applichi il contratto strutturale quando può. Secondo, convalida il payload restituito in Python con Pydantic. Terzo, usa la convalida esplicita delle regole di business per i fatti che uno schema da solo non può provare. Quarto, testa il contratto con fixture ed esempi avversariali invece di fare un cenno a uno screenshot del playground e dire che è finito. La documentazione di OpenAI sugli Output Strutturati, il modello di validatore di Pydantic, lo strumento jsonschema di Python e gli esempi di eval di output strutturato di OpenAI stesso puntano tutti in quella direzione.
Pydantic è il giusto centro di gravità per Python. Ti permette di modellare l’output come normali tipi Python, generare JSON Schema con model_json_schema() e convalidare JSON grezzo con model_validate_json(). La documentazione di Pydantic nota anche che model_validate_json() è generalmente il percorso migliore rispetto a eseguire json.loads(...) prima e poi convalidare, perché quel percorso in due passaggi aggiunge lavoro di parsing extra in Python.
Se mantieni file schema standalone nel tuo repo, o vuoi che CI convalidi i payload delle fixture indipendentemente dal codice del modello, il pacchetto jsonschema di Python ti offre il controllo del contratto più semplice possibile con jsonschema.validate(...). Se vuoi questo in pre-commit, check-jsonschema esiste specificamente come CLI e hook pre-commit costruito su jsonschema. Questo è un ottimo fit per i team che vogliono che i cambiamenti dello schema siano revisionati come cambiamenti di codice.
I framework possono ridurre la plomberia, ma non rimuovono la necessità di una convalida reale. LangChain ora seleziona automaticamente l’output strutturato nativo del provider quando il provider lo supporta e ricade a una strategia di strumento altrimenti. Instructor sovrappone modelli di risposta Pydantic, convalida, retry e supporto multi-provider sulle chiamate al modello. Guardrails si concentra su validatori e layer di guardia input-output. Strumenti utili, tutti. Ma lo schema e le regole di business appartengono ancora a te. Se stai scegliendo tra librerie di livello superiore, il confronto BAML vs Instructor per Python è un utile complemento a questo articolo.
Un esempio minimo di OpenAI e Pydantic
L’esempio più piccolo degno di produzione ha alcuni non-negotiable. Usa un insieme chiuso di valori simili a enum dove possibile. Vieta chiavi extra. Aggiungi descrizioni ai campi in modo che lo schema sia comprensibile agli umani e più leggibile per il modello. Mantieni l’oggetto radice esplicito e noioso. OpenAI consiglia nomi chiari più titoli e descrizioni per le chiavi importanti, JSON Schema usa enum per limitare i valori, e Pydantic può chiudere la forma dell’oggetto con 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="Support ticket category."
)
priority: Literal["low", "medium", "high"] = Field(
description="Operational urgency."
)
needs_human: bool = Field(
description="Whether a human should review the case."
)
summary: str = Field(
description="A one sentence summary of the issue."
)
client = OpenAI()
response = client.responses.parse(
model="gpt-4o-2024-08-06",
input=[
{
"role": "system",
"content": "Classify support tickets. Return only the structured result.",
},
{
"role": "user",
"content": "Customer reports duplicate charges after refreshing checkout.",
},
],
text_format=TicketClassification,
)
result = response.output_parsed
print(result.model_dump())
Due dettagli in quell’esempio sono facili da perdere e assolutamente degni di attenzione. extra="forbid" sul lato Pydantic specchia l’idea di JSON Schema di additionalProperties: false, che è anche un requisito per gli schema degli strumenti rigorosi nella documentazione di OpenAI sul function calling. E gli enum non sono cosmetici. Sono uno dei modi più semplici per impedire al modello di inventare un valore che il tuo codice non capisce.
Il SDK Python di OpenAI supporta client.responses.parse(...) con un modello Pydantic fornito come text_format, e l’oggetto analizzato è restituito su response.output_parsed. Lo stesso SDK supporta anche client.chat.completions.parse(...), dove l’oggetto analizzato vive su message.parsed. Se vuoi l’estrazione diretta di dati strutturati con un minimo di collante, questi helper sono il punto di partenza più pulito.
Analizza, normalizza, poi convalida
Gli Output Strutturati e model_validate_json rimuovono molto dolore di parsing quando lo stack è allineato da capo a fondo. Il momento in cui supporti un provider che restituisce testo di chat normale, un modello che avvolge il JSON in recinti, o un percorso di logging che memorizza la stringa di completamento grezza, vuoi un unico punto di strozzatura che trasformi il testo in un dict prima che Pydantic esegua.
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()
# Common "Sure, here is the JSON:" prefix before the 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)
Quel helper è intenzionalmente noioso. Gestisce blocchi recintati “json ... ” e un prefisso in linguaggio naturale quando il payload è ancora un oggetto di livello superiore singolo. Non è un estrattore JSON completo. Se il modello annida parentesi graffe all’interno di valori stringa, lo slicing ingenuo può rompersi, e la correzione giusta è solitamente un prompting più rigoroso, completamenti vincolati allo schema, o una libreria parser dedicata.
Completamenti streaming
Se fai streaming dei token di chat, non eseguire json.loads o model_validate_json su ogni delta. Buffera fino a quando l’API non segnala un messaggio finito (controlla il tuo client per la terminazione dello stream o finish_reason), concatena il testo, poi analizza una volta. La stessa regola si applica quando gli argument delle chiamate a strumento arrivano in chunk. Convalidi solo dopo che la stringa degli argument è completa.
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)
Puoi ancora passare raw_completion_text attraverso parse_json_from_llm_text prima quando ti aspetti recinti o chiacchiere intorno al JSON.
Una volta che possiedi il parsing di stringhe normali, il vincolo successivo spesso non è Python ma il dialetto JSON Schema del provider e cosa accetta effettivamente l’API remota.
Limiti dello schema del provider (prima di diventare furbo in Python)
Non scaricare ciecamente l’output di qualsiasi generatore di schema in un’API e assumere che ogni feature di JSON Schema sia supportata. OpenAI supporta un sottoinsieme di JSON Schema, richiede che tutti i campi siano obbligatori per gli Output Strutturati, richiede che la radice sia un oggetto piuttosto che un anyOf di livello superiore, e documenta limiti sulla profondità di nidificazione e sul conteggio totale delle proprietà. Mantieni lo schema rivolto al provider semplice. Non è un compromesso. È buona ingegneria.
Se hai bisogno di un percorso di convalida agnostico del provider, o vuoi convalidare fixture e mock memorizzati, Pydantic più jsonschema è ancora una grande combinazione.
from jsonschema import validate as validate_json
schema = TicketClassification.model_json_schema()
payload = {
"category": "bug",
"priority": "high",
"needs_human": True,
"summary": "Checkout duplicates charges after refresh.",
}
validate_json(instance=payload, schema=schema)
ticket = TicketClassification.model_validate(payload)
print(ticket)
Quel pattern è particolarmente utile nei test, nelle fixture di contratto e nelle integrazioni dove il provider del modello non offre l’applicazione nativa dell’output strutturato. Ricorda solo che uno schema generato localmente potrebbe essere più ampio del sottoinsieme supportato da un dato provider, quindi “valido localmente” non significa automaticamente “accettato da ogni API LLM”. Nota anche che alcuni provider preelaborano e mettono in cache gli artifact dello schema, quindi la prima richiesta per un nuovo schema può essere più lenta delle richieste calde.
Le chiamate a strumento sono un secondo contratto
Il function o tool calling è l’altra forma principale di output strutturato. Il modello sceglie un nome e passa argument che dovrebbero corrispondere a uno JSON Schema che controlli. OpenAI consiglia strict: true sulle definizioni degli strumenti in modo che gli argument rimangano allineati a quello schema. Negli stack pesanti su agenti, il campionamento cattivo si trasforma rapidamente in JSON degli strumenti non valido; mantieni le impostazioni del sampler allineate con il lavoro multi-step usando il riferimento ai parametri di inferenza agentic per Qwen e Gemma.
I frammenti sottostanti presuppongono che tu abbia già mappato l’oggetto della chiamata a strumento del provider in una stringa name e un dict arguments, ad esempio analizzando tool_calls[].function sui completamenti della chat (gli argument stringa JSON diventano json.loads prima). dispatch_tool è il passo dopo quella normalizzazione.
Due regole pratiche aiutano in Python. Prima, convalida il nome dello strumento contro un allowlist esplicito prima di instradare l’esecuzione. Secondo, convalida il dict degli argument con lo stesso modello Pydantic che usi nei test, non con l’accesso ad hoc alle chiavi. Il modalità di fallimento che stai evitando è “argument JSON validi, forma sbagliata per lo strumento che è stato attivato”, che scivola oltre i controlli delle stringhe.
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"unsupported 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"queued as {data['category']}",
),
}
Quel pattern mantiene instradamento e convalida in un unico posto. I tuoi gestori reali saranno più ricchi, ma la divisione dovrebbe rimanere la stessa: nomi consentiti, argument tipizzati, poi effetti collaterali.
La convalida dello schema ha ancora bisogno di regole di business
Un oggetto valido non è la stessa cosa di un oggetto corretto. OpenAI lo dice direttamente. Gli Output Strutturati non prevengono errori all’interno dei valori dell’oggetto JSON. Ecco perché la FAQ “perché sia la convalida dello schema che la convalida delle regole di business sono importanti” ha una risposta schietta. Perché una risposta può corrispondere perfettamente allo schema e comunque essere sbagliata in un modo che danneggia il business.
Ecco un esempio realistico. La struttura può essere valida, ma la logica di prezzi può comunque essere nonsensica.
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 required when discounted is true"
)
if self.original_amount <= self.amount:
raise ValueError(
"original_amount must be greater than amount"
)
return self
Quel validatore fa qualcosa che gli schema da soli spesso fanno male nei sistemi reali. Controlla la semantica cross-field dopo che l’intero modello è stato analizzato. Il model_validator di Pydantic esiste esattamente per questo tipo di convalida dell’intero oggetto. Nota il campo Decimal | None senza un default. Questo mantiene il campo presente consentendo comunque null, che corrisponde al pattern documentato di OpenAI per valori simili a opzionali sotto Output Strutturati rigorosi.
Se vuoi che i fallimenti di convalida si riflettano automaticamente nel modello, Instructor è un layer pratico sopra Pydantic. La sua documentazione descrive un ciclo di retry dove gli errori di convalida sono catturati, formattati come feedback e usati per chiedere al modello di riprovare.
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 the offer from this text. "
"Was 49.00 USD, now 19.00 USD."
),
}
],
)
Questa è una delle poche comodità che raccomanderò volentieri. I retry automatici legati a veri errori di convalida sono utili. La coercizione silenziosa no. Il layer del modello di Instructor, la documentazione sui retry e quella sulla convalida si concentrano tutti su quella stessa idea, e hanno ragione a farlo.
Puoi implementare la stessa idea senza un framework. Il ciclo è piccolo. Chiedi al modello, convalida con Pydantic, e se la convalida fallisce, invia i dettagli dell’errore in un messaggio utente di follow-up e chiedi solo JSON corretto. Limita i tentativi, logga il fallimento finale e mostra un errore controllato ai chiamanti. Quando ti affidi già a responses.parse o ad altri helper vincolati allo schema, raramente eserciterai questo percorso. È comunque importante per la modalità JSON, endpoint di chat più vecchi, o qualsiasi gateway che ti dia una stringa grezza.
from openai import OpenAI
from pydantic import ValidationError
client = OpenAI()
messages = [
{"role": "system", "content": "Return only JSON that matches the ticket schema."},
{"role": "user", "content": "Customer reports duplicate charges after refreshing 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"Validation failed with {exc.errors()}. Return corrected JSON only.",
}
)
else:
raise RuntimeError("exhausted structured output retries")
assert ticket is not None
Nei servizi reali attaccheresti ID di tracciamento, redaresti il testo del cliente nei log e distingueresti gli errori di convalida recuperabili dai rifiuti o dalle risposte incomplete. La parte importante è che il retry è guidato dall’output reale del validatore, non da un messaggio generico “prova di nuovo”.
Testa, riprova e fallisci in modo chiuso
Cosa dovrebbe succedere quando la convalida LLM fallisce? Non un’indifferenza. Rifiuta il payload, logga il fallimento, riprova con tentativi limitati se il compito vale la pena di essere riprovato, e fallisci in modo chiuso invece di normalizzare spazzatura in qualcosa che sembra solo accettabile. Questo è anche il punto in cui molti team dimenticano di gestire esplicitamente i rifiuti e gli output incompleti, anche se la documentazione del provider dice loro che quei percorsi esistono.
Per l’API Responses di OpenAI, la gestione dei fallimenti dovrebbe essere codice di prima classe, non un dopo-pensiero. La variabile è response da client.responses.create o parse, non completion dallo streaming della chat altrove in questo articolo.
if response.status == "incomplete":
raise RuntimeError(response.incomplete_details.reason)
content = response.output[0].content[0]
if content.type == "refusal":
raise RuntimeError(content.refusal)
Non è un’ingegneria difensiva eccessiva. È direttamente allineata con le modalità di fallimento documentate. Se il modello rifiuta, non stai tenendo un payload valido secondo lo schema. Se la risposta è incompleta, non stai tenendo un payload valido secondo lo schema. Tratta entrambi come rami espliciti nel tuo flusso di controllo.
Dovresti anche testare il contratto al di fuori della chiamata al modello stesso.
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 duplicates charges after refresh.",
}
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": "Customer wants a refund.",
}
)
def test_ticket_rejects_extra_keys():
with pytest.raises(ValidationError):
TicketClassification.model_validate(
{
"category": "bug",
"priority": "high",
"needs_human": True,
"summary": "Broken flow.",
"severity": "critical",
}
)
Questa è la forma giusta di strategia di test per la convalida dell’output LLM in Python. Convalida le fixture dorate con jsonschema in modo che ogni campo nel contratto sia esercitato. Convalida la semantica con Pydantic, poi aggiungi casi avversariali come stringhe enum illegali, chiavi extra vietate e contraddizioni cross-field che ti interessano. Se fai snapshot di output reali del modello, pulisci i PII e trattali come fixture di regressione.
Se il tuo team vive nello stack OpenAI, l’API Evals include anche ricette di valutazione dell’output strutturato specificamente per testare e iterare su compiti che dipendono da formati leggibili da macchina. E se mantieni file schema grezzi nel repo, collega check-jsonschema a CI o pre-commit. Spedisci contratti, non vibrazioni.
Controlli di produzione che ti salvano dopo
Quando la convalida fallisce, la risposta della FAQ è schietta. Rifiuta il payload, logga il perché, riprova con feedback mirato quando il compito vale un altro tentativo, e fallisci in modo chiuso invece di costringere dati cattivi in una coda.
Un breve checklist operativo aiuta i team a evitare incidenti ripetuti.
- Logga la versione dello schema o un hash del JSON Schema che hai inviato al provider in modo da poter riprodurre i fallimenti accuratamente.
- Redai input e output del modello nei log. I log strutturati sono inutili se fuoruscitano il testo del cliente.
- Emetti contatori o metriche per tasso di rifiuto, tasso di risposta incompleta, tasso di fallimento di convalida e tasso di successo di riparazione. I picchi lì battono l’indovinare quando è stata rilasciata una modifica del modello o del prompt.
Una più ampia guida per l’osservabilità dei sistemi LLM aiuta a collegare quei segnali in dashboard, tracce e recensioni SLO una volta che i contatori esistono.
La best practice non è complicata. Usa gli Output Strutturati lato provider o schema degli strumenti rigorosi quando puoi. Normalizza il testo grezzo quando devi. Specchia il contratto in Python con Pydantic. Aggiungi la convalida delle regole di business per quello che lo schema non può provare. Gestisci i rifiuti e le risposte incomplete come rami normali. Testa il contratto finché smette di essere una demo e inizia a essere software. Qualsiasi cosa meno è solo cosplay di prompt engineering.