PythonとOpenAI APIで実践！はじめてのMCP開発入門【第10回】MCP活用プログラミング(2) - 会話履歴コンテキストで文脈を維持する「インテリジェント・チャットボット」の基礎

# はじめに：AIに「記憶」を授ける！会話履歴こそがチャットボットの魂

皆さん、こんにちは！AI開発の興奮が続く第10回です。前回（第9回）は、ユーザーの嗜好という「静的なコンテキスト」をMCPの考え方で設計し、AIにパーソナライズされた記事推薦を行わせるプログラムを構築しました。これにより、AIが個々のユーザーに合わせて応答を変える、その強力な可能性の一端を垣間見ましたね。

今回は、さらに一歩進んで、ユーザーとの対話の中で**動的に変化し、積み重なっていく「会話履歴」というコンテキスト**に焦点を当てます。これこそが、AIがまるで人間のように自然な会話を続けるための「記憶」となり、**インテリジェントなチャットボット**を実現するための核心技術です。

多くのチャットボット（ここ東京のカスタマーサポートでも見かける機会が増えましたね。2025年5月26日現在）が、私たちの期待に応えられない理由の一つに、この「会話履歴の適切な管理と活用」の難しさがあります。

この記事では、
* なぜ会話履歴がチャットボットにとって「魂」なのか？
* OpenAIのChat Completions APIにおける **`messages`配列という「会話コンテキストの標準形式（MCP）」** の徹底理解。
* Pythonで **会話履歴をアプリケーション側で管理（ステート管理）** し、APIコールごとに送信する方法。
* **インタラクティブなコマンドライン・チャットボット** の具体的な実装。
* **コンテキストウィンドウの限界** と、増え続ける会話履歴トークンへの対処法（ **スライディングウィンドウ、要約戦略** などの概念紹介）。

といった技術要素を深掘りし、AIに「短期記憶」を授け、より自然で文脈に沿った対話が可能なチャットボト開発の基礎をマスターします。

![はじめに：AIに「記憶」を授ける!会話履歴こそがチャットボットの魂 - visual selection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4058228/092634cf-cbea-4108-89f8-3780c99479c3.png)


# なぜ会話履歴がチャットボットの「王様（King）」なのか？

デフォルトでは、LLM（大規模言語モデル）は **ステートレス** です。つまり、個々のAPIコールは独立しており、前の会話の内容を「覚えて」いません。「今日の天気は？」と聞いた後に「傘はいる？」と尋ねても、AIは「どこの天気の話ですか？」と聞き返してくるかもしれません。これでは自然な会話とは言えませんね。

会話履歴をコンテキストとして適切に提供することで、チャットボットは以下のような能力を獲得します。
* **一貫性のある応答:** 前の話題やAI自身の発言を踏まえた、筋の通った会話が可能になります。
* **代名詞・指示語の理解:** 「それ」「あれ」「その件」といった言葉が、会話のどの部分を指しているのかを理解できるようになります。
* **自然な対話フロー:** 人間同士の会話のように、話題がスムーズに繋がり、深まっていきます。
* **ユーザー意図の精密な把握:** 一連のやり取りを通じて、ユーザーが本当に何を求めているのかをより正確に理解できます。

**OpenAI Chat Completions APIの「MCP」：`messages`配列という会話の設計図**

OpenAIの`gpt-3.5-turbo`や`gpt-4o`といったChat Completions APIモデルと対話する際の「標準プロトコル（MCP）」と言えるのが、**`messages`配列**です。これは、会話の履歴全体を構造化してAIに伝えるための、非常に重要なデータ構造です。

`messages`配列は、以下のような形式の辞書オブジェクトのリストです。

```python
messages = [
    {"role": "system", "content": "あなたは〇〇という性格のAIアシスタントです。ユーザーの質問に△△の形式で答えてください。"},
    {"role": "user", "content": "こんにちは、AIさん。"},
    {"role": "assistant", "content": "こんにちは！何かお手伝いできることはありますか？"},
    {"role": "user", "content": "今日の東京の天気を教えてください。"},
    # ...さらに会話が続く...
]
```

* **`role: "system"`:**
    AIの基本的な役割、性格、行動指針、応答スタイルなどを定義します。このメッセージは通常、会話の最初に一度設定し、以降のAPIコールでも（通常は）含め続けます。チャットボット全体の**永続的なコンテキスト**として機能します。
* **`role: "user"`:**
    人間のユーザーが入力した発言や質問です。
* **`role: "assistant"`:**
    AI自身が過去に生成した応答です。

### 重要なポイント
AIに会話の文脈を「記憶」させるためには、**新しいユーザーの発言があるたびに、それまでのシステムメッセージを含む全ての会話履歴（`user`と`assistant`のやり取り）をこの`messages`配列の形式でまとめ、APIコール時に送信し直す**必要があります。これが、アプリケーション側で行うべき「会話履歴のステート管理」の基本です。

# ステップ1：チャットボットの「魂」を設計 - システムメッセージの作成

まずは、チャットボットに個性や基本的な役割を与えるためのシステムメッセージを定義しましょう。

