Como Usar Tool/Function Calling no LangChain

Precisa que seu modelo de IA acesse dados externos como clima, pesquisas ou cálculos precisos? O Function Calling do LangChain permite que seus chatbots executem funções Python e acessem ferramentas externas, transformando simples conversas em aplicações poderosas e práticas.

Neste tutorial completo, você vai aprender a implementar Function Calling (ou Tool Calling) em seus projetos LangChain, permitindo que modelos como GPT, Claude ou Llama resolvam problemas complexos acessando ferramentas que você mesmo define.

Caso prefira este conteúdo em formato de videoaula, assista ao vídeo abaixo ou acesse nosso canal no YouTube!

O que você vai aprender hoje

  • O que é Function Calling e por que você precisa dele
  • Como criar funções Python que modelos de IA podem usar
  • Três métodos diferentes para definir ferramentas
  • Como implementar o fluxo completo de chamada e execução
  • Exemplos práticos para resolver problemas reais
  • Dicas para melhorar a qualidade das chamadas de função

O que é Function Calling e por que é essencial?

Imagine a seguinte situação: você pergunta a um chatbot “Qual a temperatura em Belo Horizonte agora?” Um modelo de linguagem comum daria uma de duas respostas:

  1. “Não tenho acesso a informações em tempo real” (resposta honesta)
  2. “Atualmente está 28°C…” (provavelmente incorreto, já que o modelo não tem dados atualizados)

Esse é um limite fundamental dos LLMs: eles não conseguem acessar informações além de seus dados de treinamento.

É aí que o Function Calling entra como solução. Com ele, você pode:

  • Permitir que modelos chamem APIs externas para obter dados atualizados
  • Executar cálculos precisos que os modelos costumam errar
  • Acessar bancos de dados ou sistemas internos da sua empresa
  • Automatizar tarefas como envio de emails ou agendamentos

O conceito é simples: você define funções Python que realizam tarefas específicas e “empresta” essas funções ao modelo de linguagem, que então sabe quando e como chamá-las.

Configurando o ambiente

Antes de começarmos, vamos instalar o necessário:

!pip install -qU langchain-groq

Para este tutorial, usaremos o modelo Llama 3 (8B) disponível através da Groq, mas você pode adaptar para qualquer modelo compatível com LangChain que suporte Function Calling.

import os
from langchain_groq import ChatGroq

# Configurar a chave API 
os.environ["GROQ_API_KEY"] = "sua_api_key_aqui"

# Inicializar o modelo
llm = ChatGroq(model="llama3-8b-8192", temperature=0.2)

Definindo ferramentas que o modelo pode usar

Existem três maneiras principais de definir ferramentas no LangChain. Vamos explorar cada uma delas.

Método 1: Funções Python com o decorador @tool

Esta é a abordagem mais direta e recomendada:

from langchain_core.tools import tool

@tool
def add(a: int, b: int) -> int:
    """Soma dois números inteiros.

    Args:
        a: Primeiro número inteiro
        b: Segundo número inteiro
    """
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    """Multiplica dois números inteiros.

    Args:
        a: Primeiro número inteiro
        b: Segundo número inteiro
    """
    return a * b

# Lista de ferramentas disponíveis para o modelo
tools = [add, multiply]

O decorador @tool transforma uma função Python comum em uma ferramenta que o LangChain pode gerenciar. Observe os elementos importantes:

  1. Tipagem de parâmetros: a: int, b: int ajuda o modelo a entender que tipo de dados é esperado
  2. Tipo de retorno: -> int informa o que a função devolve
  3. Docstring detalhada: A documentação entre aspas triplas é crucial – o modelo usa isso para entender o propósito da função

Método 2: Usando classes Pydantic

Uma alternativa é definir ferramentas usando classes Pydantic:

from pydantic import BaseModel, Field

class add(BaseModel):
    """Soma dois números inteiros."""
    a: int = Field(..., description="Primeiro número inteiro")
    b: int = Field(..., description="Segundo número inteiro")

