【初心者向け】LangChainの基本を徹底解説：Retrieval・Agent機能から実践コード例まで

# LangChainとは何か

**LangChain**は、大規模言語モデル（LLM）を活用したアプリケーション開発をより簡単かつ強力にしてくれるフレームワークです。LLMと各種データソース（データベース、API、ファイルなど）を組み合わせ、柔軟なチェーン（処理の流れ）を構築するためのライブラリとなっています。OpenAIのGPTシリーズやその他のLLMと連携する際に便利な抽象化レイヤーを提供することで、タスク実行・対話管理・情報検索などを高度に制御できます。

以下にLangChainの主な機能やキーワードを簡単にまとめます。

- **Chain（チェーン）**: 連続的に実行される処理をひとまとめにした概念。LLMに問い合わせる前処理と後処理をまとめるなど。  
- **Memory（メモリ）**: 対話や状態を保存し、チャットボットなどが文脈を覚えて次の回答に活かせるようにする仕組み。  
- **Retrieval（リトリーバル）**: LLMから大容量データを直接扱うのが難しい場合、ベクトルデータベースなどを使って該当情報を検索・抽出してLLMに渡す仕組み。  
- **Agent（エージェント）**: LLM自身が推論を行い、必要に応じて外部のツール（APIやPythonコードなど）を実行しながらタスクを完了する仕組み。  
- **Tool（ツール）**: 外部と連携するためのモジュール。たとえば検索エンジンや計算ツール、データベースなどが該当する。

これらの要素を組み合わせることで、LLMを使った複雑な処理をコードを最小限に保ちながら実装できます。

---

## Google Colabでの準備

LangChainはPythonのライブラリです。Colabで試す場合は、まずライブラリのインストールが必要です。以下のようなセルを実行して準備します。

```bash
!pip install langchain openai chromadb tiktoken
```

- **langchain**: LangChain本体  
- **openai**: OpenAI APIを利用する場合に必要  
- **chromadb**: シンプルなベクトルデータベースとしてChromaを使う例  
- **tiktoken**: トークンの処理などに必要  

**注意**: OpenAI APIを使用する場合は、OpenAIのAPIキー（`OPENAI_API_KEY`）を取得して環境変数に設定しておく必要があります。  
Colab上では、以下のようにすることが多いです。

```python
# OpenAI APIキーの読み込み
import os
from google.colab import userdata
os.environ["OPENAI_API_KEY"] = userdata.get("OPENAI_API_KEY")
```

![openai-apikey-colab.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1850625/ef65361b-9130-ac0c-e5ab-7f5f871c3df7.png)

---

## LangChainの基本要素

### 1. Chain（チェーン）

LangChainでの開発は「Chainを組み立てる」ことが基本です。たとえば以下のような処理を一つのChainとして定義できます。

1. ユーザーから質問を受け取る  
2. 必要に応じてデータベースを検索  
3. LLMに対して質問＋検索結果をプロンプトとして渡す  
4. LLMの回答を受け取り、返却する  

#### LLMChainの簡単な例

```python
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ.get("OPENAI_API_KEY")
)

# ここでユーザーからの質問を作る
question = "LangChainとは何ですか？"

# プロンプトを手動で組み立てる (LangChainなしでシンプルに書く例)
prompt = f"""
あなたは優秀なアシスタントです。以下のユーザーからの質問に日本語で答えてください。
質問: {question}
"""

# model は "gpt-3.5-turbo" や "gpt-4o" など、使いたい名称を指定
chat_completion = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {
            "role": "user",
            "content": prompt,
        }
    ],
)

# 結果のテキストを取得して表示
print(chat_completion.choices[0].message.content)
```

実行すると、LLMが`question`に対する回答を返してくれます。

---

### 2. Memory（メモリ）

チェーンを使った連続的な対話アプリケーションを作りたい場合、これまでの会話履歴を覚えておく必要があります。LangChainのMemoryを使うと、前回の質問と回答、またはそれ以上の深い文脈を自動で管理してくれます。

#### 実装例

