CCCMKホールディングス TECH Labの Tech Blog

TECH Labスタッフによる格闘記録やマーケティング界隈についての記事など

Streamlitでユーザー認証機能を搭載したChatアプリを作ってみました。

ユーザー認証機能付きのChatアプリを作ります!

こんにちは、CCCMKホールディングスTECH LAB三浦です。

先日ゆりかもめに乗ってお台場に出かけました。モノレールのゆりかもめから見える景色は開放感があって好きです。今度は一日くらいゆっくり時間を取って遊びに行きたいな、と思いました。

さて、今回はSreamlitを使ってちょっとしたWebアプリケーションを作った話をご紹介します。StreamlitはPythonだけでWebアプリケーションのフロントエンド開発を行うことが出来るライブラリです。

streamlit.io

Streamlitのドキュメントを眺めていたところ、Streamlitを使ってLarge Language Model(LLMs)をエンジンにしたChatアプリを作ることが出来ることを知り、試してみようと思いました。またアプリにユーザー認証機能を付けたいな・・・と調べてみると、Streamlitのアプリにユーザー認証機能を実装できるStreamlit-Authenticatorというモジュールがあることが分かりました。このモジュールも合わせて使うことで、ユーザー名とパスワードによるユーザー認証機能を搭載したChatアプリを作ることが出来たので、今回ご紹介したいと思います。

アプリケーションのUI

作ったアプリケーションがどんなものなのかを画面イメージを通じてご紹介します。

まずURLにアクセスすると、以下のようなログイン画面が表示されます。

ログイン画面

正しい"Username"と"Password"を入力すると、ログインに成功し、アプリケーションのメイン画面が表示されます。

AIとチャットが出来るメイン画面

テキストを入力すると、それに対する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の実装例を参考に作っています。

github.com

Streamlitの"Generative AI"のページにはStreamlitとLLMsを使ったアプリケーションの実装例が他にもいくつも紹介されています。こちらも時間を見つけて試してみたいです。

streamlit.io

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.Authenticatecookie_expiry_daysオプションを指定すると、その有効期限を日数で指定することが出来るようです。

こちらはStreamlit-AuthenticatorのGithubのレポジトリに掲載されている実装を参考にしています。

github.com

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アプリを作ることも出来そうなので、今度はそんなアプリの開発も試してみたいと思います。