KI-Agenten zur Verarbeitung von Zeitreihen und großen Datenmengen

Von Grund auf neu erstellen, nur mit Python und Ollama (keine GPU, kein API-Schlüssel)

eine Einleitung

Agenten sind KI-Systeme, die auf großen Sprachmodellen (LLMs) basieren und in der Lage sind, über ihre Ziele nachzudenken und Maßnahmen zu ergreifen, um ein Endziel zu erreichen. Es ist nicht nur darauf ausgelegt, auf Abfragen zu antworten, sondern auch eine Reihe von Vorgängen zu organisieren, einschließlich der Verarbeitung von Daten (wie Datenrahmen und Zeitreihen). Diese Funktion ermöglicht vielen realen Anwendungen einen demokratisierten Zugriff auf die Datenanalyse, beispielsweise durch Berichtsautomatisierung, codefreie Abfragen und Unterstützung bei der Datenbereinigung und -verarbeitung.

Agenten können auf zwei verschiedene Arten mit Datenrahmen interagieren:

  • durch natürliche Sprache – Das große Sprachmodell (LLM) liest die Tabelle als Textzeichenfolge und versucht, sie basierend auf seiner Wissensbasis zu verstehen.
  • durch Erstellen und Ausführen von Code – Der Agent aktiviert Tools, um den Datensatz als Objekt zu verarbeiten.

Durch die Kombination der Leistungsfähigkeit der natürlichen Sprachverarbeitung (NLP) mit der Präzision der Codeausführung ermöglichen KI-Agenten einem größeren Benutzerkreis die Interaktion mit komplexen Datensätzen und die Gewinnung wertvoller Erkenntnisse.

In diesem Tutorial zeige ich Ihnen, wie Sie Dataframes und Zeitreihen mithilfe von KI-Agenten bearbeiten . Ich stelle Ihnen einige nützliche Python-Codebeispiele zur Verfügung, die Sie einfach in ähnlichen Fällen anwenden können (einfach kopieren, einfügen und ausführen). Jede Codezeile wird kommentiert, sodass Sie das Beispiel problemlos nachvollziehen können (Link zum vollständigen Code am Ende des Artikels).

 

Vorbereitung

Beginnen wir mit den Vorbereitungen Ollama (pip install ollama==0.4.7), eine Bibliothek, die es Benutzern ermöglicht, große Open-Source-Sprachmodelle lokal auszuführen, ohne dass Cloud-Dienste erforderlich sind, und so mehr Kontrolle über Datenschutz und Leistung bietet. Da es lokal ausgeführt wird, verlassen keine Chatdaten Ihr Gerät.

Zuerst müssen Sie Ollama von der Website herunterladen.

Laden Sie anschließend in der Eingabeaufforderung Ihres Geräts das gewünschte große Sprachmodell (LLM) herunter. Ich verwende hierfür Alibabas Qwen , da es sowohl intelligent als auch ressourcenschonend ist.

Nachdem der Download abgeschlossen ist, können Sie zu Python wechseln und mit dem Schreiben von Code beginnen.

import ollama
llm = "qwen2.5"

Versuchen wir es mit dem großen Sprachmodell:

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

Zeitreihen

Eine Zeitreihe ist eine Folge von Datenpunkten, die über einen bestimmten Zeitraum gemessen werden und häufig für Analysen und Prognosen verwendet werden. Sie ermöglicht es, Veränderungen von Variablen im Zeitverlauf zu beobachten und Trends sowie saisonale Muster zu identifizieren. Zeitreihen sind ein leistungsstarkes Werkzeug in der statistischen Analyse und Prognose.

Ich werde einen fiktiven Zeitreihendatensatz als Beispiel erstellen.

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

Typischerweise weisen Zeitreihendatensätze eine sehr einfache Struktur auf, wobei die Hauptvariable als Spalte und die Zeit als Index dient.

Bevor ich es in eine Zeichenfolge konvertiere, möchte ich sicherstellen, dass alles in einer Spalte platziert ist, damit keine Informationen verloren gehen.

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

Anschließend muss der Datentyp von DataFrame in Dictionary geändert werden.

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

Schließlich vom Wörterbuch zur Textzeichenkette.

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

Nachdem wir nun einen Textstring haben, können wir ihn in eine Eingabeaufforderung einfügen, die von jedem Sprachmodell verarbeitet werden kann. Wenn Sie einen Datensatz in eine Eingabeaufforderung einfügen, liest das Large Language Model (LLM) die Daten als Klartext, kann aber dennoch die Struktur und Bedeutung anhand der während des Trainings erkannten Muster verstehen.

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

Wir können problemlos mit einem großen Sprachmodell (LLM) kommunizieren . Bitte beachten Sie, dass es sich aktuell nicht um einen Agenten handelt, da ihm jegliche Werkzeuge fehlen; wir nutzen lediglich das linguistische Modell. Obwohl es Zahlen nicht wie ein Computer verarbeitet, kann das LLM Spaltennamen, zeitbasierte Muster, Trends und Ausreißer erkennen, insbesondere bei kleineren Datensätzen. Es kann Analysen simulieren und Ergebnisse erklären, führt aber keine präzisen Berechnungen selbstständig durch, da es keinen Code wie ein Agent ausführt.

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

Das Large Language Model (LLM) erkennt Zahlen und versteht den Gesamtkontext, so wie es beispielsweise ein Rezept oder eine Codezeile verstehen könnte.