```python
import os
from openai import OpenAI

# ❶ OpenAIクライアントを生成（v1系）
client = OpenAI(
    api_key=os.environ.get("OPENAI_API_KEY")  # 環境変数にAPIキーを入れていれば省略も可
)

# ❷ 会話の履歴を保持するためのリスト
#    最初に "system" ロールでアシスタントの性格や役割を定義しておくと良いです
messages = [
    {"role": "system", "content": "あなたは優秀なアシスタントです。"}
]

# --- 1回目の会話 ---
user_input = "こんにちは、あなたは誰ですか？"
messages.append({"role": "user", "content": user_input})

# 対話履歴をまるごと "messages" に渡す
chat_completion = client.chat.completions.create(
    model="gpt-4o-mini",  # 必要に応じてモデル名を変更
    messages=messages,
)

assistant_reply = chat_completion.choices[0].message.content
print("------1回目の会話------")
print("Assistant:", assistant_reply)

# 応答を履歴にも追加
messages.append({"role": "assistant", "content": assistant_reply})

# --- 2回目の会話 ---
user_input = "なるほど。LangChainについて教えてください。"
messages.append({"role": "user", "content": user_input})

chat_completion = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
)
assistant_reply = chat_completion.choices[0].message.content
print("------2回目の会話------")
print("Assistant:", assistant_reply)

# 応答を履歴に追加
messages.append({"role": "assistant", "content": assistant_reply})

# --- 3回目の会話 ---
user_input = "それはありがたい。前回の私の質問は何でしたか？"
messages.append({"role": "user", "content": user_input})

chat_completion = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
)
assistant_reply = chat_completion.choices[0].message.content
print("------3回目の会話------")
print("Assistant:", assistant_reply)

messages.append({"role": "assistant", "content": assistant_reply})
```

---

### 3. Retrieval（リトリーバル）

LLMは大量のデータを一度に処理するのが苦手です。例えば100ページ以上のドキュメント全文をプロンプトに入れると、トークン上限に達してしまいます。そこで、ユーザーの質問に応じて必要な箇所だけ取り出す仕組みが有効です。これが**Retrieval**で、LangChainでは以下の流れを実装できます。

1. 膨大なドキュメントをあらかじめベクトル化（OpenAI Embeddingsなど）して、ベクトルDB（ChromaやFAISSなど）に保存  
2. ユーザーのクエリを同じくベクトル化  
3. ベクトル近傍検索で最も関連性の高い文書スニペットを取得  
4. そのスニペットをLLMにコンテキストとして渡して回答を生成  

#### 簡単なRetrieval QAのコード例（Chromaを使用）

```python
# ※事前に、!pip install chromadbをしておいてください。
import os
import numpy as np
import chromadb
from chromadb.config import Settings
from openai import OpenAI

# --- 1) OpenAIクライアントの初期化 ---
client = OpenAI(
    api_key=os.environ.get("OPENAI_API_KEY"),  # 必要に応じて
)

# --- 2) ベクトルDB（Chroma）の初期化 ---
chroma_client = chromadb.Client(
    Settings(
        persist_directory="./my_chroma_db",  # データを保存するローカルディレクトリ（不要なら省略も可）
        anonymized_telemetry=False           # テレメトリをOFFにする例
    )
)

collection = chroma_client.create_collection("my_collection")

# 準備するドキュメント
docs = [
    "LangChainは大規模言語モデルをアプリ開発に活用するためのフレームワークです。",
    "LLMとベクトルDBを組み合わせて情報検索ができるようになります。",
    "LangChainにはChain、Memory、Retrieval、Agentなどの機能があります。"
]

# --- 3) ドキュメントをEmbeddingし、Chromaに登録 ---
# OpenAI埋め込みエンドポイントを使う (text-embedding-ada-002 などが一般的)
for i, doc in enumerate(docs):
    embedding_res = client.embeddings.create(
        model="text-embedding-ada-002",
        input=doc
    )
    # embeddingは embedding_res.data[0].embedding に格納
    vector = embedding_res.data[0].embedding

    # Chromaに追加
    collection.add(
        documents=[doc],
        embeddings=[vector],
        ids=[f"doc_{i}"]
    )

# --- 4) ユーザーからの質問を受けて、類似度検索 ---
query = "LangChainにはどんな機能がありますか？"

# 質問もEmbedding化
query_embedding = client.embeddings.create(
    model="text-embedding-ada-002",
    input=query
).data[0].embedding

# 類似度上位2件を取得 (k=2)
results = collection.query(
    query_embeddings=[query_embedding],
    n_results=2
)

retrieved_docs = results["documents"][0]
# 例: ['LangChainは大規模言語モデルをアプリ開発に活用するためのフレームワークです。',
#      'LangChainにはChain、Memory、Retrieval、Agentなどの機能があります。']

# --- 5) 取得したドキュメントをコンテキストにしてChatで回答 ---
context_text = "\n".join(retrieved_docs)
prompt = f"""
あなたは優秀なアシスタントです。以下の文書を参考に、ユーザーの質問に答えてください。

[参考文書]
{context_text}

[ユーザーの質問]
{query}
"""

chat_completion = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "あなたは情報検索に精通したAIアシスタントです。"},
        {"role": "user", "content": prompt}
    ],
)

answer = chat_completion.choices[0].message.content
print("回答:")
print(answer)
```

