【streamlit】音声会話アプリ開発手順②
前の記事で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の使い方についてはここでは割愛します。
公式ドキュメントを参考にしてください。
Discussion