Validação de Saída Estruturada de LLMs em Python que Funciona

Pare de interpretar vibes. Valide contratos.

Conteúdo da página

A maioria dos tutoriais sobre “saída estruturada” de LLMs é superficial. Eles ensinam você a pedir JSON educadamente e depois torcer para que o modelo se comporte. Isso não é validação. Isso é otimismo com chaves.

A própria documentação da OpenAI deixa a distinção explícita. O modo JSON fornece JSON válido, enquanto as Saídas Estruturadas (Structured Outputs) impõem a aderência ao esquema, e a OpenAI recomenda o uso de Saídas Estruturadas em vez do modo JSON sempre que possível.

infográfico de validação de saída estruturada

Isso ainda não torna o payload confiável. O JSON Schema define a estrutura e os valores permitidos, o Pydantic oferece validação tipada em Python, e a OpenAI nota explicitamente que uma resposta válida conforme o esquema ainda pode conter valores incorretos. Além disso, recusas e saídas incompletas podem contornar a estrutura esperada. Em produção, a validação de saída estruturada é um pipeline, não um interruptor. Essa mesma fronteira também precisa existir dentro do contexto mais amplo de throughput, retries e limites do agendador no hub de engenharia de desempenho de LLM.

A validação de saída estruturada é um contrato

A validação de saída estruturada para LLMs significa que você define a forma da resposta antecipadamente, restringe o modelo para produzir essa forma quando possível e, em seguida, valida o resultado novamente antes que sua aplicação o confie. Em termos práticos, isso significa verificar campos obrigatórios, tipos, enums, formas de objeto fechadas e regras de domínio antes que o payload toque no seu banco de dados, UI, fila ou serviço downstream. O JSON Schema existe exatamente para esse tipo de validação estrutural, o Pydantic foi construído para validar dados não confiáveis contra hints de tipo do Python, e a biblioteca jsonschema do Python oferece uma maneira direta de validar uma instância contra um esquema.

Existe também uma divisão clara entre dois casos de uso comuns. Se o modelo deve responder ao usuário em um formato estruturado, use um formato de resposta estruturado. Se o modelo deve chamar as ferramentas ou funções da sua aplicação, use function calling (chamada de função). A documentação da OpenAI detalha essa distinção e, para function calling, recomenda habilitar strict: true para que os argumentos aderem de forma confiável ao esquema da função.

Minha opinião forte é simples. Trate cada resposta estruturada de LLM como uma fronteira de API. Uma vez que você começa a pensar em termos de contratos em vez de prompts, a arquitetura fica mais limpa, os bugs ficam mais baratos e o problema de “por que o modelo inventou um novo campo em produção” desaparece em grande parte. Essa é a verdadeira resposta para “o que é validação de saída estruturada para LLMs” e é uma resposta muito melhor do que “pedir educadamente ao modelo por JSON”.

O modo JSON não é validação

Se você lembrar de apenas uma coisa deste artigo, que seja esta. O modo JSON não é validação de esquema. O Centro de Ajuda da OpenAI diz que o modo JSON não garantirá que a saída corresponda a nenhum esquema específico, apenas que é um JSON válido e parseado sem erros. O guia de Saídas Estruturadas diz a mesma coisa de maneira mais limpa. Tanto o modo JSON quanto as Saídas Estruturadas podem produzir JSON válido, mas apenas as Saídas Estruturadas impõem a aderência ao esquema.

Essa diferença importa mais do que as pessoas admitem. Em seu post de lançamento das Saídas Estruturadas, a OpenAI relatou que o gpt-4o-2024-08-06 com Saídas Estruturadas marcou 100 por cento em seus evals de esquema JSON complexos, enquanto o gpt-4-0613 marcou menos de 40 por cento. Você não precisa tratar esses números como verdade universal para ver o ponto mais amplo. A imposição de esquema muda a superfície de falha de “qualquer coisa pode acontecer” para “o contrato é muito mais rigoroso”.

Ainda existem casos de borda, e fingir o contrário é como demos de brinquedo se tornam dever de plantão (pager duty). A OpenAI documenta que o modelo pode recusar uma solicitação insegura, e essas recusas são exibidas fora do seu caminho de esquema normal. Ela também documenta respostas incompletas, incluindo casos como atingir max_output_tokens ou uma interrupção do filtro de conteúdo. Então, a FAQ “o modo JSON é suficiente para saída confiável de LLM” tem uma resposta curta e uma mais longa. A resposta curta é não. A resposta mais longa é que até mesmo saídas estruturadas estritas ainda precisam de tratamento explícito de falhas.

