Tool use: dando ferramentas ao modelo (function calling)
- ⬜👋 Sua primeira chamada: a Converse API na prática(AWS Bedrock — GenAI em Produção)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
Um chat responde. Um agente age — consulta um banco, dispara um e-mail, chama uma API de pagamento. O tijolo que separa os dois se chama tool use (ou function calling). A ideia é simples e às vezes mal-entendida: você descreve ferramentas ao modelo, ele decide QUANDO chamar e com quais argumentos, você executa e devolve o resultado, e ele incorpora e responde. Dominar esse loop é pré-requisito para entender Agents — que é, no fundo, esse loop rodando sozinho.
A ideia: o modelo pede, você executa
O modelo não tem acesso ao seu banco, à internet nem ao sistema de arquivos. Tool use é o protocolo que resolve isso sem dar poder de execução ao modelo. O fluxo mental tem quatro passos:
- Você DESCREVE as ferramentas: para cada função, um nome, uma descrição em linguagem natural e um JSON Schema dos parâmetros.
- O modelo DECIDE: lendo o pedido do usuário e as descrições, ele escolhe se chama alguma tool, qual, e com quais argumentos.
- Você EXECUTA: o modelo devolve um pedido estruturado (toolUse); o SEU código roda a função de verdade com aqueles argumentos.
- O modelo INCORPORA: você devolve o resultado (toolResult) e ele gera a resposta final — ou pede outra tool.
O modelo nunca executa nada sozinho
Isto é o ponto de segurança mais importante do tool use. O modelo só emite um pedido estruturado — 'chame get_weather com city=Recife'. Quem roda a função é o seu app. Se a tool apaga um registro ou cobra um cartão, a decisão de executar (validação, permissão, confirmação) é 100% sua. O modelo propõe; você dispõe.
O contrato: toolConfig na Converse API
Na Converse API, você passa as ferramentas em toolConfig.tools. Cada item tem um toolSpec com três campos: name, description e inputSchema.json (o JSON Schema dos parâmetros). É esse contrato que o modelo lê para decidir e para preencher os argumentos.
{
"toolSpec": {
"name": "get_weather",
"description": "Retorna o clima atual de uma cidade. Chame SEMPRE que o usuario perguntar sobre temperatura, chuva ou condicoes do tempo. Nao invente valores.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Nome da cidade em portugues, ex: 'Recife'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Unidade de temperatura (default celsius)"
}
},
"required": ["city"]
}
}
}
}A description e o schema são o produto
A qualidade do tool use quase sempre depende de description e inputSchema, não do modelo. Uma description que diz QUANDO usar a tool (e quando não) evita chamadas erradas. Um schema restritivo (enum, required, type) impede argumentos inválidos antes mesmo do seu código rodar. Trate a description como um mini-prompt e o schema como um contrato de validação.
O loop completo do tool use
O ciclo é sempre o mesmo, e você repete até o modelo parar de pedir tools. O sinal de que ele pediu uma ferramenta é o stopReason igual a "tool_use".
- → 1. mensagens + toolConfig
- → 2. toolUse: nome, parâmetros e um id
- → 3. você executa — o Bedrock não executa nada
- → 4. toolResult com o mesmo id
- → 5. o modelo responde com o dado real
- Compute
- IA e machine learning
- Fora da AWS
O ponto que confunde no começo: o Bedrock nunca executa a sua função. Ele devolve um pedido, você executa e devolve o resultado — e é esse vai e volta que permite ao modelo trabalhar com dado que ele não poderia inventar.
- Declarar as ferramentas. O toolConfig descreve nome, quando usar e o schema dos parâmetros. A descrição é o que decide se a tool será escolhida.
- O modelo pede. A resposta volta com um bloco de uso de ferramenta: qual tool, quais parâmetros e um identificador que você vai precisar devolver.
- Você executa. Aqui está a parte que mais confunde: a execução é sua. O Bedrock nunca chama a sua função nem acessa o seu sistema.
- Devolver e concluir. O resultado volta amarrado ao mesmo identificador, e o modelo redige a resposta em volta do dado real — em vez de inventar um valor plausível.
- O laço precisa de teto, e o teto é seu. Nada impede o modelo de pedir ferramenta a cada volta indefinidamente. Quem para é o seu orquestrador: número máximo de voltas, gasto máximo por pedido e um caminho de desistência que devolve resposta parcial em vez de girar. Sem esses três, um caso de borda vira fatura e o usuário fica esperando.
Implementando o loop com boto3
O código abaixo é o esqueleto completo e correto: define uma tool get_weather, roda o loop enquanto stopReason == "tool_use", extrai cada toolUse, executa, monta o toolResult (com status success/error) e chama converse() de novo. Repare no MAX_ITERS como trava contra loop infinito.
import boto3
bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")
MODEL_ID = "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
# 1. Voce DESCREVE a ferramenta ao modelo (name + description + JSON Schema)
tool_config = {
"tools": [
{
"toolSpec": {
"name": "get_weather",
"description": (
"Retorna o clima atual de uma cidade. Chame sempre que o "
"usuario perguntar sobre temperatura, chuva ou tempo."
),
"inputSchema": {
"json": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Nome da cidade, ex: 'Recife'",
}
},
"required": ["city"],
}
},
}
}
]
}
# A funcao REAL que o SEU app executa. O modelo nunca roda isto — ele so PEDE.
def get_weather(city: str) -> dict:
# Aqui voce chamaria uma API de verdade; hardcode para o exemplo:
return {"city": city, "temp_c": 24, "condition": "chuva fraca"}
messages = [
{
"role": "user",
"content": [{"text": "Vou treinar em Recife hoje. Preciso de guarda-chuva?"}],
}
]
MAX_ITERS = 5 # trava contra loop infinito de tools
for _ in range(MAX_ITERS):
resp = bedrock.converse(
modelId=MODEL_ID,
messages=messages,
toolConfig=tool_config,
)
output_message = resp["output"]["message"]
messages.append(output_message) # preserva o historico completo
stop_reason = resp["stopReason"]
# Sem pedido de tool => o modelo ja respondeu em linguagem natural.
if stop_reason != "tool_use":
print(output_message["content"][-1]["text"])
break
# O modelo PEDIU uma ou mais tools. Execute cada bloco toolUse.
tool_results = []
for block in output_message["content"]:
if "toolUse" not in block:
continue
tool_use = block["toolUse"]
name = tool_use["name"]
tool_use_id = tool_use["toolUseId"] # correlaciona pedido <-> resposta
args = tool_use["input"] # dict validado contra o inputSchema
try:
if name == "get_weather":
result = get_weather(args["city"])
else:
raise ValueError(f"tool desconhecida: {name}")
tool_results.append({
"toolResult": {
"toolUseId": tool_use_id,
"content": [{"json": result}],
"status": "success",
}
})
except Exception as exc:
# Falhou? Devolva status "error" — o modelo consegue se recuperar.
tool_results.append({
"toolResult": {
"toolUseId": tool_use_id,
"content": [{"text": str(exc)}],
"status": "error",
}
})
# Devolve os resultados como uma NOVA mensagem role:"user".
messages.append({"role": "user", "content": tool_results})
else:
# for-else: rodou MAX_ITERS sem resposta final.
print("Limite de iteracoes atingido — abortando o loop.")No tool use, quem efetivamente executa a função quando o modelo pede uma tool?
tool_choice: controlando quando o modelo chama
Às vezes você não quer deixar a decisão totalmente livre. O campo toolChoice (dentro de toolConfig) controla a obrigação do modelo nesta rodada.
# toolChoice vive DENTRO de toolConfig e muda a obrigacao do modelo.
# 1. auto (default): o modelo decide se chama alguma tool ou responde texto.
tool_config["toolChoice"] = {"auto": {}}
# 2. any: OBRIGA o modelo a chamar ALGUMA tool (qualquer uma). Ele nao pode
# devolver texto puro nesta rodada.
tool_config["toolChoice"] = {"any": {}}
# 3. tool: FORCA uma tool especifica pelo nome. Otimo para extracao garantida
# (structured output): o modelo e obrigado a preencher ESTE schema.
tool_config["toolChoice"] = {"tool": {"name": "get_weather"}}
resp = bedrock.converse(
modelId=MODEL_ID,
messages=messages,
toolConfig=tool_config,
)
# Nota: any/tool dependem do modelo. Claude suporta os tres; alguns modelos
# so aceitam "auto" — verifique a doc do provedor antes de forcar.| Valor | O que obriga | Quando usar |
|---|---|---|
| auto (default) | Nada — o modelo decide se chama alguma tool ou responde texto. | Chat/agente geral: deixe o modelo julgar se precisa de ferramenta. |
| any | Chamar ALGUMA tool (qualquer uma). Não pode responder texto puro. | Quando toda resposta desta etapa deve passar por uma ferramenta. |
| tool | Chamar UMA tool específica, nomeada. O modelo é obrigado a preencher aquele schema. | Extração/structured output garantido: força o modelo a devolver ESTE formato. |
Sua aplicação recebeu uma resposta da Converse com stopReason = "tool_use". O que isso significa?
Structured outputs junto com tool use
Forçar JSON de saída é só um tool_choice
Structured output e tool use são o mesmo mecanismo. Quer que o modelo devolva SEMPRE um JSON com um formato exato? Defina uma tool cujo inputSchema é o schema desejado e use toolChoice = {"tool": {"name": "..."}}. O modelo é obrigado a 'chamar' essa tool, ou seja, a preencher o schema — e você lê o input como o seu objeto estruturado, sem parsing frágil de texto. É extração confiável de graça, reaproveitando o loop que você já tem.
Três modos de execução de tools
Até aqui assumimos que o seu app executa a função (client-side). Mas há três modos, e vale saber que existem antes de projetar a arquitetura:
| Modo | Quem executa a função | Onde / API |
|---|---|---|
| Client-side | O seu aplicativo (o loop deste módulo). | Converse, InvokeModel, Responses API, Chat Completions. |
| Server-side | O próprio Bedrock, chamando uma Lambda ou um AgentCore Gateway. | Responses API — você registra a tool e o Bedrock a invoca. |
| Anthropic Claude tool use | Você/o harness, com tipos NATIVOS do provedor (computer_*, bash_*, text_editor_*, memory_*). | Anthropic Messages API dentro do Bedrock. |
Prompt caching no bloco de tools
Quando você tem muitas tools grandes e estáveis, elas são reenviadas a cada chamada e custam input tokens toda vez. Um cachePoint no array de tools resolve: as tools acima do ponto viram cache e não pagam o preço cheio nas próximas chamadas.
# Quando o bloco de tools e grande e estavel entre chamadas, marque um
# cachePoint DEPOIS das tools. As tools acima do ponto sao cacheadas e
# nao pagam input token cheio nas proximas chamadas (janela de ~5 min).
tool_config = {
"tools": [
{"toolSpec": {"name": "get_weather", "description": "...", "inputSchema": {"json": {}}}},
{"toolSpec": {"name": "search_flights", "description": "...", "inputSchema": {"json": {}}}},
# ... dezenas de tools grandes e estaveis ...
{"cachePoint": {"type": "default"}}, # tudo ACIMA vira cache
]
}
# Requer modelo com suporte a prompt caching. Ha um minimo de tokens para o
# cache valer — verifique o limite por modelo na doc do Bedrock.Qual a diferença entre toolChoice = {"any": {}} e toolChoice = {"tool": {"name": "X"}}?
Boas práticas
O que separa um tool use robusto de um frágil
A maioria dos bugs de tool use não está no modelo — está no contrato e no loop. Descrições vagas geram chamadas erradas; schemas frouxos deixam passar argumentos inválidos; loops sem trava viram gasto infinito de tokens; e falhas de função sem status "error" fazem o modelo alucinar em cima de dado vazio.
- Descriptions claras: diga o que a tool faz E quando (não) usá-la. É o que mais melhora o acerto.
- Schemas restritivos: use type, enum e required para bloquear argumentos inválidos antes do seu código rodar.
- Trate a falha: quando a função der erro, devolva toolResult com status "error" e uma mensagem — o modelo consegue se recuperar ou pedir de novo.
- Limite de iterações: sempre tenha um MAX_ITERS para evitar loops infinitos de tool call.
- Preserve o histórico: acumule as mensagens (toolUse e toolResult) em messages; o modelo precisa do contexto completo para incorporar o resultado.
Próximo passo
Você já sabe dar mãos ao modelo. A pergunta seguinte é: como dar a ele conhecimento atualizado e privado sem estourar o contexto? A resposta é RAG — e o Bedrock entrega isso pronto no próximo módulo: Knowledge Bases: RAG gerenciado de ponta a ponta.
Perguntas frequentes
❓ Quem executa a função no tool use do Bedrock?
❓ O que fazer quando o modelo escolhe a ferramenta errada?
❓ Como evitar laço infinito de chamadas de ferramenta?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…