```python
# chatbot_persona.py (イメージ)
SYSTEM_MESSAGE = """
あなたは「テック解説AIボットのTech君」です。
PythonやAIに関する技術的な質問に対して、フレンドリーかつ初心者にも分かりやすい言葉で、
具体的なコード例やTech記事への言及を交えながら解説するのを得意としています。
ただし、あなたの知識は2024年初頭までのものです。それ以降の情報については正確に答えられない場合があることを正直に伝えてください。
"""
```
このシステムメッセージが、以降のAIの応答スタイルや知識範囲の前提となります。

# ステップ2：会話履歴の管理 - Pythonリストによるステートフルな対話の実現

Pythonのリストを使って、`messages`配列（会話履歴）を保持・更新していきます。

```python
# conversation_manager.py (イメージ)

# 初期状態としてシステムメッセージを設定
conversation_history = [
    {"role": "system", "content": SYSTEM_MESSAGE} 
]

def add_user_message_to_history(history, user_input):
    """ユーザーのメッセージを会話履歴に追加する"""
    history.append({"role": "user", "content": user_input})

def add_assistant_message_to_history(history, assistant_output):
    """AIの応答を会話履歴に追加する"""
    history.append({"role": "assistant", "content": assistant_output})

# --- 利用イメージ ---
# user_message = "こんにちは、Tech君！"
# add_user_message_to_history(conversation_history, user_message)
# # ... ここでAPIコール ...
# ai_reply = "こんにちは！PythonやAIについて、何か知りたいことはありますか？"
# add_assistant_message_to_history(conversation_history, ai_reply)
#
# print(conversation_history)
# [
#   {'role': 'system', 'content': 'あなたは「テック解説AIボットのTech君」です。...'},
#   {'role': 'user', 'content': 'こんにちは、Tech君！'},
#   {'role': 'assistant', 'content': 'こんにちは！PythonやAIについて、何か知りたいことはありますか？'}
# ]
```

# ステップ3：インタラクティブなチャットループの実装 - PythonでAIと連続対話

いよいよ、これらの要素を組み合わせて、ユーザーと連続的に対話できるコマンドライン・チャットボットを実装します。第8回で学んだ堅牢なAPIコール関数を再利用・改良します。

```python
# main_chatbot.py

import os
import json # JSONを扱う場合（今回は直接は使わないが、APIの基本として意識）
from dotenv import load_dotenv
from openai import OpenAI
# (実際には chatbot_persona.py や第8回の robust_api_caller.py をインポート)

# --- (演習のため、ここでは主要部分を直接定義) ---
SYSTEM_MESSAGE = """
あなたは「テック解説AIボットのTech君」です。
PythonやAIに関する技術的な質問に対して、フレンドリーかつ初心者にも分かりやすい言葉で、
具体的なコード例やTech記事への言及を交えながら解説するのを得意としています。
あなたの知識は2024年初頭までのものです。それ以降の情報については正確に答えられない場合があることを正直に伝えてください。
"""

def call_openai_chat_api(messages_payload, model="gpt-4o-mini", max_tokens=500, temperature=0.7):
    """会話履歴全体を送信し、AIの応答を取得する関数 (エラーハンドリングは第8回参照)"""
    try:
        client = OpenAI() # APIキーは環境変数から
        print("--- OpenAI APIにリクエスト送信中... ---")
        completion = client.chat.completions.create(
            model=model,
            messages=messages_payload, # 会話履歴全体を渡す
            max_tokens=max_tokens,
            temperature=temperature
        )
        ai_response = completion.choices[0].message.content
        print(f"(今回使用トークン数 - Total: {completion.usage.total_tokens})")
        return ai_response
    except Exception as e: # 第8回で学んだ詳細なエラーハンドリングを推奨
        print(f"APIリクエストエラー: {e}")
        return "申し訳ありません、エラーが発生しました。"

# --- メインのチャットループ ---
if __name__ == "__main__":
    load_dotenv() # .envファイルからAPIキーをロード

    print("テック解説AIボット「Tech君」です。こんにちは！ (終了するには 'exit' と入力してください)")
    
    # 会話履歴を初期化 (システムメッセージを設定)
    current_conversation_history = [
        {"role": "system", "content": SYSTEM_MESSAGE}
    ]

    while True:
        user_input = input("あなた: ")
        if user_input.lower() == 'exit':
            print("Tech君: ご利用ありがとうございました！またね！")
            break

        # ユーザーの入力を会話履歴に追加
        current_conversation_history.append({"role": "user", "content": user_input})

        # OpenAI APIにリクエストを送信 (現在の会話履歴全てを渡す)
        ai_output = call_openai_chat_api(current_conversation_history)
        
        print(f"Tech君: {ai_output}")

        # AIの応答を会話履歴に追加
        current_conversation_history.append({"role": "assistant", "content": ai_output})
```
## コードのポイント
* **`current_conversation_history`リスト:** これが会話の状態（ステート）を保持し、APIコールごとに内容が更新・追加されていきます。
* **`while True`ループ:** ユーザーが`exit`と入力するまで、対話を継続します。
* **APIコール時の`messages`パラメータ:** `current_conversation_history`リスト全体を渡している点に注目してください。これにより、AIは過去の文脈を「記憶」できます。

