AAAlison AmorimBlog
alisonamorim.com ↗

IA First · Episódio 11

Skills e Ferramentas: Quando o LLM Precisa Apertar Parafusos no Seu Stack

Como deixar um LLM chamar APIs e executar ações sem perder o controle: contrato da tool, validação, risco por tipo de ação, menor privilégio e auditoria.

  • 14 min de leitura
  • IA Arquitetura
Fonte do texto
Braço robótico saindo de uma nuvem de texto com a sigla LLM aperta um parafuso numa máquina neon enquanto um homem de boné observa
Neste artigo · 13 seções
  1. O problema que aparece rápido demais
  2. O que são tools e skills, sem abstração demais
  3. O contrato da tool: a parte mais importante que quase ninguém detalha
  4. Validação de entrada: a primeira linha de defesa
  5. Validação de saída: o que a tool retorna pro modelo
  6. Classificação de tools: leitura vs. escrita vs. ação destrutiva
  7. Escopo e permissões: o princípio do menor privilégio aplicado a LLMs
  8. Timeout, rate limiting e circuit breaker: protegendo seu stack
  9. O que acontece quando o modelo chama a tool errada
  10. Observabilidade de tool use: o que logar
  11. Desenhando boas tools: alguns princípios práticos
  12. Conclusão
  13. Insights & Takeaways

O problema que aparece rápido demais

Tem um momento em todo projeto de IA que chega mais cedo do que o time espera. O LLM já responde perguntas, já classifica, já resume documentos. Aí alguém fala: "legal, mas e se ele pudesse criar o ticket no Jira direto?", ou "e se em vez de só dizer o saldo, ele pudesse processar a transferência?", ou "e se ele consultasse o status do pedido em tempo real em vez de depender de um cache de ontem?"

Esse é o momento em que o modelo deixa de ser um gerador de texto e começa a ser um ator no seu sistema. Ele precisa ler dados, chamar APIs, executar ações. Precisa, como diz o título, apertar parafusos no seu stack.

E é exatamente aqui que a maioria dos problemas sérios começa. Porque dar acesso a um sistema probabilístico pra executar ações reais num ambiente de produção é uma decisão de arquitetura com implicações que vão muito além de "qual endpoint eu exponho". Envolve segurança, validação, controle de escopo, auditoria e, acima de tudo, definir limites claros sobre o que o modelo pode e o que não pode fazer.

Esse artigo é sobre como desenhar essa camada de tools/skills de forma segura, operável e sustentável. Não é sobre frameworks de agentes. É sobre engenharia de contratos e controles pra quando o LLM precisa interagir com o mundo real.

O que são tools e skills, sem abstração demais

Vamos ser diretos na definição.

Uma tool (ferramenta) é uma função que o LLM pode decidir chamar durante a geração de uma resposta. O modelo recebe a descrição da ferramenta (nome, o que faz, quais parâmetros aceita) e, com base no contexto da conversa, decide se precisa usá-la, com quais parâmetros.

Pensa assim: o modelo é um engenheiro numa sala com acesso a um painel de botões. Cada botão tem uma etiqueta dizendo o que faz. O modelo lê as etiquetas, decide qual botão apertar, e recebe o resultado. Mas quem define quais botões existem, o que cada um faz de verdade, e quais proteções ficam entre o botão e o sistema real, é você.

O fluxo básico de tool use se parece com isso:

Qual o status do pedido #4521? Prompt + contexto + lista de tools disponíveis Tool call: get_order_status(order_id="4521") Executa get_order_status com validação { status: "em_transporte", previsao: "2026-04-06" } Resultado da tool Seu pedido #4521 está em transporte, com previsão de entrega para 06/04. Resposta formatada Usuário Serviço LLM Tool Runtime

Repara num detalhe importante: o LLM nunca chama a tool diretamente. Ele emite uma intenção de chamada ("quero chamar essa função com esses parâmetros"). Quem executa de verdade é o seu serviço, o Tool Runtime. Essa separação entre intenção e execução é onde vivem todos os controles de segurança. Se você pular essa separação e deixar o modelo executar direto, perdeu a camada de proteção.

O termo "skill" é usado em alguns contextos como sinônimo de tool, e em outros como um agrupamento de tools relacionadas. Um skill de "gestão de pedidos" pode incluir as tools get_order_status, list_recent_orders e cancel_order. A nomenclatura varia entre provedores e frameworks, mas o conceito é o mesmo: capacidades bem definidas que o modelo pode invocar.

O contrato da tool: a parte mais importante que quase ninguém detalha

