StreamlitでAIチャットボットを作ってみた

## **はじめに**

引き続き生成AIの話題が続いていますね。

私も個人的なプロジェクトとして簡単なチャットボットを作ってみたいと思い、挑戦してみました。今回は、DifyというAIアプリケーション開発プラットフォームを使ってチャットボットのバックエンドを作成し、そのAPIをStreamlitというPythonライブラリを使って構築したフロントエンド/バックエンドと連携させるという試みについて紹介します。

前の記事に[Difyについて書きました](https://qiita.com/CodingDane/items/005b583fa4dfa34dfb1e)ので、良かったらご覧ください。

※ここに前の記事のリンクを挿入

Dify（AIアプリケーションを簡単に作成できるノーコードプラットフォーム）とStreamlit（データサイエンティストやエンジニアがウェブアプリケーションを簡単に作成できるPythonライブラリ）を組み合わせることで、比較的少ない労力で機能的なチャットボットを作ることができます。

最後に、Streamlit Cloudを使って無料でアプリのデプロイをやってみました。

## **Difyでチャットボットのバックエンドを作成**

Difyを使ってチャットボットのバックエンドを作成するのは、すごく簡単です。
以下に、その手順を説明します。

まず、[Difyの公式サイト](https://dify.ai/)にアクセスし、アカウントを作成します。

ダッシュボードから「Create New App」を選択し、チャットボット用の新しいアプリケーションを作成します。

次にLLMのモデルを選択しますが、Difyは複数のLLMモデルをサポートしています。今回はGPT-4oを選択しました（Difyの無料アカウントではGPT-3.5の200メッセージクレジットを無料で使えますが、無料枠を使い切った場合や、他のモデルを使いたい場合は、OpenAI等のAPIキーを登録する必要があります）。

続いてチャットボットの性格や応答スタイルを決定するプロンプトを設定します。今回はオフィスでたまに話題になるカラスをテーマにし、プロンプトを作成しました。

![Screenshot 2024-08-23 at 8.01.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3778915/b9775f59-a95a-2c9c-e18b-529461a9f116.png)

アプリケーションの設定が完了したら、「API Access」タブからAPIキーとエンドポイントURLを取得します。これらは後でStreamlitアプリケーションと連携する際に使用します。

![Screenshot 2024-08-23 at 8.06.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3778915/6561e6d2-a877-33c7-4c02-a523dc27f878.png)

## **Streamlitでフロントエンドとバックエンドを構築**

Difyで作成したAPIを利用して、Streamlitでチャットボットのフロントエンドとバックエンドを構築します。Streamlitは、Pythonだけでインタラクティブなウェブアプリケーションを作成できるライブラリです。以下に、その実装手順を説明します。

まず、必要なライブラリをインストールします。

```
pip install streamlit --upgrade
```

**`dify_streamlit_app.py`**という名前のPythonファイルを作成し、以下のように基本的な構造を書きます。

`requests`ライブラリはHTTPリクエストを送るために使用され、`streamlit`はウェブアプリケーションを簡単に作成するためのライブラリです。`st.secrets`を使って、APIキーを安全に管理します。ここでは、`DIFY_API_KEY`という名前で保存されたAPIキーを取得しています。そしてDifyのAPIエンドポイントのURLも定義します。

```python
import requests
import streamlit as st

dify_api_key = st.secrets["DIFY_API_KEY"]
url = 'https://api.dify.ai/v1/chat-messages'
```

st.titleでアプリのタイトルとして「カラスのお悩み相談室」が表示されます。

`st.session_state`は、セッションの状態を保持するために使用されます。`conversation_id`と`messages`がまだ存在しない場合、新しく空のものを作成します。`conversation_id`はDify上の会話のIDで、`messages`は過去のメッセージを保持するリストです。

```python
st.title('カラスのお悩み相談室')

if "conversation_id" not in st.session_state:
    st.session_state.conversation_id = ""

if "messages" not in st.session_state:
    st.session_state.messages = []
```

過去のメッセージを表示します。`st.session_state.messages`に保存されている各メッセージをループで回し、ユーザーとAIのメッセージをそれぞれのロール（役割）に応じて表示します。

```python
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])
```

`st.chat_input`はユーザーがカラスに質問するための入力フィールドを表示します。入力された質問は`prompt`に保存されます。

ユーザーが質問を入力した場合、それを表示し、`st.session_state.messages`にユーザーのメッセージとして追加します。下記のコードはすべて上記のif文の中に入ります。

```python
prompt = st.chat_input("カラスに何か質問してみよう")

if prompt:
    with st.chat_message("user"):
        st.markdown(prompt)

    st.session_state.messages.append({"role": "user", "content": prompt})
```

次に、AIからの応答を生成し表示するために、APIリクエストを準備します。ヘッダーには認証情報としてAPIキーが含まれており、ペイロードにはユーザーの質問や会話IDなどの情報が含まれます。”response_mode”のところで、`”blocking”`か`”streaming”`を選択することが出来ますが、`”blocking”`だとチャットボットからの返信が出来上がってからしか返ってきません。”streaming”だと応答を部分的に生成しながら、できあがった部分から順次返します。応答が生成されるにつれて、すぐに表示されます。

```python
with st.chat_message("assistant"):
    message_placeholder = st.empty()

    headers = {
        'Authorization': f'Bearer {dify_api_key}',
        'Content-Type': 'application/json'
    }

    payload = {
        "inputs": {},
        "query": prompt,
        "response_mode": "blocking",
        "conversation_id": st.session_state.conversation_id,
        "user": "alex-123",
        "files": []
    }
```

APIリクエストを送信し、AIからの応答を取得します。応答が成功すれば、会話IDが更新され、AIの応答が表示されます。エラーが発生した場合には、エラーメッセージが表示されます。

```python
try:
    response = requests.post(url, headers=headers, json=payload)
    response.raise_for_status()

    response_data = response.json()

    full_response = response_data.get("answer", "")
    new_conversation_id = response_data.get("conversation_id", st.session_state.conversation_id)

    st.session_state.conversation_id = new_conversation_id

except requests.exceptions.RequestException as e:
    st.error(f"An error occurred: {e}")
    full_response = "An error occurred while fetching the response."
```

チャットボットからの最終的な応答を表示します。

```python
message_placeholder.markdown(full_response)
```

最後に、チャットボットの応答を`st.session_state.messages`に追加して保存します。

```python
st.session_state.messages.append({"role": "assistant", "content": full_response})
```

## Streamlit Cloudでアプリをデプロイ

Streamlit Cloudでデプロイすることは非常にシンプル、しかも無料なのでとても便利です。ただ、APIキーを利用しているアプリの場合には、アプリを悪用されないよう注意しましょう。特定の人しかアクセスできないようにしたり、使わない時にアプリをスリープさせたりできます。

ではデプロイしましょう！

まずはアプリのリポジトリをGitHubにpushします。それからは、[Streamlit Cloud](https://streamlit.io/cloud) にアクセスし、アカウントを作成したらGitHubアカウントと連携させることができます。

Streamlit Cloudのダッシュボードで「New app」ボタンをクリックし、GitHubリポジトリ、ブランチ、メインのPythonファイル（dify_streamlit_app.py）を選択します。DifyのAPIキーは「Settings」の「Secrets」で、環境変数（APIキーなど）が追加できます！

![Screenshot 2024-08-23 at 10.38.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3778915/34ef47c7-8227-a862-4ac3-7c46cdb12916.png)

## **完成したチャットボットのデモ**

アプリのURLにアクセスすると、チャットインターフェースが表示されます。チャットに書いたら、チャットボットが答えてくれますので、チャットボットが正常に動いていることが確認出来ます！

![Screenshot 2024-08-23 at 9.13.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3778915/76196a49-9db3-3520-6380-9ed4478cdef6.png)

以上、StreamlitでDifyのAPIを使用したチャットボットを作ってみました！

Streamlitの機能を活かしたカスタマイズを今後試していきたいです