🍣

【streamlit】音声会話アプリ開発手順②

2024/08/31に公開

前の記事でstreamlitのUI作成方法を掴んだと思いますので、ここからopenAI APIを使ってチャット機能を実装していきます

2-1 簡単トーク

openAIのAPIを使いますので、.envファイルを用意してAPI_KEYを入れておきましょう。
(キーをベタ書きにすると、セキュリティエラーが出ますので、めんどくさがらずにdotenvをインストールしてください)

サンプルコード
# chainで単純トーク

import streamlit as st

from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
from langchain_core.messages.base import BaseMessage
from langchain_openai import OpenAI, ChatOpenAI # langchainでOpenAIクラスを使うなら、openaiライブラリよりこっちのほうが便利

import os
from dotenv import load_dotenv
load_dotenv()
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
client = OpenAI(api_key=OPENAI_API_KEY) # type: ignore

model = ChatOpenAI(model="gpt-4o-mini")

template = """
    あなたはユーザーとトークするAIです。
    ユーザーからのメッセージに対して、気の利いた答えを返してください。

    message:{message}
"""

prompt = PromptTemplate.from_template(template=template)

chain = prompt | model

# タイトルを設定する
st.title("Echo Bot")

# ユーザーの入力が送信された際に実行される処理
if user_input := st.chat_input("メッセージを入力"):

    # ユーザの入力を表示する
    with st.chat_message("user"):
        st.markdown(user_input)

    # ChatBotの返答を表示する
    with st.chat_message("assistant"):
        response:BaseMessage = chain.invoke(input={"message":user_input})
        st.markdown(response.content)

langchainを使っている人なら特に解説しなくてもわかると思います。

先ほどまでは単にオウム返しでAIの答えを表示させていましたが、今回はchat.invoke()で得られた回答を表示させています。

2-2 会話履歴の保存とストリーム書き出し

2-1ではAIの回答が全て返ってきてからst.chat_messageに書き出しました。

この方法では返答に時間がかかる場合にAIがちゃんと動いているのか不安になるので、chatGPTにように文字が次々と出してくれたほうがユーザビリティ的に良いです。

このような機能はStreamlitCallbackHandlerを用いて実装することができます。

また、2-1ではAIから応答は返されますが会話履歴は保存されていません。
会話を保存するよう実装しましょう。

サンプルコード
# streamチャット

######## Streamlitの設定 ########
import streamlit as st

st.set_page_config(page_title="AIチャットアプリ。ストリーミングチャット", page_icon="🤖")
st.title("AIチャットアプリ")

######## 会話 ######## 
from langchain_openai import ChatOpenAI, OpenAI # langchainでOpenAIクラスを使うなら、openaiライブラリよりこっちのほうが便利
from langchain_community.callbacks import StreamlitCallbackHandler

import os
from dotenv import load_dotenv
load_dotenv()
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')

# OpenAI LLMの初期化
chat = ChatOpenAI(temperature=0.7, api_key=OPENAI_API_KEY, streaming=True, model='gpt-4o-mini') # type: ignore

######## 会話履歴 ########
from langchain.schema import HumanMessage, AIMessage, SystemMessage
from langchain_community.chat_message_histories import StreamlitChatMessageHistory

# StreamlitChatMessageHistoryの初期化
chat_history = StreamlitChatMessageHistory(key="chat_messages")

# プロンプトとしてSystemMessageを入れる
system_message = SystemMessage(content="これからの会話はすべて関西弁で返答してください。")
chat_history.messages.append(system_message)


######## メッセージ入力 ########
if user_input := st.chat_input("メッセージを入力"):
    stream_handler = StreamlitCallbackHandler(st.empty())

    # ユーザーメッセージの追加
    chat_history.add_user_message(user_input)

    # AIの応答を生成
    with st.chat_message("assistant"):
        messages = [
            HumanMessage(content=msg.content) if isinstance(msg, HumanMessage) else AIMessage(content=msg.content) 
            for msg in chat_history.messages
        ]
        
        response = chat.invoke(
            input=messages,
            config={"callbacks": [stream_handler]}
        )

    # AIの応答をチャット履歴に追加
    chat_history.add_ai_message(response.content)

ChatOpenAIをインスタンス化するときに、streaming=Trueを設定します。

StreamlitCallbackHandlerの使い方はサンプルコードのように、インスタンス化した後にchat.invokeのconfigに"callbacks"として渡してやります。

これでstreamlit上ではchatGPTのように逐次的に回答が表示されるようになります。

(なお、StreamlitCallbackHandlerは本来はAgentが動作するために利用するものです。今回はあくまで回答のストリーム表示を体験するためで、音声での使い方は次の章で改めて紹介します)

会話履歴については、StreamlitChatMessageHistoryを使って保存しています。

ちょっと前はこのライブラリがなく、1-1で示したようにst.session_statusに'messages'といったキーを設けて格納する方法が一般的でしたが、その方法だと型定義ができないので不便です。

会話履歴の型(HumanMessage, AIMessage, SystemMessage)やChatOpenAIの使い方についてはここでは割愛します。
公式ドキュメントを参考にしてください。
https://api.python.langchain.com/en/latest/messages/langchain_core.messages.system.SystemMessage.html

Discussion