Site icon imageうぇぶすく

無料でWebマーケティング、データサイエンス、Pythonが勉強できる学習サイト「うぇぶすく」へようこそ。 ※ 各記事では部分的にAIで作成されています。

PythonのLangChainでカスタマーサポートのチャットボットを作る方法

近年、デジタルコミュニケーションの進化に伴い、チャットボット技術が急速に発展しています。

特にカスタマーサポート分野では、チャットボットは顧客からの問い合わせに迅速かつ効率的に対応する手段として不可欠な存在となりつつあります。

チャットボットは24時間365日対応可能であり、待ち時間を減らし、顧客満足度を向上させることができます。

さらに、繰り返し発生する問い合わせに対して一貫した回答を提供することで、オペレーターの負担を軽減し、より複雑な問題に集中することが可能となります。

このような背景の中、Python言語で開発されたLangChainは、特に注目すべきツールの一つです。

LangChainは、自然言語処理と機械学習の最新技術を活用して、より人間らしい対話を生成するチャットボットを簡単に作成できるフレームワークを提供しています。

LangChainの利用には、Pythonの基本的な知識があれば十分で、複雑な機械学習のアルゴリズムを理解していなくても直感的なAPIを通じて高度なチャットボットを開発できる点が大きな利点です。

LangChainを使用することで、カスタマーサポートチームは以下のような多くの利点を享受できます:

  • 迅速なレスポンス: 顧客の問い合わせに対して即座に回答を提供し、顧客体験を向上させます。
  • スケーラビリティ: 大量の同時問い合わせに対応可能で、ピーク時の負荷にも柔軟に対応します。
  • コスト削減: 長時間のオペレーションや追加の人件費を削減し、全体的な運用コストを下げることができます。
  • カスタマイズ性: 特定のビジネスニーズに合わせてチャットボットの挙動をカスタマイズできます。

以上のように、LangChainはカスタマーサポートの効率化と顧客満足度の向上に貢献する強力なツールです。

次のセクションでは、LangChainの環境設定から基本的なチャットボットの設計まで、具体的な開発プロセスを詳細に解説していきます。

1. 環境準備

LangChainを使用するためには、Pythonがインストールされている環境が必要です。

Pythonと必要なパッケージをインストールした後、LangChain関連のライブラリもインストールします。

pip install openai==1.29.0
pip install anthropic ==0.25.7
pip install google-generativeai ==0.5.2

pip install streamlit==1.33.0
pip install streamlit-feedback==0.1.3
pip install langsmith==0.1.54
pip install faiss-cpu==1.7.4

pip install langchain==0.1.16
pip install langchain-community==0.0.34
pip install langchain-core==0.1.46
pip install langchain-openai==0.1.3
pip install langchain-anthropic==0.1.11
pip install langchain-google-genai==1.0.0

2. ファイル構成

  • data
    • bearmobile_QA.csv
    • bearmobile_stores.csv
  • prompt
    • system_prompt.txt
  • src
    • cache.py
    • feedback.py
  • tools
    • fetch_qa_content.py
    • fetch_stores_by_prefecture.py
  • build_qa_vectorstore.py
  • main_cache.py
  • main_feedback.py
  • main.py
  • secrets.toml … ここに「OPENAI_API_KEY="◯◯◯◯◯”」「GOOGLE_API_KEY = "◯◯◯◯◯”」「ANTHROPIC_API_KEY = “◯◯◯◯◯”」という形で各モデルのAPIキーを収納ください。
  • .gitignore … ここに「.streamlit/」と入力して、APIキーがクラウド上にアップされないようにします

3. 全体のコード

3-1. 質問に対する回答を用意するツールを作成

Fanction Callingで使用するためのツールを作成します。

3-1-1. /tools/fetch_stores_by_prefecture.py

# 必要なライブラリのインポート
import pandas as pd
import streamlit as st
from langchain_core.tools import tool
from langchain_core.pydantic_v1 import (BaseModel, Field)

