Por Que Structured Output Importa para LLMs Locais

E aí, dev! Vamos lá: se sua aplicação lida com dados sensíveis — anotações domésticas, prontuários, logs internos — rodar o modelo localmente é quase obrigatório. Mas rodar o modelo é só o primeiro passo. Na prática, a resposta do LLM vai alimentar outro componente: um scheduler, um writer de banco, um modelo em cloud. Texto livre quebra esse pipeline. Você precisa de estrutura previsível.

Structured Output resolve exatamente isso: você define o formato esperado (um schema) antes, o runtime de serving restringe a geração a esse schema, e o modelo te devolve um objeto Python que seu código consegue parsear e validar sem gambiarra. Neste guia, usamos Gemma 4 (4B) como LLM local, Ollama como runtime e Pydantic para definir e validar o schema.

O padrão funciona — mas tem uma pegadinha que derruba quase todo mundo na primeira implementação. (Se quiser ver a análise original, o artigo do Towards Data Science cobre o tema em detalhe.)

Developer setting up local LLM with Ollama to enforce JSON schema via Pydantic Software Concept Art

Mão na Massa: Um Caso de Casa Inteligente

O Cenário

O usuário pergunta: "Devo ligar a lava-louças agora ou depois?" Antes de responder, o app precisa extrair informações de dispositivos, restrições de horário e tarifas de energia das anotações privadas da casa. Um LLM local sanitiza essas notas em um objeto estruturado, que depois vai para um LLM em cloud para raciocínio.

Passo 1 — Defina o Schema com Pydantic

from pydantic import BaseModel
from typing import List

class DeviceToSchedule(BaseModel):
    name: str
    runtime_minutes: int
    earliest_start: str
    latest_end: str

class SchedulingContext(BaseModel):
    current_time: str
    device_in_question: str
    controller_capacity_kw: float
    electricity_prices: dict[str, float]
    devices_to_schedule: List[DeviceToSchedule]

Passo 2 — Instale o Ollama e Baixe o Modelo

# macOS / Linux
curl -fsSL https://ollama.com/install.sh | sh

# Baixa a variante compacta 4B
ollama pull gemma4:4b

# Client Python + Pydantic
pip install ollama pydantic

Passo 3 — Conecte Pydantic ao Ollama

import ollama
from pydantic import BaseModel

response = ollama.chat(
    model="gemma4:4b",
    messages=[{"role": "user", "content": prompt}],
    format=SchedulingContext.model_json_schema(),  # força o schema
)

# Parse do JSON de volta para um objeto Python validado
context = SchedulingContext.model_validate_json(response["message"]["content"])

Dois pontos-chave: model_json_schema() converte o modelo Pydantic em JSON Schema que o Ollama usa para restringir a geração. model_validate_json() parseia a resposta de volta em um objeto tipado — prontinho para o próximo passo do pipeline.

Python code defining nested Pydantic model for smart-home scheduling context Technical Structure Concept

A Pegadinha: Estrutura Válida ≠ Conteúdo Correto

Roda o call one-shot e inspeciona o resultado. Na superfície, tudo certo: JSON válido, schema correto, Pydantic parseia sem erro. Mas olha só — o aspirador robô aparece em devices_to_schedule, mesmo com as anotações dizendo claramente que ele terminou às 16:10 e não precisa rodar mais hoje.

Essa é a distinção crítica:

Structured output força o formato da resposta. Não garante, por si só, que o conteúdo esteja correto.

Por Que o Modelo Erra

Em um único call, o Gemma 4B precisa simultaneamente:

  1. Determinar quais dispositivos ainda precisam de agendamento
  2. Extrair os fatos relevantes desses dispositivos
  3. Mapear os fatos para os campos corretos do schema
  4. Montar o objeto aninhado final

É decisão demais coordenada para um modelo local pequeno. Quanto mais complexo o schema, maior a pressão para coordenar decisões numa única geração — e maior a taxa de erro.

A Solução: Decompor a Tarefa

Quebra o trabalho em duas etapas focadas.

Passo 1 — Determinar escopo (schema pequeno, tarefa estreita):

class SchedulingScope(BaseModel):
    devices_to_schedule: List[str]
scope = SchedulingScope.model_validate_json(
    ollama.chat(
        model="gemma4:4b",
        messages=[{"role": "user", "content": scope_prompt}],
        format=SchedulingScope.model_json_schema(),
    )["message"]["content"]
)

Resultado: ["dishwasher", "ev_charger", "washing_machine"] — aspirador robô corretamente excluído.

Passo 2 — Extrair fatos desses dispositivos selecionados para o SchedulingContext completo, alimentando o output do passo 1 no prompt.

Comparação

AbordagemEstrutura VálidaConteúdo Correto
Call one-shot✅❌ (aspirador incluído)
Decomposição em 2 passos✅✅ (3 dispositivos batem com as notas)

Armadilhas Frequentes

  • Nunca pule a validação. Sempre chame model_validate_json() — até modelo com schema forçado pode emitir output malformado em edge cases.
  • Cuidado com profundidade do schema. Modelos aninhados amplificam o problema de coordenação. Achate quando possível.
  • Tamanho do modelo importa. Modelos 4B toleram schemas rasos; aninhamento profundo exige modelos maiores ou mais decomposição.

Terminal running Ollama pull command for Gemma 4B model on local server Dev Environment Setup

Considerações Finais

LLM local + structured output é um padrão limpo para workflows com dados sensíveis: define o schema com Pydantic, restringe a geração via argumento format do Ollama, parseia a resposta de volta num objeto tipado. A implementação é realmente simples.

Mas a lição que separa protótipo de produção é essa: estrutura válida não garante conteúdo correto. Quando um modelo pequeno trava com schema complexo, decomposição — separar determinação de escopo da extração de fatos — é a solução prática.

Próximos passos pra explorar:

  • Testar outlines ou grammars do llama.cpp para restrições mais rígidas em tempo de geração
  • Comparar schema-following entre tamanhos de modelo (4B vs. 12B vs. 27B)
  • Adicionar uma camada de validação que cruze fatos extraídos com o texto-fonte

Pra times que rodam workflows confiáveis em escala, a mesma mentalidade de confiabilidade se aplica — veja como o Temporal sustenta operações cloud confiáveis na Netflix e como a Meta migrou ingestão em escala de petabytes sem perda de dados.

Este conteúdo foi elaborado com o auxílio de ferramentas de IA, com base em fontes confiáveis, e revisado pela nossa equipe editorial antes da publicação. Não substitui o aconselhamento de um profissional especializado.