Python & AI Tutorials Logo
LangChain & LangGraph

13. Conectando Ferramentas ao LLM

No Capítulo 12 construímos ferramentas que um agente pode usar. Agora é hora de conectar essas ferramentas ao LLM para que o agente possa realmente usá-las.

Por melhor que uma ferramenta seja construída, o LLM não pode usá-la sem saber que ela existe. Ele precisa saber o que está disponível e escolher a ferramenta certa para cada solicitação. No entanto, como aprendemos no Capítulo 12, o LLM não executa ferramentas diretamente. Em vez disso, ele solicita "chame esta ferramenta com estes argumentos", e o código do nosso agente invoca a ferramenta. Todo esse mecanismo é chamado de chamada de ferramentas (tool calling).

Neste capítulo, vamos vincular ferramentas ao LLM, executar solicitações de chamada de ferramenta e devolver os resultados ao LLM — implementando o ciclo completo. Esse ciclo é a base do loop do agente que construiremos no Capítulo 14.

13.1) Vinculando Ferramentas e Inspecionando Solicitações de Chamada de Ferramenta

Para que o LLM use ferramentas, ele primeiro precisa saber o que está disponível. Quando passamos o name, a description e o esquema de entrada de cada ferramenta para o LLM, ele aprende quando e como usar cada ferramenta. Esse processo é chamado de vinculação de ferramentas (tool binding).

Uma vez que as ferramentas estão vinculadas, o LLM fará uma de duas coisas quando receber uma pergunta. Se nenhuma ferramenta for necessária, ele responde com texto como de costume. Se uma ferramenta for necessária, ele retorna uma solicitação estruturada: "chame esta ferramenta com estes argumentos". Nosso agente então inspeciona essa solicitação e invoca a ferramenta especificada.

Vamos começar com como vincular ferramentas.

13.1.1) Vinculando Ferramentas com bind_tools()

A vinculação de ferramentas é tratada por um único método: bind_tools(). Todo modelo de chat que suporta chamada de ferramentas fornece esse método. Chamar bind_tools() retorna um novo objeto de modelo com as ferramentas fornecidas vinculadas a ele.

Vamos vincular a ferramenta get_weather que construímos no Capítulo 12.

python
from langchain_openai import ChatOpenAI
from langchain.tools import tool
 
llm = ChatOpenAI(model="gpt-5-mini")
 
@tool
def get_weather(city: str) -> str:
    """Obtém o clima atual de uma determinada cidade."""
    return f"It's always sunny in {city}!"
 
# Vincula a ferramenta ao modelo — um novo objeto de modelo com a ferramenta vinculada é retornado
llm_with_tools = llm.bind_tools([get_weather])

É só isso. O llm_with_tools retornado é um modelo que conhece a ferramenta get_weather. Observe que o llm original não mudou em nada — ele ainda não sabe nada sobre nenhuma ferramenta.

bind_tools() recebe uma lista de ferramentas como argumento e retorna um novo objeto de modelo com essas ferramentas vinculadas. Internamente, ele converte os metadados de cada ferramenta (name, description e esquema de entrada) em um formato que o provedor de LLM entende, de modo que os esquemas das ferramentas são enviados junto com cada invocação do modelo.

13.1.2) Inspecionando Solicitações de Chamada de Ferramenta

O que acontece quando enviamos uma pergunta sobre o clima para um modelo com uma ferramenta de clima vinculada?

python
response = llm_with_tools.invoke("Como está o clima em Paris?")
 
print(f"type: {type(response)}\n")
print(f"tool_calls: {response.tool_calls}\n")
print(f"content: {repr(response.content)}\n")

Saída:

type: <class 'langchain_core.messages.ai.AIMessage'>
 
tool_calls: [{'name': 'get_weather', 'args': {'city': 'Paris'}, 'id': 'call_hgXrHGD', 'type': 'tool_call'}]
 
content: ''

Como o llm_with_tools ainda é um modelo de chat, ele retorna um AIMessage. Mas duas coisas são diferentes nessa resposta.

Primeiro, response.tool_calls contém uma lista de solicitações de chamada de ferramenta. Cada solicitação é um dicionário com quatro chaves:

  • name — o nome da ferramenta a ser chamada. Isso corresponde ao name da ferramenta em seus metadados.
  • args — os argumentos a serem passados ao chamar a ferramenta. O modelo analisou o esquema de entrada e a pergunta do usuário para construir {'city': 'Paris'}.
  • id — um identificador único para esta chamada. O LLM usa isso posteriormente para combinar os resultados da execução da ferramenta com as solicitações de chamada de ferramenta.
  • type — sempre 'tool_call'.

Segundo, response.content é uma string vazia. Essa resposta não é uma resposta de texto final — é uma solicitação de chamada de ferramenta.

Para determinar se a resposta é uma solicitação de chamada de ferramenta, verifique tool_calls em vez de content. Se tool_calls não estiver vazio, é uma solicitação de chamada de ferramenta. Se estiver vazio, o LLM forneceu uma resposta de texto diretamente.

