0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LangChain/LangGraphでAIエージェントを作ってみた - ReActパターンでWikipedia検索エージェントを実装

0
Posted at

はじめに

最近、「AIエージェント」という言葉をよく耳にするようになりました。ChatGPTのようなLLM(大規模言語モデル)に「道具(ツール)」を持たせて、自律的にタスクを実行させる仕組みです。

「エージェントってなんだか難しそう...」と思っていましたが、実際に手を動かして作ってみると、意外とシンプルな仕組みで動いていることがわかりました。

この記事では、AIエージェント初心者の私が、LangChain/LangGraphを使ってWikipedia検索エージェントを作った過程を共有します。

この記事で学べること

  • AIエージェントの基本的な仕組み
  • ReAct(Reasoning + Acting)パターンとは何か
  • LangChainでのツール(Tool)の作り方
  • LangGraphでのエージェント構築方法

完成イメージ

最終的に、こんな感じで動くエージェントを作ります。

質問: Pythonとは何ですか?

エージェントの動き:
1. 思考: 「Pythonについて調べよう」
2. 行動: Wikipedia検索を実行
3. 観察: 検索結果を確認
4. 思考: 「もう少し詳しい情報が必要だな」
5. 行動: Wikipediaページの詳細を取得
6. 観察: 詳細情報を確認
7. 最終回答: 収集した情報をまとめて回答

回答: Pythonは、1991年にGuido van Rossumによって開発された
汎用プログラミング言語です...

前提知識

  • Python基礎(関数、クラス、デコレータ)
  • 環境変数の基本(.envファイル)
  • OpenAI APIの基本的な使い方

環境構築

1. プロジェクト作成

mkdir research-agent
cd research-agent

2. Python仮想環境の作成(uvを使用)

uv venv
source .venv/bin/activate  # macOS/Linux
# .venv\Scripts\activate   # Windows

3. 依存パッケージのインストール

pyproject.tomlを作成します。

[project]
name = "research-agent"
version = "0.1.0"
description = "LangGraph/LangChainを使用したリサーチエージェント"
requires-python = ">=3.10"

dependencies = [
    "langgraph>=1.0.0",
    "langchain>=0.3.0",
    "langchain-openai>=0.3.0",
    "wikipedia>=1.4.0",
    "pydantic>=2.0",
    "python-dotenv>=1.0.0",
    "rich>=13.0.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "ruff>=0.5.0",
]

インストール実行:

uv pip install -e ".[dev]"

4. 環境変数の設定

.envファイルを作成します。

OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxx  # OpenAI APIキー
LANGCHAIN_TRACING_V2=true              # LangSmith有効化(任意)
LANGCHAIN_API_KEY=lsv2_pt_xxxxx        # LangSmith APIキー(任意)
LANGCHAIN_PROJECT=research-agent       # プロジェクト名

ReActパターンとは?

エージェントの実装に入る前に、ReActパターンについて理解しておきましょう。

ReAct = Reasoning + Acting

ReActは、LLMに「思考」と「行動」を交互に繰り返させるパターンです。

Thought(思考): 何をすべきか考える
   ↓
Action(行動): ツールを使って情報収集
   ↓
Observation(観察): ツールの結果を確認
   ↓
Thought(思考): 次に何をすべきか考える
   ↓
... 繰り返し ...
   ↓
Final Answer(最終回答): 十分な情報が集まったら回答

なぜReActが有効なのか?

従来のLLMは、学習データに含まれる情報しか回答できませんでした。ReActパターンを使うと:

  1. 最新情報へのアクセス: 外部ツール(Wikipedia、検索エンジン等)で最新情報を取得
  2. 正確性の向上: 「思う」だけでなく「調べてから答える」
  3. 透明性: 思考過程が見えるので、なぜその回答になったか追跡可能

実装開始

ディレクトリ構成

research-agent/
├── src/
│   ├── __init__.py
│   ├── main.py           # エントリーポイント
│   ├── config.py         # 設定管理
│   ├── tools/
│   │   ├── __init__.py
│   │   └── wikipedia.py  # Wikipediaツール
│   ├── agents/
│   │   ├── __init__.py
│   │   └── researcher.py # Researcherエージェント
│   └── graph/
│       ├── __init__.py
│       └── workflow.py   # ワークフロー定義
├── tests/
│   └── test_tools/
│       └── test_wikipedia.py
├── pyproject.toml
└── .env

