← Voltar para o Blog

Tool use na prática: quando o modelo chama a sua API

Dar ferramentas ao modelo é o que transforma um gerador de texto em algo útil no seu sistema. O mecanismo é simples — e a diferença entre funcionar e frustrar está em detalhes de desenho.

Equipe EasyOps Cloud · · 8 min de leitura

Um modelo de linguagem sozinho não sabe o saldo do cliente, não consulta o seu estoque e não cria pedido. Ele produz texto convincente sobre essas coisas, que é justamente o problema.

Tool use resolve isso: você declara funções que ele pode invocar, e ele decide quando chamá-las. É o mecanismo que transforma um gerador de texto em algo útil dentro de um sistema — e é onde a maior parte do trabalho de integração acontece.

Como funciona, sem mistério

O modelo não executa nada. Ele pede que você execute.

O fluxo tem quatro tempos. Você envia a pergunta junto com a lista de ferramentas disponíveis, cada uma com nome, descrição e esquema de parâmetros. O modelo responde ou com texto, ou com um pedido estruturado de chamada. Seu código valida e executa. O resultado volta ao modelo, que então formula a resposta.

Quem executa é sempre o seu código, e essa é a propriedade que torna tudo controlável: nenhuma ferramenta faz nada que você não tenha implementado, e toda chamada passa pela sua validação.

A descrição é a interface

O ponto que mais determina a qualidade do resultado, e o mais negligenciado: o modelo escolhe a ferramenta lendo a descrição dela. Descrição ruim produz escolha ruim, independentemente do modelo.

{
  "name": "consultar_pedido",
  "description": "Busca um pedido pelo número. Use quando o cliente citar um número de pedido ou perguntar sobre o status de uma compra específica. Não use para listar todos os pedidos de um cliente — para isso use listar_pedidos_cliente.",
  "input_schema": {
    "type": "object",
    "properties": {
      "numero_pedido": {
        "type": "string",
        "description": "Número do pedido, formato PED-000000. Aceita com ou sem o prefixo."
      }
    },
    "required": ["numero_pedido"]
  }
}

Três características fazem essa descrição funcionar. Ela diz quando usar, e não apenas o que a função faz. Diz quando não usar, apontando a alternativa — o que resolve a confusão entre ferramentas parecidas, que é a causa mais comum de escolha errada. E descreve o formato esperado do parâmetro, reduzindo argumento malformado.

Vale escrever a descrição pensando em alguém que nunca viu o seu sistema e precisa decidir em dois segundos. É literalmente a situação do modelo.

Poucas ferramentas, bem separadas

A taxa de acerto cai conforme a lista cresce. Com cinco ferramentas distintas, a escolha é quase sempre correta; com trinta, incluindo várias parecidas, os erros aparecem.

Se a lista está grande, dois caminhos ajudam:

  • Agrupar por operação em vez de expor cada endpoint. Uma ferramenta gerenciar_pedido com um parâmetro de ação costuma funcionar melhor que seis ferramentas quase iguais.
  • Selecionar por contexto. Nem toda ferramenta precisa estar disponível em toda conversa. Um roteador que decide o conjunto relevante antes reduz muito o espaço de escolha.

Validar sempre, sem exceção

O modelo produz argumentos que parecem corretos. Ele pode inventar um número de pedido plausível, passar uma data em formato diferente, ou preencher um campo opcional com algo que não existe.

def executar(nome, args, usuario):
    if nome not in FERRAMENTAS_PERMITIDAS:
        return {"erro": "ferramenta desconhecida"}

    try:
        dados = ESQUEMAS[nome].validate(args)      # valida o formato
    except ValidationError as e:
        return {"erro": f"argumentos inválidos: {e}"}

    if not autorizado(usuario, nome, dados):        # valida a permissão
        return {"erro": "sem permissão para esta operação"}

    return FERRAMENTAS[nome](dados, usuario=usuario)

A verificação de permissão é a que mais se esquece, e a mais importante. O modelo não deve decidir quem pode o quê. Se a ferramenta consulta pedido, ela precisa receber a identidade do usuário da sessão — nunca do argumento produzido pelo modelo — e filtrar por ela.

Sem isso, uma pergunta bem construída pelo usuário consegue consultar pedido de outra pessoa. É a versão moderna de injeção, e o modelo colabora com quem pedir.

Erro que ensina

Quando a chamada falha, o que você devolve muda o comportamento seguinte. Mensagem genérica leva o modelo a tentar de novo igual; mensagem específica leva à correção.

# Ruim
return {"erro": "não encontrado"}

# Bom
return {
  "erro": "pedido_nao_encontrado",
  "mensagem": "Nenhum pedido com número PED-123. O formato esperado é "
              "PED- seguido de 6 dígitos.",
  "sugestao": "Se o cliente não souber o número, use "
              "listar_pedidos_cliente com o CPF."
}

O modelo lê isso e ajusta. Na prática, uma boa mensagem de erro reduz mais o retrabalho que uma melhoria de prompt — e é uma alavanca que quase ninguém usa.

Ferramenta lenta trava a conversa

Enquanto o modelo espera o resultado, o usuário espera também. Uma ferramenta que demora oito segundos torna a interação desagradável mesmo que tudo esteja correto.