class multiply(BaseModel):
    """Multiplica dois números inteiros."""
    a: int = Field(..., description="Primeiro número inteiro")
    b: int = Field(..., description="Segundo número inteiro")

Método 3: Usando TypedDict

Você também pode usar TypedDict para definições mais flexíveis:

from typing_extensions import Annotated, TypedDict

class add(TypedDict):
    """Soma dois números inteiros."""
    a: Annotated[int, ..., "Primeiro número inteiro"]
    b: Annotated[int, ..., "Segundo número inteiro"]

class multiply(TypedDict):
    """Multiplica dois números inteiros."""
    a: Annotated[int, ..., "Primeiro número inteiro"]
    b: Annotated[int, ..., "Segundo número inteiro"]

Vinculando ferramentas ao modelo

Depois de definir as ferramentas, precisamos “entregá-las” ao modelo:

# Vinculando as ferramentas ao modelo
llm_with_tools = llm.bind_tools(tools)

Isso cria uma nova versão do modelo que tem conhecimento das ferramentas disponíveis e pode gerar chamadas apropriadas quando necessário.

Entendendo o processo de chamada de função

Quando você envia uma pergunta ao modelo com ferramentas vinculadas, algo interessante acontece. Vamos testar:

# Teste simples
query = "Quanto é 293849 + 184769?"
response = llm_with_tools.invoke(query)
print("Tool Calls:", response.tool_calls)

O resultado será algo como:

Tool Calls: [{'name': 'add', 'args': {'a': 293849, 'b': 184769}}]

Isto é crucial para entender: o modelo não executa a função diretamente. Ele apenas gera uma chamada para a função, indicando:

  1. Qual função deve ser chamada (‘add’)
  2. Quais argumentos devem ser passados (a=293849, b=184769)

É sua responsabilidade como desenvolvedor pegar essa chamada e:

  1. Executar a função correspondente com os argumentos sugeridos
  2. Obter o resultado
  3. Fornecer esse resultado de volta ao modelo

Esta separação entre sugestão e execução é um recurso de segurança importante, dando a você controle completo sobre o que é realmente executado.

Implementando o fluxo completo

Agora vamos implementar o fluxo completo, desde a pergunta do usuário até a resposta final:

from langchain_core.messages import HumanMessage

def get_response(prompt: str) -> str:
    # Inicializa a conversa com a pergunta do usuário
    messages = [HumanMessage(content=prompt)]
    
    # Obtém as chamadas de função sugeridas pelo modelo
    resposta_tools = llm_with_tools.invoke(prompt).tool_calls
    
    # Para cada chamada de função recomendada
    for call in resposta_tools:
        # Seleciona a função correta com base no nome
        funcao_escolhida = {"add": add, "multiply": multiply}[call["name"].lower()]
        
        # Executa a função com os argumentos sugeridos
        resposta_funcao = funcao_escolhida.invoke(call)
        
        # Adiciona o resultado à conversa
        messages.append(resposta_funcao)
    
    # Obtém a resposta final considerando os resultados das funções
    return llm.invoke(messages).content, messages

Vamos testar com um exemplo mais complexo:

resposta, messages = get_response("Quanto é 19 × 8 e também quanto é 123456 + 246913?")
print(resposta)

O resultado será algo como:

Aqui estão os resultados:
19 × 8 = 152
123456 + 246913 = 370369

Se examinarmos as messages, veremos todo o fluxo da conversa, incluindo as chamadas de função e seus resultados.

Por que Function Calling é tão poderoso?

Observe o exemplo acima. Pedimos ao modelo para calcular “19 × 8” e “123456 + 246913”. O segundo cálculo é particularmente problemático para modelos como o Llama 3 (8B), que frequentemente erram em operações matemáticas com números grandes.

Entretanto, com Function Calling, o resultado está correto porque:

  1. O modelo reconheceu que precisava fazer cálculos
  2. Em vez de calcular diretamente, chamou as funções apropriadas
  3. As funções Python fizeram os cálculos com precisão
  4. O modelo usou esses resultados precisos em sua resposta final