Step 1: 設定管理(config.py)

まず、環境変数を管理する設定クラスを作ります。

# src/config.py
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    """アプリケーション設定"""

    # OpenAI Configuration
    openai_api_key: str = ""
    openai_model_main: str = "gpt-4o-mini"  # コスト効率の良いモデル

    # LangSmith Configuration(トレーシング用・任意)
    langchain_tracing_v2: bool = True
    langchain_api_key: str = ""
    langchain_project: str = "research-agent"

    class Config:
        env_file = ".env"


settings = Settings()

ポイント:

  • pydantic_settingsBaseSettingsを使うと、環境変数を自動で読み込んでくれます
  • gpt-4o-miniはコストパフォーマンスが良く、学習用途に最適です

Step 2: Wikipediaツールの実装(wikipedia.py)

エージェントが使う「道具」を作ります。LangChainでは@toolデコレータを使って簡単にツールを定義できます。

# src/tools/wikipedia.py
import wikipedia
from langchain_core.tools import tool


@tool
def search_wikipedia(query: str, lang: str = "ja") -> str:
    """Wikipediaで情報を検索する

    Args:
        query: 検索クエリ
        lang: 言語コード("ja"=日本語, "en"=英語)

    Returns:
        検索結果のサマリー
    """
    wikipedia.set_lang(lang)

    try:
        # 検索を実行(最大3件)
        search_results = wikipedia.search(query, results=3)

        if not search_results:
            return f"'{query}' に関するWikipediaページが見つかりませんでした。"

        # 最初の結果のサマリーを取得
        page = wikipedia.page(search_results[0], auto_suggest=False)
        summary = wikipedia.summary(
            search_results[0],
            sentences=5,  # 5文まで
            auto_suggest=False
        )

        return f"## {page.title}\n\n{summary}\n\nURL: {page.url}"

    except wikipedia.DisambiguationError as e:
        # 曖昧さ回避ページの場合
        if e.options:
            summary = wikipedia.summary(
                e.options[0],
                sentences=5,
                auto_suggest=False
            )
            return f"## {e.options[0]}\n\n{summary}"
        return f"曖昧さ回避: {', '.join(e.options[:5])}"

    except Exception as e:
        return f"Wikipedia検索中にエラーが発生しました: {e}"


@tool
def get_wikipedia_page(title: str, lang: str = "ja") -> str:
    """Wikipediaページの詳細な内容を取得する

    Args:
        title: ページタイトル
        lang: 言語コード("ja"=日本語, "en"=英語)

    Returns:
        ページの詳細内容
    """
    wikipedia.set_lang(lang)

    try:
        page = wikipedia.page(title, auto_suggest=False)

        # 長すぎる場合は最初の3000文字だけ
        content = page.content[:3000] if len(page.content) > 3000 else page.content

        return f"## {page.title}\n\n{content}\n\n...\n\nURL: {page.url}"

    except wikipedia.DisambiguationError as e:
        return f"曖昧さ回避ページです。以下から選択してください: {', '.join(e.options[:10])}"

    except wikipedia.PageError:
        return f"'{title}' のWikipediaページが見つかりませんでした。"

    except Exception as e:
        return f"Wikipedia取得中にエラーが発生しました: {e}"

ポイント:

  • @toolデコレータを付けると、LangChainがツールとして認識してくれます
  • docstringがLLMへの説明文になります。ここを丁寧に書くと、LLMが適切なタイミングでツールを選んでくれます
  • エラーハンドリングは重要!曖昧さ回避ページ(DisambiguationError)への対応を忘れずに

Step 3: Researcherエージェントの実装(researcher.py)

ツールを使う「エージェント」を作ります。

# src/agents/researcher.py
from typing import Any

from langchain.agents import create_agent
from langgraph.graph.state import CompiledStateGraph

from src.config import settings
from src.tools.wikipedia import get_wikipedia_page, search_wikipedia