Três medidas:

  • Timeout curto e explícito, com erro claro. Melhor falhar em três segundos com mensagem útil que travar em trinta.
  • Executar em paralelo quando o modelo pedir várias chamadas independentes na mesma resposta — a maioria das APIs permite isso.
  • Trabalho longo vira tarefa assíncrona. A ferramenta devolve um identificador e a resposta diz que o processamento começou, em vez de segurar a conversa. É o mesmo desenho de fila descrito no artigo sobre transcrição self-hosted.

O tamanho do resultado importa

Uma consulta que devolve trezentas linhas consome contexto, custa dinheiro e piora a qualidade — o modelo se perde no volume e ignora o que importa.

Devolva o que a resposta precisa:

# Ruim: o objeto inteiro do banco
return pedido.to_dict()

# Bom: o que a conversa usa
return {
  "numero": p.numero,
  "status": p.status,
  "total": float(p.total),
  "previsao_entrega": p.previsao.isoformat() if p.previsao else None,
  "itens": [{"nome": i.nome, "qtd": i.qtd} for i in p.itens[:10]],
  "itens_omitidos": max(0, len(p.itens) - 10),
}

O campo indicando o que foi omitido é um detalhe que evita o modelo afirmar com convicção que o pedido tem dez itens quando tem quarenta.

Segurança, em três regras

Ferramenta é código executando a pedido de algo influenciável por texto do usuário. Isso exige disciplina:

  • Nada de execução genérica. Uma ferramenta que roda SQL arbitrário ou comando de shell é uma porta aberta. Exponha operações específicas.
  • Permissão verificada no código, a partir da sessão, sempre.
  • Ação com efeito colateral pede confirmação. Enviar, cobrar, excluir, alterar cadastro. A ferramenta prepara; a pessoa confirma.

Vale lembrar que o texto que chega ao modelo pode vir de fora — um e-mail encaminhado, um documento enviado, o conteúdo de uma página. Instruções embutidas nesse conteúdo podem tentar induzir chamadas. A defesa não é filtrar o texto, é garantir que nenhuma ferramenta faça algo perigoso mesmo se chamada — princípio que se conecta ao artigo sobre IA sem vazar dado do cliente.

Um checklist de desenho

Para cada ferramenta, antes de expor:

  • A descrição diz quando usar e quando não usar?
  • Os argumentos são validados por esquema?
  • A permissão é verificada a partir da sessão, não do argumento?
  • O erro devolvido orienta a correção?
  • O resultado é enxuto, com indicação do que foi omitido?
  • Há timeout, e o que é lento virou assíncrono?
  • A ação irreversível exige confirmação humana?

Sete perguntas por ferramenta. Respondidas com honestidade, elas separam uma integração que funciona por meses de uma que precisa de ajuste toda semana — e a maior parte do tempo economizado vem das duas primeiras.

Quando o modelo não chama a ferramenta

Um problema frequente e frustrante: a ferramenta existe, está bem descrita, e o modelo responde de memória em vez de consultá-la. O resultado é uma resposta plausível e desatualizada.

Três causas, em ordem de frequência.

A instrução do sistema não deixou claro que consultar é obrigatório. Modelos tendem a responder com o que "sabem" quando isso parece suficiente. A correção é explícita:

Você NÃO tem conhecimento sobre pedidos, estoque ou clientes.
Sempre consulte as ferramentas antes de afirmar qualquer dado.
Se a ferramenta falhar, diga que não conseguiu consultar —
nunca responda de memória.

A pergunta não parece exigir consulta. "Vocês entregam em Manaus?" soa como política geral, e o modelo responde genericamente quando a resposta real depende do cadastro de regiões.

Ferramenta demais. Com a lista grande, a escolha fica difusa e o modelo às vezes opta por não escolher nenhuma.

Vale medir isso explicitamente: a proporção de respostas que envolveram chamada de ferramenta, comparada com a proporção que deveria ter envolvido. É uma métrica simples, raramente acompanhada, e que costuma revelar que boa parte das respostas está saindo sem consulta nenhuma.

Versionar a interface das ferramentas

Um cuidado de médio prazo que evita quebra silenciosa: a descrição e o esquema das ferramentas são uma interface, e mudá-los altera o comportamento do sistema tanto quanto mudar o prompt.

Renomear um parâmetro, tornar um campo obrigatório ou reescrever a descrição para "ficar mais clara" pode mudar quando o modelo escolhe aquela ferramenta — e a mudança não aparece em teste unitário, porque o código continua funcionando.

Três práticas resolvem:

  • Versionar as definições junto com o código, e tratar alteração nelas como mudança de comportamento.
  • Rodar o conjunto de avaliação antes e depois de qualquer edição de descrição.
  • Registrar qual versão das definições produziu cada interação, para conseguir correlacionar depois.

Vale a mesma disciplina que se aplica a esquema de banco: a interface é contrato, e contrato muda com cuidado. A diferença é que aqui o consumidor do contrato é um modelo, que interpreta em vez de compilar — o que torna a quebra silenciosa em vez de explícita.

Suba um servidor em minutos

Preço em real, suporte em português e dados no Brasil.

Criar conta