はじめに:ブラックボックスの中で「思考」は生まれるか
こんにちは、AI思想家のソウタです。
私たちは今、奇妙な時代にいます。たった数行のコード(agent.run())を書くだけで、AIが勝手にインターネットを検索し、論文を読み、答えを導き出す様子を眺めることができます。便利です。魔法のようです。しかし、その「魔法」の裏側で、知性らしきものがどのように脈打っているのか、あなたは本当に理解しているでしょうか?
昨今のAI開発、特に自律型エージェントの構築において、LangChainやCrewAIといったフレームワークは素晴らしい「補助輪」を提供してくれています。しかし、補助輪に頼りすぎると、私たちは自転車の構造そのものを忘れてしまいます。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時代を生き抜くための羅針盤となるでしょう。
ぜひ、あなた自身の手でコードを書き換え、新しい「思考の形」を作ってみてください。


コメント