# エージェントへの指示(システムプロンプト)
RESEARCHER_SYSTEM_PROMPT = """あなたは優秀なリサーチアシスタントです。
ユーザーの質問に対して、Wikipediaを使って正確な情報を収集します。

## 行動指針
1. まず search_wikipedia で関連する情報を検索する
2. 必要に応じて get_wikipedia_page で詳細情報を取得する
3. 複数の関連トピックを調査して、包括的な情報を集める
4. 収集した情報を整理して、要点をまとめる

## 注意事項
- 事実に基づいた情報のみを提供する
- 情報源が不明な場合は、その旨を明記する
- 専門用語は必要に応じて説明を加える
"""


def create_researcher_agent() -> CompiledStateGraph[Any, Any, Any, Any]:
    """Researcherエージェントを作成する"""

    # エージェントが使えるツールのリスト
    tools = [search_wikipedia, get_wikipedia_page]

    # エージェントを作成
    agent = create_agent(
        model=f"openai:{settings.openai_model_main}",  # "openai:gpt-4o-mini"
        tools=tools,
        system_prompt=RESEARCHER_SYSTEM_PROMPT,
    )

    return agent

ポイント:

  • create_agent()は、LangChain 1.0で推奨される方法です
  • モデルは"openai:gpt-4o-mini"のように文字列で指定します
  • システムプロンプトでエージェントの「性格」や「行動指針」を定義します

Step 4: ワークフローの実装(workflow.py)

エージェントを実行するワークフローを定義します。

# src/graph/workflow.py
from typing import Any

from langchain_core.messages import HumanMessage
from langgraph.graph.state import CompiledStateGraph

from src.agents.researcher import create_researcher_agent


def create_research_workflow() -> CompiledStateGraph[Any, Any, Any, Any]:
    """リサーチワークフローを作成する"""
    return create_researcher_agent()


def run_research_sync(query: str) -> str:
    """リサーチを同期的に実行する

    Args:
        query: ユーザーの質問

    Returns:
        リサーチ結果
    """
    # ワークフローを作成
    workflow = create_research_workflow()

    # 実行(HumanMessageとしてクエリを渡す)
    result = workflow.invoke({"messages": [HumanMessage(content=query)]})

    # 最後のAIメッセージを取得
    ai_messages = [
        m for m in result["messages"]
        if hasattr(m, "content") and m.type == "ai"
    ]

    if ai_messages:
        return str(ai_messages[-1].content)

    return "リサーチ結果を取得できませんでした。"

ポイント:

  • HumanMessageでユーザーの質問をエージェントに渡します
  • エージェントの応答はmessagesリストに蓄積されます
  • 最後のAIMessageが最終回答になります

Step 5: CLIの実装(main.py)

最後に、コマンドラインから実行できるようにします。

# src/main.py
from dotenv import load_dotenv
from rich.console import Console
from rich.panel import Panel

# 環境変数を読み込み(最初に実行)
load_dotenv()

from src.graph.workflow import run_research_sync

console = Console()


def main():
    """メイン関数"""
    console.print(
        Panel.fit(
            "[bold blue]Research Agent[/bold blue]\n"
            "Wikipediaを使ったリサーチエージェント",
            border_style="blue",
        )
    )

    while True:
        # ユーザー入力を取得
        console.print("\n[bold green]質問を入力してください[/bold green]")
        console.print("[dim](終了: quit または exit)[/dim]")

        query = console.input("[bold]> [/bold]").strip()

        if query.lower() in ["quit", "exit", "q"]:
            console.print("\n[yellow]終了します。[/yellow]")
            break

        if not query:
            continue

        # リサーチ実行
        console.print("\n[dim]リサーチ中...[/dim]\n")

        try:
            result = run_research_sync(query)
            console.print(Panel(result, title="回答", border_style="green"))
        except Exception as e:
            console.print(f"[red]エラーが発生しました: {e}[/red]")


if __name__ == "__main__":
    main()

ポイント:

  • load_dotenv()他のimportより前に実行する必要があります
  • richライブラリで見やすいCLI表示を実現しています

実行してみよう

python -m src.main
╭──────────────────────────────────────╮
│     Research Agent                   │
│ Wikipediaを使ったリサーチエージェント │
╰──────────────────────────────────────╯

質問を入力してください
(終了: quit または exit)
> Pythonとは何ですか?

リサーチ中...

