diff --git a/apps/4_streamlit_chat_history/README.md b/apps/4_streamlit_chat_history/README.md new file mode 100644 index 0000000..7984b72 --- /dev/null +++ b/apps/4_streamlit_chat_history/README.md @@ -0,0 +1,109 @@ +# Streamlit のチャットアプリに履歴機能を追加する + +Cosmos DB を利用して、チャットの履歴を保存する機能を追加します。 + +## 前提条件 + +- Python 3.11+ がインストールされていること +- Azure OpenAI Service が利用できること +- Azure OpenAI Service の API キーが取得できていること +- Azure Cosmos DB のアカウントが作成されていること +- Azure Cosmos DB の接続文字列が取得できていること + +## 手順 + +1. Azure OpenAI Service の API キーを取得する +1. Azure Cosmos DB の接続文字列を取得する +1. [.env.template](../../.env.template) をコピーして `.env` ファイルを作成する +1. `.env` ファイルに API キーを設定する +1. [main.py](./main.py) を実行する + +```shell +# 仮想環境を作成してライブラリをインストールする +python -m venv .venv + +# 仮想環境を有効化する +source .venv/bin/activate + +# ライブラリをインストールする +pip install -r requirements.txt + +# スクリプトを実行する +streamlit run ./apps/4_streamlit_chat_history/main.py +``` + +### 実行例 + +http://localhost:8501 にアクセスすると、以下のような画面が表示されます。 + +![Streamlit Chat](../../docs/images/4_streamlit_chat_history.png) + +会話履歴は、Cosmos DB の Data Explorer から確認できます。 + +![Cosmos DB Data Explorer](../../docs/images/4_streamlit_chat_history_data_explorer.png) + +以下のようにチャットの履歴が保存されています。 + +**会話 1 ターン目** + +```json +{ + "id": "45ce53f941f446dfa859d81dce9c285c", + "session_id": "f327ff27-d79e-44d5-8909-b058091f079c", + "messages": [ + { + "role": "assistant", + "content": "こんにちは、何かお手伝いできますか?" + }, + { + "role": "user", + "content": "こんにちは。私の名前はマイクロ花子です。" + }, + { + "role": "assistant", + "content": "こんにちは、マイクロ花子さん!お会いできて嬉しいです。今日はどんなことについてお話ししましょうか?" + } + ], + "_rid": "nNUPAMIaqAQBAAAAAAAAAA==", + "_self": "dbs/nNUPAA==/colls/nNUPAMIaqAQ=/docs/nNUPAMIaqAQBAAAAAAAAAA==/", + "_etag": "\"1c003a38-0000-2300-0000-66ab36150000\"", + "_attachments": "attachments/", + "_ts": 1722496533 +} +``` + +**会話 2 ターン目** + +```json +{ + "id": "c01db30dbf1d4db9b082f2cce806f29c", + "session_id": "f327ff27-d79e-44d5-8909-b058091f079c", + "messages": [ + { + "role": "assistant", + "content": "こんにちは、何かお手伝いできますか?" + }, + { + "role": "user", + "content": "こんにちは。私の名前はマイクロ花子です。" + }, + { + "role": "assistant", + "content": "こんにちは、マイクロ花子さん!お会いできて嬉しいです。今日はどんなことについてお話ししましょうか?" + }, + { + "role": "user", + "content": "自分の名前を忘れました。私は誰ですか?" + }, + { + "role": "assistant", + "content": "あなたはマイクロ花子さんです!名前を忘れることは時々ありますが、安心してください。あなたはあなた自身で、ここでお話しするためにいます。他に何かお手伝いできることがあれば教えてください!" + } + ], + "_rid": "nNUPAMIaqAQCAAAAAAAAAA==", + "_self": "dbs/nNUPAA==/colls/nNUPAMIaqAQ=/docs/nNUPAMIaqAQCAAAAAAAAAA==/", + "_etag": "\"1c003b38-0000-2300-0000-66ab362c0000\"", + "_attachments": "attachments/", + "_ts": 1722496556 +} +``` diff --git a/apps/4_streamlit_chat_history/main.py b/apps/4_streamlit_chat_history/main.py new file mode 100644 index 0000000..aabfe59 --- /dev/null +++ b/apps/4_streamlit_chat_history/main.py @@ -0,0 +1,123 @@ +from os import getenv +from pprint import pprint +from uuid import uuid4 + +import streamlit as st +from azure.cosmos import ContainerProxy, CosmosClient, PartitionKey +from dotenv import load_dotenv +from openai import AzureOpenAI +from streamlit.runtime.scriptrunner import get_script_run_ctx + +load_dotenv() + + +def get_container_client() -> ContainerProxy: + client = CosmosClient.from_connection_string(getenv("AZURE_COSMOS_DB_CONNECTION_STRING")) + database = client.create_database_if_not_exists(id=getenv("AZURE_COSMOS_DB_DATABASE_NAME")) + return database.create_container_if_not_exists( + id=getenv("AZURE_COSMOS_DB_CONTAINER_NAME"), + partition_key=PartitionKey( + path="/id", + kind="Hash", + ), + ) + + +def get_session_id(): + return get_script_run_ctx().session_id + + +def store_chat_history(container: ContainerProxy): + response = container.create_item( + body={ + "id": uuid4().hex, + "session_id": get_session_id(), + "messages": st.session_state.messages, + } + ) + pprint(response) + + +container = get_container_client() + +with st.sidebar: + azure_openai_endpoint = st.text_input( + label="AZURE_OPENAI_ENDPOINT", + value=getenv("AZURE_OPENAI_ENDPOINT"), + key="AZURE_OPENAI_ENDPOINT", + type="default", + ) + azure_openai_api_key = st.text_input( + label="AZURE_OPENAI_API_KEY", + value=getenv("AZURE_OPENAI_API_KEY"), + key="AZURE_OPENAI_API_KEY", + type="password", + ) + azure_openai_api_version = st.text_input( + label="AZURE_OPENAI_API_VERSION", + value=getenv("AZURE_OPENAI_API_VERSION"), + key="AZURE_OPENAI_API_VERSION", + type="default", + ) + azure_openai_gpt_model = st.text_input( + label="AZURE_OPENAI_GPT_MODEL", + value=getenv("AZURE_OPENAI_GPT_MODEL"), + key="AZURE_OPENAI_GPT_MODEL", + type="default", + ) + "[Go to Azure Portal to get an Azure OpenAI API key](https://portal.azure.com/)" + "[Go to Azure OpenAI Studio](https://oai.azure.com/resource/overview)" + "[View the source code](https://github.com/ks6088ts-labs/workshop-azure-openai/blob/main/apps/4_streamlit_chat_history/main.py)" + +st.title("Streamlit Chat") +st.write(f"Session ID: {get_session_id()}") + +if "messages" not in st.session_state: + st.session_state["messages"] = [ + { + "role": "assistant", + "content": "こんにちは、何かお手伝いできますか?", + } + ] + +# 既存のメッセージを表示 +for msg in st.session_state.messages: + st.chat_message(msg["role"]).write(msg["content"]) + +# ユーザーからの入力を受け付ける +if prompt := st.chat_input(): + if ( + not azure_openai_api_key + or not azure_openai_endpoint + or not azure_openai_api_version + or not azure_openai_gpt_model + ): + st.info("サイドバーに Azure OpenAI の設定を入力してください") + st.stop() + + client = AzureOpenAI( + api_key=azure_openai_api_key, + api_version=azure_openai_api_version, + azure_endpoint=azure_openai_endpoint, + ) + + st.session_state.messages.append( + { + "role": "user", + "content": prompt, + } + ) + st.chat_message("user").write(prompt) + response = client.chat.completions.create( + model=azure_openai_gpt_model, + messages=st.session_state.messages, + ) + msg = response.choices[0].message.content + st.session_state.messages.append( + { + "role": "assistant", + "content": msg, + } + ) + store_chat_history(container) + st.chat_message("assistant").write(msg) diff --git a/docs/images/4_streamlit_chat_history.png b/docs/images/4_streamlit_chat_history.png new file mode 100644 index 0000000..0c5ab62 Binary files /dev/null and b/docs/images/4_streamlit_chat_history.png differ diff --git a/docs/images/4_streamlit_chat_history_data_explorer.png b/docs/images/4_streamlit_chat_history_data_explorer.png new file mode 100644 index 0000000..297903b Binary files /dev/null and b/docs/images/4_streamlit_chat_history_data_explorer.png differ