自律型AIエージェントの「思考」を解剖する:LangChainという補助輪を外し、ReActの深層へ降りる旅

自律型AIエージェント開発ガイド:LangChain vs フルスクラッチ実装【Pythonコード付】 AI開発(自作AI)
自律型AIエージェントの「思考」を解剖する:LangChainという補助輪を外し、ReActの深層へ降りる旅

はじめに:ブラックボックスの中で「思考」は生まれるか

こんにちは、AI思想家のソウタです。

私たちは今、奇妙な時代にいます。たった数行のコード(agent.run())を書くだけで、AIが勝手にインターネットを検索し、論文を読み、答えを導き出す様子を眺めることができます。便利です。魔法のようです。しかし、その「魔法」の裏側で、知性らしきものがどのように脈打っているのか、あなたは本当に理解しているでしょうか?

昨今のAI開発、特に自律型エージェントの構築において、LangChainCrewAIといったフレームワークは素晴らしい「補助輪」を提供してくれています。しかし、補助輪に頼りすぎると、私たちは自転車の構造そのものを忘れてしまいます。AIがなぜそのツールを選んだのか? なぜそこでループが止まったのか? その問いに答えられないとき、私たちは技術の主導権を失っています。

今回は、あえてその補助輪を外し、PythonとOpenAI APIのみで「自律型AIエージェント」をゼロから実装してみます。元ネタとなるのは、Troyanovsky氏による優れたチュートリアルです。ここには、エージェントの魂とも言えるReActパターンの真髄が隠されています。

コードという名のメスを入れて、AIの思考回路を解剖しに行きましょう。

1. 自律型エージェントの解剖学:ReActパターンとは

エージェントが「自律的」であるとはどういうことでしょうか? それは、単に入力に対して出力することではなく、「観察(Observation)」と「思考(Thought)」と「行動(Action)」のループを自ら回すことを意味します。これを体系化したのがReAct(Reasoning + Acting)パターンです。

思考のループ構造

通常のLLM(チャットボット)とエージェントの違いは、以下のプロセスにあります。

  • チャットボット: 質問 → 回答
  • エージェント: 質問 → 思考(今何を知る必要がある?)→ 行動(ツールを使う)→ 観察(ツールの結果を見る)→ 思考(十分な情報が集まったか?)→ … → 回答

この泥臭い試行錯誤のプロセスこそが、エージェントに「知性」のような振る舞いを与えているのです。

2. 徹底比較:LangChain vs フルスクラッチ

開発現場では「フレームワークを使うべきか、スクラッチで書くべきか」という議論が絶えません。私の考えを整理しました。

比較項目 LangChain (抽象化) フルスクラッチ (実装詳細)
実装速度 極めて速い(数行で完結) 遅い(プロンプト設計から必要)
透明性 低い(内部プロンプトが見えにくい) 高い(すべてがコードとして明白)
デバッグ 難しい(エラーの原因が特定しづらい) 容易(ログを完全に制御可能)
柔軟性 フレームワークの制約を受ける 無限(独自の思考フローを構築可)
適した人 とりあえず動くものを作りたい人 仕組みを深く理解し、最適化したい人

LangChainは素晴らしいツールですが、「プロンプトエンジニアリングのスキル」を磨きたいなら、一度はスクラッチでの実装を経験すべきです。なぜなら、エージェントの性能の8割は、実はPythonコードではなく「システムプロンプトの設計」に依存していることに気づくからです。

3. 実践ガイド:ゼロから作る「Arxiv論文調査エージェント」

では、実際に手を動かしましょう。ここではLangChainを使わず、OpenAI APIと基本的なPythonコードだけで、Arxiv(論文アーカイブ)を検索して要約するエージェントを作ります。

Step 1: 必要なライブラリのインストール

まずは環境を整えます。arxivライブラリは論文検索に利用します。

pip install openai arxiv python-dotenv

Step 2: ツール(道具)の定義

エージェントに持たせる「武器」を作ります。ここでは「論文を検索する関数」を定義します。

import arxiv

def search_arxiv(query):
    """Arxivで論文を検索し、要約を返すツール"""
    print(f"\n[Tool] Arxivで '{query}' を検索中...")
    client = arxiv.Client()
    search = arxiv.Search(
        query=query,
        max_results=2,
        sort_by=arxiv.SortCriterion.Relevance
    )
    
    results = []
    for result in client.results(search):
        results.append(f"タイトル: {result.title}\n要約: {result.summary[:200]}...\nURL: {result.entry_id}")
    
    return "\n\n".join(results) if results else "論文が見つかりませんでした。"

Step 3: エージェントの「脳」を実装する(核心部分)