Onde a saída estruturada ainda falha

A imposição de esquema reduz o problema. Não o elimina. No tráfego real, você ainda vê payloads quebrados ou surpreendentes por razões que têm pouco a ver com a formulação do seu prompt.

Formas de falha que valem a pena projetar

Modelos e clientes discordam sobre detalhes. Você pode obter prosa extra antes ou depois do JSON, blocos cercados por Markdown ao redor do payload, ou uma chamada de ferramenta cujo nome é válido, mas cujos argumentos são JSON que não correspondem ao seu modelo Pydantic. O streaming piora porque você pode validar um buffer parcialmente concluído. O código defensivo deve assumir “string entra, talvez JSON dentro” em vez de “bytes na rede já correspondem ao meu modelo”.

Diferenças de provedor e API

Nem todo host expõe a mesma superfície de saída estruturada. Uma pilha pode oferecer uma conclusão vinculada a esquema de primeira classe, outra pode apenas garantir a sintaxe JSON, e runtimes locais podem ficar para trás em relação às APIs hospedadas. Essa é uma das razões pelas quais a FAQ “como você valida JSON de LLM em Python” começa com a imposição do provedor quando existe e ainda termina com validação no lado do Python. Para uma visão mais ampla de como os fornecedores se comparam, veja a comparação de saída estruturada entre provedores populares de LLM. Se você executa modelos localmente, o mesmo pipeline de validação se aplica após normalizar o formato da rede, por exemplo, após extração com o Ollama, como em saída estruturada de LLM com Ollama em Python e Go. Quando um runtime ainda envolve JSON com prefixos estranhos ou rastros de raciocínio, espere a mesma classe de falhas de parser descritas em problemas de saída estruturada do Ollama GPT-OSS.

A pilha Python que realmente funciona

Minha recomendação é entediantemente proposital. Primeiro, deixe o provedor do modelo impor o contrato estrutural quando possível. Segundo, valide o payload retornado em Python com Pydantic. Terceiro, use validação explícita de regras de negócio para fatos que um esquema sozinho não pode provar. Quarto, teste o contrato com fixtures e exemplos adversários em vez de acenar para um print de tela de playground e chamar de feito. A documentação das Saídas Estruturadas da OpenAI, o modelo de validador do Pydantic, as ferramentas jsonschema do Python e os próprios exemplos de eval de saída estruturada da OpenAI todos apontam nessa direção.

Pydantic é o centro de gravidade certo para Python. Ele permite que você modele a saída como tipos normais do Python, gere JSON Schema com model_json_schema() e valide JSON bruto com model_validate_json(). A documentação do Pydantic também nota que model_validate_json() é geralmente o caminho melhor do que fazer json.loads(...) primeiro e depois validar, porque essa rota de dois passos adiciona trabalho extra de parsing em Python.

Se você mantém arquivos de esquema standalone no seu repositório, ou quer que o CI valide payloads de fixture independentemente do código do modelo, o pacote jsonschema do Python oferece a verificação de contrato mais simples possível com jsonschema.validate(...). Se você quer isso no pre-commit, check-jsonschema existe especificamente como um CLI e hook pre-commit construído sobre jsonschema. Isso é um ajuste muito bom para equipes que querem que mudanças de esquema sejam revisadas como mudanças de código.

Frameworks podem reduzir a tubulação, mas não removem a necessidade de validação real. O LangChain agora seleciona automaticamente a saída estruturada nativa do provedor quando o provedor suporta e recua para uma estratégia de ferramenta caso contrário. O Instructor camada modelos de resposta Pydantic, validação, retries e suporte multi-provedor sobre chamadas de modelo. O Guardrails foca em validadores e camadas de guarda de entrada-saída. Ferramentas úteis, todas elas. Mas o esquema e as regras de negócio ainda são seus. Se você está escolhendo entre bibliotecas de nível superior, a comparação BAML vs Instructor para Python é um companheiro útil para este artigo.

Um exemplo mínimo da OpenAI e Pydantic

O exemplo menor digno de produção tem alguns não-negociáveis. Use um conjunto fechado de valores semelhantes a enum quando possível. Proíba chaves extra. Adicione descrições de campo para que o esquema seja compreensível para humanos e mais legível para o modelo. Mantenha o objeto raiz explícito e entediante. A OpenAI recomenda nomes claros mais títulos e descrições para chaves importantes, o JSON Schema usa enum para restringir valores, e o Pydantic pode fechar a forma do objeto com 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="Categoria do ticket de suporte."
    )
    priority: Literal["low", "medium", "high"] = Field(
        description="Urgência operacional."
    )
    needs_human: bool = Field(
        description="Se um humano deve revisar o caso."
    )
    summary: str = Field(
        description="Um resumo de uma frase do problema."
    )


