こんにちは、CCCMKホールディングスTECH LAB三浦です。
先日ゆりかもめに乗ってお台場に出かけました。モノレールのゆりかもめから見える景色は開放感があって好きです。今度は一日くらいゆっくり時間を取って遊びに行きたいな、と思いました。
さて、今回はSreamlitを使ってちょっとしたWebアプリケーションを作った話をご紹介します。StreamlitはPythonだけでWebアプリケーションのフロントエンド開発を行うことが出来るライブラリです。
Streamlitのドキュメントを眺めていたところ、Streamlitを使ってLarge Language Model(LLMs)をエンジンにしたChatアプリを作ることが出来ることを知り、試してみようと思いました。またアプリにユーザー認証機能を付けたいな・・・と調べてみると、Streamlitのアプリにユーザー認証機能を実装できるStreamlit-Authenticatorというモジュールがあることが分かりました。このモジュールも合わせて使うことで、ユーザー名とパスワードによるユーザー認証機能を搭載したChatアプリを作ることが出来たので、今回ご紹介したいと思います。
アプリケーションのUI
作ったアプリケーションがどんなものなのかを画面イメージを通じてご紹介します。
まずURLにアクセスすると、以下のようなログイン画面が表示されます。
正しい"Username"と"Password"を入力すると、ログインに成功し、アプリケーションのメイン画面が表示されます。
テキストを入力すると、それに対するChatGPT("gpt-turbo-35")の応答がメッセージとして表示されていきます。
以降、このアプリケーションを実装するまでの手順を順を追ってご説明します。
ユーザー登録機能の実装
アプリケーションの利用ユーザーの情報は、yamlファイルで管理するようにしています。yamlファイルの構成は以下の様になります。
credentials: usernames: user1: email: user1@example.com name: user1 password: user1のハッシュ化されたパスワード user2: email: user2@example.com name: user2 password: user2のハッシュ化されたパスワード
パスワードはハッシュ化した文字列を書き込んでいます。ハッシュ化するためのモジュールはstreamlit-authenticator
にも含まれており、今回はこちらを使用します。このモジュールの内部ではPythonのbcrypt
ライブラリを呼び出してハッシュ化処理を行っています。
yamlファイルを生成するためのアプリケーションを以下のようなソースコードで作成してみました。このPythonのスクリプトを実行すると、ターミナル上で必要なユーザー情報を入力出来、入力が完了するとyamlファイルに追記される仕組みです。
from getpass import getpass import os import sys import streamlit_authenticator as stauth import yaml yaml_path = "/path/to/.config.yaml" if __name__=="__main__": yaml_data = {"credentials":{"usernames":{}}} person = {} if os.path.exists(yaml_path): with open(yaml_path,"r") as f: yaml_data = yaml.safe_load(f) username = input("your name: ") password = getpass("your password: ") password = stauth.Hasher([password]).generate()[0] email = input("your email: ") person = { "name":username, "email":email, "password":password } yaml_data["credentials"]["usernames"][username] = person with open(yaml_path, "w") as f: yaml.dump(yaml_data, f) print("write yaml file!")
input()
を呼び出すことで、ターミナルへのキーボード入力を文字列として受け取ることが出来ます。パスワードが画面に表示されるのを避けるため、パスワードの入力はgetpass
を使用しました。getpass
でキーボード入力を受け付けた場合、入力した文字列がターミナル上に表示されなくなります。
Chatアプリの動作の実装
次はChatアプリのメインの動作を実装します。main()
という関数の中に実装しておき、後でユーザー認証機能を実装する際に認証を通過した時だけmain()
を呼び出すようにします。
また今回会話エンジンとして使用したのはAzure OpenAI Serviceのgpt-35-turbo
です。プロンプトの管理やAPIの実行を簡単にするため、langchain
を使って実装しました。
st.chat_message("assistant")
やst.chat_message("user")
を呼び出すことでAIアシスタントとユーザーの会話の吹き出しを表示させることが出来、それらのオブジェクトが持つwrite
メソッドを.write("メッセージ")
のように実行すると、テキストを表示することが出来ます。今回はテキストだけですが、たとえば以下の様にすればグラフも表示することが出来るようです。いつか使ってみたいですね!
st.chat_message("assistant").bar_chart(np.random.randn(30, 3))
なお以下のソースコードはStreamlitのLLMsの実装例を参考に作っています。
Streamlitの"Generative AI"のページにはStreamlitとLLMsを使ったアプリケーションの実装例が他にもいくつも紹介されています。こちらも時間を見つけて試してみたいです。
from langchain import LLMChain, PromptTemplate from langchain.chat_models import AzureChatOpenAI from langchain.schema import ( AIMessage, HumanMessage, SystemMessage ) import streamlit as st import streamlit_authenticator as stauth def main(): def draw_conversation(msg): """ LangChainのMessageの内容に応じて描画するchat_messageを出し分ける関数 """ if isinstance(msg, AIMessage): st.chat_message("assistant").write(msg.content) elif isinstance(msg, HumanMessage): st.chat_message("user").write(msg.content) else: return None SYSTEM_CONTENT = """ あなたは会話を通じてユーザーの支援を行うアシスタントです。 """ system_message = SystemMessage(content=SYSTEM_CONTENT) if "messages" not in st.session_state: # 会話のやり取りはsession_stateに保持する st.session_state["messages"] = [system_message] llm = AzureChatOpenAI(deployment_name="gpt-35-turbo") st.title("👦Chat with AI🤖") with st.sidebar: # サイドバーを表示する。サイドバーではmax_tokensとtemperatureを調整可能にする。 max_tokens = st.number_input(label="max_token", min_value=1, max_value=4096, value=128,step=1) temperature = st.number_input(label="temperature", min_value=0.0, max_value=1.0, value=0.7) llm = AzureChatOpenAI( deployment_name="gpt-35-turbo-16k", max_tokens=max_tokens, temperature=temperature) if prompt := st.chat_input(): # 会話の受付と、会話の履歴の描画などを行う。 human_message = HumanMessage(content=prompt) st.session_state.messages.append(human_message) for msg in st.session_state.messages: draw_conversation(msg) msg = llm(st.session_state.messages) st.session_state.messages.append(msg) st.chat_message("assistant").write(msg.content)
ユーザー認証機能の実装
最後はユーザー認証機能をstreamlit_authenticator
を使って実装していきます。以下でログイン画面、ユーザー認証処理、そしてログアウト機能など全て実装することが出来ます。便利ですね。
ログインに成功すると、一時的にcookieにJWTが格納されるようです。stauth.Authenticate
にcookie_expiry_days
オプションを指定すると、その有効期限を日数で指定することが出来るようです。
こちらはStreamlit-AuthenticatorのGithubのレポジトリに掲載されている実装を参考にしています。
with open("/path/to/.config.yaml","r") as file: config = yaml.safe_load(file) authenticator = stauth.Authenticate( config["credentials"], cookie_name="some_cookie_name", key="some_key", cookie_expiry_days="1" ) name, authentication_status, username = authenticator.login("Login","main") if authentication_status: # 認証に成功 authenticator.logout("Logout","main") main() elif authentication_status == False: # 認証に失敗(入力値が不正) st.error("username/password is incorrect") elif authentication_status == None: # 入力せずにログインを試みた場合 st.warning("Please enter your username and password")
まとめ
ということで、今回はStreamlitとStreamlit-Authenticatorを使ってユーザー認証機能付のChatアプリを作ってみた話をご紹介しました。Streamlitは実は久しぶりに使ったのですが、LLMsに使えそうな機能がたくさん実装されており、とても面白かったです。ファイルをアップロードしてもらい、そのファイルの内容について答えてもらうChatアプリを作ることも出来そうなので、今度はそんなアプリの開発も試してみたいと思います。