Agentes de IA para processamento de séries temporais e big data frames

Crie do zero usando apenas Python e Ollama (sem GPU, sem chave de API)

uma introdução

Agentes são sistemas de IA alimentados por grandes modelos de linguagem (LLMs), capazes de raciocinar sobre seus objetivos e tomar ações para atingir um objetivo final. Ele foi projetado não apenas para responder a consultas, mas para organizar uma série de operações, incluindo o processamento de dados (como dataframes e séries temporais). Esse recurso permite que muitos aplicativos do mundo real democratizem o acesso à análise de dados, como automação de relatórios, consultas sem código e suporte para limpeza e processamento de dados.

Os agentes podem interagir com dataframes de duas maneiras diferentes:

  • por linguagem natural – O modelo de linguagem grande (LLM) lê a tabela como uma sequência de texto e tenta entendê-la com base em sua base de conhecimento.
  • por Criar e executar código – O agente ativa ferramentas para processar o conjunto de dados como um objeto.

Ao combinar o poder do processamento de linguagem natural (PLN) com a precisão da execução do código, os agentes de IA permitem que uma gama maior de usuários interaja com conjuntos de dados complexos e extraia insights valiosos.

Neste tutorial, demonstrarei como manipular dataframes e séries temporais usando agentes de IA . Fornecerei alguns trechos de código Python úteis que podem ser facilmente aplicados em outros casos semelhantes (basta copiar, colar e executar) e explicarei cada linha de código com comentários para que você possa replicar este exemplo (link para o código completo no final do artigo).

 

preparação

Vamos começar a nos preparar Ollama (pip install ollama==0.4.7), uma biblioteca que permite aos usuários executar grandes modelos de linguagem de código aberto localmente, sem a necessidade de serviços de nuvem, dando mais controle sobre a privacidade e o desempenho dos dados. Como ele é executado localmente, nenhum dado de bate-papo sai do seu dispositivo.

Primeiro, você precisa baixar o Ollama do site.

Em seguida, no prompt de comando do seu dispositivo, use o comando para baixar o Modelo de Linguagem Amplo (LLM) de sua escolha. Usarei o Qwen da Alibaba , pois ele é inteligente e leve.

Após a conclusão do download, você pode mudar para Python e começar a escrever código.

import ollama
llm = "qwen2.5"

Vamos tentar o grande modelo de linguagem:

stream = ollama.generate(model=llm, prompt='''what time is it?''', stream=True)
for chunk in stream:
    print(chunk['response'], end='', flush=True)

Séries temporais

Uma série temporal é uma sequência de pontos de dados medidos ao longo de um período de tempo, frequentemente usada para análise e previsão. Ela nos permite observar como as variáveis ​​mudam ao longo do tempo e é usada para identificar tendências e padrões sazonais. As séries temporais são uma ferramenta poderosa em análise estatística e previsão.

Vou criar um conjunto de dados de séries temporais fictício para usar como exemplo.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

## create data
np.random.seed(1) #<--for reproducibility
length = 30
ts = pd.DataFrame(data=np.random.randint(low=0, high=15, size=length),
                  columns=['y'],
                  index=pd.date_range(start='2023-01-01', freq='MS', periods=length).strftime('%Y-%m'))

## plot
ts.plot(kind="bar", figsize=(10,3), legend=False, color="black").grid(axis='y')

Normalmente, os conjuntos de dados de séries temporais têm uma estrutura muito simples, com a variável principal como uma coluna e o tempo como um índice.

Antes de convertê-lo em uma string, quero ter certeza de que tudo esteja colocado em uma coluna, para que não percamos nenhuma informação.

dtf = ts.reset_index().rename(columns={"index":"date"})
dtf.head()

Em seguida, o tipo de dados deve ser alterado de DataFrame para Dictionary.

data = dtf.to_dict(orient='records')
data[0:5]

Finalmente, do dicionário para a sequência de texto.

str_data = "\n".join([str(row) for row in data])
str_data

Agora que temos uma sequência de texto, ela pode ser inserida em um prompt que qualquer modelo de linguagem pode processar. Quando você cola um conjunto de dados em um prompt, o Modelo de Linguagem Amplo (LLM) lê os dados como texto simples, mas ainda consegue entender a estrutura e o significado com base nos padrões observados durante o treinamento.

prompt = f'''
Analyze this dataset, it contains monthly sales data of an online retail product:
{str_data}
'''

Podemos facilmente iniciar uma conversa com um Modelo de Linguagem Amplo (LLM) . Observe que, atualmente, este não é um agente, pois não possui ferramentas; estamos simplesmente usando o modelo linguístico. Embora não processe números como um computador, o LLM pode reconhecer nomes de colunas, padrões temporais, tendências e valores discrepantes, especialmente com conjuntos de dados menores. Ele pode simular análises e explicar resultados, mas não realizará cálculos precisos de forma independente, pois não executa código como um agente.

messages = [{"role":"system", "content":prompt}]

