Pular para o conteúdo
IAIntermediário

Function calling na prática: dando ferramentas ao LLM

Aprenda function calling (tool use) com código: declare ferramentas em JSON Schema, rode o loop de chamadas, valide argumentos e confirme ações críticas.

Por Equipe IAUAI Estudos · 11 de agosto de 2026 · 9 min de leitura

Nesta página

O problema real

Você quer que um LLM faça coisas, como consultar o estoque, buscar um preço ou abrir um chamado. A ideia ingênua é descrever tudo no prompt e pedir que o modelo "responda no formato certo" para você interpretar depois. Funciona em demonstração. Em produção, o modelo inventa nomes de função, esquece parâmetros e você gasta dias depurando texto livre.

Function calling, também chamado de tool use, resolve isso de forma estruturada. Você declara as ferramentas com JSON Schema, o modelo diz qual quer usar e com quais argumentos, o seu código executa e devolve o resultado. O modelo nunca executa nada. Quem executa, valida e decide o que é permitido é você.

Como funciona o ciclo

O ciclo se repete até o modelo parar de pedir ferramentas.

  1. Você envia a mensagem do usuário e a lista de ferramentas.
  2. O modelo responde com um pedido de ferramenta, por exemplo consultar_preco com {"produto_id": "PROD-001"}.
  3. Seu código valida os argumentos e executa a função de verdade.
  4. Você devolve o resultado ao modelo, ligado ao identificador do pedido.
  5. O modelo usa o resultado para responder ao usuário, ou pede outra ferramenta.

Os exemplos usam o SDK da OpenAI (pip install openai) e depois mostram a diferença para o SDK da Anthropic. Os nomes de campos mudam entre provedores, mas a lógica é a mesma. Defina a variável MODELO com o nome de um modelo que suporte ferramentas, conforme a documentação do seu provedor.

Mão na massa

1. Declarar as ferramentas

A descrição de cada ferramenta funciona como um prompt, porque é ela que ajuda o modelo a decidir quando chamar. Descrições vagas levam a ferramentas ignoradas ou mal usadas.

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "consultar_preco",
            "description": "Retorna o preço atual de um produto. Use quando o usuário perguntar quanto custa.",
            "parameters": {
                "type": "object",
                "properties": {
                    "produto_id": {"type": "string", "description": "Código do produto, como PROD-001"},
                    "moeda": {"type": "string", "enum": ["BRL", "USD"], "description": "Padrão BRL"},
                },
                "required": ["produto_id"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "consultar_estoque",
            "description": "Informa a quantidade disponível de um produto em um armazém.",
            "parameters": {
                "type": "object",
                "properties": {
                    "produto_id": {"type": "string", "description": "Código do produto, como PROD-001"},
                    "armazem": {"type": "string", "enum": ["SP", "RJ", "MG"], "description": "Padrão SP"},
                },
                "required": ["produto_id"],
            },
        },
    },
]

2. Implementar as funções com validação

Trate os argumentos como entrada de usuário, porque na prática são. O modelo pode errar, ou ser induzido por um texto malicioso a enviar valores perigosos.

import json

PRECOS = {"PROD-001": {"BRL": 99.90, "USD": 19.99}, "PROD-002": {"BRL": 199.90, "USD": 39.99}}
ESTOQUE = {"PROD-001": {"SP": 10, "RJ": 5, "MG": 0}, "PROD-002": {"SP": 0, "RJ": 3, "MG": 2}}

def executar_ferramenta(nome: str, args: dict) -> str:
    produto = args.get("produto_id", "")
    if not isinstance(produto, str) or produto not in PRECOS:
        return json.dumps({"erro": f"produto não encontrado: {produto!r}"})

    if nome == "consultar_preco":
        moeda = args.get("moeda", "BRL")
        if moeda not in ("BRL", "USD"):
            return json.dumps({"erro": "moeda inválida"})
        return json.dumps({"produto_id": produto, "preco": PRECOS[produto][moeda], "moeda": moeda})

    if nome == "consultar_estoque":
        armazem = args.get("armazem", "SP")
        if armazem not in ESTOQUE[produto]:
            return json.dumps({"erro": "armazém inválido"})
        return json.dumps({"produto_id": produto, "armazem": armazem, "quantidade": ESTOQUE[produto][armazem]})

    return json.dumps({"erro": f"ferramenta desconhecida: {nome}"})

Aqui os dados são fixos para o exemplo rodar sozinho. Na vida real, a função consulta seu banco ou sua API, sempre com consultas parametrizadas.

3. O loop completo

import os
from openai import OpenAI

client = OpenAI()
MODELO = os.environ["MODELO"]

def conversar(mensagem: str, historico: list | None = None, max_voltas: int = 8) -> str:
    historico = historico if historico is not None else []
    historico.append({"role": "user", "content": mensagem})

    for _ in range(max_voltas):
        resp = client.chat.completions.create(
            model=MODELO, messages=historico, tools=TOOLS, tool_choice="auto"
        )
        msg = resp.choices[0].message
        historico.append(msg)  # mantém a mensagem do assistente, com os tool_calls

        if not msg.tool_calls:
            return msg.content

        for chamada in msg.tool_calls:
            try:
                args = json.loads(chamada.function.arguments)
                resultado = executar_ferramenta(chamada.function.name, args)
            except json.JSONDecodeError:
                resultado = json.dumps({"erro": "argumentos não são um JSON válido"})
            historico.append({
                "role": "tool",
                "tool_call_id": chamada.id,
                "content": resultado,
            })

    return "Não consegui concluir dentro do limite de passos."