Cada tool precisa de um contrato. Não um contrato no sentido jurídico, mas no sentido de API contract: uma especificação precisa do que a tool faz, o que aceita, o que retorna e, tão importante quanto, o que não faz.

O contrato tem quatro partes.

A descrição é o que o modelo lê pra decidir se usa a tool. Parece simples, mas a qualidade dessa descrição afeta diretamente a qualidade das decisões do modelo. Uma descrição vaga como "busca informações de pedidos" vai fazer o modelo chamar essa tool pra qualquer pergunta que mencione "pedido", inclusive perguntas que seriam melhor respondidas de outra forma. Uma descrição precisa como "retorna o status atual e a previsão de entrega de um pedido específico, dado o ID do pedido. Não retorna histórico de status nem detalhes de itens" guia o modelo a usar a tool corretamente e a não usá-la quando não é o caso.

Os parâmetros de entrada são o schema do que a tool aceita. Tipo de cada campo, obrigatoriedade, validações, enums de valores possíveis. Quanto mais restrito, melhor. Se o order_id é um número inteiro positivo de 4 dígitos, declare isso. O modelo vai gerar parâmetros inválidos em algum momento; a validação do schema é a primeira linha de defesa.

O schema de saída é o que a tool retorna. Tipos, campos, formatos. Isso ajuda o modelo a interpretar o resultado e formatar a resposta pro usuário.

As restrições são o que a tool não faz, quando não deve ser usada, e quais efeitos colaterais tem. Uma tool que cancela um pedido tem efeito colateral irreversível. Isso precisa estar na descrição. O modelo precisa saber que essa ação tem consequências, e idealmente, precisa confirmar com o usuário antes de executar.

Na prática, o contrato de uma tool pode ser assim:

{
  "name": "cancel_order",
  "description": "Cancela um pedido que ainda não foi enviado. AÇÃO IRREVERSÍVEL. Só use após confirmação explícita do usuário. Não funciona para pedidos já em transporte.",
  "parameters": {
    "type": "object",
    "properties": {
      "order_id": {
        "type": "integer",
        "description": "ID numérico do pedido (4 dígitos)",
        "minimum": 1000,
        "maximum": 9999
      },
      "reason": {
        "type": "string",
        "enum": ["changed_mind", "wrong_item", "duplicate", "other"],
        "description": "Motivo do cancelamento"
      }
    },
    "required": ["order_id", "reason"]
  },
  "returns": {
    "type": "object",
    "properties": {
      "success": { "type": "boolean" },
      "cancellation_id": { "type": "string" },
      "refund_status": { "type": "string", "enum": ["pending", "processing", "not_applicable"] }
    }
  },
  "side_effects": "Cancela o pedido permanentemente e inicia processo de estorno se pagamento já foi processado.",
  "requires_user_confirmation": true
}

Esse contrato é lido pelo modelo, validado pelo seu runtime, e documentado pra auditoria. As três funções em um artefato só.

Validação de entrada: a primeira linha de defesa

O modelo vai gerar parâmetros errados. Não é questão de "se", é "quando". Vai mandar um order_id como string quando o schema pede inteiro. Vai inventar um valor que não está no enum. Vai omitir um campo obrigatório. Vai preencher um campo com um valor que faz sentido linguisticamente mas não existe no sistema.

A validação de entrada acontece no Tool Runtime, antes da execução. É idêntica à validação que você faria numa API REST: valida schema, tipos, ranges, formatos, obrigatoriedade. Se não passa, retorna erro estruturado pro modelo, que pode tentar de novo com parâmetros corrigidos.

Inválido Válido Inválido Válido Sim Não Confirmado Negado LLM emite tool call Validação de schema Retorna erro estruturado pro LLM LLM tenta novamente com parâmetros corrigidos Validação de negócio Retorna erro de negócio pro LLM Requer confirmação do usuário? Pausa e pede confirmação Executa a tool Retorna cancelamento pro LLM

A validação de negócio é a segunda camada. O schema pode estar correto (order_id é inteiro, está no range), mas o pedido pode não existir, ou pertencer a outro usuário, ou já ter sido cancelado. Essa validação depende do estado do sistema e precisa acontecer antes de qualquer efeito colateral.

O loop de retry é intencional. Modelos modernos se saem razoavelmente bem quando recebem um erro estruturado: "Parâmetro order_id inválido: valor 99999 não encontrado. IDs válidos têm 4 dígitos." O modelo ajusta e tenta novamente. Mas esse loop precisa ter um limite. Três tentativas, por exemplo. Se em três tentativas o modelo não acerta, devolve pro usuário com uma mensagem clara em vez de ficar num loop infinito de chamadas que custam tokens.

