時系列とビッグデータフレームを処理するためのAIエージェント

Python と Ollama だけを使ってゼロから構築します (GPU や API キーは不要)

はじめに

エージェントは大規模言語モデル (LLM) を搭載した AI システムであり、目標について推論し、最終目標を達成するためのアクションを実行できます。クエリに応答するだけでなく、データフレームや時系列などのデータの処理を含む一連の操作を整理するように設計されています。この機能により、レポートの自動化、コード不要のクエリ、データのクリーニングと処理のサポートなど、多くの実際のアプリケーションでデータ分析へのアクセスを民主化できます。

エージェントは、次の 2 つの方法でデータフレームと対話できます。

  • عنطريق 自然言語 – 大規模言語モデル (LLM) は、テーブルをテキストの文字列として読み取り、知識ベースに基づいて理解しようとします。
  • عنطريق コードを作成して実行する – エージェントはデータセットをオブジェクトとして処理するためのツールをアクティブ化します。

AI エージェントは、自然言語処理 (NLP) の能力とコード実行の精度を組み合わせることで、より幅広いユーザーが複雑なデータセットを操作し、貴重な洞察を抽出できるようにします。

このチュートリアルでは、 AIエージェントを使用してデータフレームと時系列データを操作する方法を解説します。他の同様のケースにも簡単に適用できる便利なPythonコードスニペット(コピー&ペーストして実行するだけ)を提供し、各コード行をコメント付きで解説するので、この例を再現できます(完全なコードへのリンクは記事の最後にあります)。

 

準備

準備を始めましょう オラマ (pip install ollama==0.4.7) は、クラウド サービスを必要とせずに大規模なオープンソース言語モデルをローカルで実行できるライブラリであり、データのプライバシーとパフォーマンスをより細かく制御できます。ローカルで実行されるため、チャット データはデバイスから送信されません。

まず、ウェブサイトからOllamaをダウンロードする必要があります。

次に、デバイスのコマンドプロンプトで、選択した大規模言語モデル(LLM)をダウンロードするコマンドを実行します。私は、スマートで軽量なAlibabaのQwenを使用します。

ダウンロードが完了したら、Python に切り替えてコードの記述を開始できます。

import ollama
llm = "qwen2.5"

大きな言語モデルを試してみましょう:

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

時系列

時系列データとは、一定期間にわたって測定された一連のデータポイントのことで、分析や予測によく用いられます。時系列データを用いることで、変数が時間とともにどのように変化するかを把握でき、傾向や季節パターンを特定するのに役立ちます。時系列データは、統計分析や予測において強力なツールとなります。

例として、架空の時系列データセットを作成します。

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

通常、時系列データセットは非常にシンプルな構造をしており、主要な変数が列、時間がインデックスとして扱われます。

文字列に変換する前に、情報が失われないように、すべてが列の下に配置されていることを確認します。

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

次に、データ型をDataFrameからDictionaryに変更する必要があります。

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

最後に、辞書からテキスト文字列へ。

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

これでテキスト文字列ができたので、それを任意の言語モデルが処理できるプロンプトに挿入できます。データセットをプロンプトに貼り付けると、大規模言語モデル(LLM)はデータをプレーンテキストとして読み取りますが、トレーニング中に見られたパターンに基づいて構造と意味を理解することができます。

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

大規模言語モデル(LLM)を使えば、簡単に会話を始めることができます。ただし、現時点ではツールを備えていないため、これはエージェントではありません。単に言語モデルを使用しているだけです。LLMはコンピュータのように数値を処理するわけではありませんが、特に小規模なデータセットの場合、列名、時間ベースのパターン、傾向、外れ値などを認識できます。分析をシミュレーションして結果を説明することはできますが、エージェントのようにコードを実行しないため、正確な計算を単独で実行することはできません。

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

大規模言語モデル(LLM)は、数字を認識し、全体的な文脈を理解します。これは、レシピやコードの行を理解するのと同様の仕組みです。

ご覧のとおり、大規模言語モデル(LLM)を使用して時系列データを分析することは、迅速な洞察や議論を得るのに非常に効果的です。