client = OpenAI()

response = client.responses.parse(
    model="gpt-4o-2024-08-06",
    input=[
        {
            "role": "system",
            "content": "Classifique tickets de suporte. Retorne apenas o resultado estruturado.",
        },
        {
            "role": "user",
            "content": "Cliente relata cobranças duplicadas após atualizar o checkout.",
        },
    ],
    text_format=TicketClassification,
)

result = response.output_parsed
print(result.model_dump())

Dois detalhes naquele exemplo são fáceis de perder e absolutamente dignos de cuidado. extra="forbid" no lado do Pydantic espelha a ideia do JSON Schema de additionalProperties: false, que também é um requisito para esquemas de ferramentas estritas na documentação de function calling da OpenAI. E enums não são cosméticos. Eles são uma das maneiras mais simples de impedir que o modelo invente um valor que seu código não entende.

O SDK Python da OpenAI suporta client.responses.parse(...) com um modelo Pydantic fornecido como text_format, e o objeto parseado é retornado em response.output_parsed. O mesmo SDK também suporta client.chat.completions.parse(...), onde o objeto parseado vive em message.parsed. Se você quer extração direta de dados estruturados com mínimo de cola, esses helpers são o ponto de partida mais limpo.

Parse, normalize, então valide

Saídas Estruturadas e model_validate_json removem muita dor de parsing quando a pilha está alinhada de ponta a ponta. O momento em que você suporta um provedor que retorna texto de chat puro, um modelo que envolve JSON em cercas, ou um caminho de log que armazena a string de conclusão bruta, você quer um ponto de estrangulamento que transforme texto em um dict antes que o Pydantic rode.

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()

    # Prefixo comum "Certo, aqui está o JSON:" antes do objeto.
    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)

Esse helper é intencionalmente entediante. Ele lida com blocos cercados “json ... ” e um preâmbulo de linguagem natural inicial quando o payload ainda é um objeto de nível superior único. Não é um extrator completo de JSON. Se o modelo aninha chaves dentro de valores de string, o slicing ingênuo pode quebrar, e a correção certa geralmente é prompting mais estrito, conclusões vinculadas a esquema, ou uma biblioteca de parser dedicada.

Conclusões em streaming

Se você streamar tokens de chat, não execute json.loads ou model_validate_json em cada delta. Bufferize até que a API relate uma mensagem concluída (verifique seu cliente pela terminação do stream ou finish_reason), concatene o texto, então parse uma vez. A mesma regra se aplica quando argumentos de chamada de ferramenta chegam em pedaços. Você só valida após a string de argumentos estar 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)

Você ainda pode passar raw_completion_text através de parse_json_from_llm_text primeiro quando espera cercas ou babado ao redor do JSON.

Uma vez que você possui o parsing de string plana, o próximo constrangimento geralmente não é o Python, mas o dialeto JSON Schema do provedor e o que a API remota realmente aceita.

Limites de esquema do provedor (antes de ficar esperto em Python)

Não despeje cegamente qualquer saída de gerador de esquema em uma API e assuma que cada recurso do JSON Schema é suportado. A OpenAI suporta um subconjunto do JSON Schema, requer que todos os campos sejam obrigatórios para Saídas Estruturadas, requer que a raiz seja um objeto em vez de um anyOf de nível superior, e documenta limites na profundidade de aninhamento e contagem total de propriedades. Mantenha o esquema voltado para o provedor simples. Isso não é uma concessão. Isso é boa engenharia.

Se você precisa de um caminho de validação agnóstico ao provedor, ou quer validar fixtures e mocks armazenados, Pydantic mais jsonschema ainda é uma ótima combinação.

from jsonschema import validate as validate_json

schema = TicketClassification.model_json_schema()

payload = {
    "category": "bug",
    "priority": "high",
    "needs_human": True,
    "summary": "Checkout duplica cobranças após atualização.",
}

validate_json(instance=payload, schema=schema)
ticket = TicketClassification.model_validate(payload)
print(ticket)

Esse padrão é especialmente útil em testes, fixtures de contrato e integrações onde o provedor do modelo não oferece imposição nativa de saída estruturada. Apenas lembre-se de que um esquema gerado localmente pode ser mais amplo do que o subconjunto suportado por um determinado provedor, então “válido localmente” não significa automaticamente “aceito por toda API de LLM”. Observe também que alguns provedores pré-processam e cacheiam artefatos de esquema, então a primeira requisição para um novo esquema pode ser mais lenta do que requisições quentes.