Validação de saída: o que a tool retorna pro modelo

A validação de saída é menos discutida mas igualmente importante. O que a tool retorna pro modelo afeta diretamente o que o modelo diz pro usuário.

Primeiro princípio: retorne só o necessário. Se a tool consulta um banco e retorna 40 campos, mas o modelo só precisa de 3 pra responder a pergunta, filtre. Cada campo extra é contexto a mais que o modelo precisa processar, que consome tokens, e que pode confundir a resposta. Pior: se entre os 40 campos tem informação sensível (CPF, saldo, dados internos), e você manda tudo pro modelo, essa informação pode aparecer na resposta ao usuário.

Segundo princípio: formate pra consumo do modelo, não pra consumo humano. Se a tool retorna uma data como timestamp Unix, converta pra formato legível antes de mandar pro modelo. Se retorna um código de status como "SHP_003", traduza pra "em transporte". O modelo consegue lidar com dados brutos, mas gasta tokens e capacidade de raciocínio interpretando o que já poderia vir interpretado.

Terceiro princípio: inclua contexto de limitação. Se a tool consultou os últimos 10 pedidos de 50 que o cliente tem, diga isso no retorno: "Mostrando os 10 pedidos mais recentes de 50 no total." Assim o modelo pode informar o usuário que a lista não é completa, em vez de apresentar 10 resultados como se fossem todos.

Classificação de tools: leitura vs. escrita vs. ação destrutiva

Nem toda tool tem o mesmo nível de risco. Consultar um status é diferente de criar um registro, que é diferente de deletar algo. A classificação por nível de risco define quais controles cada tool precisa ter.

Tools de leitura são as mais seguras. Consultam dados, não alteram estado. get_order_status, search_products, get_account_balance. O risco é de vazamento de informação (retornar dados que o usuário não deveria ver), não de corrupção de estado. Controle principal: filtro por permissão do usuário que está interagindo.

Tools de escrita são onde começa o perigo. Criam ou alteram dados. create_ticket, update_address, submit_review. O risco é de criar dados incorretos ou indesejados. Controles: validação rigorosa de todos os campos, limitação de escopo (o modelo só pode criar ticket de determinadas categorias, por exemplo), e log completo da ação pra auditoria.

Tools destrutivas são as que exigem mais cuidado. Cancelam, deletam, revertem. cancel_order, delete_account, revoke_access. O risco é de ação irreversível baseada em interpretação errada do modelo. Controles: confirmação explícita do usuário antes da execução, sem exceção; período de cooldown (impedir cancelamento e recriação em sequência); e em alguns casos, aprovação de um segundo fator que não é o modelo.

A arquitetura deveria tratar cada categoria de forma diferente:

Leitura Escrita Destrutiva Confirmado Negado LLM decide usar tool Classificação da tool Validação de acesso Executa Retorna resultado filtrado Validação de acesso + schema + negócio Executa Log de auditoria + retorna confirmação Validação de acesso + schema + negócio Solicita confirmação do usuário Executa Log de auditoria detalhado + retorna confirmação Aborta e informa LLM

Uma decisão de design que vale considerar: separar as tools destrutivas em um grupo que o modelo só pode sugerir, nunca executar diretamente. O modelo diz "parece que você quer cancelar o pedido #4521, posso prosseguir?", o usuário confirma, e só aí o serviço executa. O modelo nunca teve a capacidade de executar sozinho. Isso é diferente de um flag requires_confirmation no contrato; é uma restrição arquitetural, não uma convenção.

Escopo e permissões: o princípio do menor privilégio aplicado a LLMs

Aqui vai uma analogia que funciona bem. Tools pra um LLM são como permissões de IAM pra um serviço na cloud. Você não dá AdministratorAccess pra um lambda que só precisa ler de um bucket S3. Pelo mesmo motivo, você não dá ao modelo acesso a todas as tools possíveis quando ele só precisa de duas pra resolver a tarefa.

O princípio é: cada contexto de uso do modelo deveria ter acesso só às tools que fazem sentido naquele contexto.

Se o usuário está no fluxo de "consultar pedido", as tools disponíveis são get_order_status e list_recent_orders. Não cancel_order, não update_payment, não create_refund. Mesmo que essas tools existam no sistema.

Se o usuário está no fluxo de "suporte pós-venda", aí sim, cancel_order entra, junto com get_order_status e request_refund. Mas update_product_catalog continua fora.

Isso se implementa montando a lista de tools dinamicamente por contexto, não tendo uma lista global estática. O serviço de domínio sabe qual é o contexto (por fluxo, por role do usuário, por feature) e injeta só as tools relevantes na chamada ao modelo.