- **Chroma**: シンプルなベクトルDB。`from_texts`で一括登録が可能。  
- `query`に対して、関連の高いチャンクを取得し、それをLLMにコンテキストとして与えて回答を生成します。

---

### 4. Agent（エージェント）

Agentは、LangChainを使う上での強力な機能の一つです。Agentを使うと、LLM自身が「ツール」を使って目的を達成します。具体的には、以下のようなイメージです。

1. ユーザーの指示を受け取る  
2. LLMが自分の知識や外部のツールを活用すべきかを判断  
3. 必要に応じてツールを呼び出して中間結果を取得  
4. 最終的な回答を生成  

ツールには、検索APIや計算機、データベース読み書きなど、さまざまなものを定義できます。Agentの仕組みは、LLMの「思考の鎖（Chain of Thought）」を外部のコード実行などと結びつける強力な概念です。

#### Agentの簡単な例

LangChainでは、よく以下のような形でAgentを定義します。

```python
import os
import json
from openai import OpenAI

# ❶ OpenAIクライアントの初期化
client = OpenAI(
    api_key=os.environ.get("OPENAI_API_KEY")  # 必要に応じて設定
)

def calculator_function(expression: str) -> float:
    """Pythonで数式をevalして結果を返す簡易計算関数。"""
    try:
        return eval(expression)
    except:
        return "エラー：数式が不正です。"

# ❷ LLMに教える「呼び出せる関数」のメタデータ
functions = [
    {
        "name": "calculator_function",
        "description": "数式を評価して結果を返す関数",
        "parameters": {
            "type": "object",
            "properties": {
                "expression": {
                    "type": "string",
                    "description": "Pythonで解釈できる数式（例: 3+4）"
                }
            },
            "required": ["expression"]
        }
    }
]

# ❸ ユーザーからの入力（計算が必要そうな問い）
user_message = "2の20乗を計算して、その結果は何になるか教えて。"

# ❹ LLMにFunction Callingを有効にしてリクエスト
chat_response = client.chat.completions.create(
    model="gpt-4o-mini",  # 本番で使用可能なモデルを指定してください (例: "gpt-3.5-turbo")
    messages=[
        {"role": "user", "content": user_message}
    ],
    functions=functions,
)

# ❺ LLMの応答を解析
response_message = chat_response.choices[0].message

# function_callはオブジェクト属性として返る
function_call = response_message.function_call

if function_call is not None:
    # LLMが関数呼び出しを指示してきた場合
    func_name = function_call.name
    arguments_str = function_call.arguments

    # JSONパース
    try:
        arguments = json.loads(arguments_str)
    except:
        arguments = {}

    if func_name == "calculator_function":
        expression = arguments.get("expression", "")
        calc_result = calculator_function(expression)

        # 関数実行結果をLLMに再度送って、最終回答を生成
        second_response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "user", "content": user_message},
                # 関数呼び出しを含むメッセージ (function_callの内容をJSON文字列に変換して渡す)
                {
                    "role": "assistant",
                    "content": json.dumps({
                        "name": func_name,
                        "arguments": arguments_str
                    })
                },
                # 関数の実行結果を "function" ロールで渡す
                {
                    "role": "function",
                    "name": func_name,
                    "content": str(calc_result)
                }
            ],
        )
        final_answer = second_response.choices[0].message.content
        print("エージェントの回答:", final_answer)
    else:
        print("未対応の関数呼び出し:", func_name)

else:
    # 関数呼び出しがなく、直接回答が返ってきた場合
    print("エージェントの回答:", response_message.content)
```

---

## ユースケース（活用例）

LangChainを使うことで、さまざまなアプリケーションを短いコードで実装できます。ここではいくつかのユースケースを挙げてみます。