# 入力データの構造を定義するクラス
class FetchStoresInput(BaseModel):
    """ 型を指定するためのクラス """
    pref: str = Field()  # 都道府県名を受け取るフィールド

# CSVファイルから店舗データをロードし、キャッシュする関数
@st.cache_data(ttl="1d")  # データを1日間キャッシュする
def load_stores_from_csv():
    df = pd.read_csv('./data/bearmobile_stores.csv')  # CSVファイルを読み込む
    return df.sort_values(by='pref_id')  # 都道府県IDに基づいてソートして返す

# 都道府県名を入力として店舗情報を返すツール
@tool(args_schema=FetchStoresInput)  # LangChainツールで使用する引数のスキーマを定義
def fetch_stores_by_prefecture(pref):
    """
    都道府県別に店舗を検索するツールです。

    検索する際に都道府県名に「県」「府」「都」を付ける必要はありません。
    (例:「東京都」→「東京」、「大阪府」→「大阪」、「北海道」→「北海道」、「沖縄県」→「沖縄」)

    全国の店舗リストが欲しい場合は、「全国」と入力して検索してください。
    - ただし、この検索方法はおすすめしません。
    - ユーザーが「どこに店舗があるのが一般的ですか?」と尋ねてきた場合、
      まずユーザーの居住都道府県を確認してください。

    空のリストが返された場合、その都道府県に店舗が見つからなかったことを意味します。
    その場合、ユーザーに質問内容を明確にしてもらうのが良いでしょう。
    """
    df = load_stores_from_csv()  # CSVからデータをロード
    if pref != "全国":  # 入力が「全国」でなければ特定の都道府県でフィルタリング
        df = df[df['pref'] == pref]
    return [
        {
            "store_name": row['name'],        # 店舗名
            "post_code": row['post_code'],    # 郵便番号
            "address": row['address'],        # 住所
            "tel": row['tel']                 # 電話番号
        }
        for _, row in df.iterrows()  # データフレームを反復処理してリストを作成
    ]

このコードは、都道府県別に店舗情報を検索し、その結果をリスト形式で返す役割を持っています。

また、Streamlitのキャッシュ機能を利用して、データの読み込みを効率化しています。

3-1-2. build_qa_vectorstore.py

# 必要なライブラリをインポート
import pandas as pd
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS

def main():
    # CSVファイルからFAQデータを読み込む
    qa_df = pd.read_csv('./data/bearmobile_QA.csv')  # CSVファイルのパス指定

    # ベクトルDBに書き込むデータのリストを作成
    qa_texts = []
    for _, row in qa_df.iterrows():  # データフレームの行をイテレート
        qa_texts.append(f"question: {row['question']}\nanswer: {row['answer']}")  # 質問と回答のフォーマット

    # OpenAIの埋め込みを使用してテキストをベクトル化
    embeddings = OpenAIEmbeddings()  # OpenAIのモデルをインスタンス化
    db = FAISS.from_texts(qa_texts, embeddings)  # テキストリストをベクトル化してFAISSデータベースを作成
    db.save_local('./vectorstore/qa_vectorstore')  # ローカルにベクトルデータベースを保存

if __name__ == '__main__':
    main()  # main関数の実行
    print('done')  # 処理完了後に'done'を出力

このコードは、FAQの質問と回答のペアを含むCSVファイルを読み込み、各ペアをテキスト形式で連結してリストに追加します。

それらのテキストを使用して、OpenAIのモデルを用いてテキストの埋め込みベクトルを生成し、FAISSライブラリを使用してこれらのベクトルを高速検索可能な形式で保存します。

このプロセスにより、後で高速に類似質問を検索できるようになります。

3-1-3. /tools/fetch_qa_content.py

# 必要なライブラリをインポート
import streamlit as st
from langchain_core.tools import tool
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_core.pydantic_v1 import (BaseModel, Field)

# クエリデータの型を定義するクラス
class FetchQAContentInput(BaseModel):
    """ 型を指定するためのクラス """
    query: str = Field()  # ユーザーからのクエリを受け取るフィールド