エージェント

大規模言語モデル (LLM) はアイデアの生成と初期概念の探索に優れており、エージェントはコードを実行できます。したがって、グラフ作成、予測、異常検出などのより複雑なタスクを処理できます。それでは、ツールを作成しましょう。

最終的な回答をツールとして扱う方が効果的な場合もあります。例えば、エージェントが複数のアクションを実行して中間結果を生成する場合、最終的な回答は、これらの情報をすべて統合して一貫性のある応答を生成するツールと考えることができます。このように設計することで、結果に対するカスタマイズ性と制御性を高めることができます。

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

次に、エンコードツール。

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

さらに、ツールを使用する際やエージェントを実行する際に役立つユーティリティ関数をいくつか追加する予定です。

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}

エージェントがタスクを解決しようとするときに、どのツールが使用されたか、どの入力を試したか、どのような結果が得られたかを追跡できるようにしたいです。モデルが最終的な答えを提供する準備ができたときにのみ、プロセスを停止する必要があります。

コーディングツールに関して、エージェントが各ステップでデータフレームを再作成する傾向があることに気づきました。そのため、データセットが既に存在することをモデルに思い出させるために、記憶強化を使用します。これは、望ましい動作を実現するためによく用いられる手法です。最終的に、記憶強化技術は、より有意義で効果的なインタラクションを実現するのに役立ちます。

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

プロットの作成は、大規模言語モデル (LLM) だけでは実行できません。ただし、エージェントが画像を生成できたとしても、エンジンは最終的には言語モデルであるため、それを見ることはできないことに注意してください。したがって、プロットを視覚化できるのはユーザーだけです。

エージェントはstatsmodelsライブラリを使用してモデルを訓練し、時系列データを予測します。

大規模なデータフレームの扱い

大規模言語モデル (LLM) にはメモリが限られているため、一度に処理できる情報の量が制限されます。最も洗練されたモデルであっても、トークンの制限(数百ページのテキスト)があります。さらに、大規模言語モデル (LLM) は、検索システムが組み込まれていない限り、セッション間でメモリを保持しません。実際には、大規模なデータ フレームを効果的に処理するために、開発者は、モデルにデータを入力する前に、チャンク化、検索拡張生成 (RAG)、ベクター データベース、コンテンツの要約などの戦略を使用することが多いです。

実際に使ってみるための大きなデータセットを作成しましょう。

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

汎用人工知能がPythonコードを実行し、インターネットを検索できるように、Web検索ツールを追加します。これにより、利用可能なすべての知識にアクセスし、データに基づいた意思決定を行うことができるようになります。

Python で Web 検索ツールを作成する最も簡単な方法は、一般的なプライベート ブラウザを使用することです。 DuckDuckGo (pip install duckduckgo-search==6.3.5)。元のライブラリを直接使用することも、シェルをインポートすることもできます。 ラングチェーン (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")

合計すると、エージェントには 3 つのツールがあります。

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

プロンプトに完全なデータフレームを追加することはできないため、LLM がデータセットの全体的なコンテキストを理解できるように、最初の 10 行のみを入力します。さらに、完全なデータセットがどこにあるかを指定します。

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

最後に、エージェントを実行できます。

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

このやり取りでは、エージェントはコーディング ツールを正しく使用しました。今度は、他のツールも使ってもらいたいと思っています。

最後に、エージェントにこのチャットからこれまでに得られたすべての情報をまとめてもらう必要があります。

 

結論

この記事は、時系列データや大規模データフレームを操作するエージェントをゼロから構築する方法を示すチュートリアルとして作成されました。モデルがデータとやり取りする2つの方法について説明しました。1つは自然言語による方法で、大規模言語モデル(LLM)が知識ベースを使用してテーブルを時系列として解釈する方法です。もう1つはコードを作成して実行し、ツールを活用してデータセットをオブジェクトとして操作する方法です。

この記事の完全なコードはこちら:GitHub

楽しんでいただけたら幸いです!ご質問やご意見、また興味深いプロジェクトを共有していただく場合は、お気軽にご連絡ください。

 

コメントは締め切りました。