Wie Sie sehen können, ist die Verwendung von Large Language Models (LLMs) zur Analyse von Zeitreihen hervorragend geeignet, um schnell Erkenntnisse und Gespräche zu gewinnen.

Agent

Große Sprachmodelle (LLMs) eignen sich hervorragend zum Generieren von Ideen und Erkunden anfänglicher Konzepte, während ein Agent Code ausführen kann. Daher kann es komplexere Aufgaben wie die Erstellung grafischer Darstellungen, Prognosen und Anomalieerkennung bewältigen. Lassen Sie uns also die Tools erstellen.

Manchmal ist es effektiver, das Endergebnis als Werkzeug zu betrachten . Wenn ein Agent beispielsweise mehrere Aktionen ausführt, um Zwischenergebnisse zu generieren, kann das Endergebnis als das Werkzeug dienen, das all diese Informationen zu einer kohärenten Antwort zusammenführt. Durch diese Vorgehensweise lassen sich die Ergebnisse besser anpassen und steuern.

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

Dann das Kodierungswerkzeug.

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

Darüber hinaus werde ich einige Hilfsfunktionen für die Verwendung des Tools und für die Ausführung des Agenten hinzufügen.

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}

Wenn ein Agent versucht, eine Aufgabe zu lösen, möchte ich, dass er nachverfolgt, welche Tools verwendet wurden, welche Eingaben er ausprobiert hat und welche Ergebnisse er erzielt hat. Der Prozess sollte erst beendet werden, wenn das Modell bereit ist, die endgültige Antwort zu liefern.

Bezüglich des Codierungstools ist mir aufgefallen, dass Agenten dazu neigen, den Dataframe bei jedem Schritt neu zu erstellen. Daher werde ich eine Gedächtnisverstärkung einsetzen , um das Modell daran zu erinnern, dass der Datensatz bereits existiert. Dies ist eine gängige Taktik, um das gewünschte Verhalten zu erzielen. Letztendlich tragen Gedächtnisverstärkungstechniken zu sinnvolleren und effektiveren Interaktionen bei.

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

Das Erstellen einer Handlung ist mit einem großen Sprachmodell (LLM) allein nicht möglich. Bedenken Sie jedoch, dass Agenten zwar Bilder generieren können, diese jedoch nicht sehen können, da die Engine letztendlich immer noch ein Sprachmodell ist. Der Benutzer ist somit der einzige, der die Handlung visualisiert.

Der Agent verwendet die statsmodels -Bibliothek , um ein Modell zu trainieren und Zeitreihendaten vorherzusagen.

Umgang mit großen Dataframes

Große Sprachmodelle (LLMs) verfügen über einen begrenzten Speicher, wodurch die Menge an Informationen, die sie gleichzeitig verarbeiten können, begrenzt ist. Selbst die ausgefeiltesten Modelle haben Token-Grenzen (einige hundert Seiten Text). Darüber hinaus behalten große Sprachmodelle (LLMs) den Speicher nicht über Sitzungen hinweg, sofern kein Abrufsystem integriert ist. Um in der Praxis effektiv mit großen Datenrahmen arbeiten zu können, verwenden Entwickler häufig Strategien wie Chunking, Retrieval Augmented Generation (RAG), Vektordatenbanken und das Zusammenfassen von Inhalten, bevor sie diese in das Modell einspeisen.

Lassen Sie uns einen großen Datensatz zum Spielen erstellen.

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

Ich werde ein Web-Suchtool hinzufügen , damit die universelle künstliche Intelligenz, die in der Lage ist, Python-Code auszuführen und das Internet zu durchsuchen, Zugriff auf das gesamte verfügbare Wissen erhält und datengestützte Entscheidungen treffen kann.

In Python lässt sich ein Websuchtool am einfachsten mithilfe des beliebten privaten Browsers erstellen. DuckDuckGo (pip install duckduckgo-search==6.3.5). Sie können die Originalbibliothek direkt verwenden oder eine Shell importieren. 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")

Insgesamt stehen dem Agenten nun 3 Werkzeuge zur Verfügung.

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

Da ich in der Eingabeaufforderung nicht den vollständigen Datenrahmen hinzufügen kann, gebe ich nur die ersten 10 Zeilen ein, damit das LLM den Gesamtkontext des Datensatzes verstehen kann. Darüber hinaus werde ich angeben, wo der vollständige Datensatz zu finden ist.

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

Schließlich können wir den Agenten ausführen.

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

Bei dieser Interaktion hat der Agent das Codiertool korrekt verwendet. Jetzt möchte ich ihn dazu bringen, auch das andere Werkzeug zu verwenden.

Abschließend muss der Agent alle bisher aus diesem Chat erhaltenen Informationen zusammentragen.

 

Fazit

Dieser Artikel diente als Tutorial, um zu demonstrieren, wie man Agenten von Grund auf entwickelt, um Zeitreihen und große Dataframes zu bearbeiten . Wir behandelten die zwei Möglichkeiten, wie Modelle mit Daten interagieren können: über natürliche Sprache, wobei ein großes Sprachmodell (LLM) die Tabelle mithilfe seiner Wissensbasis als Reihe interpretiert, und durch das Erstellen und Ausführen von Code, wobei Werkzeuge genutzt werden, um den Datensatz als Objekt zu bearbeiten.

Der vollständige Code zu diesem Artikel: GitHub

Ich hoffe, es hat Ihnen gefallen! Kontaktieren Sie mich gerne bei Fragen und Anmerkungen oder teilen Sie mir Ihre interessanten Projekte mit.

 

Kommentarfunktion ist geschlossen.