LoginSignup
5

Streamlit + Cloud Run + IAPで自分だけのChatGPTアプリをデプロイしてみた

ChatGPT APIを使ってちょっと便利なアプリケーションを開発すると、それを自分(またはクローズドな組織)用に使えるよう認証を掛けてデプロイしたくなります。今回はPythonで手軽にWebアプリケーションのデモを作れるStreamlitをCloud Runにデプロイし、Identity-Aware ProxyでGoogle認証を掛けてみました。

要件と利用技術

環境

  • python = "^3.10"
  • streamlit = "^1.21.0"
  • openai = "^0.27.4"

細かいバージョンなどを示すすべてのコードはGitHubにあります

Poetry + Docker Composeによる環境構築

開発環境はDocker Composeを用いてPoetryを使いました。以下のようなDockerfilecompose.ymlで構築しました。マルチステージビルドの最適化などは割と中途半端なので、お好みで調整してください。

Dockerfile
FROM python:3.10-slim AS builder
WORKDIR /app
ENV PYTHONPATH="/app:$PYTHONPATH"

RUN apt-get update && apt-get install -y \
    build-essential \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

RUN pip install poetry
RUN poetry config virtualenvs.create false

COPY pyproject.toml ./
COPY poetry.lock ./
RUN poetry install --without dev

CMD ["poetry", "run", "streamlit", "run", "src/chatgpt_summarizer/main.py", "--server.port", "8080"]

FROM builder AS dev

RUN apt-get update && apt-get install -y \
    git \
    curl
RUN git config --global --add safe.directory /app

RUN poetry install

FROM builder AS prod
COPY ./src /app/src
compose.yml
services:
  app:
    build:
      context: .
      target: dev
    volumes:
      - .:/app
    env_file:
      - .env
    ports:
      - 8080:8080

OpenAIのAPIキーを発行して$ echo 'OPENAI_API_KEY=XXXXXXXX' > .envしておきます。

実装

StreamlitでChatGPTのインタラクティブなチャットUIを実装する

まずは自分用に使うChatGPTアプリケーションを実装します。ここでは例として普通のチャットを実装しますが、お好みで様々な機能を実装してください。

2023-04-23_chatgpt_streamlit.gif

StreamlitでChatGPTへの入力+出力を1回ずつ行うだけのアプリであれば簡単です。ですが普通のChatGPTのように可変長複数回のやりとりからなるチャットは、Streamlit上で無限ループを掛けながらUIを良い感じに生成する必要があるのと、ループ時に過去実行した箇所でChatGPT APIを毎回叩かないようにする点に注意が必要です。
session_stateを使う方法はよく見かけましたが、ここではStreamlitのCache機能を使って実装してみました。

stream_write()関数については拙稿のStreamlitでChatGPTのStreamを表示するも参考にしてください。

import openai
import streamlit

# @see: https://qiita.com/suzuki_sh/items/64d84c417cba43cd6351
def stream_write(chunks, key=None):
    result_area = streamlit.empty()
    text = ''
    for chunk in chunks:
        next: str = chunk['choices'][0]['delta'].get('content', '') # type: ignore
        text += next
        if "。" in next:
            text += "\n"
        result_area.write(text, key=key)
    return text

@streamlit.cache_data
def cached_chat(messages):
    completion = openai.ChatCompletion.create(
        model='gpt-3.5-turbo',
        messages=messages,
        stream=True,
    )
    text = stream_write(completion, key=f'output_{messages}')
    return text

messages = []
chat_widget = streamlit.empty()

while True:
    with chat_widget.container():
        for message in messages:
            streamlit.write(message['content'])
        input_text = streamlit.text_input('入力', key=f'input_{messages}')
        if len(input_text) == 0:
            streamlit.stop()
        messages.append({"role": "user", "content": input_text})
        text = cached_chat(messages)
        messages.append({"role": "assistant", "content": text})

StreamlitアプリをCloud Runにデプロイする

Google Cloudの利用準備はできているものとします。開発環境にDockerを使っていれば、Cloud Runにデプロイすることは簡単です。ビルドしたイメージを指定してgcloud run deployするだけです。

$ gcloud run deploy $SERVICE \
      --image $IMAGE \
      --project $PROJECT \
      --region asia-northeast1 \
      --ingress internal-and-cloud-load-balancing \
      --max-instances=1 \
      --allow-unauthenticated \
      --set-env-vars "OPENAI_API_KEY=$OPENAI_API_KEY" \
      --platform=managed

IAPを使う上で、以下が必要な点に注意してください。他はお好みで設定してください。

  • 上り(内向き)を内部および Cloud Load Balancing に設定するために--ingress internal-and-cloud-load-balancing
  • allUsers に起動元ロールを付与するために--allow-unauthenticated

デプロイが無事に完了し、発行されたURLを直接アクセスしてみて403 Forbiddenが表示されたら成功です。

image.png

Cloud RunでデプロイしたアプリにIAPを付ける

基本的にはGoogle Cloudの公式ドキュメントに沿って設定していきますが、既にGAしたはずなのに2023-04-23時点ではまだ「プレビュー」と書いてあったりとやや頼りないので、要点に絞って説明します。

Cloud RunでデプロイしたアプリにIAPを付けるには、HTTPS ロードバランサが必要です。以下のように設定します。

  • ロードバランサ
    • 「HTTP(S) ロード バランシング」を構成します
  • フロントエンド
    • プロトコルはHTTPS
    • 今回IPv6を選択しましたが、IPv4でも行けると思います。後にDNSにレコードを登録する時、IPv4にしたならAレコード、IPv6ならAAAAレコードを設定することになります。
  • 証明書
    • ここでドメインを設定してください
    • image.png
  • バックエンドサービス
    • CDNを有効にしていると後にIAPを有効にするときにエラーが出るため、無効にしてください
    • image.png
  • エンドポイントグループ
    • デプロイしたCloud Runサービスをここで設定します
    • image.png

ロードバランサがデプロイされるまでは結構時間がかかるので、待ちます。IPアドレスが発行されたら、DNSにAまたはAAAAレコードを設定しておきます。

ロードバランサが構成されたらIAP画面に移動し、先ほどのロードバランサを選択してIAPを有効にします。設定に不備があるとここでエラーが出ます。

IAPを有効にしたら、画面右側の「プリンシパルを追加」から、許可したいユーザを設定します。

image.png

今回私は自分用のGoogle Workspace ドメインを持っているのでそちらを指定しましたが、個人利用であればGmailアカウントを指定する感じが一般的だと思います。

ロールには「IAP-secured Web App User」を設定します。

設定したドメインにアクセスし、以下のような感じのGoogleログイン画面が表示されたら成功です。

image.png

ログインして、構築したStreamlitアプリが使えるか確かめてみましょう。

おわりに

Streamlitを使ってChatGPTアプリを開発し、Cloud Run IAPで自分だけがアクセスできるようにしてみました。Cloud Run自体は自分だけのアプリをデプロイして使うなら料金も安くて手軽に使える仕組みですが、IAPのためにロードバランサを用意しなければいけない点が惜しいなと思いました。

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5