Chamadas de ferramenta são um segundo contrato

Chamada de função ou ferramenta é a outra forma principal de saída estruturada. O modelo escolhe um nome e passa argumentos que devem corresponder a um JSON Schema que você controla. A OpenAI recomenda strict: true nas definições de ferramenta para que os argumentos permaneçam alinhados com esse esquema. Em pilhas pesadas em agentes, amostragem ruim se transforma em JSON de ferramenta inválido rapidamente; mantenha as configurações do sampler alinhadas com trabalho multi-etapa usando a referência de parâmetros de inferência agêntica para Qwen e Gemma.

Os snippets abaixo assumem que você já mapeou o objeto de chamada de ferramenta do provedor para uma string name e um dict arguments, por exemplo, parseando tool_calls[].function em conclusões de chat (argumentos de string JSON tornam-se json.loads primeiro). dispatch_tool é o passo após essa normalização.

Duas regras práticas ajudam em Python. Primeiro, valide o nome da ferramenta contra uma allowlist explícita antes de rotear a execução. Segundo, valide o dict de argumentos com o mesmo modelo Pydantic que você usa em testes, não com acesso de chave ad hoc. O modo de falha que você está evitando é “argumentos JSON válidos, forma errada para a ferramenta que disparou”, que passa despercebido em verificações de string.

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"ferramenta não suportada {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"enfileirado como {data['category']}",
    ),
}

Esse padrão mantém o roteamento e a validação em um só lugar. Seus manipuladores reais serão mais ricos, mas a divisão deve permanecer a mesma: nomes permitidos, argumentos tipados, então efeitos colaterais.

A validação de esquema ainda precisa de regras de negócio

Um objeto válido não é a mesma coisa que um objeto correto. A OpenAI diz isso diretamente. Saídas Estruturadas não previnem erros dentro dos valores do objeto JSON. É por isso que a FAQ “por que a validação de esquema e a validação de regras de negócio ambas importam” tem uma resposta direta. Porque uma resposta pode corresponder perfeitamente ao esquema e ainda estar errada de uma maneira que prejudica o negócio.

Aqui está um exemplo realista. A estrutura pode ser válida, mas a lógica de precificação ainda pode ser absurda.

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 é obrigatório quando discounted é verdadeiro"
                )
            if self.original_amount <= self.amount:
                raise ValueError(
                    "original_amount deve ser maior que amount"
                )
        return self

Esse validador faz algo que esquemas sozinhos frequentemente fazem mal em sistemas reais. Ele verifica semânticas entre campos após todo o modelo ter sido parseado. O model_validator do Pydantic existe exatamente para esse tipo de validação de objeto inteiro. Observe o campo Decimal | None sem um padrão. Isso mantém o campo presente enquanto ainda permite null, o que corresponde ao padrão documentado da OpenAI para valores semelhantes a opcionais sob Saídas Estruturadas estritas.

Se você quer que falhas de validação alimentem de volta no modelo automaticamente, o Instructor é uma camada prática sobre o Pydantic. Sua documentação descreve um loop de retry onde erros de validação são capturados, formatados como feedback e usados para pedir ao modelo para tentar novamente.

import instructor

retrying_client = instructor.from_provider("openai/gpt-4o", max_retries=2)

offer = retrying_client.create(
    response_model=Offer,
    messages=[
        {
            "role": "user",
            "content": (
                "Extraia a oferta deste texto. "
                "Era 49.00 USD, agora 19.00 USD."
            ),
        }
    ],
)

Esta é uma das poucas conveniências que eu recomendaria felizmente. Retries automáticos vinculados a erros reais de validação são úteis. Coerção silenciosa não é. A camada de modelo do Instructor, a documentação de retry e a documentação de validação todos se apoiam nessa mesma ideia, e estão corretos em fazê-lo.

Você pode implementar a mesma ideia sem um framework. O loop é pequeno. Peça ao modelo, valide com Pydantic, e se a validação falhar, envie os detalhes do erro de volta em uma mensagem de usuário de acompanhamento e peça apenas JSON corrigido. Limite as tentativas, logue a falha final e exponha um erro controlado aos chamadores. Quando você já confia em responses.parse ou outros helpers vinculados a esquema, pode raramente exercitar este caminho. Ainda assim, isso importa para o modo JSON, endpoints de chat mais antigos, ou qualquer gateway que lhe entregue uma string bruta.

from openai import OpenAI
from pydantic import ValidationError

client = OpenAI()