Este padrão se aplica a inúmeros cenários além de matemática:

  • Buscar informações em tempo real (clima, cotações, notícias)
  • Consultar bancos de dados internos
  • Realizar operações específicas do seu domínio de negócio
  • Interagir com APIs externas

Casos de uso práticos

Aqui estão alguns exemplos reais de como você pode usar Function Calling:

1. Assistente de previsão do tempo

@tool
def get_weather(city: str, country: str = "Brasil") -> str:
    """Obtém a previsão do tempo atual para uma cidade.
    
    Args:
        city: Nome da cidade
        country: País (padrão: Brasil)
    """
    # Aqui você faria uma chamada real para uma API de clima
    # Para este exemplo, retornamos dados fictícios
    import random
    temp = random.randint(15, 30)
    conditions = random.choice(["ensolarado", "nublado", "chuvoso"])
    return f"Em {city}, {country}: {temp}°C e {conditions}"

2. Calculadora de conversão de moedas

@tool
def convert_currency(amount: float, from_currency: str, to_currency: str) -> float:
    """Converte valores entre diferentes moedas.
    
    Args:
        amount: Valor a ser convertido
        from_currency: Moeda de origem (ex: USD, BRL, EUR)
        to_currency: Moeda de destino (ex: USD, BRL, EUR)
    """
    # Aqui você chamaria uma API real de câmbio
    # Versão simplificada para exemplo
    rates = {"USD": 1.0, "BRL": 5.0, "EUR": 0.85}
    
    # Convertendo para USD primeiro
    in_usd = amount / rates[from_currency]
    # Depois para moeda final
    result = in_usd * rates[to_currency]
    return round(result, 2)

3. Consulta a banco de dados

@tool
def query_database(product_id: str) -> dict:
    """Busca informações de um produto no banco de dados.
    
    Args:
        product_id: ID único do produto
    """
    # Simulação de consulta a banco de dados
    products = {
        "P001": {"name": "Smartphone Galaxy", "price": 1999.90, "stock": 15},
        "P002": {"name": "Notebook Dell", "price": 4500.00, "stock": 8},
        "P003": {"name": "Smart TV LG", "price": 2799.00, "stock": 12}
    }
    
    if product_id in products:
        return products[product_id]
    else:
        return {"error": "Produto não encontrado"}

Dicas avançadas para Function Calling eficaz

1. Documentação detalhada

A qualidade das docstrings é crucial para o bom funcionamento do Function Calling. Sempre inclua:

  • Descrição clara da função
  • Explicação de cada parâmetro
  • Exemplos de valores esperados
  • Informações sobre o retorno

2. Tratamento de erros

Implemente tratamento de erros em suas funções:

@tool
def divide(a: float, b: float) -> float:
    """Divide dois números.
    
    Args:
        a: Numerador
        b: Denominador (não pode ser zero)
    """
    try:
        if b == 0:
            return "Erro: Divisão por zero não é permitida"
        return a / b
    except Exception as e:
        return f"Erro ao executar a divisão: {str(e)}"

3. Funções para múltiplos propósitos

Você pode criar funções mais genéricas que lidam com vários casos:

@tool
def calculator(operation: str, a: float, b: float) -> float:
    """Realiza operações matemáticas básicas.
    
    Args:
        operation: Tipo de operação ("soma", "subtração", "multiplicação", "divisão")
        a: Primeiro número
        b: Segundo número
    """
    if operation.lower() == "soma":
        return a + b
    elif operation.lower() == "subtração":
        return a - b
    elif operation.lower() == "multiplicação":
        return a * b
    elif operation.lower() == "divisão":
        if b == 0:
            return "Erro: Divisão por zero"
        return a / b
    else:
        return f"Operação '{operation}' não reconhecida"

4. Usando o PydanticToolsParser

Para processamento mais estruturado das chamadas de ferramentas:

from langchain_core.output_parsers import PydanticToolsParser
from pydantic import BaseModel, Field

