"Function calling: como dar ferramentas ao seu LLM"
Entenda como o function calling permite que o modelo chame funções e APIs externas de forma estruturada, transformando um chat em um agente que executa tarefas — com código, boas práticas e armadilhas de segurança.

Um modelo de linguagem, sozinho, só sabe gerar texto: ele não consulta seu banco de dados, não verifica o clima de hoje nem dispara um e-mail. O function calling é o mecanismo que muda isso, permitindo que o modelo peça, de forma estruturada, para o seu código executar funções reais. É a ponte entre "um chatbot que conversa" e "um sistema que faz coisas".
O problema que o function calling resolve
LLMs têm três limitações práticas incômodas. Eles não conhecem dados em tempo real (não sabem a cotação do dólar agora), não têm acesso aos seus sistemas privados (seu CRM, seu estoque) e não executam ações no mundo (não criam um registro, não enviam uma mensagem).
Há ainda um quarto problema: mesmo quando o modelo sabe algo, a saída dele é texto livre, difícil de consumir por programa. Se você pede "extraia o nome e o e-mail desta mensagem", o modelo pode responder em prosa, e você fica refém de parsing frágil. O function calling resolve isso também, forçando a saída a seguir um formato estruturado e previsível.
A ideia de ensinar modelos a usar ferramentas externas para superar essas barreiras foi formalizada na pesquisa: o Toolformer mostrou que um modelo pode aprender sozinho quando e como chamar APIs — calculadora, busca, tradução — para melhorar suas respostas (Schick et al., 2023). O function calling é a versão de engenharia dessa ideia, exposta de forma prática nas APIs comerciais.
Se você ainda não fez sua primeira integração, vale começar por API da OpenAI na prática: primeiros passos para devs, porque o function calling é uma extensão da mesma chamada de chat.
Como funciona, em alto nível
É importante entender uma coisa logo de início: o modelo não executa a função. Ele apenas decide que ela deveria ser chamada e produz os argumentos. Quem executa é o seu código. O fluxo tem cinco passos:
Esse ciclo de "pensar, decidir agir, observar o resultado" é exatamente o padrão de raciocínio descrito no ReAct, que intercala reasoning (raciocínio) e acting (ação) para resolver tarefas (Yao et al., 2023).
Por que o modelo não executa nada
Essa separação não é um detalhe — é uma decisão de design fundamental e de segurança. O LLM roda na infraestrutura do provedor; suas funções rodam no seu ambiente, com suas credenciais e seu acesso a dados. Se o modelo executasse diretamente, ele precisaria das suas chaves e do seu acesso, o que seria inviável e perigoso. Mantendo a execução do seu lado, você decide se a chamada proposta pelo modelo é válida, segura e autorizada antes de rodá-la.
Descrevendo funções com JSON Schema
O modelo precisa saber o que cada ferramenta faz. Você comunica isso com uma descrição estruturada, geralmente em JSON Schema, que define o nome, a finalidade e os parâmetros:
tools = [
{
"type": "function",
"function": {
"name": "consultar_clima",
"description": "Retorna o clima atual de uma cidade.",
"parameters": {
"type": "object",
"properties": {
"cidade": {
"type": "string",
"description": "Nome da cidade, ex: São Paulo"
},
"unidade": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["cidade"]
}
}
}
]A qualidade da description importa muito. É lendo essas descrições que o modelo decide quando usar a ferramenta e como preencher os argumentos. Descrições vagas levam a chamadas erradas; descrições claras e específicas, com exemplos, melhoram bastante a precisão.
Dicas para schemas que o modelo entende bem
Muitas APIs hoje também oferecem saída estruturada garantida (structured outputs): o provedor força o JSON a respeitar o schema, eliminando erros de sintaxe. Vale ativar quando disponível.
Executando o ciclo completo
Veja o ciclo em código. Primeiro, você manda o pedido junto com as ferramentas:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Qual o clima em Recife?"}],
tools=tools,
)
tool_call = response.choices[0].message.tool_calls[0]
print(tool_call.function.name) # "consultar_clima"
print(tool_call.function.arguments) # '{"cidade": "Recife"}'O modelo não respondeu o clima — ele pediu para você chamar consultar_clima. Agora seu código executa de verdade e devolve o resultado:
import json
args = json.loads(tool_call.function.arguments)
resultado = consultar_clima(**args) # sua função real
messages.append(response.choices[0].message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(resultado),
})
final = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
)
print(final.choices[0].message.content)
# "Em Recife, o clima está 29°C e ensolarado."Note o papel tool na mensagem: é assim que você devolve a observação ao modelo para ele formular a resposta humana. O tool_call_id precisa casar com o id da chamada original — é isso que liga o resultado à pergunta certa quando há várias ferramentas em jogo.
Chamadas paralelas e múltiplas rodadas
Modelos modernos podem pedir várias ferramentas de uma vez (parallel tool calling) — por exemplo, consultar o clima de duas cidades simultaneamente. Nesse caso, tool_calls é uma lista, e você precisa executar todas e devolver uma mensagem tool para cada tool_call_id. Além disso, uma tarefa pode exigir várias rodadas: o modelo chama uma ferramenta, vê o resultado, decide chamar outra, e só então responde. Seu código precisa de um laço que continue chamando o modelo enquanto houver tool_calls pendentes.
Um exemplo de ponta a ponta com várias ferramentas
Para fixar o conceito, imagine um assistente de suporte que precisa de duas ferramentas: buscar um pedido e abrir um chamado. As funções reais (que falam com seu banco) ficam por trás de um despacho seguro:
def buscar_pedido(numero: str) -> dict:
pedido = db.pedidos.get(numero)
if not pedido:
return {"erro": "pedido nao encontrado"}
return {"numero": numero, "status": pedido.status}
def abrir_chamado(numero_pedido: str, motivo: str) -> dict:
chamado = db.chamados.criar(numero_pedido, motivo)
return {"chamado_id": chamado.id}
FUNCOES = {
"buscar_pedido": buscar_pedido,
"abrir_chamado": abrir_chamado,
}
def executar(nome: str, args: dict) -> dict:
fn = FUNCOES.get(nome)
if fn is None:
return {"erro": f"funcao desconhecida: {nome}"}
try:
return fn(**args)
except TypeError:
return {"erro": "argumentos invalidos"}Repare em três decisões de robustez: o dicionário FUNCOES restringe o que pode ser chamado, o try/except transforma erro de argumento em uma observação em vez de uma exceção que derruba o fluxo, e cada função retorna um dicionário simples e serializável. Esse executar é exatamente o que o laço do agente, mais adiante, chama a cada tool_call.
Function calling e agentes
Quando você combina function calling com um loop — o modelo chama uma ferramenta, observa o resultado, decide a próxima ação e repete até concluir a tarefa — você tem a base de um agente. É essa repetição que dá autonomia.
# Esboço do laço de um agente
while True:
resp = client.chat.completions.create(
model="gpt-4o-mini", messages=messages, tools=tools,
)
msg = resp.choices[0].message
messages.append(msg)
if not msg.tool_calls:
break # o modelo respondeu, tarefa concluída
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
out = executar(call.function.name, args) # despacho seguro
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(out),
})Para entender o conceito antes de programar, veja O que é um agente de IA? Definição e exemplos. E quando estiver pronto para montar o loop completo na prática, Como construir um agente de IA que executa tarefas mostra a arquitetura passo a passo. O function calling é, em resumo, o "verbo" que o agente usa para agir no mundo.
Repare no detalhe executar(...) acima: você nunca deve fazer algo como eval(nome_da_funcao). Mantenha um dicionário explícito de funções permitidas e despache só por ele. Deixar o nome vindo do modelo escolher código arbitrário é uma porta aberta para execução indevida.
Boas práticas e armadilhas
Algumas lições ajudam a evitar dores de cabeça:
Segurança: a parte que ninguém pode pular
O function calling amplia o que um LLM pode fazer — e, com isso, amplia a superfície de risco. Dois pontos merecem destaque:
A regra de ouro: o modelo propõe, mas seu código dispõe. Toda decisão sensível passa por validação determinística sua antes de virar efeito no mundo.
Custo, latência e depuração
Cada rodada de function calling é uma chamada à API — com seu custo de tokens e sua latência. Uma tarefa que exige cinco rodadas de ferramenta custa cinco chamadas ao modelo, e o histórico (que cresce a cada rodada, acumulando chamadas e resultados) é reenviado por inteiro a cada vez. Isso tem implicações práticas:
Uma técnica útil de depuração é rodar o mesmo pedido várias vezes: como o modelo é probabilístico, ele pode escolher ferramentas diferentes em execuções distintas. Se a escolha é instável, em geral o problema está na descrição da ferramenta, não no modelo.
A relação com o MCP
À medida que mais aplicações expõem ferramentas a modelos, surgiu a necessidade de um padrão comum para descrever e conectar essas ferramentas, em vez de cada aplicação reinventar o formato. É justamente esse o papel do Model Context Protocol. Se você quer entender como padronizar a integração de ferramentas e dados externos em escala, vale ler O que é MCP (Model Context Protocol)?, que se apoia exatamente nos conceitos de function calling que vimos aqui.
Quando o function calling NÃO é a melhor escolha
Apesar de poderoso, o function calling não é a resposta para tudo. Em alguns cenários, alternativas mais simples ou mais robustas se encaixam melhor:
A pergunta que ajuda a decidir: a tarefa exige que o sistema aja sobre o mundo ou consulte dados vivos de forma que você não consegue prever de antemão? Se sim, function calling brilha. Se o fluxo é fixo ou só envolve gerar texto, há ferramentas mais simples.
Perguntas frequentes
Function calling e tool use são a mesma coisa? Na prática, sim. "Tool use" é o termo mais genérico (usar ferramentas); "function calling" é como vários provedores nomearam o recurso. A mecânica — descrever, decidir, executar do seu lado, devolver — é a mesma.
O modelo sempre vai chamar uma ferramenta quando eu oferecer uma? Não. Ele decide. Você pode influenciar com um parâmetro de escolha (forçar uma ferramenta, deixar automático ou proibir ferramentas), mas, no modo automático, o modelo pode responder direto se julgar que não precisa de nenhuma.
Posso usar function calling só para extrair dados estruturados, sem executar nada? Sim, e é um uso muito comum. Você define uma "função" cujo único papel é descrever o formato de saída desejado; o modelo "chama" essa função preenchendo os campos, e você usa o JSON resultante. É uma forma robusta de obter saída estruturada.
O que acontece se os argumentos vierem inválidos? Você valida e devolve uma mensagem de erro como observação (role: tool). O modelo costuma reagir corrigindo a chamada na rodada seguinte. Por isso tratar erro como observação é melhor do que lançar exceção e quebrar o fluxo.
Conclusão
O function calling é o que transforma um modelo de linguagem de um gerador de texto em um componente capaz de agir. O mecanismo é elegante: você descreve as ferramentas, o modelo decide e produz argumentos estruturados, e o seu código executa de verdade. Dominar esse ciclo — com descrições claras, validação rigorosa, tratamento de erros, controle do loop e cuidado redobrado com segurança e ações sensíveis — é pré-requisito para construir agentes e integrações sérias. É a partir dele que se constroem desde assistentes simples até sistemas autônomos completos.