Agenci AI do przetwarzania szeregów czasowych i ramek dużych danych
Zbuduj od podstaw, używając tylko Pythona i Ollama (bez GPU, bez klucza API)
wstęp
Agenci to systemy sztucznej inteligencji oparte na dużych modelach językowych (LLM), które potrafią wnioskować o swoich celach i podejmować działania w celu ich osiągnięcia. Są one zaprojektowane nie tylko do odpowiadania na zapytania, ale także do koordynowania szeregu operacji, w tym przetwarzania danych (takich jak ramki danych i szeregi czasowe). Ta możliwość umożliwia wielu aplikacjom w świecie rzeczywistym demokratyzację dostępu do analizy danych, na przykład poprzez automatyzację raportów, zapytania bezkodowe oraz obsługę czyszczenia i przetwarzania danych.

Agenci mogą wchodzić w interakcje z ramkami danych na dwa różne sposoby:
- przez język naturalny – Model dużego języka (LLM) odczytuje tabelę jako ciąg tekstowy i próbuje go zrozumieć na podstawie swojej bazy wiedzy.
- przez Utwórz i wykonaj kod – Agent aktywuje narzędzia do przetwarzania zbioru danych jako obiektu.
Łącząc możliwości przetwarzania języka naturalnego (NLP) z precyzją wykonywania kodu, agenci AI umożliwiają szerszemu gronu użytkowników interakcję ze złożonymi zbiorami danych i wyciąganie z nich cennych wniosków.
W tym samouczku pokażę, jak manipulować ramkami danych i szeregami czasowymi za pomocą agentów AI . Przedstawię kilka przydatnych fragmentów kodu Pythona, które można łatwo zastosować w innych podobnych przypadkach (wystarczy skopiować, wkleić i uruchomić). Wyjaśnię każdą linijkę kodu wraz z komentarzami, aby można było powtórzyć ten przykład (link do pełnego kodu znajduje się na końcu artykułu).
przygotowanie
Zacznijmy przygotowania Ollama (pip install ollama==0.4.7), biblioteka umożliwiająca użytkownikom uruchamianie dużych, otwartych modeli językowych lokalnie, bez potrzeby korzystania z usług w chmurze, zapewniając większą kontrolę nad prywatnością danych i wydajnością. Ponieważ działa lokalnie, żadne dane konwersacji nie opuszczają Twojego urządzenia.
Najpierw musisz pobrać Ollamę ze strony internetowej.
Następnie w wierszu poleceń urządzenia użyj polecenia, aby pobrać wybrany model LLM (Large Language Model). Ja użyję Qwen firmy Alibaba , ponieważ jest inteligentny i lekki.
Po zakończeniu pobierania możesz przełączyć się na Pythona i zacząć pisać kod.
import ollama
llm = "qwen2.5"
Wypróbujmy model dużego języka:
stream = ollama.generate(model=llm, prompt='''what time is it?''', stream=True)
for chunk in stream:
print(chunk['response'], end='', flush=True)
Szeregi czasowe
Szereg czasowy to sekwencja punktów danych mierzonych w określonym czasie, często wykorzystywana do analiz i prognozowania. Pozwala ona obserwować zmiany zmiennych w czasie i służy do identyfikacji trendów i wzorców sezonowych. Szeregi czasowe to potężne narzędzie w analizie statystycznej i prognozowaniu.
Utworzę fałszywy zbiór danych szeregów czasowych , który posłuży jako przykład.
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')
Zazwyczaj zestawy danych szeregów czasowych mają bardzo prostą strukturę, w której główną zmienną jest kolumna, a czasem indeks.
Zanim przekonwertuję go na ciąg znaków, chcę mieć pewność, że wszystko znajduje się w kolumnie, dzięki czemu nie utracimy żadnych informacji.
dtf = ts.reset_index().rename(columns={"index":"date"})
dtf.head()
Następnie należy zmienić typ danych z DataFrame na Dictionary.
data = dtf.to_dict(orient='records')
data[0:5]
Na koniec ze słownika do ciągu tekstowego.
str_data = "\n".join([str(row) for row in data])
str_data
Mając teraz ciąg tekstu, można go wstawić do monitu , który może przetworzyć dowolny model językowy. Po wklejeniu zbioru danych do monitu, model LLM (Large Language Model) odczytuje dane jako zwykły tekst, ale nadal potrafi zrozumieć strukturę i znaczenie na podstawie wzorców zaobserwowanych podczas trenowania.
prompt = f'''
Analyze this dataset, it contains monthly sales data of an online retail product:
{str_data}
'''
Możemy łatwo rozpocząć konwersację z Modelem Dużego Języka (LLM) . Należy pamiętać, że obecnie nie jest on agentem, ponieważ nie posiada żadnych narzędzi; po prostu korzystamy z modelu lingwistycznego. Chociaż nie przetwarza liczb jak komputer, LLM potrafi rozpoznawać nazwy kolumn, wzorce czasowe, trendy i wartości odstające, szczególnie w przypadku mniejszych zbiorów danych. Potrafi symulować analizę i objaśniać wyniki, ale nie wykona dokładnych obliczeń samodzielnie, ponieważ nie wykonuje kodu jak 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} )
Model Dużego Języka (LLM) rozpoznaje liczby i rozumie ogólny kontekst w taki sam sposób, w jaki rozumie przepis lub linię kodu.
Jak widać, wykorzystanie modeli dużego języka (LLM) do analizy szeregów czasowych jest świetnym sposobem na szybkie uzyskanie spostrzeżeń i prowadzenie rozmów.
Agent
Modele dużego języka (LLM) doskonale radzą sobie z generowaniem pomysłów i eksploracją wstępnych koncepcji, podczas gdy agenci potrafią wykonywać kod. Dzięki temu mogą obsługiwać bardziej złożone zadania, takie jak tworzenie wykresów, prognozowanie i wykrywanie anomalii. Zatem, stwórzmy te narzędzia.
Czasami traktowanie ostatecznej odpowiedzi jako narzędzia może być bardziej skuteczne. Na przykład, jeśli agent wykonuje wiele działań w celu wygenerowania wyników pośrednich, ostateczną odpowiedź można uznać za narzędzie, które integruje wszystkie te informacje w spójną odpowiedź. Projektując ją w ten sposób, można osiągnąć większą personalizację i kontrolę nad wynikami.
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")
Następnie narzędzie kodujące.
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'))")
Ponadto dodam kilka funkcji użytkowych umożliwiających korzystanie z narzędzia i uruchamianie agenta.
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}
Kiedy agent próbuje rozwiązać zadanie, chcę, aby śledził użyte narzędzia, wypróbowane dane wejściowe i uzyskane wyniki. Proces powinien zostać zatrzymany dopiero wtedy, gdy model będzie gotowy do udzielenia ostatecznej odpowiedzi.
Odnośnie narzędzia do kodowania, zauważyłem, że agenci mają tendencję do ponownego tworzenia ramki danych na każdym kroku. Dlatego zastosuję wzmocnienie pamięci , aby przypomnieć modelowi, że zbiór danych już istnieje. Jest to powszechna taktyka stosowana w celu osiągnięcia pożądanego zachowania. Ostatecznie techniki wzmacniania pamięci pomagają osiągnąć bardziej znaczące i efektywne interakcje.
# 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} )
Wygenerowanie wykresu to coś, czego nie jest w stanie zrobić sam model dużego języka (LLM). Należy jednak pamiętać, że nawet jeśli agenci potrafią generować obrazy, nie mogą ich zobaczyć, ponieważ silnik jest przecież modelem języka. Dlatego użytkownik jest jedyną osobą, która wizualizuje wykres.
Agent używa biblioteki statsmodels do trenowania modelu i przewidywania danych szeregów czasowych.
Praca z dużymi ramkami danych
Duże modele językowe (LLM) mają ograniczoną pamięć, co ogranicza ilość informacji, które mogą przetwarzać jednocześnie. Nawet najbardziej zaawansowane modele mają limity tokenów (kilkaset stron tekstu). Ponadto, LLM nie zachowują pamięci między sesjami, chyba że wdrożony jest system wyszukiwania. W praktyce, aby efektywnie pracować z dużymi zbiorami danych, programiści często stosują strategie takie jak grupowanie, generowanie wspomagane wyszukiwaniem (RAG), bazy danych wektorowych i podsumowywanie treści przed wprowadzeniem jej do modelu.
Utwórzmy duży zbiór danych, z którym będziemy mogli eksperymentować.
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()
Dodam narzędzie do wyszukiwania w sieci , dzięki któremu sztuczna inteligencja ogólnego przeznaczenia, potrafiąca wykonywać kod Pythona i przeszukiwać Internet, uzyska dostęp do całej dostępnej wiedzy i będzie mogła podejmować decyzje w oparciu o dane.
Najprostszym sposobem utworzenia wyszukiwarki internetowej w Pythonie jest użycie popularnej przeglądarki prywatnej. DuckDuckGo (pip install duckduckgo-search==6.3.5). Możesz użyć oryginalnej biblioteki bezpośrednio lub zaimportować powłokę. 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")
Łącznie agent ma teraz 3 narzędzia.
dic_tools = {'final_answer':final_answer,
'search_web':search_web,
'code_exec':code_exec}
Ponieważ nie mogę dodać pełnej ramki danych w wierszu poleceń, podam tylko pierwsze 10 wierszy, aby LLM mógł zrozumieć ogólny kontekst zbioru danych. Dodatkowo określę, gdzie znaleźć pełny zbiór danych.
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}
'''
Na koniec możemy uruchomić agenta.
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} )
W tej interakcji agent poprawnie użył narzędzia do kodowania. Teraz chcę, żeby użył również innego narzędzia.
Na koniec chciałbym, aby agent zebrał wszystkie informacje uzyskane dotychczas podczas tej rozmowy.
Wniosek
Ten artykuł posłużył jako samouczek, który pokazuje, jak od podstaw budować agentów do manipulowania szeregami czasowymi i dużymi ramkami danych . Omówiliśmy dwa sposoby interakcji modeli z danymi: poprzez język naturalny, gdzie model dużego języka (LLM) interpretuje tabelę jako serię, korzystając z bazy wiedzy, oraz poprzez tworzenie i wykonywanie kodu, wykorzystując narzędzia do manipulowania zbiorem danych jako obiektem.
Pełny kod tego artykułu: GitHub
Mam nadzieję, że Ci się podobało! Zachęcam do kontaktu z pytaniami i komentarzami lub do podzielenia się swoimi interesującymi projektami.
Możliwość dodawania komentarzy nie jest dostępna.