AI-agenten voor het verwerken van tijdreeksen en grote dataframes

Vanaf nul bouwen met alleen Python en Ollama (geen GPU, geen API-sleutel)

een introductie

Agenten zijn AI-systemen die werken met grote taalmodellen (LLM's) en die kunnen redeneren over hun doelen en actie kunnen ondernemen om dat einddoel te bereiken. Het is niet alleen ontworpen om te reageren op vragen, maar ook om een ​​reeks bewerkingen te organiseren, waaronder het verwerken van gegevens (zoals dataframes en tijdreeksen). Dankzij deze mogelijkheid kunnen veel praktische toepassingen de toegang tot gegevensanalyse democratiseren, met onder meer geautomatiseerde rapportages, codevrije query's en ondersteuning voor het opschonen en verwerken van gegevens.

Agenten kunnen op twee manieren met dataframes communiceren:

  • door natuurlijke taal – Het grote taalmodel (LLM) leest de tabel als een tekstreeks en probeert deze te begrijpen op basis van zijn kennisbank.
  • door Code maken en uitvoeren – De agent activeert hulpmiddelen om de dataset als object te verwerken.

Door de kracht van natuurlijke taalverwerking (NLP) te combineren met de precisie van code-uitvoering, zorgen AI-agenten ervoor dat een breder scala aan gebruikers met complexe datasets kan werken en waardevolle inzichten kan verkrijgen.

In deze tutorial laat ik zien hoe je dataframes en tijdreeksen kunt manipuleren met behulp van AI-agenten . Ik geef een aantal handige Python-codefragmenten die je gemakkelijk in andere vergelijkbare gevallen kunt toepassen (gewoon kopiëren, plakken en uitvoeren). Ik leg elke regel code uit met commentaar, zodat je dit voorbeeld kunt namaken (link naar de volledige code aan het einde van het artikel).

 

voorbereiding

Laten we beginnen met voorbereiden Ollama (pip install ollama==0.4.7), een bibliotheek waarmee gebruikers grote open-source taalmodellen lokaal kunnen uitvoeren, zonder dat ze daarvoor cloudservices nodig hebben. Zo krijgen ze meer controle over de privacy en prestaties van hun gegevens. Omdat het lokaal wordt uitgevoerd, verlaten geen chatgegevens uw apparaat.

Allereerst moet je Ollama downloaden van de website.

Gebruik vervolgens in de opdrachtprompt van je apparaat de opdracht om het gewenste Large Language Model (LLM) te downloaden. Ik gebruik Qwen van Alibaba , omdat het zowel slim als lichtgewicht is.

Zodra het downloaden voltooid is, kunt u overschakelen naar Python en beginnen met het schrijven van code.

import ollama
llm = "qwen2.5"

Laten we het grote taalmodel eens proberen:

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

Tijdreeks

Een tijdreeks is een opeenvolging van gegevenspunten die over een bepaalde periode zijn gemeten en vaak worden gebruikt voor analyse en voorspellingen. Het stelt ons in staat te zien hoe variabelen in de loop van de tijd veranderen en wordt gebruikt om trends en seizoenspatronen te identificeren. Tijdreeksen zijn een krachtig instrument in statistische analyse en voorspellingen.

Ik zal een fictieve tijdreeksdataset maken om als voorbeeld te gebruiken.

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')

Doorgaans hebben tijdreeksdatasets een zeer eenvoudige structuur, waarbij de hoofdvariabele een kolom is en de tijd een index.

Voordat ik het naar een string converteer, wil ik ervoor zorgen dat alles onder een kolom staat, zodat er geen informatie verloren gaat.

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

Vervolgens moet het gegevenstype worden gewijzigd van DataFrame naar Dictionary.

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

Tot slot, van woordenboek naar tekstreeks.

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

Nu we een tekstreeks hebben, kan deze worden ingevoegd in een prompt die elk taalmodel kan verwerken. Wanneer je een dataset in een prompt plakt, leest het Large Language Model (LLM) de gegevens als platte tekst, maar het kan nog steeds de structuur en betekenis begrijpen op basis van de patronen die tijdens de training zijn waargenomen.

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

We kunnen gemakkelijk een gesprek beginnen met een Large Language Model (LLM) . Houd er rekening mee dat dit momenteel geen agent is, omdat het geen tools heeft; we gebruiken simpelweg het taalkundige model. Hoewel het geen getallen verwerkt zoals een computer, kan het LLM kolomnamen, tijdspatronen, trends en uitschieters herkennen, vooral bij kleinere datasets. Het kan analyses simuleren en resultaten verklaren, maar het zal geen nauwkeurige berekeningen zelfstandig uitvoeren, omdat het geen code uitvoert zoals een agent.

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} )

Het Large Language Model (LLM) herkent getallen en begrijpt de algehele context, op dezelfde manier als waarop het een recept of een regel code zou begrijpen.

