LLMを用いた生成AI・AIエージェントアプリでは、会話履歴を活用しない一問一答の質問応答だけでは、自然なやり取りを実現できません。
前章で学んだプロンプトテンプレートやパイプライン構文を応用し、過去の会話を踏まえた応答を作る仕組みを理解することが重要です。
この章では、会話の文脈とは何かを解説し、LangChainで文脈を保持する方法、会話履歴を統合した実装例まで丁寧に紹介します。ここで学ぶ仕組みを利用することで、単発の質問応答ではなく、連続的な会話を実現できます。
1. 会話の文脈とは
会話の文脈とは、過去のやり取りの情報を保持し、次の応答に反映することです。単発の質問にしか答えられないAIでは、自然な会話は成立しません。
例を見てみましょう。
ユーザー:Pythonについて教えて
AI:初心者にやさしいプログラミング言語です
ユーザー:どんなライブラリが有名?
2回目の質問「どんなライブラリが有名?」は、1回目の質問の内容を理解していなければ、正確な回答は返せません。「Javaのライブラリ」を回答するかもしれませんし、「ライブラリ=図書館」と解釈してしまうかもしれません。
会話の文脈を保持することで、AIは過去の発言を踏まえ、自然で的確な応答を生成できます。
LangChainでは、この文脈情報を 「メッセージ履歴(Message History)」 として管理します。
2. LangChainで文脈を保持する仕組み
LangChainでは、ユーザとAIの過去のやり取りを踏まえた応答を作るために、会話履歴を管理します。会話履歴には、ユーザの発言とAIの応答を時系列で保存したデータが含まれます。会話履歴を用いることで、次の応答に前の発言の文脈を反映させることができます。
LangChainでは、会話履歴を Message History として管理します。履歴はリスト形式で保存され、各発言が「role」と「content」として記録されます。
[
{"role": "user", "content": "Pythonについて教えて"},
{"role": "ai", "content": "初心者にやさしいプログラミング言語です"},
{"role": "user", "content": "どんなライブラリが有名?"}
]
この履歴をもとに、LangChainは以下の流れで応答を生成します。
- ユーザ入力を受け取る
- 過去の会話履歴を取得
- プロンプトに過去の会話履歴と新しい入力を組み込み、LLMに渡す
- 応答を生成
- 会話履歴に応答を追加
3. 会話履歴の保存
会話履歴をどこに保存するかによって、アプリの性質が変わります。
-
メモリ上の保存
開発時や簡易アプリでは、会話履歴をメモリに保持するだけで
十分です。ただし、アプリを終了すると会話履歴は失われます。 -
永続的な保存
本番環境では、データベースやファイルに履歴を保存するのが一般的です。これにより、アプリを再起動しても過去の会話を参照できます。
LangChainでは、BaseChatMessageHistory を継承したクラスを利用すると、履歴をメモリだけでなく、ファイルやデータベースに保存可能です。
# ライブラリのインストール
# pip install langchain-community
from langchain_community.chat_message_histories import FileChatMessageHistory
# 履歴をファイルに保存
history = FileChatMessageHistory(file_path="history_user1.json")
このようにすることで、アプリを再起動しても以前の会話内容を読み込み、続きから対話を再開できます。
4. セッション管理
「誰の会話なのか」を識別して履歴を分けるのがセッション管理です。
同じアプリに複数のユーザがアクセスする場合、それぞれ独立した会話履歴が必要です。LangChainでは session_id を指定することで、会話をユーザ単位で分離できます。
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
# ユーザーごとに履歴を取得
def get_history(session_id: str) -> BaseChatMessageHistory:
return ChatMessageHistory()
# ユーザーA
history_a = get_history("user_a")
history_a.add_user_message("こんにちは")
# ユーザーB
history_b = get_history("user_b")
history_b.add_user_message("はじめまして")
このように session_id を付与すれば、複数ユーザの会話が混ざることを防ぐことができます。
5. パイプライン構文と会話履歴の統合
LangChainでは、RunnableWithMessageHistory を利用すると、プロンプト、LLM、会話履歴をまとめて扱うことができます。
この機能を利用すると、ユーザごとの会話履歴を保持しつつ、プロンプトの生成から応答取得までを一括で処理できます。
イメージとしては以下の流れです。
- ユーザ入力を受け取る
- プロンプトテンプレートで入力を加工
- 過去の会話履歴を参照して文脈を追加
- LLMに渡して応答を生成
- 応答と入力を履歴に保存
- 次のユーザ入力で履歴を参照
RunnableWithMessageHistory は、この一連の流れを簡単に統合します。
LangChainでは、以下のように使用します。
from langchain_core.runnables.history import RunnableWithMessageHistory
# chain: プロンプトテンプレートとLLMをつなげたパイプライン
# get_history: セッションIDから会話履歴を取得する関数
# input_messages_key: 最新入力が格納されるキー
# history_messages_key: 履歴を差し込む変数名
chatbot = RunnableWithMessageHistory(
chain,
get_history,
input_messages_key="input",
history_messages_key="history"
)
6. 実例コード
ここでは、RunnableWithMessageHistory を利用した会話型アプリの具体例を示します。メモリ上に履歴を保持する簡易版と、外部ファイルに履歴を保存する永続化版の両方を示します。また、複数ユーザの同時セッション管理の例も紹介します。
6.1 メモリ上に履歴を保存する例(単一ユーザ)
メモリ上に履歴を保持する方法は、開発時や簡単なデモアプリに適しています。プロセスが終了すると履歴は消えますが、履歴管理の仕組みを理解するには最適です。
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_ollama import ChatOllama
# 1. LLM(大規模言語モデル)の初期化
# ChatOllama を使用して、会話用のモデルを準備
llm = ChatOllama(model="llama3.1:8b")
# 2. チャット用プロンプト定義
prompt = ChatPromptTemplate.from_messages([
# 2-1. システムメッセージ: AIの性格や振る舞いを指定
("system", "あなたは親切なAIアシスタントです。"),
# 2-2. 過去の会話履歴を挿入するプレースホルダ
MessagesPlaceholder(variable_name="history"),
# 2-3. ユーザからの入力を受け取るプレースホルダ
("human", "{input}")
])
# 3. プロンプトとモデルをつなげてチェーンを作成
chain = prompt | llm
# 4. セッションごとの会話履歴を管理するストア
# session_id をキーに ChatMessageHistory を保持
store = {}
def get_history(session_id: str):
"""4-1. 指定された session_id に対応する履歴オブジェクトを返す。
4-2. まだ存在しない場合は新規作成。
"""
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# 5. 履歴付きのチャット実行用オブジェクト作成
# RunnableWithMessageHistory を使うと、過去の会話履歴を自動的に反映してモデルに入力できる
chatbot = RunnableWithMessageHistory(
chain,
get_history,
input_messages_key="input", # 5-1. ユーザ入力のキー名
history_messages_key="history" # 5-2. 履歴として渡すキー名
)
# 6. 実行テスト
if __name__ == "__main__":
# 6-1. 1つのユーザセッションIDを用意
session = {"configurable": {"session_id": "user1"}}
# 6-2. 1回目の質問
# 前回の履歴はないため、モデルは初回入力として処理
r1 = chatbot.invoke({"input": "Pythonについて教えて"}, config=session)
print("1:", r1.content)
# 6-3. 2回目の質問
# 前回の質問履歴が chain に自動的に渡されるため、文脈を考慮した応答が返る
r2 = chatbot.invoke({"input": "どんなライブラリが有名?"}, config=session)
print("2:", r2.content)
出力例は以下の通りです。
1: Pythonは、プログラミング言語の1つです。易しくて高performanceで有名な言語です。
**Pythonの特徴**
1. **簡単**: Pythonは、他の言語よりも簡単に学べるように設計されています。
2. **高速**: Pythonは、高性能で高速に実行できるように作られています。
3. **汎用性**: Pythonは、データ分析からWeb開発、ゲーム開発まで幅広い分野で使用できます。
**Pythonの基本的な構文**
1. **変数**: `x = 5` (変数 x に値 5 を代入)
2. **演算子**: `a + b` (a と b の合計を計算)
3. **条件分岐**: `if a > b: print("a は b より大きい")`
4. **ループ**: `for i in range(5): print(i)`
**Pythonのモジュールとライブラリ**
1. **NumPy**: 数値計算に最適化されたライブラリ
2. **Pandas**: データ分析に使われるライブラリ
3. **Flask**: Webアプリケーションを作成するためのフレームワーク
**Pythonの実行方法**
1. **IDLE**: Pythonの標準的なインタープリターです。
2. **Jupyter Notebook**: Web上でコードを実行できる環境です。
これがPythonについての基本情報です。もし何か具体的な質問がある場合は、教えてください!
2: Pythonには多くの著名なライブラリがあります。以下は、いくつかの例です。
**データ分析**
1. **Pandas**: データフレームを扱うためのライブラリです。
2. **NumPy**: 多次元配列や数値計算を扱うためのライブラリです。
3. **Matplotlib**: グラフを描画するためのライブラリです。
**Web開発**
1. **Flask**: マイクロフレームワークです。
2. **Django**: フルスタックフレームワークです。
3. **Scrapy**: スクレイピング用のフレームワークです。
**機械学習**
1. **TensorFlow**: ニューラルネットワークを扱うためのライブラリです。
2. **Keras**: ニューラルネットワークを扱うためのライブラリです。
3. **Scikit-learn**: 機械学習アルゴリズムを扱うためのライブラリです。
**データサイエンス**
1. **Seaborn**: データ可視化用のライブラリです。
2. **Plotly**: グラフを作成するためのライブラリです。
3. **Bokeh**: グラフを作成するためのライブラリです。
**その他**
1. **Requests**: HTTPリクエストを扱うためのライブラリです。
2. **BeautifulSoup**: HTMLパーザを扱うためのライブラリです。
3. **OpenCV**: 映像処理を扱うためのライブラリです。
これは、有名なライブラリの一部だけです。Pythonには多くのライブラリが存在し、利用できる選択肢は非常に幅広いです。
このコードでは、user1 というセッションに対して会話履歴が紐づきます。2回目の質問でも1回目の履歴が参照され、文脈を保持した自然な応答が返ります。
6.2 外部ファイルに履歴を保存する例
実運用や長期的な会話保持には、履歴を外部ファイルに保存する方法が便利です。アプリを再起動しても過去の会話を参照できます。ここでは、単一ユーザの会話履歴を JSON ファイルに保存し、再起動後も会話を継続可能にします。
from pathlib import Path
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import FileChatMessageHistory
from langchain_ollama import ChatOllama
# 1. LLM(大規模言語モデル)の初期化
# ChatOllama を使用して会話モデルを準備
llm = ChatOllama(model="llama3.1:8b")
# 2. チャット用プロンプト定義
prompt = ChatPromptTemplate.from_messages([
# 2-1. システムメッセージ: AIの性格や振る舞いを指定
("system", "あなたは親切なAIアシスタントです。"),
# 2-2. 過去の会話履歴を挿入するプレースホルダ
MessagesPlaceholder(variable_name="history"),
# 2-3. ユーザ入力を受け取るプレースホルダ
("human", "{input}")
])
# 3. プロンプトとモデルをつなげてチェーンを作成
chain = prompt | llm
# 4. ファイル保存版の履歴取得関数
def get_history(session_id: str):
"""
4-1. 指定された session_id ごとに会話履歴をファイルに保存
4-2. 保存先は history/ ディレクトリに <session_id>.json 形式
"""
# 4-3. ディレクトリがなければ作成
Path("history").mkdir(exist_ok=True)
# 4-4. FileChatMessageHistory オブジェクトを返す
return FileChatMessageHistory(file_path=f"history/{session_id}.json")
# 5. 履歴付きチャットオブジェクトの作成
# RunnableWithMessageHistory を使うことで、履歴を自動的に読み書きして会話を継続できる
chatbot = RunnableWithMessageHistory(
chain,
get_history,
input_messages_key="input", # 5-1. ユーザ入力のキー名
history_messages_key="history" # 5-2. 履歴として渡すキー名
)
# 6. 動作確認
if __name__ == "__main__":
# 6-1. 1つのユーザセッションIDを指定
session = {"configurable": {"session_id": "user1"}}
# 6-2. 1回目の質問
r1 = chatbot.invoke({"input": "Pythonについて教えて"}, config=session)
print("1:", r1.content)
# 6-3. 2回目の質問
# 1回目の履歴が自動的に読み込まれ、文脈を踏まえた応答が返る
r2 = chatbot.invoke({"input": "どんなライブラリが有名?"}, config=session)
print("2:", r2.content)
出力例は以下の通りです。
1: Pythonは、プログラミング言語の一つで、特にデータ分析や機械学習など分野でよく使われています。
**基本的な構文**
Pythonの基本的な構文は以下の通りです。
* `print()`関数を使って出力する
* `=`演算子を使って変数に値を代入する
* `if`句を使って条件分岐を行う
* `for`ループを使って繰り返し処理を行う
**例**
* `print("Hello, World!")` : 画面に出力します
* `x = 5` : 変数 `x` に値 `5` を代入する
* `if x > 3:` : `x` の値が `3` より大きいかどうかを判断する
**Pythonの文法**
* インデントを使ってブロックを表現する (`if`, `for`, `while`など)
* 文末にセミコロン (`;`) を付けない
* 式内で演算子と変数が隣接しているときは、スペースを入れない
**Pythonのデータ型**
Pythonには多くのデータ型があります。
* `int` (整数)
* `float` (実数)
* `str` (文字列)
* `list` (リスト)
* `dict` (辞書)
例:
x = 5 # int
y = 3.14 # float
name = "John" # str
fruits = ["apple", "banana", "cherry"] # list
person = {"name": "John", "age": 30} # dict
**Pythonの関数**
Pythonでは、関数を使って再利用可能なコードを書くことができます。
例:
def greet(name):
print(f"Hello, {name}!")
greet("John") # Hello, John!
これで、基本的なPythonに関する知識ができました。どんな質問がありますか?
2: Pythonには多くの便利なライブラリがあります。
**データ分析**
* **Pandas**: データの操作と視覚化に役立つ。
* **NumPy**: 数値演算や統計計算に使う。
* **Matplotlib**: グラフを描くのに使う。
**機械学習と深層学習**
* **scikit-learn**: 機械学習のアルゴリズムやモデルを提供する。
* **TensorFlow**: 深層学習のフレームワークで、Googleが開発した。
* **Keras**:DeepLearningライブラリ
**Webアプリケーション**
* **Flask**: Webアプリケーションの構築に使う。
* **Django**: フルスタックWebアプリケーションフレームワークです。
**データベース**
* **SQLAlchemy**: SQLをPythonコードで扱えるようにします。
* **Pandas-DB**: Pandasを使ってデータベースの操作ができるようにします。
これらは有名なライブラリですが、ありとあふれていて、Pythonの世界では多くのライブラリが利用されています。どんな質問がありますか?
プログラムを実行すると、history/user1.jsonが作成され、履歴が保存されます。外部ファイルに履歴を保存することで、プロセス終了後も履歴が保持されるため、長期的な会話を実現できます。
6.3 複数ユーザ同時セッション管理の例(ファイル保存)
複数ユーザが同時にアクセスする場合、それぞれの履歴を分離する必要があります。session_id ごとに履歴を管理すれば、異なるユーザ間で会話が混ざることはありません。
from pathlib import Path
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_community.chat_message_histories import FileChatMessageHistory
from langchain_ollama import ChatOllama
# 1. LLM(大規模言語モデル)の初期化
# ChatOllama を使用して会話モデルを準備
llm = ChatOllama(model="llama3.1:8b")
# 2. チャット用プロンプト定義
prompt = ChatPromptTemplate.from_messages([
# 2-1. システムメッセージ: AIの性格や振る舞いを指定
("system", "あなたは親切なAIアシスタントです。"),
# 2-2. 過去の会話履歴を挿入するプレースホルダ
MessagesPlaceholder(variable_name="history"),
# 2-3. ユーザ入力を受け取るプレースホルダ
("human", "{input}")
])
# 3. プロンプトとモデルをつなげてチェーンを作成
chain = prompt | llm
# 4. 永続化版の履歴取得関数
def get_history(session_id: str) -> BaseChatMessageHistory:
"""
4-1. 指定された session_id ごとに会話履歴をファイルに保存
4-2. 保存先は history/ ディレクトリに <session_id>.json 形式
4-3. ディレクトリが存在しなければ作成
"""
Path("history").mkdir(parents=True, exist_ok=True)
return FileChatMessageHistory(file_path=f"history/{session_id}.json")
# 5. 会話履歴付きランナブルの作成
# RunnableWithMessageHistory を使用して、履歴を自動管理しつつ会話を継続可能
chatbot = RunnableWithMessageHistory(
chain,
get_history,
input_messages_key="input", # 5-1. ユーザ入力のキー名
history_messages_key="history" # 5-2. 履歴として渡すキー名
)
# 6. 動作確認
if __name__ == "__main__":
# 6-1. 複数ユーザセッションの用意
sessions = ["user1", "user2"]
# 6-2. user1 の会話
r1_user1 = chatbot.invoke(
{"input": "Scalaについて教えて"},
config={"configurable": {"session_id": sessions[0]}}
)
print("user1-1:", r1_user1.content)
r2_user1 = chatbot.invoke(
{"input": "どんなライブラリが有名?"},
config={"configurable": {"session_id": sessions[0]}}
)
print("user1-2:", r2_user1.content)
# 6-3. user2 の会話
r1_user2 = chatbot.invoke(
{"input": "どんなライブラリが有名?"},
config={"configurable": {"session_id": sessions[1]}}
)
print("user2-1:", r1_user2.content)
出力例は以下の通りです。
user1-1: Scala!
ScalaはJavaと同様にオブジェクト指向プログラミング言語であり、2003年に開発されました。現在では広く使用されているプログラミング言語の一つです。
** Scalaの特徴**
1. **静的型付け**: Scalaは静的型付けを採用しており、コンパイル時にも型チェックが行われます。
2. **関数型プログラミング**: Scalaは関数型プログラミングをサポートしており、関数の再利用と組み合わせが容易になります。
3. **アークテクチャ**: ScalaはAkkaというフレームワークを使用して並列処理や分布処理を行うことができます。
**Scalaの基本概念**
1. **クラス**: オブジェクトを表すための定義。`class MyClass { ... }`
2. **オブジェクト**: クラスに基づいて生成された実体。
3. **メソッド**: オブジェクトが実行できる動作。`def myMethod() { ... }`
**Scalaの基本的な操作**
1. **変数宣言**: `val x = 5`
2. **演算子**: 算術演算子 (`+`, `-`, `\*`, `/`), 比較演算子 (`==`, `!=`, `<`, `>`)などを使います。
3. **条件分岐**: `if (x > 5) { ... }`
**Scalaの基本的なライブラリ**
1. **Scala Standard Library**: Scala言語自体に組み込まれたライブラリ。
2. **Akka**: 分布処理と並列処理をサポートするフレームワーク。
3. **Scalaz**: 関数型プログラミングのためのライブラリ。
**Scalaの基本的なアプローチ**
1. **コンパイル時型チェック**: Scalaはコンパイル時に型チェックを行うため、エラーが早く発生します。
2. **関数型プログラミング**: 関数を再利用し、組み合わせることが容易になります。
3. **分散処理**: Akkaなどのフレームワークを使用して分散処理を行うことができます。
ScalaはJavaと似ているですが、静的型付けや関数型プログラミングなどが特徴的です。以下のリソースを参考にしてください。
* [Scala Documentation](https://docs.scala-lang.org/)
* [Udemy Scala Course](https://www.udemy.com/scala-tutorial/)
あなたはScalaに関する特定のトピックについて学びたい場合は、気軽に質問してください!
**Scalaを使用する分野**
1. **Web開発**: Webアプリケーションの構築を支援するためのフレームワーク、AkkaなどがScalaで開発されています。
2. **エンタープライズソフトウェア**: 大規模な企業向けのソフトウェアを作成するためにScalaが使用されます。
3. **大規模なデータ分析**: Scalaは大規模なデータを処理するためによく使用されます。
あなたはどのような分野で Scala を使いたいですか?
user1-2: Scalaには多くの有名なライブラリがあります。以下にいくつか挙げましょう。
**データ分析**
1. **Apache Commons Math**: 数値計算のためのライブラリ。
2. **Weka**: データミニングおよびデータマイニングのためのライブラリ。
3. **Scalaz**: 関数型プログラミングのためのライブラリ。
**Web開発**
1. **Akka**: 分布処理と並列処理をサポートするフレームワーク。
2. **Play Framework**: Webアプリケーションの構築を支援するためのフレームワーク。
3. **Scala.js**: JavaScriptに翻訳されるScalaのライブラリ。
**機械学習**
1. **Weka**: データミニングおよびデータマイニングのためのライブラリ。
2. **Scikit-learn**: さまざまな機械学習アルゴリズムを提供するライブラリ。
3. **TensorFlow**: 大規模な機械学習処理に適したライブラリ。
**データベース**
1. **Akka**: 分布処理と並列処理をサポートするフレームワーク。
2. **Play Framework**: Webアプリケーションの構築を支援するためのフレームワーク。
3. **Slick**: Scalaでデータベース処理を行うためのライブラリ。
**その他**
1. **Guava**: コレクションやプログラミング用ユーティリティなどの機能を提供するライブラリ。
2. **Lombok**: Javaでオートジェネレーションなどを行うライブラリ。
3. **Maven**: プロジェクト管理に利用できるビルドツール。
これらのライブラリはScalaコミュニティで広く使用されています。具体的な使用方法について知りたい場合は、各ライブラリのドキュメントを参照してください。
あなたはどの分野で Scala を使いたいですか?
user2-1: C# の場合、以下のようなライブラリが人気があります。
* `Unity`: ゲーム開発などに使用されるゲームエンジン
* `ASP.NET Core`: Web アプリケーションの開発を容易にするフレームワークです。
* `Entity Framework Core`: データベース操作を容易にするライブラリです。
出力結果より、user1のセッションでは、最初の質問応答と関連して、2つ目の応答が出力できています。具体的には、2つ目の回答では「Scalaのライブラリ」を回答できています。
user2 のセッションは、user1とは独立した会話として扱うことができています。回答では、「C#のライブラリ」を回答しています。
プログラムを実行すると、history配下に user1.json, user2.json が作成され、履歴が保存されます。
7. まとめ
- AIに自然な会話をさせるには、「文脈の保持」が重要
- LangChainでは Message History を使って履歴を管理
- 履歴はメモリ・ファイル・DBに保存可能
- session_id によりユーザごとに会話を分離
- RunnableWithMessageHistory を利用する事で、プロンプト・LLM・履歴を一連の処理としてシンプルに記述できる