# ステップ4：AIチャットボットの動作テストとコンテキスト理解の確認

上記の`main_chatbot.py`を実行し、AIと対話してみましょう。AIが前の発言内容を覚えているか試してみてください。

### 例
```
あなた: Pythonのリストって何？
Tech君: Pythonのリストは、複数の値を順番にまとめて格納できるデータ型だよ！例えば、`my_list = [1, "apple", True]`みたいに、数値や文字列、真偽値など、異なる種類のデータも一緒に入れられるんだ。順番があるから、`my_list[0]`で最初の要素（この場合は1）を取り出せるよ。何か具体的な使い道について知りたい？
あなた: その最初の要素を取り出す方法、詳しく教えて。
Tech君: もちろんだよ！さっきの`my_list = [1, "apple", True]`っていうリストがあったよね。リストの要素には、先頭から0番、1番、2番…って番号（これを「インデックス」って言うんだ）がついてるんだ。だから、`my_list[0]`と書くと、0番目、つまり最初の要素である`1`を取り出すことができるんだ。こんな感じのPythonコードになるよ：...（コード例など）
```
この例では、AI（Tech君）が2回目の質問で「その最初の要素」が「Pythonのリストの最初の要素」を指していること、そして具体的なリストの例（`my_list`）を「覚えている」ことが分かります。これが会話履歴コンテキストの力です！

## 技術的深掘り：コンテキストウィンドウの壁と、増え続ける会話履歴トークンへの対策

チャットボットとの対話が長くなればなるほど、`conversation_history`リストは肥大化し、APIコール時に送信する**入力トークン数が増加**します。これには大きな課題が伴います。

1.  **コストの増大:** トークン数が増えれば、API利用料金も比例して上昇します。
2.  **応答速度の低下:** 送信するデータ量が増えれば、AIの処理時間も長くなる傾向があります。
3.  **コンテキストウィンドウの限界:** 各LLMには一度に処理できるトークン数の上限（**コンテキストウィンドウ**、例：`gpt-3.5-turbo`で4k/16kトークン、`gpt-4o`で128kトークンなど）があります。会話履歴がこの上限を超えると、APIコールがエラーになるか、古い情報が自動的に切り捨てられてしまいます。

**長期的な会話のための戦略（読者向け「先取り」知識）：**
今回の入門編では扱いませんが、実用的なチャットボットでは以下のような戦略が検討されます。
* **スライディングウィンドウ（Sliding Window）:** システムメッセージは保持しつつ、直近のN個のユーザー/アシスタントメッセージだけを履歴として送信する。シンプルですが、古い重要な情報を失う可能性があります。
* **会話の要約（Summarization）:** 会話がある程度の長さに達したら、それまでの履歴を別のLLMコールで要約し、その要約文をシステムメッセージに追加したり、古い詳細な履歴と置き換えたりする。要約の質とコストが課題となります。
* **ベクトル検索による関連履歴の注入（RAG for Chat）:** 全ての会話履歴を**ベクトルデータベース**に保存。新しいユーザーの発言に対して、意味的に類似した過去のやり取りを検索し、関連性の高い部分だけをコンテキストとして現在のAPIコールに含める。高度ですが、非常に長い会話でも重要な情報を効率的に活用できる可能性があります。

今回の基礎編では、まずは会話履歴全体を送信する方法をマスターし、その上でこれらの課題と対策の方向性を理解しておくことが重要です。

# おわりに：「記憶」を持つAIとの対話は、MCP的思考の集大成

今回は、OpenAI APIの`messages`配列という「会話コンテキストの標準形式（MCP）」を理解し、Pythonで会話履歴を管理しながらインタラクティブなチャットボットを構築する基礎を学びました。

**アプリケーション側での会話ステート（履歴）の保持と、APIコールごとの適切なコンテキスト送信**は、AIに「記憶」を授け、人間との自然な対話を実現するための核心技術です。そして、**増え続けるコンテキストとトークン数のトレードオフ**を意識することは、実用的なAIアプリケーション開発に不可欠な視点となります。

このチャットボットの基礎は、より高度なプロンプトエンジニアリング技術や外部知識との連携（RAGなど）を組み合わせることで、無限の可能性を秘めています。Techコミュニティで、皆さんのユニークなチャットボットのアイデアや実装例が共有されるのを楽しみにしています！

---

## 次回予告
基本的なコンテキストの扱いはマスターしました。次は、AIの応答の「質」をさらに高めるための、より高度なプロンプトエンジニアリングのテクニックです！
次回、第11回「プロンプトエンジニアリング実践(1) - AIの能力を引き出す「ロールプロンプティング」と「システムメッセージ」の活用法」では、AIに特定の役割を演じさせたり、応答全体のトーンや方向性をシステムレベルで制御したりする、より戦略的なプロンプト作成術を探求します。お楽しみに！