print(conversar("Quanto custa o PROD-001 e tem estoque em SP?"))

Três detalhes costumam quebrar implementações. O fim do ciclo é detectado pela ausência de tool_calls na mensagem, e não por um campo de motivo de parada. A mensagem do assistente com os pedidos de ferramenta precisa entrar no histórico antes dos resultados. E toda chamada pede uma resposta com o mesmo tool_call_id, mesmo quando deu erro. Devolver o erro como resultado deixa o modelo corrigir o argumento ou explicar o problema ao usuário.

Quando o modelo pede várias ferramentas de uma vez, o loop acima executa uma por vez. Se elas forem lentas e independentes, dá para rodá-las em paralelo com concurrent.futures, mantendo a ordem das respostas no histórico.

4. A mesma ideia com a API da Anthropic

No SDK da Anthropic (pip install anthropic), as ferramentas usam input_schema, o pedido chega como um bloco tool_use dentro de content, e o resultado volta numa mensagem do usuário com bloco tool_result. O sinal de que o modelo quer ferramentas é stop_reason == "tool_use".

import anthropic

claude = anthropic.Anthropic()

TOOLS_CLAUDE = [{
    "name": "consultar_preco",
    "description": "Retorna o preço atual de um produto. Use quando o usuário perguntar quanto custa.",
    "input_schema": {
        "type": "object",
        "properties": {"produto_id": {"type": "string"}},
        "required": ["produto_id"],
    },
}]

mensagens = [{"role": "user", "content": "Quanto custa o PROD-001?"}]
while True:
    resp = claude.messages.create(
        model=os.environ["MODELO_CLAUDE"], max_tokens=1024,
        tools=TOOLS_CLAUDE, messages=mensagens,
    )
    mensagens.append({"role": "assistant", "content": resp.content})
    if resp.stop_reason != "tool_use":
        break
    resultados = [
        {"type": "tool_result", "tool_use_id": b.id,
         "content": executar_ferramenta(b.name, b.input)}
        for b in resp.content if b.type == "tool_use"
    ]
    mensagens.append({"role": "user", "content": resultados})

print("".join(b.text for b in resp.content if b.type == "text"))

Em produção, adicione o mesmo limite de voltas do exemplo anterior.

Ações com efeito: confirmação e permissões

Consultar é seguro. Criar pedido, apagar dados ou mudar permissões não é. Para esse tipo de ferramenta, não deixe o modelo executar sozinho. Retorne uma proposta e peça confirmação do usuário na interface, fora da conversa com o modelo.

def propor_pedido(produto_id: str, quantidade: int) -> dict:
    # Não cria nada. A interface mostra o resumo e só um clique do usuário chama criar_pedido().
    return {"status": "aguardando_confirmacao", "resumo": f"{quantidade} x {produto_id}"}

Dê a cada ferramenta o menor poder possível, como usuário de banco somente leitura para consultas, e use as permissões do usuário final, não as do sistema. Para os riscos de injeção de prompt, leia Segurança em aplicações com LLM.

Armadilhas comuns

Descrição vaga

Se o nome é get_price e a descrição está vazia, o modelo não sabe quando chamar. Descreva o que a ferramenta faz, quando usar e o formato dos argumentos.

Confiar nos argumentos

Valide tipo, formato e domínio de cada argumento, e nunca monte SQL ou comandos de shell concatenando o que veio do modelo.

Histórico que cresce sem controle

Cada volta adiciona mensagens, e os resultados das ferramentas podem ser grandes. Resuma ou corte resultados longos antes de devolvê-los, e acompanhe o tamanho do contexto.

Ferramentas demais

Com muitas ferramentas parecidas, o modelo erra mais na escolha. Mantenha poucas, com nomes distintos e propósito claro, e meça a taxa de acerto com evals.

Quando não usar function calling

  • O fluxo é fixo e conhecido. Se você já sabe que sempre vai chamar A e depois B, escreva em código, sem pedir ao modelo para decidir.
  • A latência é crítica. Cada volta é uma chamada ao modelo a mais.
  • A tarefa exige garantias fortes sobre o que será executado. Nesse caso, restrinja as ferramentas ao mínimo ou use um fluxo determinístico com o modelo só na interpretação do texto.

Próximos passos

Function calling é a base dos agentes. Veja Agentes de IA em produção para orquestração, limites e custos. Para saber se o modelo escolhe a ferramenta certa, monte casos de teste com Avaliação de LLM com evals.

Quer aplicar isso na sua empresa? Marque uma conversa de 45 minutos em https://iauaicloud.com.br/consultoria

Aprenda jogando

Mestre do Prompt

Monte o prompt certo com os blocos certos — e descarte o ruído.

Jogar Mestre do Prompt

Teste seu conhecimento

Teste o que você aprendeu sobre function calling

Pergunta 1 de 6

Quem executa a função quando o modelo pede uma ferramenta?

Um conteúdo prático por quinzena

Deixe o seu e-mail para receber um conteúdo prático de IA e cloud a cada quinzena. Sem spam, e você pede a exclusão quando quiser.

Continue lendo