messages = [
    {"role": "system", "content": "Retorne apenas JSON que corresponda ao esquema do ticket."},
    {"role": "user", "content": "Cliente relata cobranças duplicadas após atualizar o 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"Validação falhou com {exc.errors()}. Retorne apenas JSON corrigido.",
            }
        )
else:
    raise RuntimeError("esgotadas retries de saída estruturada")

assert ticket is not None

Em serviços reais, você anexaria IDs de tracing, redigiria texto do cliente nos logs e distinguiria erros de validação recuperáveis de recusas ou respostas incompletas. A parte importante é que o retry é impulsionado pela saída real do validador, não por uma mensagem genérica de “tente novamente”.

Teste, retry e falhe fechado

O que deve acontecer quando a validação de LLM falha? Não um ombro. Rejeite o payload, logue a falha, retry com tentativas limitadas se a tarefa valer a pena, e falhe fechado em vez de normalizar lixo em algo que apenas parece aceitável. Este também é o lugar onde muitas equipes esquecem de lidar com recusas e saídas incompletas explicitamente, mesmo que a documentação do provedor lhes diga que esses caminhos existem.

Para a API de Respostas da OpenAI, o tratamento de falha deve ser código de primeira classe, não uma reflexão tardia. A variável é response de client.responses.create ou parse, não completion do streaming de chat em outro lugar neste artigo.

if response.status == "incomplete":
    raise RuntimeError(response.incomplete_details.reason)

content = response.output[0].content[0]

if content.type == "refusal":
    raise RuntimeError(content.refusal)

Isso não é super-engenharia defensiva. Está diretamente alinhado com os modos de falha documentados. Se o modelo recusa, você não está segurando um payload válido de esquema. Se a resposta é incompleta, você não está segurando um payload válido de esquema. Trate ambos como ramos explícitos em seu fluxo de controle.

Você também deve testar o contrato fora da chamada do modelo em si.

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 duplica cobranças após atualização.",
    }
    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": "Cliente quer reembolso.",
            }
        )


def test_ticket_rejects_extra_keys():
    with pytest.raises(ValidationError):
        TicketClassification.model_validate(
            {
                "category": "bug",
                "priority": "high",
                "needs_human": True,
                "summary": "Fluxo quebrado.",
                "severity": "critical",
            }
        )

Esta é a forma certa de estratégia de teste para validação de saída de LLM em Python. Valide fixtures dourados com jsonschema para que cada campo no contrato seja exercido. Valide semânticas com Pydantic, então adicione casos adversários como strings de enum ilegais, chaves extra proibidas e contradições entre campos que você se importa. Se você snapshotar saídas reais do modelo, limpe PII e trate-as como fixtures de regressão.

Se sua equipe vive na pilha da OpenAI, a API de Evals também inclui receitas de avaliação de saída estruturada especificamente para testar e iterar em tarefas que dependem de formatos legíveis por máquina. E se você mantém arquivos de esquema brutos no repositório, conecte check-jsonschema no CI ou pre-commit. Entregue contratos, não vibes.

Verificações de produção que salvam você mais tarde

Quando a validação falha, a resposta da FAQ é direta. Rejeite o payload, logue o porquê, retry com feedback direcionado quando a tarefa valer a pena outra tentativa, e falhe fechado em vez de coagir dados ruins para uma fila.

Uma lista de verificação operacional curta ajuda equipes a evitar incidentes repetidos.

  • Logue a versão do esquema ou um hash do JSON Schema que você enviou ao provedor para que possa replayar falhas com precisão.
  • Redija entradas e saídas do modelo nos logs. Logs estruturados são inúteis se vazarem texto do cliente.
  • Emita contadores ou métricas para taxa de recusa, taxa de resposta incompleta, taxa de falha de validação e taxa de sucesso de reparo. Picos lá batem adivinhar quando uma mudança de modelo ou prompt foi lançada.

Mais ampla observabilidade para sistemas de LLM orientação ajuda a conectar esses sinais em dashboards, traces e revisões de SLO uma vez que os contadores existem.

A melhor prática não é complicada. Use Saídas Estruturadas do lado do provedor ou esquemas de ferramenta estritos quando puder. Normalize texto bruto quando precisar. Espelhe o contrato em Python com Pydantic. Adicione validação de regras de negócio para o que o esquema não pode provar. Lide com recusas e respostas incompletas como ramos normais. Teste o contrato até que deixe de ser uma demo e comece a ser software. Qualquer coisa menos é apenas cosplay de engenharia de prompt.

Assinar

Receba novos artigos sobre sistemas, infraestrutura e engenharia de IA.