# FAQのベクトルストア(類似度検索用データベース)をロードする関数
@st.cache_resource  # Streamlitのキャッシング機能を使用してリソースをキャッシュする
def load_qa_vectorstore(
    vectorstore_path="./vectorstore/qa_vectorstore"
):
    """「よくある質問」のベクトルDBをロードする"""
    embeddings = OpenAIEmbeddings()  # OpenAIの埋め込みモデルを使用
    return FAISS.load_local(
        vectorstore_path,
        embeddings=embeddings,
        allow_dangerous_deserialization=True
    )

# ユーザーのクエリに基づいて関連するFAQコンテンツを検索するツール
@tool(args_schema=FetchQAContentInput)  # LangChainツールで入力スキーマを定義
def fetch_qa_content(query):
    """
    「よくある質問」リストから、あなたの質問に関連するコンテンツを見つけるツールです。
    "ベアーモバイル"に関する具体的な知識を得るのに役立ちます。

    このツールは `similarity`(類似度)と `content`(コンテンツ)を返します。
    - 'similarity'は、回答が質問にどの程度関連しているかを示します。
        値が高いほど、質問との関連性が高いことを意味します。
        'similarity'値が0.5未満のドキュメントは返されません。
    - 'content'は、質問に対する回答のテキストを提供します。
        通常、よくある質問とその対応する回答で構成されています。

    空のリストが返された場合、ユーザーの質問に対する回答が見つからなかったことを意味します。
    その場合、ユーザーに質問内容を明確にしてもらうのが良いでしょう。
    """
    db = load_qa_vectorstore()  # ベクトルストアをロード
    docs = db.similarity_search_with_score(
        query=query,
        k=5,  # 上位5件のドキュメントを取得
        score_threshold=0.5  # 類似度スコアが0.5以上のドキュメントのみを取得
    )
    return [
        {
            "similarity": 1 - similarity,  # 類似度を計算(1からの距離)
            "content": i.page_content  # ページコンテンツを抽出
        }
        for i, similarity in docs  # ドキュメントと類似度のタプルをイテレート
    ]
   

このコードは、ユーザーのクエリに基づき、「よくある質問」の中から関連する回答を検索し、類似度スコアと共にその回答を返します。

これにより、ユーザーは特定の問題に対して迅速かつ関連性の高い情報を得ることができます。

3-2. 過去の質問をキャッシュする

3-2-1. /src/cache.py

# 必要なライブラリをインポート
import os
import streamlit as st
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS

class Cache:
    # Cacheクラスの初期化関数
    def __init__(self, vectorstore_path="./vectorstore/cache"):
        # ベクトルストアの保存パスをインスタンス変数に設定
        self.vectorstore_path = vectorstore_path
        # OpenAIの埋め込みを使用するためのインスタンスを作成
        self.embeddings = OpenAIEmbeddings()

    # ベクトルストアをロードするメソッド
    def load_vectorstore(self):
        # 指定したパスにベクトルストアが存在する場合
        if os.path.exists(self.vectorstore_path):
            # FAISSを使用してベクトルストアをロード
            return FAISS.load_local(
                self.vectorstore_path,
                embeddings=self.embeddings,
                allow_dangerous_deserialization=True
            )
        else:
            # 存在しない場合はNoneを返す
            return None

    # クエリと回答をベクトルストアに保存するメソッド
    def save(self, query, answer):
        """ (初回質問に対する) 回答をキャッシュとして保存する """
        # ベクトルストアをロード
        self.vectorstore = self.load_vectorstore()
        if self.vectorstore is None:
            # ベクトルストアが存在しない場合、新規作成
            self.vectorstore = FAISS.from_texts(
                texts=[query],  # クエリをテキストとして
                metadatas=[{"answer": answer}],  # 回答をメタデータとして
                embedding=self.embeddings
            )
        else:
            # 既存のベクトルストアにテキストとメタデータを追加
            self.vectorstore.add_texts(
                texts=[query],
                metadatas=[{"answer": answer}]
            )
        # ベクトルストアをローカルに保存
        self.vectorstore.save_local(self.vectorstore_path)

    # クエリに基づき類似する質問を検索し、関連する回答を返すメソッド
    def search(self, query):
        """ 質問に類似する過去の質問を検索し、その回答を返す。 """
        # ベクトルストアをロード
        self.vectorstore = self.load_vectorstore()
        if self.vectorstore is None:
            # ベクトルストアが存在しない場合、Noneを返す
            return None

        # 類似度検索を実行
        docs = self.vectorstore.similarity_search_with_score(
            query=query,
            k=1,  # 最も類似した1件のみ取得
            score_threshold=0.05  # 類似度の閾値(小さいほど厳密なマッチング)
        )
        if docs:
            # 類似度検索結果が存在する場合、最初のドキュメントの回答を返す
            return docs[0][0].metadata["answer"]
        else:
            # 類似度検索結果が存在しない場合、Noneを返す
            return None

