Lambda+Bedrock+TiDB Cloudで、LangChainを使って会話履歴を含めたやり取りをしてみた

Lambda+Bedrock+TiDB Cloudで、LangChainを使って会話履歴を含めたやり取りをしてみた

Clock Icon2024.11.28

こんにちは、ゲームソリューション部のsoraです。
今回は、Lambda+Bedrock+TiDB Cloudで、LangChainを使って会話履歴を含めたやり取りをしてみたことについて書いていきます。

構成

構成は簡単のものですが以下です。
会話履歴をTiDB Cloudに保存して、LambdaからLangChainを使って履歴を取得します。
その後、その会話履歴を使用して、Bedrockにリクエストを実行します。
sr-langchain-tidb01

環境

  • モデル:Claude 3.5 Sonnet
  • Lambdaランタイム:Python 3.10
    • ※ローカルの環境に合わせたこともあり古いバージョンになっています
  • DB:TiDB Cloud Serverless v7.1.3

TiDB Cloudにてテーブルの準備

TiDB Cloudにてクラスタを作成します。
今回は無料枠のあるServerlessクラスタを作成します。
TiDB Cloudのクラスタ作成などを知りたい方は、以下デモ動画を投稿した内容のブログをご参照ください。
https://dev.classmethod.jp/articles/tidb-cloud-demo/

作成後に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ソースコード

ソースコードは以下です。解説はコメントに記載しています。

main.py
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=の書き方をしている情報が多く、結構苦戦しました。

その中で、特に以下が参考になりました。
https://zenn.dev/khisa/articles/7f56f4e66cae43

パッケージインストール・アップロード

パッケージインストールを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接続用の情報を追加します。
sr-langchain-tidb02

Bedrockの準備

Bedrock側ではモデルへのアクセスを有効化しておく以外で必要な作業はありません。

実行

準備ができたためLambdaテストから実行します。
まず、システム設定として与えているプロンプトが正常に伝わっているかを確認するため、そのことがわかる質問を投げかけてみます。
sessionIdは適当です。

{
    "input": "昨日は何を食べましたか?",
    "sessionId": "hugahuga"
}

sr-langchain-tidb03

正常にプロンプトとして与えていたシチューを食べたことが回答されています。

次に先ほどの質問を理解しているかを確認するための質問を投げかけてみます。
先ほどの会話履歴を使用するため、sessionIdは先ほどと同様のものを使用します。
※若干リクエストに誤字がありますがスルーしてください。

{
    "input": "私は先ほどした質問は何ですか?",
    "sessionId": "hugahuga"
}

sr-langchain-tidb04
会話履歴を含んで回答できていることが確認できました。

TiDB Cloud側で確認してみるとデータが格納されていることが確認できました。
きれいに構造化できておらず、messageにjson形式のまま入っているため、この部分は試行錯誤しながらコード改善していこうかなと思いました。
sr-langchain-tidb05

参考

https://zenn.dev/khisa/articles/7f56f4e66cae43
https://zenn.dev/meson/articles/how-to-use-langchan_v02
https://blog.serverworks.co.jp/langchain-runnable-memo-1
https://note.com/makokon/n/n850579c34f7d

最後に

今回は、Lambda+Bedrock+TiDB Cloudで、LangChainを使って会話履歴を含めたやり取りをしてみたことを記事にしました。
どなたかの参考になると幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.