Restringindo LLMs com Saída Estruturada: Ollama, Qwen3 e Python ou Go
Algumas maneiras de obter saídas estruturadas do Ollama
Modelos de Linguagem de Grande Escala (LLMs) são poderosos, mas em produção raramente queremos parágrafos de texto livre. Em vez disso, queremos dados previsíveis: atributos, fatos ou objetos estruturados que você possa alimentar em um aplicativo. Isso é Saída Estruturada de LLM.
Há algum tempo, o Ollama introduziu suporte para saída estruturada (anúncio), tornando possível restringir as respostas de um modelo para corresponder a um esquema JSON. Isso desbloqueia pipelines de extração de dados consistentes para tarefas como catalogar recursos de LLM, realizar benchmark de modelos ou automatizar a integração de sistemas.

Neste post, cobriremos:
- O que é saída estruturada e por que é importante
- Uma maneira simples de obter saída estruturada de LLMs
- Como a nova funcionalidade do Ollama funciona
- Exemplos de extração de capacidades de LLM:
O Que é Saída Estruturada?
Normalmente, LLMs geram texto livre:
“O Modelo X suporta raciocínio com chain-of-thought, tem uma janela de contexto de 200K e fala Inglês, Chinês e Espanhol.”
Isso é legível, mas difícil de analisar.
Em vez disso, com saída estruturada, pedimos um esquema estrito:
{
"name": "Modelo X",
"supports_thinking": true,
"max_context_tokens": 200000,
"languages": ["Inglês", "Chinês", "Espanhol"]
}
Este JSON é fácil de validar, armazenar em um banco de dados ou alimentar uma interface de usuário (UI).
Maneira Simples de Obter Saída Estruturada de LLM
Os LLMs às vezes entendem o que é o esquema e podemos pedir ao LLM que retorne a saída em JSON usando um esquema particular. O modelo Qwen3 da Alibaba é otimizado para raciocínio e respostas estruturadas. Você pode instruí-lo explicitamente a responder em JSON.
Exemplo 1: Usando Qwen3 com ollama em Python, solicitando JSON com esquema
import json
import ollama
prompt = """
Você é um extrator de dados estruturados.
Retorne apenas JSON.
Texto: "Elon Musk tem 53 anos e mora em Austin."
Esquema: { "name": string, "age": int, "city": string }
"""
response = ollama.chat(model="qwen3", messages=[{"role": "user", "content": prompt}])
output = response['message']['content']
# Analisar JSON
try:
data = json.loads(output)
print(data)
except Exception as e:
print("Erro ao analisar JSON:", e)
Saída:
{"name": "Elon Musk", "age": 53, "city": "Austin"}
Aplicando Validação de Esquema com Pydantic
Para evitar saídas malformadas, você pode validar contra um esquema Pydantic em Python.
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
city: str
# Suponha que 'output' seja a string JSON do Qwen3
data = Person.model_validate_json(output)
print(data.name, data.age, data.city)
Isso garante que a saída esteja em conformidade com a estrutura esperada.
Saída Estruturada do Ollama
O Ollama agora permite passar um esquema no parâmetro format. O modelo é então restringido a responder apenas em JSON que esteja em conformidade com o esquema (docs).
Em Python, você geralmente define seu esquema com Pydantic e deixa o Ollama usar isso como um esquema JSON.
Exemplo 2: Extrair Metadados de Recursos de LLM
Suponha que você tenha um trecho de texto descrevendo as habilidades de um LLM:
“O Qwen3 tem forte suporte multilíngue (Inglês, Chinês, Francês, Espanhol, Árabe). Permite etapas de raciocínio (chain-of-thought). A janela de contexto é de 128K tokens.”
Você quer dados estruturados:
from pydantic import BaseModel
from typing import List
from ollama import chat
class LLMFeatures(BaseModel):
name: str
supports_thinking: bool
max_context_tokens: int
languages: List[str]
prompt = """
Analise a descrição a seguir e retorne os recursos do modelo apenas em JSON.
Descrição do modelo:
'O Qwen3 tem forte suporte multilíngue (Inglês, Chinês, Francês, Espanhol, Árabe).
Permite etapas de raciocínio (chain-of-thought).
A janela de contexto é de 128K tokens.'
"""
resp = chat(
model="qwen3",
messages=[{"role": "user", "content": prompt}],
format=LLMFeatures.model_json_schema(),
options={"temperature": 0},
)
print(resp.message.content)
Saída possível:
{
"name": "Qwen3",
"supports_thinking": true,
"max_context_tokens": 128000,
"languages": ["Inglês", "Chinês", "Francês", "Espanhol", "Árabe"]
}
Exemplo 3: Comparar Múltiplos Modelos
Alimente descrições de múltiplos modelos e extraia-os em forma estruturada:
from typing import List
class ModelComparison(BaseModel):
models: List[LLMFeatures]
prompt = """
Extraia os recursos de cada modelo para JSON.
1. Llama 3.1 suporta raciocínio. Janela de contexto de 128K. Idiomas: Apenas Inglês.
2. GPT-4 Turbo suporta raciocínio. Janela de contexto de 128K. Idiomas: Inglês, Japonês.
3. Qwen3 suporta raciocínio. Janela de contexto de 128K. Idiomas: Inglês, Chinês, Francês, Espanhol, Árabe.
"""
resp = chat(
model="qwen3",
messages=[{"role": "user", "content": prompt}],
format=ModelComparison.model_json_schema(),
options={"temperature": 0},
)
print(resp.message.content)
Saída:
{
"models": [
{
"name": "Llama 3.1",
"supports_thinking": true,
"max_context_tokens": 128000,
"languages": ["Inglês"]
},
{
"name": "GPT-4 Turbo",
"supports_thinking": true,
"max_context_tokens": 128000,
"languages": ["Inglês", "Japonês"]
},
{
"name": "Qwen3",
"supports_thinking": true,
"max_context_tokens": 128000,
"languages": ["Inglês", "Chinês", "Francês", "Espanhol", "Árabe"]
}
]
}
Isso torna trivial realizar benchmark, visualizar ou filtrar modelos por seus recursos.
Exemplo 4: Detectar Lacunas Automaticamente
Você pode até permitir valores null quando um campo estiver ausente:
from typing import Optional
class FlexibleLLMFeatures(BaseModel):
name: str
supports_thinking: Optional[bool]
max_context_tokens: Optional[int]
languages: Optional[List[str]]
Isso garante que seu esquema permaneça válido mesmo que algumas informações sejam desconhecidas.
Benefícios, Cuidados e Melhores Práticas
O uso de saída estruturada através do Ollama (ou qualquer sistema que o suporte) oferece muitas vantagens, mas também tem alguns cuidados.
Benefícios
- Garantias mais fortes: O modelo é solicitado a estar em conformidade com um esquema JSON em vez de texto livre.
- Análise mais fácil: Você pode diretamente
json.loadsou validar com Pydantic / Zod, em vez de regex ou heurísticas. - Evolução baseada em esquema: Você pode versionar seu esquema, adicionar campos (com valores padrão) e manter a compatibilidade com versões anteriores.
- Interoperabilidade: Sistemas downstream esperam dados estruturados.
- Determinismo (melhor com temperatura baixa): Quando a temperatura é baixa (ex: 0), o modelo tem maior probabilidade de aderir rigidamente ao esquema. A documentação do Ollama recomenda isso.
Cuidados e Armadilhas
- Incompatibilidade de esquema: O modelo ainda pode desviar — por exemplo, perder uma propriedade obrigatória, reordenar chaves ou incluir campos extras. Você precisa de validação.
- Esquemas complexos: Esquemas JSON muito profundos ou recursivos podem confundir o modelo ou levar a falhas.
- Ambiguidade no prompt: Se seu prompt for vago, o modelo pode adivinhar campos ou unidades incorretamente.
- Inconsistência entre modelos: Alguns modelos podem ser melhores ou piores em honrar restrições estruturadas.
- Limites de tokens: O próprio esquema adiciona custo de token ao prompt ou chamada de API.
Melhores Práticas e Dicas (baseadas no blog do Ollama + experiência)
- Use Pydantic (Python) ou Zod (JavaScript) para definir seus esquemas e gerar automaticamente esquemas JSON. Isso evita erros manuais.
- Sempre inclua instruções como “responder apenas em JSON” ou “não incluir comentários ou texto extra” no seu prompt.
- Use temperature = 0 (ou muito baixo) para minimizar a aleatoriedade e maximizar a adesão ao esquema. O Ollama recomenda determinismo.
- Valide e potencialmente faça fallback (ex: retentar ou limpar) quando a análise JSON falhar ou a validação do esquema falhar.
- Comece com um esquema mais simples e, em seguida, estenda gradualmente. Não o complique inicialmente.
- Inclua instruções de erro úteis, mas restritas: por exemplo, se o modelo não puder preencher um campo obrigatório, responda com
nullem vez de omiti-lo (se seu esquema permitir).
Exemplo Go 1: Extraindo Recursos de LLM
Aqui está um programa simples Go que pede ao Qwen3 uma saída estruturada sobre os recursos de um LLM.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/ollama/ollama/api"
)
type LLMFeatures struct {
Name string `json:"name"`
SupportsThinking bool `json:"supports_thinking"`
MaxContextTokens int `json:"max_context_tokens"`
Languages []string `json:"languages"`
}
func main() {
client, err := api.ClientFromEnvironment()
if err != nil {
log.Fatal(err)
}
prompt := `
Analise a descrição a seguir e retorne os recursos do modelo apenas em JSON.
Descrição:
"O Qwen3 tem forte suporte multilíngue (Inglês, Chinês, Francês, Espanhol, Árabe).
Permite etapas de raciocínio (chain-of-thought).
A janela de contexto é de 128K tokens."
`
// Defina o esquema JSON para saída estruturada
formatSchema := map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]string{
"type": "string",
},
"supports_thinking": map[string]string{
"type": "boolean",
},
"max_context_tokens": map[string]string{
"type": "integer",
},
"languages": map[string]any{
"type": "array",
"items": map[string]string{
"type": "string",
},
},
},
"required": []string{"name", "supports_thinking", "max_context_tokens", "languages"},
}
// Converta o esquema para JSON
formatJSON, err := json.Marshal(formatSchema)
if err != nil {
log.Fatal("Falha ao marshar o esquema de formato:", err)
}
req := &api.GenerateRequest{
Model: "qwen3:8b",
Prompt: prompt,
Format: formatJSON,
Options: map[string]any{"temperature": 0},
}
var features LLMFeatures
var rawResponse string
err = client.Generate(context.Background(), req, func(response api.GenerateResponse) error {
// Acumule o conteúdo conforme ele é transmitido
rawResponse += response.Response
// Analise apenas quando a resposta estiver completa
if response.Done {
if err := json.Unmarshal([]byte(rawResponse), &features); err != nil {
return fmt.Errorf("Erro de análise JSON: %v", err)
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Struct analisado: %+v\n", features)
}
Para compilar e executar este exemplo de programa Go - vamos assumir que tenhamos este arquivo main.go em uma pasta ollama-struct,
Precisamos executar dentro desta pasta:
# inicializar módulo
go mod init ollama-struct
# puxar todas as dependências
go mod tidy
# compilar e executar
go build -o ollama-struct main.go
./ollama-struct
Exemplo de Saída
Struct analisado: {Name:Qwen3 SupportsThinking:true MaxContextTokens:128000 Languages:[Inglês Chinês Francês Espanhol Árabe]}
Exemplo Go 2: Comparando Múltiplos Modelos
Você pode estender isso para extrair uma lista de modelos para comparação.
type ModelComparison struct {
Models []LLMFeatures `json:"models"`
}
prompt = `
Extraia recursos das seguintes descrições de modelo e retorne como JSON:
1. PaLM 2: Este modelo tem capacidades de raciocínio limitadas e foca na compreensão básica de linguagem. Suporta uma janela de contexto de 8.000 tokens. Suporta principalmente apenas a língua inglesa.
2. LLaMA 2: Este modelo tem habilidades moderadas de raciocínio e pode lidar com algumas tarefas lógicas. Pode processar até 4.000 tokens em seu contexto. Suporta inglês, espanhol e italiano.
3. Codex: Este modelo tem fortes capacidades de raciocínio especificamente para programação e análise de código. Tem uma janela de contexto de 16.000 tokens. Suporta inglês, Python, JavaScript e Java.
Retorne um objeto JSON com um array "models" contendo todos os modelos.
`
// Defina o esquema JSON para comparação de modelos
comparisonSchema := map[string]any{
"type": "object",
"properties": map[string]any{
"models": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]string{
"type": "string",
},
"supports_thinking": map[string]string{
"type": "boolean",
},
"max_context_tokens": map[string]string{
"type": "integer",
},
"languages": map[string]any{
"type": "array",
"items": map[string]string{
"type": "string",
},
},
},
"required": []string{"name", "supports_thinking", "max_context_tokens", "languages"},
},
},
},
"required": []string{"models"},
}
// Converta o esquema para JSON
comparisonFormatJSON, err := json.Marshal(comparisonSchema)
if err != nil {
log.Fatal("Falha ao marshar o esquema de comparação:", err)
}
req = &api.GenerateRequest{
Model: "qwen3:8b",
Prompt: prompt,
Format: comparisonFormatJSON,
Options: map[string]any{"temperature": 0},
}
var comp ModelComparison
var comparisonResponse string
err = client.Generate(context.Background(), req, func(response api.GenerateResponse) error {
// Acumule o conteúdo conforme ele é transmitido
comparisonResponse += response.Response
// Analise apenas quando a resposta estiver completa
if response.Done {
if err := json.Unmarshal([]byte(comparisonResponse), &comp); err != nil {
return fmt.Errorf("Erro de análise JSON: %v", err)
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
for _, m := range comp.Models {
fmt.Printf("%s: Contexto=%d, Idiomas=%v\n", m.Name, m.MaxContextTokens, m.Languages)
}
Exemplo de Saída
PaLM 2: Contexto=8000, Idiomas=[Inglês]
LLaMA 2: Contexto=4000, Idiomas=[Inglês Espanhol Italiano]
Codex: Contexto=16000, Idiomas=[Inglês Python JavaScript Java]
A propósito, o qwen3:4b nesses exemplos funciona bem, assim como o qwen3:8b.
Melhores Práticas para Desenvolvedores Go
- Defina a temperatura em 0 para máxima adesão ao esquema.
- Valide com
json.Unmarshale faça fallback se a análise falhar. - Mantenha os esquemas simples — estruturas JSON profundamente aninhadas ou recursivas podem causar problemas.
- Permita campos opcionais (use
omitemptynas tags de struct Go) se você espera dados ausentes. - Adicione re-tentativas se o modelo ocasionalmente emitir JSON inválido.
Exemplo Completo - Desenhando um Gráfico com Especificações de LLM (Passo a passo: de JSON estruturado para tabelas de comparação)

- Defina um esquema para os dados que você quer
Use Pydantic para que você possa (a) gerar um Esquema JSON para o Ollama e (b) validar a resposta do modelo.
from pydantic import BaseModel
from typing import List, Optional
class LLMFeatures(BaseModel):
name: str
supports_thinking: bool
max_context_tokens: int
languages: List[str]
- Peça ao Ollama para retornar apenas JSON nessa forma
Passar o esquema em format= e reduzir a temperatura para determinismo.
from ollama import chat
prompt = """
Extraia recursos para cada modelo. Retorne apenas JSON correspondente ao esquema.
1) Qwen3 suporta chain-of-thought; contexto 128K; Inglês, Chinês, Francês, Espanhol, Árabe.
2) Llama 3.1 suporta chain-of-thought; contexto 128K; Inglês.
3) GPT-4 Turbo suporta chain-of-thought; contexto 128K; Inglês, Japonês.
"""
resp = chat(
model="qwen3",
messages=[{"role": "user", "content": prompt}],
format={"type": "array", "items": LLMFeatures.model_json_schema()},
options={"temperature": 0}
)
raw_json = resp.message.content # Lista JSON de LLMFeatures
- Validar & normalizar
Sempre valide antes de usar em produção.
from pydantic import TypeAdapter
adapter = TypeAdapter(list[LLMFeatures])
models = adapter.validate_json(raw_json) # -> list[LLMFeatures]
- Construa uma tabela de comparação (pandas)
Transforme seus objetos validados em um DataFrame que você pode classificar/filtrar e exportar.
import pandas as pd
df = pd.DataFrame([m.model_dump() for m in models])
df["languages_count"] = df["languages"].apply(len)
df["languages"] = df["languages"].apply(lambda xs: ", ".join(xs))
# Reordene colunas para legibilidade
df = df[["name", "supports_thinking", "max_context_tokens", "languages_count", "languages"]]
# Salve como CSV para uso posterior
df.to_csv("llm_feature_comparison.csv", index=False)
- (Opcional) Visualizações rápidas
Gráficos simples ajudam a visualizar as diferenças entre os modelos rapidamente.
import matplotlib.pyplot as plt
plt.figure()
plt.bar(df["name"], df["max_context_tokens"])
plt.title("Janela de Contexto Máximo por Modelo (tokens)")
plt.xlabel("Modelo")
plt.ylabel("Tokens de Contexto Máximo")
plt.xticks(rotation=20, ha="right")
plt.tight_layout()
plt.savefig("max_context_window.png")
TL;DR
Com o novo suporte a saída estruturada do Ollama, você pode tratar LLMs não apenas como chatbots, mas como motores de extração de dados.
Os exemplos acima mostraram como extrair automaticamente metadados estruturados sobre recursos de LLM, como suporte a raciocínio, tamanho da janela de contexto e idiomas suportados — tarefas que, de outra forma, exigiriam análise frágil.
Seja você construindo um catálogo de modelos LLM, um painel de avaliação ou um assistente de pesquisa alimentado por IA, as saídas estruturadas tornam a integração suave, confiável e pronta para produção.
Links Úteis
- https://ollama.com/blog/structured-outputs
- Resumo do Ollama
- Resumo de Python
- AWS SAM + AWS SQS + Python PowerTools
- Resumo do Golang
- Comparando ORMs Go para PostgreSQL: GORM vs Ent vs Bun vs sqlc
- Reranking de documentos de texto com Ollama e modelo de Embedding Qwen3 - em Go
- Desempenho AWS lambda: JavaScript vs Python vs Golang