generated from ks6088ts/template-python
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from ks6088ts-labs/feature/issue-9_store-chat-…
…history-cosmosdb 会話履歴を保持する Chat を実装する
- Loading branch information
Showing
4 changed files
with
232 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.