Domínio 4 — Prompt Engineering & Structured Output · Lição 5 de 7

Validação, retry e revisão multi-pass

Objetivos de aprendizagem

Retry com feedback do erro

Depois que o schema elimina os erros de sintaxe (Lição 4), sobram os erros semânticos, detectados por validação programática (Pydantic, regras de negócio). O padrão eficaz de correção não é simplesmente repetir a chamada — é o retry com feedback: uma nova requisição contendo (1) o documento original, (2) a extração que falhou e (3) o erro de validação específico. O erro concreto direciona o modelo para a correção; repetir sem feedback só re-amostra o mesmo erro.

import anthropic
from pydantic import BaseModel, ValidationError
from typing import Optional, List

class Invoice(BaseModel):
    supplier: str
    stated_total: Optional[float]
    line_items: List[float]

client = anthropic.Anthropic()

SCHEMA = {
    "type": "json_schema",
    "schema": {
        "type": "object",
        "properties": {
            "supplier": {"type": "string"},
            "stated_total": {"type": ["number", "null"]},
            "line_items": {"type": "array", "items": {"type": "number"}},
        },
        "required": ["supplier", "stated_total", "line_items"],
        "additionalProperties": False,
    },
}

def extract_once(prompt: str) -> str:
    response = client.messages.create(
        model="claude-opus-5",
        max_tokens=1024,
        output_config={"format": SCHEMA},
        messages=[{"role": "user", "content": prompt}],
    )
    return next(b.text for b in response.content if b.type == "text")

def extract_with_retry(document: str, max_retries: int = 2) -> Invoice:
    prompt = "<document>\n" + document + "\n</document>\nExtraia os dados."
    last_error = "sem erro"
    for attempt in range(max_retries + 1):
        raw = extract_once(prompt)
        try:
            invoice = Invoice.model_validate_json(raw)
            # validação semântica além do schema:
            calculated = sum(invoice.line_items)
            if invoice.stated_total is not None:
                if abs(calculated - invoice.stated_total) > 0.01:
                    raise ValueError(
                        f"line_items somam {calculated}, "
                        f"mas stated_total é {invoice.stated_total}"
                    )
            return invoice
        except (ValidationError, ValueError) as e:
            last_error = str(e)
            # retry COM feedback: documento + extração falha + erro específico
            prompt = (
                "<document>\n" + document + "\n</document>\n"
                "<failed_extraction>\n" + raw + "\n</failed_extraction>\n"
                "<validation_error>\n" + last_error + "\n</validation_error>\n"
                "Corrija a extração considerando o erro acima."
            )
    raise RuntimeError(f"Extração falhou após retries: {last_error}")

Quando o retry é inútil

Retry corrige erros de formato e de estrutura (data no formato errado, valor no campo errado, soma inconsistente por leitura equivocada). Retry não corrige ausência de informação: se o campo exigido só existe em um anexo que não foi fornecido, nenhuma quantidade de tentativas fará o valor aparecer — cada retry só queima tokens e, pior, aumenta a chance de fabricação. Diagnóstico na prática: se a validação falha porque um campo é null e o documento realmente não traz a informação, o caminho é aceitar o null (schema nullable), buscar a fonte que falta ou rotear para revisão humana — não retry.

Padrão de prova: "a extração de X falha repetidamente; logs mostram que X só aparece no documento Y, não enviado" → a resposta correta nunca é "aumentar retries" nem "melhorar o prompt": é fornecer a fonte correta ou tratar como ausente.

Autovalidação no schema: calculated_total e conflict_detected

Parte da validação pode ser embutida na própria extração:

Feedback loop de falsos positivos: detected_pattern

No cenário de revisão de código, incluir no achado estruturado um campo detected_pattern — qual construção de código disparou o achado (ex.: "atribuição dentro de condição", "SQL concatenado") — permite agrupar os achados que os desenvolvedores descartam e identificar sistematicamente quais padrões geram falso positivo. Sem esse campo, cada descarte é um dado isolado; com ele, o time enxerga "80% dos descartes vêm do padrão X" e corrige o critério certo (ou desabilita a categoria — Lição 2).

Self-review e suas limitações

Pedir ao modelo "revise seu próprio código antes de finalizar" na mesma sessão tem eficácia limitada: o modelo mantém o contexto de raciocínio da geração e tende a não questionar as próprias decisões — os mesmos vieses que produziram o bug o escondem na revisão. Extended thinking não elimina essa limitação, pois o problema é o contexto compartilhado, não a profundidade do raciocínio.

A alternativa eficaz é uma instância independente: uma segunda chamada/sessão que recebe o código (e os requisitos), sem o histórico de raciocínio da geração. Sem o compromisso com as decisões anteriores, ela questiona premissas e captura problemas sutis que o self-review deixa passar. É o mesmo princípio do exam guide para CI: a sessão que gerou o código é menos eficaz revisando as próprias mudanças do que uma instância de revisão independente. Uma variação útil: a passada de verificação reporta a confiança por achado, permitindo rotear achados de baixa confiança para revisão humana (calibração no Domínio 5).

Revisão multi-pass: por arquivo + integração

Revisar um PR de muitos arquivos em uma única passada dilui a atenção: profundidade desigual entre arquivos, bugs óbvios perdidos, feedback contraditório (o mesmo padrão sinalizado num arquivo e aprovado noutro). A estrutura recomendada é multi-pass:

  1. Passadas locais, por arquivo: cada arquivo é analisado individualmente, com profundidade consistente, focando problemas locais.
  2. Passada de integração, cross-file: uma análise separada examina o conjunto — fluxo de dados entre arquivos, contratos de interface, duplicações e inconsistências entre os achados locais.
flowchart TD
    PR[PR com 14 arquivos] --> F1[Pass 1: arquivo A - problemas locais]
    PR --> F2[Pass 1: arquivo B - problemas locais]
    PR --> F3[Pass 1: arquivo C...N - problemas locais]
    F1 --> INT[Pass 2: integração cross-file]
    F2 --> INT
    F3 --> INT
    INT --> OUT[Achados consolidados e consistentes]
  

Pegadinhas da prova

Resumo em 5 linhas

  1. Retry eficaz = documento + extração falha + erro de validação específico; repetir sem feedback re-amostra o erro.
  2. Retry não cria informação ausente da fonte — nesse caso: nullable, outra fonte ou revisão humana.
  3. calculated_total vs stated_total e conflict_detected embutem autovalidação na extração.
  4. detected_pattern em cada achado permite análise sistemática de falsos positivos a partir dos descartes.
  5. Self-review na mesma sessão é fraco (contexto compartilhado); use instância independente e revisão multi-pass (por arquivo + integração cross-file).

Documentação oficial

Questões de fixação

1. A validação Pydantic rejeita uma extração porque due_date veio como "15/12/2026" em vez de ISO 8601. Qual é o conteúdo correto da requisição de retry?

2. Um campo "número da apólice" falha validação em 100% dos retries para certo lote. Investigando, descobre-se que essa informação só existe no anexo B, que não é enviado ao modelo. O que fazer?

3. Por que extrair calculated_total (soma dos itens) junto com stated_total (total impresso no documento)?

4. Uma equipe usa a mesma sessão de Claude Code que gerou o código para revisá-lo em seguida ("agora revise o que você escreveu com muito cuidado"). Bugs sutis continuam passando. Qual mudança o exame espera?

5. Para que serve o campo detected_pattern em achados estruturados de revisão de código?