while True:
    ## User
    q = input('🙂 >')
    if q == "quit":
        break
    messages.append( {"role":"user", "content":q} )
   
    ## Model
    agent_res = ollama.chat(model=llm, messages=messages, tools=[])
    res = agent_res["message"]["content"]
   
    ## Response
    print("👽 >", f"\x1b[1;30m{res}\x1b[0m")
    messages.append( {"role":"assistant", "content":res} )

O Modelo de Linguagem Amplo (LLM, na sigla em inglês) reconhece números e compreende o contexto geral, da mesma forma que poderia compreender uma receita ou uma linha de código.

Como você pode ver, usar Modelos de Linguagem de Grande Porte (LLMs, na sigla em inglês) para analisar séries temporais é ótimo para obter insights e conversas rápidas.

Agente

Grandes modelos de linguagem (LLMs) são excelentes para gerar ideias e explorar conceitos iniciais, enquanto um agente pode executar código. Portanto, ele pode lidar com tarefas mais complexas, como gráficos, previsões e detecção de anomalias. Então, vamos criar as ferramentas.

Às vezes, tratar a resposta final como uma ferramenta pode ser mais eficaz. Por exemplo, se um agente realiza várias ações para gerar resultados intermediários, a resposta final pode ser considerada a ferramenta que integra todas essas informações em uma resposta coerente. Ao projetá-la dessa forma, você pode obter maior personalização e controle sobre os resultados.

def final_answer(text:str) -> str:
    return text

tool_final_answer = {'type':'function', 'function':{
  'name': 'final_answer',
  'description': 'Returns a natural language response to the user',
  'parameters': {'type': 'object',
                'required': ['text'],
                'properties': {'text': {'type':'str', 'description':'natural language response'}}
}}}

final_answer(text="hi")

Em seguida, a ferramenta de codificação.

import io
import contextlib

def code_exec(code:str) -> str:
    output = io.StringIO()
    with contextlib.redirect_stdout(output):
        try:
            exec(code)
        except Exception as e:
            print(f"Error: {e}")
    return output.getvalue()

tool_code_exec = {'type':'function', 'function':{
  'name': 'code_exec',
  'description': 'Execute python code. Use always the function print() to get the output.',
  'parameters': {'type': 'object',
                'required': ['code'],
                'properties': {
                    'code': {'type':'str', 'description':'code to execute'},
}}}}

code_exec("from datetime import datetime; print(datetime.now().strftime('%H:%M'))")

Além disso, adicionarei algumas funções utilitárias para usar a ferramenta e para executar o agente.

dic_tools = {"final_answer":final_answer, "code_exec":code_exec}

# Utils
def use_tool(agent_res:dict, dic_tools:dict) -> dict:
    ## use tool
    if "tool_calls" in agent_res["message"].keys():
        for tool in agent_res["message"]["tool_calls"]:
            t_name, t_inputs = tool["function"]["name"], tool["function"]["arguments"]
            if f := dic_tools.get(t_name):
                ### calling tool
                print('🔧 >', f"\x1b[1;31m{t_name} -> Inputs: {t_inputs}\x1b[0m")
                ### tool output
                t_output = f(**tool["function"]["arguments"])
                print(t_output)
                ### final res
                res = t_output
            else:
                print('🤬 >', f"\x1b[1;31m{t_name} -> NotFound\x1b[0m")
    ## don't use tool
    if agent_res['message']['content'] != '':
        res = agent_res["message"]["content"]
        t_name, t_inputs = '', ''
    return {'res':res, 'tool_used':t_name, 'inputs_used':t_inputs}

Quando um agente tenta resolver uma tarefa, quero que ele rastreie quais ferramentas foram usadas, quais entradas ele tentou e quais resultados obteve. O processo só deve parar quando o modelo estiver pronto para fornecer a resposta final.

Em relação à ferramenta de codificação, notei que os agentes tendem a recriar o dataframe a cada passo. Portanto, utilizarei reforço de memória para lembrar o modelo de que o conjunto de dados já existe. Essa é uma tática comum usada para alcançar o comportamento desejado. Em última análise, as técnicas de reforço de memória ajudam a obter interações mais significativas e eficazes.

# Start a chat
messages = [{"role":"system", "content":prompt}]
memory = '''
The dataset already exists and it's called 'dtf', don't create a new one.
'''
while True:
    ## User
    q = input('🙂 >')
    if q == "quit":
        break
    messages.append( {"role":"user", "content":q} )

    ## Memory
    messages.append( {"role":"user", "content":memory} )     
   
    ## Model
    available_tools = {"final_answer":tool_final_answer, "code_exec":tool_code_exec}
    res = run_agent(llm, messages, available_tools)
   
    ## Response
    print("👽 >", f"\x1b[1;30m{res}\x1b[0m")
    messages.append( {"role":"assistant", "content":res} )

Criar um enredo é algo que um grande modelo de linguagem (LLM) sozinho não consegue fazer. Mas tenha em mente que mesmo que os agentes possam gerar imagens, eles não podem vê-las, porque o mecanismo ainda é, em última análise, um modelo de linguagem. Portanto, o usuário é o único que visualiza o gráfico.