# Definir classes Pydantic
class Add(BaseModel):
    """Soma dois números."""
    a: int = Field(..., description="Primeiro número")
    b: int = Field(..., description="Segundo número")

# Criar chain com parser
chain = llm_with_tools | PydanticToolsParser(tools=[Add])

# Testar
result = chain.invoke("Quanto é 42 + 28?")
print("Resultado analisado:", result)

Exemplo completo: Assistente multifuncional

Vamos criar um assistente que pode responder perguntas gerais, fazer cálculos e verificar o clima:

from langchain_core.tools import tool
from langchain_core.messages import HumanMessage
import random

@tool
def add(a: float, b: float) -> float:
    """Soma dois números.
    
    Args:
        a: Primeiro número
        b: Segundo número
    """
    return a + b

@tool
def multiply(a: float, b: float) -> float:
    """Multiplica dois números.
    
    Args:
        a: Primeiro número
        b: Segundo número
    """
    return a * b

@tool
def get_weather(city: str) -> str:
    """Obtém a previsão do tempo para uma cidade (simulado).
    
    Args:
        city: Nome da cidade
    """
    temp = random.randint(15, 30)
    conditions = random.choice(["ensolarado", "nublado", "chuvoso"])
    return f"Em {city}: {temp}°C e {conditions}"

tools = [add, multiply, get_weather]
llm_with_tools = llm.bind_tools(tools)

def assistant_response(user_prompt):
    messages = [HumanMessage(content=user_prompt)]
    tool_calls = llm_with_tools.invoke(user_prompt).tool_calls
    
    if tool_calls:
        for call in tool_calls:
            func_name = call["name"].lower()
            if func_name == "add":
                result = add.invoke(call)
            elif func_name == "multiply":
                result = multiply.invoke(call)
            elif func_name == "get_weather":
                result = get_weather.invoke(call)
            
            messages.append(result)
    
    final_response = llm.invoke(messages).content
    return final_response

# Teste 1: Matemática
print(assistant_response("Quanto é 123 × 456?"))

# Teste 2: Clima
print(assistant_response("Como está o tempo em São Paulo hoje?"))

# Teste 3: Pergunta mista
print(assistant_response("Quanto é 42 × 18 e como está o tempo no Rio de Janeiro?"))

Este exemplo mostra como é possível combinar diferentes ferramentas para criar um assistente versátil que pode lidar com diversos tipos de solicitações.

Exercícios práticos para você experimentar

Agora é sua vez de praticar! Tente implementar estas ferramentas:

  1. Uma ferramenta que converte temperatura entre Celsius e Fahrenheit
  2. Uma função que pesquisa informações sobre filmes (com dados simulados)
  3. Um tradutor simples entre idiomas (pode usar dados fictícios)

Dicas para os exercícios:

  • Comece com funções simples e expanda gradualmente
  • Teste com diferentes tipos de perguntas
  • Experimente tanto o formato de função quanto Pydantic/TypedDict

Conclusão: O poder real do Function Calling

O Function Calling transforma modelos de linguagem de ferramentas passivas para sistemas ativos que podem interagir com o mundo real. Suas possibilidades são limitadas apenas pela sua criatividade em definir as ferramentas que o modelo pode acessar.

Com esta técnica, você pode:

  • Criar assistentes virtuais que realmente resolvem problemas práticos
  • Integrar modelos de linguagem com sistemas existentes
  • Superar as limitações inerentes dos LLMs (como cálculos precisos)
  • Construir aplicações que combinam a criatividade da IA com a precisão do código

A abordagem do LangChain para Function Calling é particularmente poderosa porque:

  1. Mantém uma separação clara entre sugestão e execução
  2. Permite definir ferramentas de várias maneiras
  3. Integra-se perfeitamente com o resto do ecossistema LangChain
  4. Funciona com diversos modelos de linguagem

Quer aprender mais técnicas avançadas para desenvolver com LangChain? Acesse nossa plataforma de ensino de Inteligência Artificial e Automações e crie sua conta gratuitamente!

E você, já implementou Function Calling em algum projeto? Compartilhe sua experiência nos comentários!