Limitando LLMs com Saída Estruturada: Ollama, Qwen3 & Python ou Go
Alguns modos de obter saída estruturada do Ollama
Grandes Modelos de Linguagem (LLMs) são poderosos, mas, em produção, raramente queremos parágrafos livres. Em vez disso, queremos dados previsíveis: atributos, fatos ou objetos estruturados que você pode alimentar em um aplicativo. Isso é Saída Estruturada de LLM.
Há algum tempo 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 libera pipelines de extração de dados consistentes para tarefas como catalogar recursos de LLM, benchmarking de modelos ou automatizar a integração do sistema.
Neste post, vamos abordar:
- O que é saída estruturada e por que importa
- Uma maneira simples de obter saída estruturada de LLMs
- Como o novo recurso de 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 pensamento em cadeia, 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 em uma interface do usuário.
Maneira Simples de Obter Saída Estruturada de LLM
Às vezes, os LLMs entendem o que é o esquema e podemos pedir ao LLM para retornar a saída em JSON usando um esquema específico. O modelo Qwen3 da Alibaba está otimizado para raciocínio e respostas estruturadas. Você pode instruí-lo explicitamente para 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']
# Parse 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"}
Forçando 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 de Qwen3
data = Person.model_validate_json(output)
print(data.name, data.age, data.city)
Isso garante que a saída corresponda à estrutura esperada.
Saída Estruturada de Ollama
Agora, Ollama permite que você passe um esquema no parâmetro format
. O modelo é então restringido a responder apenas em JSON que corresponda ao esquema (docs).
Em Python, você normalmente define seu esquema com Pydantic e deixa Ollama usá-lo como um esquema JSON.
Exemplo 2: Extrair Metadados de Recursos de LLM
Suponha que você tenha um trecho de texto descrevendo as capacidades de um LLM:
“Qwen3 tem forte suporte multilíngue (Inglês, Chinês, Francês, Espanhol, Árabe). Ele permite etapas de raciocínio (pensamento em cadeia). 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 seguinte descrição e retorne as características do modelo em JSON apenas.
Descrição do modelo:
'Qwen3 tem forte suporte multilíngue (Inglês, Chinês, Francês, Espanhol, Árabe).
Ele permite etapas de raciocínio (pensamento em cadeia).
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 em forma estruturada:
from typing import List
class ModelComparison(BaseModel):
models: List[LLMFeatures]
prompt = """
Extraia as características de cada modelo em JSON.
1. Llama 3.1 suporta raciocínio. Janela de contexto é de 128K. Idiomas: Inglês apenas.
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 benchmarking, visualização ou filtragem de modelos com base em suas características.
Exemplo 4: Detectar Falhas Automaticamente
Você até pode 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 se alguma informação estiver desconhecida.
Benefícios, Cuidados e Boas Práticas
Usar saída estruturada por meio do Ollama (ou qualquer sistema que suporte) oferece muitas vantagens — mas também tem alguns cuidados.
Benefícios
- Garantias mais fortes: O modelo é pedido para corresponder a um esquema JSON em vez de texto livre.
- Análise mais fácil: Você pode diretamente
json.loads
ou validar com Pydantic / Zod, em vez de expressões regulares ou heurísticas. - Evolução baseada em esquema: Você pode versionar seu esquema, adicionar campos (com valores padrão) e manter compatibilidade para trás.
- Interoperabilidade: Sistemas downstream esperam dados estruturados.
- Determinismo (melhor com temperatura baixa): Quando a temperatura é baixa (ex: 0), o modelo é mais propenso a seguir estritamente o esquema. Os documentos do Ollama recomendam isso.
Cuidados e Armadilhas
- Desconcordância de esquema: O modelo ainda pode se desviar — por exemplo, omitir uma propriedade necessá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 token: O próprio esquema adiciona custo de token ao prompt ou chamada de API.
Boas Práticas e Dicas (extraídas do 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 “responda apenas em JSON” ou “não inclua comentários ou texto extra” em seu prompt.
- Use temperature = 0 (ou muito baixo) para minimizar aleatoriedade e maximizar a adesão ao esquema. Ollama recomenda determinismo.
- Valide e potencialmente recupere (ex: retenção ou limpeza) quando a análise de JSON falhar ou a validação do esquema falhar.
- Comece com um esquema mais simples, depois estenda gradualmente. Não complice inicialmente.
- Inclua instruções de erro úteis, mas restritas: por exemplo, se o modelo não puder preencher um campo necessário, responda com
null
em vez de omiti-lo (se o esquema permitir).
Exemplo em Go 1: Extrair Recursos de LLM
Aqui está um programa simples Go que pede ao Qwen3 para 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 seguinte descrição e retorne as características do modelo em JSON apenas.
Descrição:
"Qwen3 tem forte suporte multilíngue (Inglês, Chinês, Francês, Espanhol, Árabe).
Ele permite etapas de raciocínio (pensamento em cadeia).
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 serializar 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 conteúdo conforme o streaming
rawResponse += response.Response
// Apenas analise 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("Estrutura analisada: %+v\n", features)
}
Para compilar e executar este exemplo de programa Go — suponha que tenhamos este arquivo main.go em uma pasta ollama-struct
,
Precisamos executar dentro desta pasta:
# inicialize o módulo
go mod init ollama-struct
# obtenha todas as dependências
go mod tidy
# compile e execute
go build -o ollama-struct main.go
./ollama-struct
Saída de Exemplo
Estrutura analisada: {Name:Qwen3 SupportsThinking:true MaxContextTokens:128000 Languages:[Inglês Chinês Francês Espanhol Árabe]}
Exemplo em Go 2: Comparar 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 as características das seguintes descrições de modelos e retorne como JSON:
1. PaLM 2: Este modelo tem capacidades de raciocínio limitadas e se concentra em compreensão básica da linguagem. Ele suporta uma janela de contexto de 8.000 tokens. Ele suporta principalmente o idioma Inglês.
2. LLaMA 2: Este modelo tem capacidades de raciocínio moderadas e pode lidar com algumas tarefas lógicas. Ele pode processar até 4.000 tokens em seu contexto. Ele suporta os idiomas 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. Ele tem uma janela de contexto de 16.000 tokens. Ele suporta os idiomas Inglês, Python, JavaScript e Java.
Retorne um objeto JSON com uma "array de modelos" 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 serializar 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 conteúdo conforme o streaming
comparisonResponse += response.Response
// Apenas analise 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)
}
Saída de Exemplo
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]
De passagem, qwen3:4b nestes exemplos funciona bem, da mesma forma que qwen3:8b.
Boas Práticas para Desenvolvedores em Go
- Defina a temperatura como 0 para adesão máxima ao esquema.
- Valide com
json.Unmarshal
e recupere se a análise falhar. - Mantenha os esquemas simples — estruturas JSON profundas ou recursivas podem causar problemas.
- Permita campos opcionais (use
omitempty
nas tags de struct em Go) se esperar dados ausentes. - Adicione tentativas se o modelo ocasionalmente emitir JSON inválido.
Exemplo completo - Desenhar 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ê deseja
Use Pydantic para que você possa tanto (a) gerar um esquema JSON para Ollama quanto (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
Envie o esquema em format=
e reduza a temperatura para determinismo.
from ollama import chat
prompt = """
Extraia as características de cada modelo. Retorne apenas JSON que corresponda ao esquema.
1) Qwen3 suporta pensamento em cadeia; 128K contexto; Inglês, Chinês, Francês, Espanhol, Árabe.
2) Llama 3.1 suporta pensamento em cadeia; 128K contexto; Inglês.
3) GPT-4 Turbo suporta pensamento em cadeia; 128K contexto; 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
- Valide & normalize
Sempre valide antes de usar em produção.
from pydantic import TypeAdapter
adapter = TypeAdapter(list[LLMFeatures])
models = adapter.validate_json(raw_json) # -> lista[LLMFeatures]
- Construa uma tabela de comparação (pandas)
Transforme seus objetos validados em um DataFrame que você possa ordenar/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 as 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 você a visualizar rapidamente as diferenças entre modelos.
import matplotlib.pyplot as plt
plt.figure()
plt.bar(df["name"], df["max_context_tokens"])
plt.title("Janela de contexto máxima 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 pensamento, 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 com 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
- Dica rápida de Ollama
- Dica rápida de Python
- AWS SAM + AWS SQS + Python PowerTools
- Dica rápida de Go
- Comparando ORMs Go para PostgreSQL: GORM vs Ent vs Bun vs sqlc
- Reclassificação de documentos de texto com Ollama e modelo de embedding Qwen3 - em Go
- Desempenho de lambda AWS: JavaScript vs Python vs Go