Lambda+Bedrock+TiDB Cloudで、LangChainを使って会話履歴を含めたやり取りをしてみた
こんにちは、ゲームソリューション部のsoraです。
今回は、Lambda+Bedrock+TiDB Cloudで、LangChainを使って会話履歴を含めたやり取りをしてみたことについて書いていきます。
構成
構成は簡単のものですが以下です。
会話履歴をTiDB Cloudに保存して、LambdaからLangChainを使って履歴を取得します。
その後、その会話履歴を使用して、Bedrockにリクエストを実行します。
環境
- モデル:Claude 3.5 Sonnet
- Lambdaランタイム:Python 3.10
- ※ローカルの環境に合わせたこともあり古いバージョンになっています
- DB:TiDB Cloud Serverless v7.1.3
TiDB Cloudにてテーブルの準備
TiDB Cloudにてクラスタを作成します。
今回は無料枠のあるServerlessクラスタを作成します。
TiDB Cloudのクラスタ作成などを知りたい方は、以下デモ動画を投稿した内容のブログをご参照ください。
作成後にTiDB Cloud上のSQL Editorからテーブルを作成しておきます。
CREATE TABLE chat_history (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
session_id VARCHAR(255) NOT NULL,
message TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
Lambdaの作成
次にLambdaを作成します。
Pythonソースコード
ソースコードは以下です。解説はコメントに記載しています。
import json
import os
from langchain_community.chat_message_histories.tidb import TiDBChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.messages import BaseMessage, AIMessage, SystemMessage
from langchain_aws.chat_models.bedrock import ChatBedrock
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableLambda, ConfigurableFieldSpec, RunnablePassthrough
from langchain_community.chat_message_histories import ChatMessageHistory
# DB接続用の文字列を作成
connection_string = (
f"mysql+pymysql://{os.getenv('TIDB_USER')}:{os.getenv('TIDB_PASSWORD')}@"
f"{os.getenv('TIDB_HOST')}:{os.getenv('TIDB_PORT')}/{os.getenv('TIDB_DATABASE')}?"
f"ssl_ca=./isrgrootx1.pem"
)
# 履歴格納用
store = {}
# セッションIDごとの履歴の取得
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = TiDBChatMessageHistory(
table_name="chat_history",
connection_string=connection_string,
session_id=session_id
)
return store[session_id]
# プロンプトテンプレートの作成
# SystemMessageの部分は、("system", "指示内容")でも良いと思います
prompt_template = ChatPromptTemplate.from_messages(
[
SystemMessage(content="あなたは優秀なAIです。あなたは昨日シチューを食べました。設定上、あなたは食事をするAIということになっています。"),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
]
)
# モデル指定(Claude 3.5 Sonnet)
model = ChatBedrock(
model_id="anthropic.claude-3-5-sonnet-20240620-v1:0",
region_name="ap-northeast-1",
)
# Runnableの作成
runnable = prompt_template | model
chain_with_history = RunnableWithMessageHistory(
runnable=runnable,
get_session_history=get_session_history,
input_messages_key="question",
history_messages_key="history",
)
# Lambdaメイン
def lambda_handler(event, context):
user_input = event.get("input")
session_id = event.get("sessionId")
print("User input:", user_input)
# AIの応答を生成
response = chain_with_history.invoke(
{"question": user_input},
config={"configurable": {"session_id": session_id}},
)
print("Response:", response)
# レスポンスを返す
return {
"statusCode": 200,
"body": json.dumps({"response": response.content})
}
個人的に苦戦したポイントは、LangChain v0.2 から追加されたLCEL記法です。
LCEL記法ではMemoryコンポーネントを利用するChainやエージェント全体がRunnableとして扱われています。
LCEL記法ではないmemory=
の書き方をしている情報が多く、結構苦戦しました。
その中で、特に以下が参考になりました。
パッケージインストール・アップロード
パッケージインストールをrequirements.txt
にて行います。
langchain
langchain-aws
langchain-core
langchain-community
pymysql
pydantic
pip install -r requirements.txt -t package/
TiDB Cloud Serverlessに接続するため、pemファイルを含めてLambdaへアップロードします。
その他設定
環境変数として、TiDB Cloud接続用の情報を追加します。
Bedrockの準備
Bedrock側ではモデルへのアクセスを有効化しておく以外で必要な作業はありません。
実行
準備ができたためLambdaテストから実行します。
まず、システム設定として与えているプロンプトが正常に伝わっているかを確認するため、そのことがわかる質問を投げかけてみます。
sessionIdは適当です。
{
"input": "昨日は何を食べましたか?",
"sessionId": "hugahuga"
}
正常にプロンプトとして与えていたシチューを食べたことが回答されています。
次に先ほどの質問を理解しているかを確認するための質問を投げかけてみます。
先ほどの会話履歴を使用するため、sessionIdは先ほどと同様のものを使用します。
※若干リクエストに誤字がありますがスルーしてください。
{
"input": "私は先ほどした質問は何ですか?",
"sessionId": "hugahuga"
}
会話履歴を含んで回答できていることが確認できました。
TiDB Cloud側で確認してみるとデータが格納されていることが確認できました。
きれいに構造化できておらず、messageにjson形式のまま入っているため、この部分は試行錯誤しながらコード改善していこうかなと思いました。
参考
最後に
今回は、Lambda+Bedrock+TiDB Cloudで、LangChainを使って会話履歴を含めたやり取りをしてみたことを記事にしました。
どなたかの参考になると幸いです。