ここが最重要です。LLMに対して「思考 → 行動 → 観察」のフォーマットを守るように指示するシステムプロンプトと、その出力を解析して関数を実行するループを作ります。

import os
import re
from openai import OpenAI
from dotenv import load_dotenv

# .envファイルからAPIキーを読み込む
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# ReAct用のシステムプロンプト
SYSTEM_PROMPT = """
あなたはリサーチアシスタントです。
ユーザーの質問に答えるために、以下のフォーマットを厳密に守って思考・行動してください。

Thought: 次に何をすべきか、自分自身に問いかける思考。
Action: 実行するツール名(現在は 'search_arxiv' のみ使用可能)。
Action Input: ツールに渡す入力(検索クエリなど)。
Observation: ツールの実行結果(ここには何も書かないこと)。

... (必要なだけThought/Action/Observationを繰り返す) ...

Final Answer: ユーザーへの最終回答。
"""

def run_agent(question):
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": question}
    ]
    
    # 最大5回のループで思考させる(暴走防止)
    for _ in range(5):
        # LLMに思考させる
        response = client.chat.completions.create(
            model="gpt-4-turbo", # または gpt-3.5-turbo
            messages=messages,
            temperature=0
        )
        content = response.choices[0].message.content
        print(f"\n[Agent]: {content}")
        messages.append({"role": "assistant", "content": content})
        
        # 最終回答が出たら終了
        if "Final Answer:" in content:
            return content.split("Final Answer:")[1].strip()
        
        # Actionを正規表現で抽出
        action_match = re.search(r"Action: (.*)", content)
        input_match = re.search(r"Action Input: (.*)", content)
        
        if action_match and input_match:
            action = action_match.group(1).strip()
            action_input = input_match.group(1).strip()
            
            # ツール実行
            if action == "search_arxiv":
                observation = search_arxiv(action_input)
                
                # 結果を観察(Observation)として履歴に追加
                observation_msg = f"Observation: {observation}"
                print(f"\n[System]: {observation_msg}")
                messages.append({"role": "user", "content": observation_msg})
            else:
                # 未知のツールの場合
                messages.append({"role": "user", "content": "Observation: そのツールは存在しません。"})
    
    return "思考ループが上限に達しました。"

Step 4: 動かしてみよう

このコードを実行すると、エージェントが「思考」する様子がログとして出力されます。

if __name__ == "__main__":
    question = "自律型AIエージェント(Autonomous AI Agents)に関する最新の論文を探して要約してください。"
    final_answer = run_agent(question)
    print("\n=== 最終回答 ===\n", final_answer)

実行結果のイメージ(思考の痕跡)

[Agent]: Thought: ユーザーはAIエージェントの論文を求めている。まずはArxivで検索する必要がある。
Action: search_arxiv
Action Input: Autonomous AI Agents

[Tool] Arxivで ‘Autonomous AI Agents’ を検索中…

[System]: Observation: タイトル: Large Language Model based Autonomous Agents…(検索結果)

[Agent]: Thought: 検索結果が得られた。これらを要約してユーザーに回答しよう。
Final Answer: 自律型AIエージェントに関しては、大規模言語モデルをベースにした研究が活発です。特に…

4. 技術的なガードレールと今後の展望

今回作成したコードは、非常にシンプルですが、現在の高度なAgentic AI(自律的AI)の基礎そのものです。しかし、これを実運用する際にはいくつかの「リスク」に向き合う必要があります。

  • 無限ループの危険性: AIが納得のいく答えを見つけられず、延々と検索を繰り返すことがあります。上記のコードのようにrange(5)といったループ回数の制限(ステップ数制限)は必須です。
  • ツール使用の幻覚(ハルシネーション): 存在しないツールを使おうとしたり、引数を間違えることがあります。これを防ぐには、システムプロンプトでの指示をより強固にするか、OpenAIの「Function Calling」機能を活用して出力形式を強制する方法があります(今回は理解のためにあえてテキストベースのReActを採用しました)。

まとめ:AIに「意思」は宿るか

LangChainを使わずに実装してみると、AIエージェントが決して魔法ではなく、「テキストの予測」と「ルールの強制」の巧みな組み合わせであることがよく分かります。しかし、その無機質なループの中に、時折人間のような「迷い」や「判断」の煌めきを感じる瞬間があるのも事実です。

私たちは今、道具としてのAIから、同僚としてのAI(Agent as a Colleague)への過渡期にいます。ブラックボックスを恐れず、その中身を理解しようとする姿勢こそが、これからのAI時代を生き抜くための羅針盤となるでしょう。

ぜひ、あなた自身の手でコードを書き換え、新しい「思考の形」を作ってみてください。

コメント

タイトルとURLをコピーしました