Output Layer
TL;DR
A Output Layer define em que formato o modelo entrega o resultado — e por que isso é uma decisão arquitetural, não estética. Markdown para leitura humana, JSON tipado para consumo por código, schema rígido para pipelines que não toleram variação. As decisões-chave: formato primário, seções obrigatórias, como sinalizar incerteza, e se o output é ação direta ou sugestão. Sistemas que definem o contrato de saída antes do prompt conseguem encadear pipelines determinísticos; sistemas que não definem produzem texto bonito que nenhum código consegue parsear.
O problema que a Output Layer resolve
Por que definir o output antes do prompt e não depois?
Porque o prompt precisa saber o que exigir. Se você define que o output é um JSON com campos
risk_levelerecommendation, o prompt instrui o modelo a preencher esses campos. Se você define o output depois, frequentemente descobre que o prompt prometeu algo que o modelo não consegue manter de forma consistente — e você reescreve o prompt cinco vezes para alinhar. A ordem correta é: contrato de saída primeiro, instrução depois.
Você finalmente conseguiu um modelo respondendo bem. Bom. Agora o próximo passo do pipeline precisa dos dados em JSON para gravar no banco. O modelo responde em markdown com o JSON dentro de um bloco de código. O parser quebra na metade dos casos porque às vezes o modelo coloca json no fence e às vezes não coloca. Você adiciona uma instrução no prompt: “responda em JSON”. O modelo começa a responder em JSON mas às vezes adiciona um parágrafo introdutório antes do {. O parser quebra de novo.
Esse é o problema da Output Layer indefinida: o formato do output é tratado como detalhe, mas quem consome o output (código, outro modelo, usuário final) precisa de uma interface previsível. “Texto em linguagem natural” é suficiente quando o consumidor é humano. Quando o consumidor é código, você precisa de um contrato de saída.
A Output Layer define esse contrato antes do prompt — porque sabendo o que precisa sair, você sabe o que o prompt precisa exigir.
flowchart LR subgraph "Sem Output Layer" A1["Prompt: 'responda útil'"] A2["Modelo decide\nformato por conta"] A3["Markdown / JSON / texto misto\n→ parser quebra"] A4["Retrabalho:\nreescrever prompt\naté funcionar"] end subgraph "Com Output Layer" B1["Output schema\ndefinido primeiro"] B2["Prompt instrui\nformato exato"] B3["Structured output\ncom schema forçado"] B4["Pipeline downstream\nconsume sem quebrar"] end A1 --> A2 --> A3 --> A4 B1 --> B2 --> B3 --> B4 style A4 fill:#fff5f5,stroke:#ff6b6b style B4 fill:#f0fff4,stroke:#51cf66
O que é esta camada
A Output Layer é o contrato de saída do sistema. Define o que sai, em qual estrutura, com quais campos obrigatórios, e como o modelo sinaliza incerteza ou casos fora do esperado.
Template mínimo (adaptado do thread @hooeem):
output:
primary_format: "markdown | json | xml | tabela | checklist | texto-livre"
required_sections:
- "<seção obrigatória 1>"
- "<seção obrigatória 2>"
confidence_level: "obrigatório | opcional | não-aplicável"
uncertainty_flags:
- "campo 'assumptions': premissas que o modelo fez"
- "campo 'missing_data': o que faltou pra responder com certeza"
actionability: "ação direta | sugestão com raciocínio | análise pura"Para output não-trivial consumido por código, vale formalizar via JSON Schema, Pydantic ou TypeScript types. Modelos de fronteira suportam structured outputs que garantem aderência ao schema — o modelo é forçado a produzir o formato certo, não apenas instruído.
Decisões-chave
1. Markdown vs JSON — qual o consumidor do output. Markdown é legível por humano; JSON é consumível por código. A escolha não é estética — é quem vai ler. Misturar (“responda em markdown, com um bloco JSON quando relevante”) é o pior dos dois mundos: o humano precisa interpretar o JSON, o código precisa parsear o markdown. Quando o output vai para uma pipeline, vá direto para JSON estrito com schema validado.
2. Schema rígido vs leve. Schema rígido (campos obrigatórios, tipos validados, sem campos extras) reduz alucinação de campos, facilita validação downstream, mas pode penalizar quando o modelo precisa sinalizar caso fora do esperado. Schema leve (required mínimo, muitos optional) dá flexibilidade mas exige validação pós-hoc mais robusta. Regra prática: rígido para pipelines automatizados; leve para assistentes com revisão humana.
3. Confidence como campo obrigatório muda comportamento. Forçar o modelo a emitir um campo confidence (high|medium|low) o faz se calibrar durante a geração — ele precisa escolher um nível, o que muda como constrói a resposta. Útil como entrada para a Guardrail Layer: outputs com confidence: low rodam para revisão humana antes de ir à produção.
4. Uncertainty flags nomeados previnem hedge prosaico. Campos como assumptions, missing_data e caveats criam um lugar explícito para o modelo guardar incerteza. Sem eles, a incerteza vira linguagem hedgeada (“pode ser que…”, “em geral…”) no meio do texto útil — impossível de parsear ou filtrar.
5. Actionability: output como ação vs como sugestão. Output que é a ação (uma chamada de função pronta para executar) tem latência mais baixa e menos risco de interpretação incorreta. Output que sugere uma ação preserva humano-no-loop. A escolha depende de quão irreversível é a ação e de quanto você confia na qualidade atual do modelo para esse tipo de tarefa.
Schema rígido vs leve — o mesmo caso, dois contratos
A Decisão-chave #2 fica mais concreta lado a lado. Um sistema de extração de dados de currículos precisa emitir o cargo mais recente do candidato. Duas formas de modelar o mesmo campo:
# Schema rígido — pipeline automatizado, sem revisão humana
class CargoRigido(BaseModel):
model_config = ConfigDict(extra="forbid")
titulo: str
empresa: str
data_inicio: str # ISO 8601, obrigatório
data_fim: str | None # None só se for o cargo atual
# Schema leve — assistente de triagem, com revisão humana
class CargoLeve(BaseModel):
titulo: str
empresa: str | None = None
periodo: str | None = None # texto livre: "2021-atual", "3 anos", etc.
observacao: str | None = NoneCom CargoRigido, um currículo que descreve o período como “há cerca de 3 anos” (sem data exata) falha a validação — o pipeline precisa de outra etapa (normalização de data, ou rejeição com fila de correção manual) antes de aceitar o registro. Com CargoLeve, o mesmo currículo passa direto, mas o campo periodo chega como texto livre para o sistema downstream — que agora precisa ele mesmo lidar com a variação que o schema rígido teria barrado. Não existe opção “sem custo”: schema rígido move o custo de tratar variação para antes da validação (o modelo, ou uma etapa de normalização); schema leve move esse custo para depois (o consumidor do JSON).
Na prática, a escolha entre os dois raramente é binária ao longo do tempo: sistemas costumam começar com schema leve (menos atrito para lançar), acumular casos de erro no consumidor, e migrar campos específicos para rígido conforme o volume de dados ruins naquele campo justifica o custo de validação antecipada. titulo e empresa tendem a enrijecer cedo (baixa ambiguidade); periodo/datas tendem a ficar leves por mais tempo (alta variação de formato na fonte).
Vale notar que “rígido” e “leve” não são as únicas opções no meio do caminho: Pydantic também permite extra="allow", que aceita campos extras sem defini-los no schema — meio-termo útil quando você quer capturar o inesperado sem travar a validação, mas ainda quer os campos conhecidos tipados.
Casos práticos
Cenário 1 — O pipeline que quebra silenciosamente
Sistema de análise de contratos: modelo lê PDFs jurídicos e extrai cláusulas de risco. Output definido como “lista de riscos em markdown”. Funciona bem no protótipo — um analista lê a lista e toma decisão. Na versão v2, querem automatizar: outro sistema lê a lista de riscos e dispara alertas.
O problema: “lista em markdown” tem 15 formatos possíveis (com traço, com asterisco, numerada, com sub-listas). O segundo sistema parseia com regex e quebra em 40% dos casos. A solução não é melhorar o regex — é definir o contrato de saída antes de construir o pipeline:
{
"risks": [
{
"clause": "string",
"risk_level": "low|medium|high|critical",
"description": "string",
"recommendation": "string"
}
],
"confidence": "high|medium|low",
"missing_data": ["string"]
}Com esse schema, o segundo sistema tem uma interface estável. Mudanças no modelo não quebram o pipeline — só os campos do contrato importam.
Cenário 2 — Confidence como roteador
Sistema de triagem de suporte que classifica tickets automaticamente. Output com confidence obrigatório:
output:
primary_format: json
required_sections:
- category
- priority
- confidence
- reasoning
actionability: "ação direta: auto-assign abaixo de 'medium'; revisão humana em 'low'"Com confidence: high → o ticket é roteado automaticamente. Com confidence: medium → roteado automaticamente mas com flag para revisão aleatória (10% de sampling). Com confidence: low → vai sempre para fila de revisão humana. O mesmo modelo, o mesmo prompt — mas o output estruturado permite políticas de roteamento por confiança.
Cenário 3 — Output como ação direta: geração de código
Sistema de refatoração automática: o modelo lê um arquivo com um code smell (função duplicada, import morto) e produz a correção. Aqui a Decisão-chave #5 (actionability) pesa diferente dos dois cenários anteriores — o output não é uma sugestão que um humano lê, é uma ação executável: um diff que vai direto para o disco.
output:
primary_format: json
required_sections:
- file_path
- diff # formato unified diff, aplicável via patch
- risk_level # baixo: renomear variável; alto: mudar assinatura pública
- reversible # true se o diff pode ser revertido sem efeito colateral
actionability: "ação direta condicionada: aplica sozinho se risk_level=baixo E reversible=true; abre PR para revisão nos demais casos"A diferença central em relação ao Cenário 2 (confidence como roteador): ali o roteamento decidia quem revisa o quê; aqui risk_level e reversible decidem se existe revisão nenhuma. Isso muda o cálculo de risco da Output Layer inteira — um campo mal calibrado (reversible: true quando na verdade não é) não gera um ticket mal-roteado, gera uma mudança de código aplicada sem revisão. Por isso, sistemas de output-como-ação-direta tipicamente compensam com um schema mais conservador do que sistemas de output-como-sugestão: preferem reversible: false por padrão (fail-safe) e exigem que o modelo comprove reversibilidade — por exemplo, anexando o diff inverso — antes de assumir true.
Esse é o mesmo eixo da Decisão-chave #5 levado ao extremo: quanto mais irreversível a ação e menos madura a confiança no modelo para aquela classe de tarefa, mais o contrato de saída precisa carregar campos que forcem o modelo a justificar por que é seguro agir sozinho — não apenas o que fazer.
Instruction-only vs structured outputs — quando cada um
A distinção prática entre pedir JSON no prompt versus usar structured outputs da API:
| Critério | Instruction-only (“responda em JSON”) | Structured outputs (schema na API) |
|---|---|---|
| Garantia de formato | Nenhuma — o modelo pode violar | Garantida — forçada na camada de sampling |
| Edge cases | Modelo pode adicionar prosa antes do { | Impossível sair do schema |
| Overhead | Nenhum — só texto no prompt | Pequena latência adicional na API |
| Modelos suportados | Todos | GPT-4o/4.1, Claude 3.5+, Gemini 1.5+ |
| Quando usar | Protótipos, saída para humanos | Pipelines em produção, dados críticos |
A regra de ouro: se um humano vai ler o output e corrigir se necessário, instruction-only é suficiente. Se o output vai direto para código ou banco de dados sem revisão humana, use structured outputs.
Validação pós-output
Se structured outputs "garantem" o schema, por que ainda preciso validar depois?
Porque a garantia da API é sintática, não semântica. Structured outputs garantem que o JSON tem a forma certa — os campos existem, os tipos batem. Eles não garantem que o
risk_level: "critical"faz sentido dado o texto do contrato, nem que o modelo não vai, um dia, rodar sem o schema forçado (fallback de instruction-only, versão antiga da API, provider diferente). Validação pós-output é a rede de segurança que trata o schema como contrato, não como promessa.
Mesmo com structured outputs habilitados, dois tipos de falha continuam possíveis na prática — e cada um pede um tratamento diferente.
Falha 1 — prosa antes do JSON. Acontece quando você depende de instruction-only (sem schema forçado pela API) e o modelo, sob pressão de contexto longo ou instrução ambígua, decide “ajudar” o consumidor com uma frase de abertura:
import json
resposta_do_modelo = '''Claro! Aqui está a análise em JSON:
{
"risk_level": "high",
"clause": "Cláusula 4.2 - rescisão unilateral",
"recommendation": "Revisar com jurídico antes de assinar"
}'''
dados = json.loads(resposta_do_modelo)
# json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)O parser falha na primeira linha porque json.loads espera que a string inteira seja JSON válido — “Claro! Aqui está…” não é. A correção ingênua (resposta_do_modelo.strip()) não resolve nada, porque o problema não é espaço em branco, é texto de verdade antes da estrutura. Duas saídas práticas: (1) extrair o primeiro { até o } correspondente com uma busca de substring antes do parse, tratando qualquer coisa fora dele como ruído descartável; ou (2) eliminar a classe inteira do problema migrando para structured outputs, onde a API restringe o sampling para nunca emitir texto fora do schema.
Falha 2 — campo extra inesperado rejeitado pelo schema. Acontece do lado oposto: o schema é rígido demais e o modelo (corretamente, do ponto de vista dele) tenta comunicar algo que o contrato não previu:
from pydantic import BaseModel, ConfigDict, ValidationError
class RiscoContrato(BaseModel):
model_config = ConfigDict(extra="forbid") # rígido: nenhum campo fora do schema
clause: str
risk_level: str
recommendation: str
payload_do_modelo = {
"clause": "Cláusula 4.2 - rescisão unilateral",
"risk_level": "high",
"recommendation": "Revisar com jurídico antes de assinar",
"confidence_note": "Baixa confiança: cláusula redigida de forma ambígua",
}
RiscoContrato(**payload_do_modelo)
# pydantic_core._pydantic_core.ValidationError: 1 validation error for RiscoContrato
# confidence_note
# Extra inputs are not permitted [type=extra_forbidden, ...]O modelo tentou sinalizar incerteza (confidence_note) porque a Decisão-chave #4 desta camada (uncertainty flags) não previu esse campo no schema — e extra="forbid" descarta a mensagem inteira em vez de só o campo excedente. Aqui a correção não é relaxar para extra="ignore" cegamente (isso descarta silenciosamente sinais que podem importar, como incerteza) — é decidir explicitamente: schema rígido pipeline-a-pipeline deve antecipar os campos de incerteza que quer capturar (voltando à Decisão-chave #4), e usar extra="forbid" só depois de o schema já cobrir os campos que o modelo plausivelmente vai querer emitir. extra="ignore" é aceitável como rede de segurança temporária durante prototipagem, nunca como decisão permanente para pipelines críticos.
A lição comum às duas falhas: validação pós-output não é feature opcional de “hardening” — é onde a Output Layer encontra a realidade de que o modelo é probabilístico e o contrato é uma expectativa, não uma lei física.
Armadilhas comuns
Definir output depois do prompt
A ordem importa: defina o contrato de saída antes de escrever o system prompt. Saber o que precisa sair informa o que o prompt precisa exigir. Quando o output é definido depois, você frequentemente descobre que o prompt prometeu um formato que o modelo não consegue manter de forma consistente — e reescreve o prompt cinco vezes para corrigir.
Misturar markdown e JSON no mesmo output
A instrução “responda em markdown, com blocos JSON onde relevante” parece flexível mas é um parser pesadelo. O consumidor de código vai encontrar 10 variações de como o modelo coloca o JSON dentro do markdown. Se o consumidor é código, output é JSON. Se é humano, output é markdown. Não misture — escolha baseado no consumidor, não na conveniência de escrita do prompt.
Não usar structured outputs quando disponível
Instruction-only (“responda em JSON”) vs structured outputs (schema forçado pela API) não são equivalentes. Com instruction-only, o modelo pode violar o formato sob pressão de contexto longo ou em edge cases. Structured outputs forçam o schema na camada de sampling — o modelo literalmente não consegue produzir tokens fora do schema. Para pipelines em produção, use structured outputs sempre que disponível.
Como explicar em inglês
The Output Layer defines the output contract of the system — the format, required sections, uncertainty signals, and whether the output is a direct action or a suggestion. The key insight: the output format is an architectural decision, not an aesthetic one. When the consumer is code, you need structured outputs with enforced schemas, not markdown prose. The Output Layer is best defined before writing the system prompt — knowing what needs to come out tells you what the prompt needs to require.
In a technical interview, you might say:
“I define the output contract before writing the system prompt — because what the prompt needs to require depends on what needs to come out. For pipelines, I use schema-enforced structured outputs rather than just telling the model to ‘respond in JSON’: instruction-only doesn’t guarantee the format under edge cases. I also include a confidence field as an output requirement — it lets the Guardrail Layer route low-confidence outputs to human review instead of directly to production.”
| PT | EN |
|---|---|
| Camada de saída | Output Layer |
| Contrato de saída | Output contract |
| Saída estruturada | Structured output |
| Schema de saída | Output schema |
| Nível de confiança | Confidence level |
| Flag de incerteza | Uncertainty flag |
| Acionabilidade | Actionability |
| Modo de ação direta | Direct action mode |
| Saídas forçadas por schema | Schema-enforced outputs |
O que vem a seguir
Com o contrato de saída definido, você sabe o que o sistema precisa produzir. Mas e quando o modelo precisa de informação que não está nos pesos de treino — documentos internos da empresa, dados que mudam frequentemente, fontes externas verificáveis? Isso é responsabilidade da Retrieval Layer: define quando buscar informação externa, de quais fontes, com que hierarquia de prioridade.
- 06 - Retrieval Layer — quando e como puxar conhecimento externo para o sistema
- Structured Outputs — trilha completa: schemas, Pydantic, APIs de structured outputs
Onde aprofundar
- Structured Outputs — trilha completa sobre schemas tipados, Pydantic, structured outputs nas APIs dos modelos.
- structured output — definição canônica e casos de uso.
Veja também
- 03 - Prompt Layer — o comportamento que produz o output
- 04 - Context Layer — o conhecimento que alimenta o output
- 09 - Evaluation Layer — a rubrica aplica sobre o que sai aqui
- 10 - Guardrail Layer —
confidencedo output pode acionar guardrails
Fontes
- @hooeem — Become an AI Engineer, chapter #18, Step 4 (Output layer template). X/Twitter, 2025.
- OpenAI — Structured Outputs guide. Schema enforcement na API.
- Anthropic — Tool use with Claude. JSON schema em tool calls.