DEV Community

Alberto Luiz Souza
Alberto Luiz Souza

Posted on

Function Calling na Prática: Construindo um Sistema de Controle de Gastos via WhatsApp com Spring AI

Disclaimer

Este texto foi inicialmente concebido pela IA Generativa em função da transcrição de um vídeo do canal Dev + Eficiente. Se preferir acompanhar por vídeo, é só dar o play.

Introdução

A integração entre Large Language Models (LLMs) e aplicações reais tem se tornado cada vez mais comum, especialmente quando falamos de interfaces conversacionais. Neste post, vamos explorar um exemplo prático da utilização da API de Function Calling suportada por vários LLMs através do Spring AI, aplicada a um projeto real de controle de gastos via WhatsApp.

O Projeto: Controle de Gastos via WhatsApp

O projeto em questão é um serviço de controle de gastos que funciona através do WhatsApp - uma trend atual onde cada vez mais aplicações utilizam o WhatsApp como plataforma de interação. A ideia é simples: ir ao mercado e enviar uma mensagem como "50 reais de pão" para cadastrar uma despesa, ou "liste as despesas de hoje" para consultar os gastos.

O diferencial está na forma de comunicação: quando você se comunica pelo WhatsApp, é principalmente via texto. Esse texto precisa gerar alguma ação no backend, e é aqui que os LLMs entram em cena para interpretar as intenções e executar as ações apropriadas.

O Desafio da Interpretação e Execução

Imagine que você envia as seguintes mensagens para o sistema:

  • "50 reais de pão"
  • "Liste despesas de hoje"
  • "Quanto gastei na semana?"
  • Ou até mesmo algo que não faz sentido algum

O LLM precisa ser capaz de:

  1. Cadastrar despesas: Como o LLM vai cadastrar "50 reais de pão"?
  2. Listar informações: Como ele vai listar as despesas?
  3. Contextualizar tempo: Como ele vai saber que "hoje" é hoje?
  4. Categorizar gastos: Como ele vai saber que "pão" pertence à categoria "habitação"?

O LLM, por padrão, não tem essas capacidades. Ele não sabe chamar APIs, não tem noção de tempo real, e não consegue acessar bancos de dados. É necessário ensinar-lhe como fazer isso através do Function Calling.

Como Funciona o Function Calling

O Function Calling permite que você registre funções que o LLM pode chamar quando necessário. No nosso exemplo, poderíamos ter as seguintes funções:

  1. Cadastrar Despesa: Para registrar novas despesas
  2. Listar Despesas: Para consultar despesas existentes
  3. Listar Categorias: Para obter as categorias disponíveis
  4. Obter Data Atual: Para contextualizar "hoje" e "ontem"

Segundo a documentação do Spring AI, existem dois tipos principais de funções:

  • Information Retrieval: Funções que retornam informações para enriquecer o contexto do LLM (como listar categorias ou obter a data atual)
  • Taking Action: Funções que executam ações no sistema (como cadastrar ou listar despesas)

O Fluxo de Processamento

O fluxo funciona da seguinte maneira:

  1. Chegada da mensagem: "50 reais de pão"
  2. Composição do prompt: A mensagem é mesclada com um prompt base
  3. Análise pelo LLM: O motor de Function Calling analisa quais funções estão disponíveis
  4. Execução em cadeia: O LLM pode chamar múltiplas funções em sequência
    • Primeiro: "Preciso das categorias" → chama listar categorias
    • Segundo: "Preciso da data atual" → chama obter data
    • Terceiro: "Agora posso cadastrar" → chama cadastrar despesa

Cada função tem uma descrição que orienta o LLM sobre quando utilizá-la e quais parâmetros são esperados.

Implementação Prática com Spring AI

Configuração Básica

// Prompt base do sistema
String promptBase = """
    Você é um assistente especializado em ajudar no controle de despesas.
    A partir do texto a seguir, decida a melhor ação a ser tomada.
    Data atual: {data}
    Texto: {mensagem}
    """;