Tem um benefício técnico nisso além de segurança: quanto menos tools na lista, melhor a decisão do modelo. Com 5 tools, o modelo escolhe bem. Com 50, começa a confundir, a escolher tools parecidas por engano, a gerar parâmetros pra tool errada. Reduzir o espaço de decisão melhora a acurácia da seleção de tool.

Timeout, rate limiting e circuit breaker: protegendo seu stack

O modelo não tem noção de custo computacional. Se você dá uma tool que faz uma query pesada no banco, o modelo vai chamar sem pensar duas vezes. Se o resultado não é satisfatório, pode chamar de novo com parâmetros levemente diferentes. E de novo.

Sem controles, um loop de tool calling pode derrubar um serviço. Não é cenário hipotético; é algo que acontece em qualquer implementação de agentes com acesso a sistemas reais se você não colocar travas.

Timeout por tool call é o controle mais básico. Cada tool tem um tempo máximo de execução. Se estoura, retorna timeout pro modelo. O modelo pode tentar de novo ou informar o usuário que o serviço está lento. Sem timeout, uma query lenta segura o request inteiro.

Rate limiting por sessão controla quantas vezes o modelo pode chamar tools dentro de uma mesma interação. Um limite razoável é algo entre 5 e 15 tool calls por turno de conversa, dependendo do caso de uso. Se o modelo precisa de mais que isso, provavelmente o design da tool está errado, a tarefa é complexa demais pro modelo, ou ele está preso num loop de retry.

Circuit breaker protege o serviço de destino. Se a tool get_order_status está retornando erro em 50% das chamadas, o circuit breaker abre e as próximas chamadas retornam imediatamente com uma mensagem de indisponibilidade, sem bater no serviço. Quando o serviço se recupera, o circuit volta a fechar.

Limite de custo por sessão controla o gasto com tokens. Cada tool call consome tokens (o resultado da tool vai no contexto da próxima chamada ao modelo). Se o modelo faz 10 tool calls numa sessão, os tokens acumulam. Um limite de custo por sessão impede que uma conversa problemática gere uma fatura inesperada.

Sim Não Sim Não Timeout Sucesso Erro LLM quer chamar tool Rate limit atingido? Retorna: limite de chamadas atingido Circuit breaker aberto? Retorna: serviço temporariamente indisponível Executa tool com timeout Retorna: tempo esgotado, tente simplificar Retorna resultado Registra falha no circuit breaker Retorna erro estruturado

O que acontece quando o modelo chama a tool errada

Modelos erram na seleção de tools. Chamam update_address quando o usuário queria consultar o endereço. Chamam cancel_order quando o usuário perguntou se pode cancelar, sem ter pedido pra cancelar de fato. Chamam uma tool com parâmetros que passam na validação de schema mas não fazem sentido no contexto.

O primeiro nível de proteção é o contrato bem escrito. Descrições precisas reduzem erros de seleção. Mas não eliminam.

O segundo nível é a camada de confirmação pra ações com efeito colateral. Já falamos disso: tools de escrita e destrutivas pedem confirmação. Isso é uma rede de segurança contra seleção errada.

O terceiro nível, menos comum mas eficaz em cenários de alto risco, é uma etapa de validação de intenção. Antes de executar, o serviço faz uma verificação: "o usuário disse X, o modelo quer chamar Y, isso é consistente?" Essa verificação pode ser heurística (regras simples tipo "se o usuário não mencionou cancelar, bloqueia a tool cancel") ou pode ser uma segunda chamada ao modelo com um prompt focado ("dado que o usuário disse '...', a chamada a cancel_order é apropriada? Responda sim ou não."). A segunda chamada custa tokens a mais, mas em cenários onde uma ação errada tem custo alto, o trade-off compensa.

Observabilidade de tool use: o que logar

Cada chamada de tool é um evento que precisa ser logado com o mesmo rigor que uma chamada entre microserviços. No mínimo:

{
  "interaction_id": "uuid-da-interacao-com-o-modelo",
  "tool_call_id": "uuid-desta-chamada-especifica",
  "tool_name": "cancel_order",
  "tool_version": "2.1",
  "classification": "destructive",
  "parameters_received": { "order_id": 4521, "reason": "changed_mind" },
  "validation_result": "passed",
  "user_confirmation": "confirmed",
  "execution_result": "success",
  "response_returned": { "success": true, "cancellation_id": "CNC-9981" },
  "latency_ms": 342,
  "timestamp": "2026-04-01T09:14:22Z",
  "user_id": "usr_abc123",
  "tenant_id": "acme-corp",
  "session_id": "sess_xyz789"
}