13.2) Executando Ferramentas e Devolvendo Resultados

O LLM solicitou uma chamada da ferramenta get_weather. Agora o código do nosso agente precisa executar a ferramenta e devolver o resultado ao LLM.

Processar uma solicitação de chamada de ferramenta envolve três etapas:

  1. Extrair o nome da ferramenta e os argumentos de tool_calls e executar a ferramenta correspondente.
  2. Converter o resultado da execução em um ToolMessage.
  3. Enviar a conversa completa (pergunta do usuário + solicitação de chamada de ferramenta do LLM + resultado da execução da ferramenta) de volta ao LLM para receber uma resposta.

Aqui está um diagrama desse processo:

LLMget_weatherAgenteUsuárioLLMget_weatherAgenteUsuário"Como está o clima em Paris?"HumanMessageAIMessage (tool_calls: get_weather)get_weather(city='Paris')"It's always sunny in Paris!"ToolMessageAIMessage ("O clima em Paris está ensolarado!")"O clima em Paris está ensolarado!"

13.2.1) Executando a Ferramenta e Criando um ToolMessage

Primeiro, vamos extrair o nome da ferramenta e os argumentos de tool_calls e executar a ferramenta correspondente. Para procurar uma ferramenta pelo nome, criamos um dicionário indexado pelo nome da ferramenta.

python
# Um dicionário para procurar ferramentas pelo nome
tool_map = {get_weather.name: get_weather}

Em seguida, retiramos uma solicitação de chamada de ferramenta de tool_calls, encontramos a ferramenta correspondente em tool_map e a executamos.

python
if response.tool_calls:
    tool_call = response.tool_calls[0]
    # {'name': 'get_weather', 'args': {'city': 'Paris'}, 'id': 'call_hgXrHGD', 'type': 'tool_call'}
 
    selected_tool = tool_map[tool_call["name"]]
    result = selected_tool.invoke(tool_call["args"])
    print(result)  # Saída: It's always sunny in Paris!

Para devolver o resultado da execução da ferramenta ao LLM, precisamos envolvê-lo em um ToolMessage. Um ToolMessage tem dois campos obrigatórios:

  • content — o resultado da execução da ferramenta como uma string.
  • tool_call_id — o id de tool_calls. O LLM usa isso para identificar a qual solicitação o resultado pertence.
python
from langchain_core.messages import ToolMessage
 
if response.tool_calls:
    tool_call = response.tool_calls[0]
 
    selected_tool = tool_map[tool_call["name"]]
    result = selected_tool.invoke(tool_call["args"])
 
    tool_message = ToolMessage(
        content=result,
        tool_call_id=tool_call["id"],   # deve corresponder ao id da solicitação
    )
    print(tool_message)

Saída:

content="It's always sunny in Paris!" tool_call_id='call_hgXrHGD'

No código acima, passamos args para executar a ferramenta e, em seguida, combinamos o resultado com o tool_call_id para construir o ToolMessage.

Há uma maneira mais simples de fazer isso em uma única etapa. Em vez de passar apenas args para .invoke(), passe o dicionário tool_call inteiro. O LangChain executará a ferramenta e retornará um ToolMessage automaticamente.

python
if response.tool_calls:
    tool_call = response.tool_calls[0]
 
    selected_tool = tool_map[tool_call["name"]]
    tool_message = selected_tool.invoke(tool_call)
    print(tool_message)

Saída:

content="It's always sunny in Paris!" tool_call_id='call_hgXrHGD'

Mesmo resultado, mas o código é muito mais simples e não há chance de incompatibilidade de tool_call_id. Usaremos essa abordagem daqui em diante.

13.2.2) Enviando Resultados de Ferramentas ao LLM

Agora vamos passar o resultado da execução da ferramenta ao LLM. Enviamos o histórico completo da conversa — pergunta do usuário, a solicitação de chamada de ferramenta do LLM e o resultado da execução da ferramenta — de volta ao LLM, e ele gera uma resposta com base no resultado da ferramenta.

Vamos juntar todo o processo do início ao fim em um único trecho de código.

python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain.tools import tool
 
# Define as ferramentas e vincula
llm = ChatOpenAI(model="gpt-5-mini")
 
@tool
def get_weather(city: str) -> str:
    """Obtém o clima atual de uma determinada cidade."""
    return f"It's always sunny in {city}!"
 
llm_with_tools = llm.bind_tools([get_weather])
tool_map = {get_weather.name: get_weather}
 
# Etapa 1: Envia a pergunta do usuário e recebe a resposta do LLM
messages = [HumanMessage("Como está o clima em Paris?")]
ai_msg = llm_with_tools.invoke(messages)
messages.append(ai_msg)
 