O agente utiliza a biblioteca statsmodels para treinar um modelo e prever dados de séries temporais.

Lidando com grandes dataframes

Grandes modelos de linguagem (LLMs) têm memória limitada, o que restringe a quantidade de informações que eles podem processar de uma só vez. Mesmo os modelos mais sofisticados têm limites simbólicos (algumas centenas de páginas de texto). Além disso, modelos de linguagem grandes (LLMs) não retêm memória entre sessões, a menos que um sistema de recuperação seja incorporado. Na prática, para trabalhar efetivamente com grandes quadros de dados, os desenvolvedores geralmente usam estratégias como fragmentação, geração aumentada de recuperação (RAG), bancos de dados vetoriais e resumo de conteúdo antes de inseri-lo no modelo.

Vamos criar um grande conjunto de dados para brincar.

import random
import string

length = 1000

dtf = pd.DataFrame(data={
    'Id': [''.join(random.choices(string.ascii_letters, k=5)) for _ in range(length)],
    'Age': np.random.randint(low=18, high=80, size=length),
    'Score': np.random.uniform(low=50, high=100, size=length).round(1),
    'Status': np.random.choice(['Active','Inactive','Pending'], size=length)
})

dtf.tail()

Vou adicionar uma ferramenta de busca na web para que a inteligência artificial de propósito geral, com a capacidade de executar código Python e pesquisar na internet, tenha acesso a todo o conhecimento disponível e possa tomar decisões baseadas em dados.

Em Python, a maneira mais fácil de criar uma ferramenta de pesquisa na web é usando o popular navegador privado. DuckDuckGo (pip install duckduckgo-search==6.3.5). Você pode usar a biblioteca original diretamente ou importar um shell. LangChain (pip install langchain-community==0.3.17).

from langchain_community.tools import DuckDuckGoSearchResults

def search_web(query:str) -> str:
  return DuckDuckGoSearchResults(backend="news").run(query)

tool_search_web = {'type':'function', 'function':{
  'name': 'search_web',
  'description': 'Search the web',
  'parameters': {'type': 'object',
                'required': ['query'],
                'properties': {
                    'query': {'type':'str', 'description':'the topic or subject to search on the web'},
}}}}

search_web(query="nvidia")

No total, o agente agora tem 3 ferramentas.

dic_tools = {'final_answer':final_answer,
             'search_web':search_web,
             'code_exec':code_exec}

Como não posso adicionar o dataframe completo no prompt, alimentarei apenas as 10 primeiras linhas para que o LLM possa entender o contexto geral do conjunto de dados. Além disso, especificarei onde encontrar o conjunto de dados completo.

str_data = "\n".join([str(row) for row in dtf.head(10).to_dict(orient='records')])

prompt = f'''
You are a Data Analyst, you will be given a task to solve as best you can.
You have access to the following tools:
- tool 'final_answer' to return a text response.
- tool 'code_exec' to execute Python code.
- tool 'search_web' to search for information on the internet.

If you use the 'code_exec' tool, remember to always use the function print() to get the output.
The dataset already exists and it's called 'dtf', don't create a new one.

This dataset contains credit score for each customer of the bank. Here's the first rows:
{str_data}
'''

Finalmente, podemos executar o agente.

messages = [{"role":"system", "content":prompt}]
memory = '''
The dataset already exists and it's called 'dtf', don't create a new one.
'''
while True:
    ## User
    q = input('🙂 >')
    if q == "quit":
        break
    messages.append( {"role":"user", "content":q} )

    ## Memory
    messages.append( {"role":"user", "content":memory} )     
   
    ## Model
    available_tools = {"final_answer":tool_final_answer, "code_exec":tool_code_exec, "search_web":tool_search_web}
    res = run_agent(llm, messages, available_tools)
   
    ## Response
    print("👽 >", f"\x1b[1;30m{res}\x1b[0m")
    messages.append( {"role":"assistant", "content":res} )

Nessa interação, o agente utilizou a ferramenta de codificação corretamente. Agora, quero fazê-lo usar a outra ferramenta também.

Por fim, preciso que o agente reúna todas as informações obtidas até agora neste bate-papo.

 

Conclusão

Este artigo serviu como um tutorial para demonstrar como construir agentes do zero para manipular séries temporais e grandes conjuntos de dados . Abordamos as duas maneiras pelas quais os modelos podem interagir com os dados: por meio da linguagem natural, onde um modelo de linguagem natural (LLM) interpreta a tabela como uma série usando sua base de conhecimento, e criando e executando código, aproveitando ferramentas para manipular o conjunto de dados como um objeto.

O código completo deste artigo está disponível no GitHub .

Espero que tenham gostado! Sinta-se à vontade para entrar em contato comigo com perguntas e comentários ou para compartilhar seus projetos interessantes.

 

Comentários estão fechados.