╭───────────────── 回答 ─────────────────╮
│ Pythonは、1991年にオランダ人の        │
│ グイド・ヴァン・ロッサムによって      │
│ 開発された汎用プログラミング言語です。│
│                                        │
│ ## 特徴                                │
│ - 読みやすく書きやすい文法            │
│ - 豊富な標準ライブラリ                │
│ - オブジェクト指向プログラミング対応  │
│ - インタプリタ型言語                  │
│                                        │
│ 情報源: https://ja.wikipedia.org/...  │
╰────────────────────────────────────────╯

テストを書く

ツールが正しく動作するか、テストを書いて確認しましょう。

# tests/test_tools/test_wikipedia.py
import pytest
from src.tools.wikipedia import search_wikipedia, get_wikipedia_page


class TestSearchWikipedia:
    """search_wikipedia のテスト"""

    def test_search_existing_topic(self):
        """存在するトピックの検索"""
        result = search_wikipedia.invoke({"query": "Python"})

        assert "Python" in result
        assert "エラー" not in result

    def test_search_nonexistent_topic(self):
        """存在しないトピックの検索"""
        result = search_wikipedia.invoke(
            {"query": "xyznonexistenttopic12345"}
        )

        assert "見つかりませんでした" in result or "エラー" in result

    def test_search_with_english(self):
        """英語での検索"""
        result = search_wikipedia.invoke(
            {"query": "Quantum computing", "lang": "en"}
        )

        assert "quantum" in result.lower() or "Quantum" in result


class TestGetWikipediaPage:
    """get_wikipedia_page のテスト"""

    def test_get_existing_page(self):
        """存在するページの取得"""
        result = get_wikipedia_page.invoke(
            {"title": "Python", "lang": "en"}
        )

        assert "Python" in result
        assert "エラー" not in result

テスト実行:

pytest tests/test_tools/test_wikipedia.py -v

LangSmithでトレーシング

環境変数を設定していれば、LangSmithでエージェントの動作を可視化できます。

  1. LangSmithにアクセス
  2. プロジェクト「research-agent」を確認
  3. エージェントの思考過程がすべて記録されています

これにより、エージェントが「なぜそのツールを選んだか」「どんな思考をしたか」が分かります。デバッグに非常に便利です。

学んだこと・つまずいたポイント

学んだこと

  1. AIエージェントは意外とシンプル

    • LLM + ツール + ループ、これだけで動く
    • LangChainが複雑な部分を隠蔽してくれる
  2. システムプロンプトが重要

    • エージェントの「性格」はプロンプトで決まる
    • 行動指針を明確に書くと、期待通りに動く
  3. ツールのdocstringが命

    • LLMはdocstringを読んでツールを選ぶ
    • 説明が曖昧だと、意図しないタイミングで呼ばれる

つまずいたポイント

  1. LangChain 1.0の変更

    • 古いチュートリアルはlangchain.agents.initialize_agentを使っている
    • 現在はlangchain.agents.create_agentを使う
  2. 環境変数の読み込み順序

    • load_dotenv()は他のimportより先に実行する
    • そうしないと、設定クラスが空の環境変数を読んでしまう
  3. Wikipedia APIの例外

    • 曖昧さ回避ページでDisambiguationErrorが発生する
    • 適切にハンドリングしないとエージェントが止まる

次のステップ

このPhase 1のエージェントには、いくつかの課題があります:

  1. 単一エージェント: すべてを1つのエージェントが担当
  2. レビューなし: 生成内容のチェック機能がない
  3. 人間の介入なし: 完全自動で進んでしまう

これらを解決するために、

  • マルチエージェント化(Supervisor/Writer/Reviewer)
  • Human-in-the-Loop(人間がレビュー・介入)

などの方法が考えられます。

まとめ

この記事では、LangChain/LangGraphを使ってシンプルなAIエージェントを実装しました。

作ったもの:

  • Wikipedia検索ツール(2つ)
  • ReActパターンのResearcherエージェント
  • シンプルなCLI

学んだこと:

  • ReActパターン(Reasoning + Acting)
  • @toolデコレータでのツール定義
  • create_agent()でのエージェント作成
  • LangSmithでのトレーシング

AIエージェントは難しそうに見えますが、基本的な仕組みはシンプルです。ぜひ自分でも作ってみてください!

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?