このコードは、特定の質問に対する回答をキャッシュし、後に同じまたは類似の質問がされたときに高速に回答を提供するために設計されています。

FAQシステムやチャットボットでの応用が考えられ、効率的な情報提供を支援します。

3-2-2. main_cache.py

# 必要なライブラリをインポート
import streamlit as st
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain.memory import ConversationBufferWindowMemory
from langchain_core.prompts import MessagesPlaceholder, ChatPromptTemplate
from langchain_core.runnables import RunnableConfig
from langchain_community.callbacks import StreamlitCallbackHandler

# 複数のモデルインポート
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_google_genai import ChatGoogleGenerativeAI

# カスタムツールのインポート
from tools.fetch_qa_content import fetch_qa_content
from tools.fetch_stores_by_prefecture import fetch_stores_by_prefecture
from src.cache import Cache

# システムプロンプトをファイルから読み込み、キャッシュする関数
@st.cache_data
def load_system_prompt(file_path):
    with open(file_path, "r", encoding="utf-8") as f:
        return f.read()

# Streamlitページの基本設定を行う関数
def init_page():
    st.set_page_config(page_title="カスタマーサポート", page_icon="🐻")
    st.header("カスタマーサポート🐻")
    st.sidebar.title("Options")

# 初期メッセージ設定と会話メモリの初期化
def init_messages():
    clear_button = st.sidebar.button("Clear Conversation", key="clear")
    if clear_button or "messages" not in st.session_state:
        welcome_message = "ベアーモバイル カスタマーサポートへようこそ。ご質問をどうぞ🐻"
        st.session_state.messages = [{"role": "assistant", "content": welcome_message}]
        st.session_state['memory'] = ConversationBufferWindowMemory(
            return_messages=True, memory_key="chat_history", k=10
        )
    # 最初の質問であるかのフラグを設定
    st.session_state['first_question'] = len(st.session_state.messages) == 1

# モデル選択用の関数
def select_model():
    models = ("GPT-4", "Claude 3.5 Sonnet", "Gemini 1.5 Pro", "GPT-3.5 (not recommended)")
    model = st.sidebar.radio("Choose a model:", models)
    if model == "GPT-3.5 (not recommended)":
        return ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo")
    elif model == "GPT-4":
        return ChatOpenAI(temperature=0, model_name="gpt-4o")
    elif model == "Claude 3.5 Sonnet":
        return ChatAnthropic(temperature=0, model_name="claude-3-5-sonnet-20240620")
    elif model == "Gemini 1.5 Pro":
        return ChatGoogleGenerativeAI(temperature=0, model="gemini-1.5-pro-latest")