Esse log permite responder perguntas como: "quantas vezes o modelo chamou cancel_order na última semana?", "qual a taxa de erro por tool?", "qual a latência p95 de cada tool?", "quantas vezes o modelo tentou chamar uma tool com parâmetros inválidos?" (essa última é especialmente útil pra melhorar descrições de tools e schemas).

Métricas agregadas que importam: taxa de chamada por tool (pra entender quais são mais usadas), taxa de erro de validação por tool (pra melhorar contratos), taxa de confirmação negada pelo usuário em tools destrutivas (se está alta, o modelo está sugerindo ações que os usuários não querem), e número médio de tool calls por sessão (se está subindo, o modelo pode estar ficando ineficiente ou preso em loops).

Desenhando boas tools: alguns princípios práticos

Depois de ver implementações de tool use que funcionam e que não funcionam, alguns padrões ficam claros.

Tools devem ser atômicas. Uma tool faz uma coisa. get_order_status retorna status. Não retorna status, detalhes de pagamento, endereço de entrega e recomendações de produtos similares. Se o modelo precisa de várias informações, ele chama várias tools. Isso parece menos eficiente, mas é mais previsível, mais fácil de validar e mais fácil de manter.

Tools devem ter nomes autoexplicativos. O modelo decide qual tool usar baseado no nome e na descrição. get_order_status é melhor que orderInfo. cancel_order é melhor que updateOrderState. Se você precisa de um parágrafo pra explicar o que a tool faz, o nome está ruim.

Tools não devem expor complexidade interna. O modelo não precisa saber que get_order_status consulta o microserviço de pedidos via gRPC que por sua vez faz join de três tabelas. A tool é uma abstração. O modelo vê a interface; o runtime lida com a implementação.

Tools devem retornar erros estruturados, não stacktraces. Se o pedido não existe, retorne { "error": "order_not_found", "message": "Pedido #9999 não encontrado" }. Não retorne uma exceção Java de 40 linhas. O modelo precisa entender o erro pra reagir apropriadamente (informar o usuário, tentar outro ID, perguntar se está correto).

Tools devem ser testáveis isoladamente. Cada tool deveria ter testes unitários e de integração, independente do modelo. "Dada essa entrada, essa tool retorna essa saída." Se a tool funciona corretamente de forma isolada, os problemas em produção são de seleção ou parametrização pelo modelo, não de implementação da tool.

Conclusão

Tools são a ponte entre um LLM que gera texto e um LLM que executa ações no mundo real. Essa ponte precisa de controles que não existem quando o modelo é só um gerador de respostas.

A disciplina é tratar cada tool como uma API que vai ser consumida por um cliente imprevisível. Porque é exatamente isso que um LLM é. Ele vai mandar parâmetros inesperados, vai chamar tools fora de contexto, vai tentar ações que não deveria. O contrato bem definido, a validação em camadas, a classificação por risco, o princípio de menor privilégio e a observabilidade de cada chamada são o que transformam tool use de uma demo impressionante numa funcionalidade operável em produção.

E uma última reflexão: se você não se sente confortável dando a um estagiário de primeiro dia acesso irrestrito a um sistema sem supervisão, não dê ao modelo. A diferença é que o estagiário pergunta quando não sabe. O modelo executa quando não sabe.

Insights & Takeaways

  1. O modelo nunca executa a tool diretamente. Ele emite uma intenção de chamada; quem executa é o seu runtime. Essa separação entre intenção e execução é onde vivem todos os controles de segurança.

  2. O contrato da tool (descrição, parâmetros, retorno e restrições) é o artefato mais importante do design de tool use. Uma descrição vaga causa seleção errada; um schema frouxo causa parâmetros inválidos; restrições ausentes causam ações indesejadas.

  3. Classifique tools por nível de risco: leitura, escrita e destrutiva. Cada categoria exige controles diferentes. Tools destrutivas exigem confirmação do usuário, sem exceção.

  4. Monte a lista de tools disponíveis dinamicamente por contexto, não uma lista global. Menos tools na chamada significa decisões melhores pelo modelo e menor superfície de risco.

  5. Proteja seu stack com timeout, rate limiting por sessão e circuit breaker em cada tool. O modelo não tem noção de custo computacional e, sem travas, um loop de tool calling pode derrubar serviços reais.

Comente no LinkedIn

A conversa sobre este artigo acontece no LinkedIn, onde ele também foi publicado. Discordou de algo, viu de outro jeito em produção? Conta lá.

Leia também