# Etapa 2: Se uma chamada de ferramenta foi solicitada, executa a ferramenta e adiciona o resultado
if ai_msg.tool_calls:
    tool_call = ai_msg.tool_calls[0]
    selected_tool = tool_map[tool_call["name"]]
    tool_message = selected_tool.invoke(tool_call)
    messages.append(tool_message)
 
    # Etapa 3: Envia o histórico completo da conversa de volta ao LLM
    final_response = llm_with_tools.invoke(messages)
    print(final_response.content)

Saída:

O clima atual em Paris está ensolarado. Tenha um ótimo dia!

O LLM gerou uma resposta de texto final com base no resultado da execução da ferramenta. Lá atrás, na seção 13.1, content era uma string vazia — mas, após devolver o resultado da ferramenta, recebemos uma resposta de verdade.

Vamos verificar quais mensagens estão na lista messages na Etapa 3.

python
for m in messages:
    print(f"{type(m).__name__}: {m.content!r}")

Saída:

HumanMessage: "Como está o clima em Paris?"
AIMessage: ''
ToolMessage: "It's always sunny in Paris!"

Três mensagens são armazenadas em ordem: a pergunta do usuário, a solicitação de chamada de ferramenta do LLM (conteúdo vazio) e o resultado da execução da ferramenta. O LLM viu a pergunta do usuário, decidiu que precisava de uma ferramenta e solicitou uma chamada get_weather. O código do agente executou get_weather e adicionou o resultado à lista de mensagens. Uma vez que tinha tudo o que precisava, o LLM gerou sua resposta final.

13.3) Fornecendo Múltiplas Ferramentas ao LLM

Agentes do mundo real normalmente têm múltiplas ferramentas. Tudo o que cobrimos até agora funciona exatamente da mesma forma com múltiplas ferramentas.

Vamos adicionar calculate e search_web junto com get_weather para vincular três ferramentas.

python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain.tools import tool
 
llm = ChatOpenAI(model="gpt-5-mini")
 
@tool
def get_weather(city: str) -> str:
    """Obtém o clima atual de uma determinada cidade."""
    return f"It's always sunny in {city}!"
 
@tool
def calculate(expression: str) -> str:
    """Avalia uma expressão aritmética simples como '123 * 456'."""
    return str(eval(expression))  # Aviso: eval() é um risco de segurança. Não use em produção.
 
@tool
def search_web(query: str) -> str:
    """Pesquisa na web informações atuais sobre um tópico."""
    return f"Top result for '{query}': ..."
 
tools = [get_weather, calculate, search_web]
llm_with_tools = llm.bind_tools(tools)
tool_map = {t.name: t for t in tools}

Vamos fazer uma pergunta de cálculo.

python
response = llm_with_tools.invoke("Quanto é 123 multiplicado por 456?")
print(response.tool_calls)

Saída:

[{'name': 'calculate', 'args': {'expression': '123 * 456'}, 'id': 'call_xyz789', 'type': 'tool_call'}]

O LLM solicitou uma chamada calculate. Ele comparou a description de cada ferramenta com a pergunta do usuário e determinou que calculate era a ferramenta certa.

Podemos executar a ferramenta e obter uma resposta final usando o mesmo padrão da seção 13.2.

python
messages = [HumanMessage("Quanto é 123 multiplicado por 456?")]
ai_msg = llm_with_tools.invoke(messages)
messages.append(ai_msg)
 
if ai_msg.tool_calls:
    for tool_call in ai_msg.tool_calls:
        selected_tool = tool_map[tool_call["name"]]
        tool_message = selected_tool.invoke(tool_call)
        messages.append(tool_message)
 
    final_response = llm_with_tools.invoke(messages)
    print(final_response.content)  # Saída: 123 multiplicado por 456 é 56.088.

Na seção 13.2 tratamos apenas a primeira solicitação com tool_calls[0], mas aqui iteramos sobre todas as solicitações com um laço for. A razão é que o LLM pode solicitar múltiplas chamadas de ferramenta de uma só vez.

Múltiplas Solicitações Simultâneas de Ferramentas

Vamos ver o que acontece quando múltiplas ferramentas são solicitadas ao mesmo tempo. O código é o mesmo — apenas a pergunta muda.

python
messages = [HumanMessage("Como está o clima em Tóquio e quanto é 123 * 456?")]
ai_msg = llm_with_tools.invoke(messages)
messages.append(ai_msg)
 
if ai_msg.tool_calls:
    for tool_call in ai_msg.tool_calls:
        print(f"Tool: {tool_call['name']}")
        selected_tool = tool_map[tool_call["name"]]
        tool_message = selected_tool.invoke(tool_call)
        messages.append(tool_message)
 
    final_response = llm_with_tools.invoke(messages)
    print(final_response.content)

Saída:

Tool: get_weather
Tool: calculate
O clima em Tóquio está ensolarado no momento, e 123 multiplicado por 456 é 56.088.

O LLM solicitou tanto get_weather quanto calculate, e o laço for executou todas as solicitações em tool_calls e adicionou seus resultados à lista de mensagens. O LLM então usou esses resultados para gerar a resposta final.