Zoals je ziet, is het gebruik van grote taalmodellen (LLM's) voor het analyseren van tijdreeksen uitstekend geschikt om snel inzichten te verkrijgen en gesprekken op gang te brengen.

Tussenpersoon

Grote taalmodellen (LLM's) zijn uitermate geschikt voor het genereren van ideeën en het verkennen van initiële concepten, terwijl een agent code kan uitvoeren. Daarom kan het complexere taken aan, zoals grafieken maken, prognoses maken en anomaliedetectie. Laten we dus de tools creëren.

Soms kan het effectiever zijn om het eindresultaat als een hulpmiddel te beschouwen . Als een agent bijvoorbeeld meerdere acties uitvoert om tussenresultaten te genereren, kan het eindresultaat worden gezien als het hulpmiddel dat al deze informatie integreert in een samenhangend antwoord. Door het op deze manier te ontwerpen, kunt u meer maatwerk en controle over de resultaten verkrijgen.

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")

Vervolgens de codeertool.

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'))")

Daarnaast zal ik enkele hulpprogramma's toevoegen voor het gebruik van de tool en voor het uitvoeren van de agent.

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}

Wanneer een agent een taak probeert op te lossen, wil ik dat er wordt bijgehouden welke hulpmiddelen zijn gebruikt, welke invoer is geprobeerd en welke resultaten zijn behaald. Het proces mag pas stoppen als het model klaar is om het definitieve antwoord te geven.

Wat betreft de codeertool, merkte ik dat agents de dataframe bij elke stap opnieuw aanmaken. Daarom zal ik geheugenversterking gebruiken om het model eraan te herinneren dat de dataset al bestaat. Dit is een veelgebruikte tactiek om het gewenste gedrag te bereiken. Uiteindelijk helpen geheugenversterkingstechnieken je om betekenisvollere en effectievere interacties te realiseren.

# 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} )

Een plot creëren is iets wat een groot taalmodel (LLM) alleen niet kan. Houd er echter rekening mee dat agenten weliswaar afbeeldingen kunnen genereren, maar deze niet kunnen zien. De engine is uiteindelijk nog steeds een taalmodel. Daarom is de gebruiker de enige die het verhaal visualiseert.

De agent gebruikt de statsmodels- bibliotheek om een ​​model te trainen en tijdreeksgegevens te voorspellen.

Omgaan met grote dataframes

Grote taalmodellen (LLM's) hebben een beperkt geheugen, waardoor de hoeveelheid informatie die ze tegelijkertijd kunnen verwerken, beperkt is. Zelfs de meest geavanceerde modellen hebben tokenlimieten (een paar honderd pagina's tekst). Bovendien bewaren grote taalmodellen (LLM's) het geheugen niet tussen sessies, tenzij er een ophaalsysteem in is opgenomen. Om in de praktijk effectief met grote dataframes te kunnen werken, maken ontwikkelaars vaak gebruik van strategieën als chunking, retrieval augmented generation (RAG), vectordatabases en het samenvatten van inhoud voordat deze in het model wordt ingevoerd.

Laten we een grote dataset maken om mee te spelen.

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()

Ik zal een webzoektool toevoegen , zodat de algemene kunstmatige intelligentie, die in staat is Python-code uit te voeren en op internet te zoeken, toegang krijgt tot alle beschikbare kennis en datagestuurde beslissingen kan nemen.

De makkelijkste manier om in Python een webzoekmachine te maken, is door gebruik te maken van de populaire privébrowser. DuckDuckGo (pip install duckduckgo-search==6.3.5). U kunt de originele bibliotheek direct gebruiken of een shell importeren. 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")

In totaal heeft de agent nu 3 tools.

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

Omdat ik niet de volledige dataframe in de prompt kan toevoegen, vul ik alleen de eerste 10 rijen in, zodat de LLM de algehele context van de dataset kan begrijpen. Bovendien zal ik aangeven waar de volledige dataset te vinden is.

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}
'''

Ten slotte kunnen we de agent uitvoeren.

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} )

Bij deze interactie gebruikte de agent de coderingstool correct. Nu wil ik hem ook het andere gereedschap laten gebruiken.

Tot slot moet de agent alle informatie die hij tot nu toe tijdens het chatgesprek heeft verkregen, samenvoegen.

 

Conclusie

Dit artikel diende als handleiding om te laten zien hoe je agents helemaal zelf kunt bouwen om tijdreeksen en grote dataframes te manipuleren . We hebben de twee manieren behandeld waarop modellen met data kunnen interageren: via natuurlijke taal, waarbij een groot taalmodel (LLM) de tabel interpreteert als een reeks met behulp van zijn kennisbasis, en door code te creëren en uit te voeren, waarbij tools worden gebruikt om de dataset als object te manipuleren.

De volledige code voor dit artikel is te vinden op GitHub .

Ik hoop dat je het leuk vond! Neem gerust contact met mij op als u vragen en opmerkingen heeft of uw interessante projecten wilt delen.

 

Reacties zijn gesloten.