# エージェントを作成する関数
def create_agent():
    tools = [fetch_qa_content, fetch_stores_by_prefecture]
    custom_system_prompt = load_system_prompt("./prompt/system_prompt.txt")
    prompt = ChatPromptTemplate.from_messages([
        ("system", custom_system_prompt),
        MessagesPlaceholder(variable_name="chat_history"),
        ("user", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad")
    ])
    llm = select_model()
    agent = create_tool_calling_agent(llm, tools, prompt)
    return AgentExecutor(agent=agent, tools=tools, verbose=True, memory=st.session_state['memory'])

# メイン関数
def main():
    init_page()
    init_messages()
    customer_support_agent = create_agent()

    # キャッシュの初期化
    cache = Cache()

    for msg in st.session_state['memory'].chat_memory.messages:
        st.chat_message(msg.type).write(msg.content)

    if prompt := st.chat_input(placeholder="法人で契約することはできるの?"):

このコードは、ユーザーが提出する質問に対してAIモデルを利用して回答を生成し、回答のキャッシュを管理する機能を含んでいます。

これにより、よくある質問に対する回答を迅速に提供できます。また、Streamlitのウェブインターフェースを通じてユーザーフレンドリーな対話が実現されています。

3-2. フィードバックを収集する

3-2-1. /src/feedback.py

# 必要なライブラリをインポート
import streamlit as st
from langsmith import Client
from streamlit_feedback import streamlit_feedback

def add_feedback():
    # LangsmithのClientインスタンスを作成
    langsmith_client = Client()

    # Streamlitのセッション状態からrun_idを取得
    run_id = st.session_state["run_id"]

    # フィードバックウィジェットを表示し、ユーザーからのフィードバックを取得
    feedback = streamlit_feedback(
        feedback_type="thumbs",  # サムズアップ・サムズダウンのフィードバックタイプ
        optional_text_label="[任意] 説明を入力してください",  # テキスト入力のラベル
        key=f"feedback_{run_id}",  # ウィジェットの一意のキー
    )

    # フィードバックスコアの対応表を定義
    scores = {"👍": 1, "👎": 0}

    # フィードバックが存在する場合に処理
    if feedback:
        # ユーザーが選んだフィードバックオプションに基づくスコアを取得
        score = scores.get(feedback["score"])

        if score is not None:
            # フィードバックタイプとスコア値を文字列で組み立て
            feedback_type_str = f"thumbs {feedback['score']}"

            # LangsmithのClientを用いてフィードバックレコードを作成
            feedback_record = langsmith_client.create_feedback(
                run_id,  # 実行ID
                feedback_type_str,  # フィードバックタイプ
                score=score,  # フィードバックスコア
                comment=feedback.get("text"),  # ユーザーが入力した任意のコメント
            )

            # フィードバックIDとスコアをStreamlitのセッション状態に保存
            st.session_state.feedback = {
                "feedback_id": str(feedback_record.id),
                "score": score,
            }
        else:
            # スコアが無効な場合、ユーザーに警告メッセージを表示
            st.warning("無効なフィードバックスコアです。")

この関数は、Webアプリケーション内でユーザーからの直感的なフィードバック(サムズアップまたはサムズダウン)を収集し、それをデータベースに記録するプロセスを簡潔に実装しています。

フィードバックは、その性質やコメントと共にLangsmithのシステムに送信され、後で分析やレポートの生成に使用されることができます。

3-2-2. main_feedback.py

import streamlit as st
from langchain import callbacks
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain.memory import ConversationBufferWindowMemory
from langchain_core.prompts import MessagesPlaceholder, ChatPromptTemplate
from langchain_core.runnables import RunnableConfig
from langchain_community.callbacks import StreamlitCallbackHandler

# 複数の言語モデルをインポート
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_google_genai import ChatGoogleGenerativeAI

# カスタムツールをインポート
from tools.fetch_qa_content import fetch_qa_content
from tools.fetch_stores_by_prefecture import fetch_stores_by_prefecture

# キャッシュとフィードバック機能を提供するモジュールをインポート
from src.cache import Cache
from src.feedback import add_feedback

###### dotenv を利用しない場合は消してください ######
try:
    from dotenv import load_dotenv
    load_dotenv()  # 環境変数ファイルを読み込む
except ImportError:
    import warnings
    warnings.warn("dotenv not found. Please make sure to set your environment variables manually.", ImportWarning)
################################################

@st.cache_data  # Streamlitのキャッシュ機能を利用
def load_system_prompt(file_path):
    with open(file_path, "r", encoding="utf-8") as f:
        return f.read()  # システムプロンプトをファイルから読み込む

def init_page():
    st.set_page_config(
        page_title="カスタマーサポート",
        page_icon="🐻"
    )
    st.header("カスタマーサポート🐻")
    st.sidebar.title("Options")  # サイドバーのオプション

def init_messages():
    clear_button = st.sidebar.button("Clear Conversation", key="clear")
    if clear_button or "messages" not in st.session_state:
        welcome_message = "ベアーモバイル カスタマーサポートへようこそ。ご質問をどうぞ🐻"
        st.session_state.messages = [
            {"role": "assistant", "content": welcome_message}
        ]
        st.session_state['memory'] = ConversationBufferWindowMemory(
            return_messages=True,
            memory_key="chat_history",
            k=10
        )

def select_model():
    models = ("GPT-4", "Claude 3.5 Sonnet", "Gemini 1.5 Pro", "GPT-3.5 (not recommended)")
    model = st.sidebar.radio("Choose a model:", models)
    # モデル選択に基づいたエージェントのインスタンスを作成
    if model == "GPT-3.5 (not recommended)":
        return ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo")
    elif model == "GPT-4":
        return ChatOpenAI(temperature=0, model_name="gpt-4o")
    elif model == "Claude 3.5 Sonnet":
        return ChatAnthropic(temperature=0, model_name="claude-3-5-sonnet-20240620")
    elif model == "Gemini 1.5 Pro":
        return ChatGoogleGenerativeAI(temperature=0, model="gemini-1.5-pro-latest")

def create_agent():
    tools = [fetch_qa_content, fetch_stores_by_prefecture]
    custom_system_prompt = load_system_prompt("./prompt/system_prompt.txt")  # システムプロンプトをロード
    prompt = ChatPromptTemplate.from_messages([
        ("system", custom_system_prompt),
        MessagesPlaceholder(variable_name="chat_history"),
        ("user", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad")
    ])
    llm = select_model()  # 選択されたモデルをロード
    agent = create_tool_calling_agent(llm, tools, prompt)  # ツール呼び出しエージェントを作成
    return AgentExecutor(
        agent=agent,
        tools=tools,
        verbose=True,
        memory=st.session_state['memory']
    )

def main():
    init_page()
    init_messages()
    customer_support_agent = create_agent()  # エージェントを初期化

    cache = Cache()  # キャッシュを初期化

    for msg in st.session_state['memory'].chat_memory.messages:
        st.chat_message(msg.type).write(msg.content)

    if prompt := st.chat_input(placeholder="法人で契約することはできるの?"):
        st.chat_message("user").write(prompt)

        if st.session_state['first_question']:
            if cache_content := cache.search(query=prompt):
                with st.chat_message("assistant"):
                    st.write(f"(cache) {cache_content}")
                st.session_state.messages.append(
                    {"role": "assistant", "content": cache_content})
                st.stop()  # キャッシュの内容で応答が完了した場合、処理を停止

        with st.chat_message("assistant"):
            st_cb = StreamlitCallbackHandler(
                st.container(), expand_new_thoughts=True)

            with callbacks.collect_runs() as cb:
                response = customer_support_agent.invoke(
                    {'input': prompt},
                    config=RunnableConfig({'callbacks': [st_cb]})
                )
                st.session_state.run_id = cb.traced_runs[0].id
                st.write(response["output"])

        if st.session_state['first_question']:
            cache.save(prompt, response["output"])  # 初回の質問であればキャッシュに保存

    if st.session_state.get("run_id"):
        add_feedback()  # フィードバック機能を呼び出し

if __name__ == '__main__':
    main()

このコードは、ユーザーからの質問に対してキャッシュを使用して迅速に回答を提供し、未回答または新しい質問にはAIモデルを用いて答えます。

また、ユーザーの入力に基づいてモデルやツールを選択し、最適な応答を生成するよう設計されています。

3-3. プログラムを実行する

main.py

import streamlit as st
from langchain_community.callbacks import StreamlitCallbackHandler
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain.agents import AgentExecutor
from langchain_core.prompts import MessagesPlaceholder, ChatPromptTemplate
from langchain_core.runnables import RunnableConfig
from langchain.memory import ConversationBufferWindowMemory

# 複数のモデルをインポート
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_google_genai import ChatGoogleGenerativeAI

# カスタムツールをインポート
from tools.fetch_qa_content import fetch_qa_content
from tools.fetch_stores_by_prefecture import fetch_stores_by_prefecture

# カスタマーサポートのシステムプロンプト設定
CUSTOM_SYSTEM_PROMPT = """
あなたは日本の格安携帯電話会社「ベアーモバイル」のカスタマーサポート(CS)担当者です。
お客様からのお問い合わせに対して、誠実かつ正確な回答を心がけてください。
...
"""

def init_page():
    st.set_page_config(
        page_title="カスタマーサポート",
        page_icon="🐻"
    )
    st.header("カスタマーサポート🐻")
    st.sidebar.title("Options")

def init_messages():
    clear_button = st.sidebar.button("Clear Conversation", key="clear")
    if clear_button or "messages" not in st.session_state:
        welcome_message = "ベアーモバイル カスタマーサポートへようこそ。ご質問をどうぞ🐻"
        st.session_state.messages = [
            {"role": "assistant", "content": welcome_message}
        ]
        st.session_state['memory'] = ConversationBufferWindowMemory(
            return_messages=True,
            memory_key="chat_history",
            k=10
        )

def select_model():
    models = ("GPT-4", "Claude 3.5 Sonnet", "Gemini 1.5 Pro", "GPT-3.5 (not recommended)")
    model = st.sidebar.radio("Choose a model:", models)
    # 各モデルの設定とインスタンス化
    if model == "GPT-3.5 (not recommended)":
        return ChatOpenAI(
            temperature=0, model_name="gpt-3.5-turbo")
    elif model == "GPT-4":
        return ChatOpenAI(
            temperature=0, model_name="gpt-4o")
    elif model == "Claude 3.5 Sonnet":
        return ChatAnthropic(
            temperature=0, model_name="claude-3-5-sonnet-20240620")
    elif model == "Gemini 1.5 Pro":
        return ChatGoogleGenerativeAI(
            temperature=0, model="gemini-1.5-pro-latest")

def create_agent():
    tools = [fetch_qa_content, fetch_stores_by_prefecture]  # 使用するツール
    prompt = ChatPromptTemplate.from_messages([
        ("system", CUSTOM_SYSTEM_PROMPT),
        MessagesPlaceholder(variable_name="chat_history"),
        ("user", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad")
    ])
    llm = select_model()  # モデルの選択
    agent = create_tool_calling_agent(llm, tools, prompt)
    return AgentExecutor(
        agent=agent,
        tools=tools,
        verbose=True,
        memory=st.session_state['memory']
    )

def main():
    init_page()  # ページの初期設定
    init_messages()  # メッセージの初期設定
    customer_support_agent = create_agent()  # エージェントの作成

    # メッセージの表示
    for msg in st.session_state['memory'].chat_memory.messages:
        st.chat_message(msg.type).write(msg.content)

    # ユーザー入力の受付と処理
    if prompt := st.chat_input(placeholder="法人で契約することはできるの?"):
        st.chat_message("user").write(prompt)

        with st.chat_message("assistant"):
            st_cb = StreamlitCallbackHandler(
                st.container(), expand_new_thoughts=True)
            response = customer_support_agent.invoke(
                {'input': prompt},
                config=RunnableConfig({'callbacks': [st_cb]})
            )
            st.write(response["output"])

if __name__ == '__main__':
    main()

実行手順

一番先に「build_qa_vectorstore.py」を実行して、質問のデータベースをベクトライズ+保存ください。

その後に「main.py」を実行すると、チャットボットが使用できるようになります。