### 1. カスタマーサポートのチャットボット

- **目的**: 自社のFAQデータやマニュアルを参照しながら、ユーザーからの問い合わせに回答する。  
- **実装の流れ**:
  1. 既存のFAQやマニュアルをテキストとしてまとめる  
  2. ベクトルDBに登録（Embedding）  
  3. ユーザーからの問い合わせが来たら関連する文書を検索（Retrieval）  
  4. LLMに関連文書を渡して回答を生成  
  5. 過去のやり取りはMemoryに保持し、文脈を継続  

LangChainの**RetrievalQAチェーン**を使えば、ほんの数行で実装可能です。  
また、複数のドキュメントソースをまとめて管理するために、フレキシブルなメモリを駆使することもできます。

### 2. データ分析アシスタント

- **目的**: Pythonコードを動的に生成して、データフレームから統計情報を取り出したり、可視化を行う。  
- **実装の流れ**:
  1. AgentにPython実行ツールを追加する  
  2. ユーザーが「このCSVを読み込んで平均値を出して」と指示すると、AgentがPythonコードを自動生成  
  3. Pythonツールで実際にコードが実行され、結果を返す  
  4. Agentが最終的にわかりやすい文章で結果を報告  

これにより、ユーザーは直接Pythonを書かずともAgentに任せることができます。

### 3. 情報収集＋レポート自動生成

- **目的**: 特定のトピックに関してWeb上から情報を集め、まとめたレポートを自動作成する。  
- **実装の流れ**:
  1. エージェントにウェブ検索ツール（例: Google SerpAPIなど）を組み込む  
  2. ユーザーが「最新のAIトレンドについて要約レポートを作成して」と指示  
  3. エージェントがウェブ検索ツールを呼び出し、検索結果を取得  
  4. 検索結果をもとにLLMが要約レポートを生成してユーザーに返す  

---

## 総括

LangChainを利用することで、LLM単体では難しい以下の点をクリアできます。

1. **大容量のドキュメントを扱う**  
   - Retrieval機能＋ベクトルDBを使い、必要な部分のみをLLMに渡すことで効率的にQAが可能。  

2. **複数ステップの処理を簡単に行う**  
   - Chainを組み合わせて前処理・後処理をひとつのフローとして管理。Memoryを使えば会話形式にも対応。  

3. **外部ツールを動的に呼び出す**  
   - Agentにより、検索や計算、コード実行などをLLMが必要に応じて自発的に呼び出し、最終結果をまとめられる。  

このような仕組みを使うことで、カスタマーサポートボット、データ分析アシスタント、情報収集レポート作成、教育アプリ、要約ツールなど、多様なアプリケーションを素早く構築できます。

---

## まとめ

本記事では、以下の点を解説しました。

- **LangChainの概要**: LLMの活用を支援するフレームワーク  
- **主要なコンポーネント**:  
  - Chain: 処理フローをまとめる  
  - Memory: 会話や状態を記憶する  
  - Retrieval: ベクトル検索などで情報を取り出す  
  - Agent & Tool: LLMが外部リソースを呼び出してタスクを遂行する  
- **実際のコード例**: Google Colabで動かすときのインストールや基本的なサンプル  
- **ユースケース**: カスタマーサポート、データ分析、自動レポート生成など  

LangChainは日々更新が進んでおり、ドキュメントや公式リポジトリに新しい機能・改善が追加されています。特にエージェント機能は、多岐にわたるツールへのアクセス方法がどんどん追加されているので、興味があれば公式ドキュメントをチェックしてみてください。

ぜひ、今回の解説とコードを参考にして、Google Colab上でLangChainを試してみてください。  
初心者の方でも、この記事のサンプルを動かすだけで「LLMを使った高度なチャットボット」や「カスタマイズされたQAシステム」を体感できるはずです。

---

## 参考リンク

- [LangChain公式GitHub](https://github.com/hwchase17/langchain)  
- [LangChain公式ドキュメント](https://python.langchain.com/en/latest/)  
- [OpenAI Embeddings API](https://platform.openai.com/docs/guides/embeddings)  

※ **最後に**: LangChainは新しいアップデートが頻繁にあるため、最新の情報を追いかけて開発を進めるとより豊富な機能を活用できます。今後の発展も楽しみですね。ぜひ、色々試してみてください！