// Execução com Function Calling
ChatResponse response = chatClient.prompt()
    .user(promptFinal)
    .tools(vetorDeFuncoes)
    .call()
    .chatResponse();
Enter fullscreen mode Exit fullscreen mode

Criando uma Tool

Para criar uma função que o LLM pode chamar, você utiliza a anotação @Tool:

@Component
public class CadastrarDespesaTool {

    @Tool("cadastra_despesa", "Realize o cadastro de uma determinado despesa")
    public String cadastraDespesa(
        @Description("Valor da despesa") BigDecimal valor,
        @Description("Descrição da despesa") String descricao,
        @Description("Se houver data no texto, converta para este formato. Se não houver, deixe em branco") 
        LocalDateTime dataHora,
        @Description("UID da categoria da despesa") String uidCategoria
    ) {
        try {
            // Lógica de cadastro da despesa
            return "Despesa cadastrada com sucesso";
        } catch (Exception e) {
            return "Aconteceu algum problema ao cadastrar a despesa";
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Exemplo de Information Retrieval

@Tool("lista_categorias", "Retorna a lista de categorias para cadastro de despesas")
public String listaCategorias() {
    // Busca as categorias no banco
    List<Categoria> categorias = categoriaService.listarTodas();

    // Retorna uma string formatada para o LLM
    return categorias.stream()
        .map(cat -> cat.getId() + " - " + cat.getNome())
        .collect(Collectors.joining("\n"));
}
Enter fullscreen mode Exit fullscreen mode

Pontos de Atenção

Natureza Não Determinística

É fundamental entender que o Function Calling é não determinístico. Não é porque funcionou agora que vai funcionar sempre. O LLM:

  • Pode não encontrar a função correta
  • Pode executar a função errada
  • Pode falhar na extração de parâmetros

Por isso, sempre implemente tratamento de erros e logs adequados.

Segurança das Informações

Tudo que você retorna das funções vai para o contexto do LLM. Tome cuidado com:

  • Informações sensíveis
  • Dados pessoais
  • Logs que podem vazar informações privadas

Estratégias de Otimização

Informações Frequentes no Prompt Base: Se você sabe que sempre vai precisar de determinada informação (como a data atual), inclua-a diretamente no prompt inicial ao invés de criar uma função para isso.

Cache para Dados Estáticos: Para funções como listar categorias, que retornam dados que mudam raramente, implemente estratégias de cache para evitar consultas desnecessárias ao banco.

Demonstração do Sistema Funcionando

No exemplo prático demonstrado:

  • Entrada: "100 reais gasolina"
  • Resultado: "A despesa de 100 reais referente à gasolina foi cadastrada com sucesso"

  • Entrada: "Liste as despesas"

  • Resultado: "Aqui estão suas despesas: gasolina 100 reais, almoço 50 reais, total 150 reais"

  • Entrada: "Liste despesas de ontem"

  • Resultado: "Não há despesas registradas para 30 de julho" (demonstrando que o LLM consegue calcular "ontem" baseado na data atual fornecida)

Conclusão

O Function Calling representa uma evolução significativa na forma como podemos integrar LLMs em aplicações reais. Permite criar interfaces conversacionais naturais que executam ações concretas no sistema, mantendo a flexibilidade da linguagem natural.

Os pontos principais para uma implementação bem-sucedida são:

  • Descrições claras e precisas das funções
  • Tratamento robusto de erros
  • Consciência da natureza não determinística
  • Cuidado com informações sensíveis
  • Estratégias de otimização para dados frequentes

Embora apresente desafios pela sua natureza não determinística, o Function Calling abre possibilidades interessantes para criar experiências de usuário mais naturais e intuitivas.

Dev+ Eficiente

Se você gostou deste conteúdo, conheça a Jornada Dev + Eficiente, nosso treinamento focado em fazer com que você se torne uma pessoa cada vez mais capaz de entregar software que gera valor na ponta final, com máximo de qualidade e eficiência.

Acesse https://deveficiente.com/oferta-20-por-cento para conhecer tudo que oferecemos.

Top comments (0)