03-02-03 — Structured outputs: JSON mode, function calling, tool use
TL;DR
LLMs retornam texto — mas sua aplicação precisa de dados. JSON mode garante JSON válido. Function calling faz o modelo decidir qual função chamar com quais parâmetros. Structured outputs (OpenAI) garante conformidade com um JSON Schema exato. A diferença é garantia: JSON mode evita erros de parse, structured outputs garante o schema. Use function calling/tool use sempre que o LLM precisar se integrar com código real.
O problema: LLMs retornam texto livre
Sem restrições, um LLM pode responder "Claro! Aqui está o resultado: {…}" ou colocar o JSON dentro de um bloco markdown. Isso quebra qualquer json.loads(). As três abordagens a seguir resolvem isso em níveis crescentes de rigidez.
Abordagem 1: JSON mode
Garante que a resposta seja JSON válido. Não garante o schema — o modelo decide a estrutura. Use quando você quer JSON mas tem flexibilidade na estrutura.
from openai import OpenAI
import json
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
response_format={"type": "json_object"}, # JSON mode
messages=[
{
"role": "system",
"content": "Você extrai dados de texto. Sempre responda em JSON válido."
},
{
"role": "user",
"content": "Extraia os dados: 'Reunião com Pedro Alves (pedro@vale.com) da Vale, diretor de TI, amanhã às 14h.'"
}
]
)
# Garantido ser JSON válido — sem try/except necessário
data = json.loads(response.choices[0].message.content)
print(data)
# {"nome": "Pedro Alves", "email": "pedro@vale.com", "empresa": "Vale", "cargo": "Diretor de TI", "horario": "amanhã às 14h"}
Abordagem 2: Function calling
Você define funções com schemas JSON. O modelo decide quando e como chamar cada função — ele retorna um "tool call" com os parâmetros preenchidos. Você executa a função. É o mecanismo de integração LLM → código.
from openai import OpenAI
import json
client = OpenAI()
# Define as funções disponíveis
tools = [
{
"type": "function",
"function": {
"name": "criar_ticket_suporte",
"description": "Cria um ticket de suporte no sistema. Use quando o usuário reportar um problema técnico.",
"parameters": {
"type": "object",
"properties": {
"titulo": {
"type": "string",
"description": "Título conciso do problema"
},
"prioridade": {
"type": "string",
"enum": ["baixa", "media", "alta", "critica"],
"description": "Prioridade do ticket"
},
"descricao": {
"type": "string",
"description": "Descrição detalhada do problema"
},
"sistema_afetado": {
"type": "string",
"description": "Nome do sistema ou módulo afetado"
}
},
"required": ["titulo", "prioridade", "descricao"]
}
}
},
{
"type": "function",
"function": {
"name": "consultar_status_sistema",
"description": "Consulta o status operacional de um sistema.",
"parameters": {
"type": "object",
"properties": {
"sistema": {"type": "string", "description": "Nome do sistema a consultar"}
},
"required": ["sistema"]
}
}
}
]
# Simulação: funções reais que seriam chamadas
def criar_ticket_suporte(titulo, prioridade, descricao, sistema_afetado=None):
return {"ticket_id": "INC-2847", "status": "criado", "titulo": titulo}
def consultar_status_sistema(sistema):
return {"sistema": sistema, "status": "operacional", "uptime": "99.9%"}
# Conversa com o assistente
messages = [
{"role": "system", "content": "Você é o assistente de suporte técnico da Impar. Ajude usuários a resolver problemas e criar tickets quando necessário."},
{"role": "user", "content": "O sistema de autenticação está retornando erro 500 desde as 10h. É urgente, tem 200 usuários bloqueados."}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto" # modelo decide quando chamar
)
message = response.choices[0].message
# Verifica se o modelo quer chamar uma função
if message.tool_calls:
messages.append(message) # adiciona resposta do assistente ao histórico
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f"Chamando: {func_name}({func_args})")
# Executa a função real
if func_name == "criar_ticket_suporte":
result = criar_ticket_suporte(**func_args)
elif func_name == "consultar_status_sistema":
result = consultar_status_sistema(**func_args)
# Adiciona resultado da função ao histórico
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
# Segunda chamada: modelo processa o resultado e responde ao usuário
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
print(final_response.choices[0].message.content)
Abordagem 3: Structured outputs (OpenAI — schema garantido)
A OpenAI garante conformidade exata com o JSON Schema. Zero chance de campos extras, campos faltando, ou tipos errados. Use quando o output alimenta código downstream diretamente.
from pydantic import BaseModel
from openai import OpenAI
client = OpenAI()
# Define o schema via Pydantic — a OpenAI converte para JSON Schema automaticamente
class ContatoExtraido(BaseModel):
nome: str
email: str
empresa: str
cargo: str
telefone: str | None = None # campo opcional
class ListaContatos(BaseModel):
contatos: list[ContatoExtraido]
total_encontrados: int
# Structured outputs — schema garantido
response = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{"role": "system", "content": "Extraia todos os contatos mencionados no texto."},
{"role": "user", "content": """
Reunião confirmada com Ana Lima (ana@tim.com.br), gerente de projetos da TIM,
e com Roberto Souza (roberto@axia.com, (21) 99999-1234), CTO da Axia.
"""}
],
response_format=ListaContatos # passa a classe Pydantic diretamente
)
# response.choices[0].message.parsed é um objeto ListaContatos tipado
result: ListaContatos = response.choices[0].message.parsed
print(f"Total: {result.total_encontrados}")
for contato in result.contatos:
print(f" {contato.nome} — {contato.email} — {contato.empresa}")
Tool use no Anthropic (Claude)
A Anthropic tem implementação equivalente, mas com sintaxe ligeiramente diferente:
import anthropic
import json
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": "Retorna a temperatura atual de uma cidade.",
"input_schema": {
"type": "object",
"properties": {
"cidade": {"type": "string", "description": "Nome da cidade"}
},
"required": ["cidade"]
}
}
]
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "Qual a temperatura em São Paulo agora?"}
]
)
# Verifica se Claude quer usar uma tool
for block in response.content:
if block.type == "tool_use":
print(f"Tool: {block.name}")
print(f"Input: {block.input}")
# Simula execução
tool_result = {"temperatura": "28°C", "condicao": "ensolarado"}
# Retorna resultado para Claude finalizar a resposta
final = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "Qual a temperatura em São Paulo agora?"},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(tool_result)
}
]
}
]
)
print(final.content[0].text)
Parallel tool calls
Em GPT-4o, o modelo pode solicitar múltiplas tool calls simultaneamente. Isso é crítico para performance:
# O modelo pode retornar múltiplas tool_calls de uma vez
# Exemplo: "Compare o weather de SP, RJ e BH"
# O modelo retorna 3 tool_calls simultâneos em vez de 3 chamadas sequenciais
if message.tool_calls and len(message.tool_calls) > 1:
import asyncio
async def execute_tool(tool_call):
# executa em paralelo
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
result = await some_async_function(func_name, func_args)
return tool_call.id, result
# Executa todas as tools em paralelo
results = await asyncio.gather(*[execute_tool(tc) for tc in message.tool_calls])
Como isso se conecta
- → 03-02-02 ReAct: o "Action" do ReAct é implementado via function calling/tool use — agora você sabe o mecanismo técnico
- → 03-03-03 Desafio: o desafio final usa structured outputs para extração de entidades — você vai implementar o que aprendeu aqui
- → Módulo 04 RAG: retrieval tools são o pattern mais comum de function calling — buscar documentos relevantes antes de responder