Skip to content

Commit

Permalink
Merge pull request #15 from ks6088ts-labs/feature/issue-9_store-chat-…
Browse files Browse the repository at this point in the history
…history-cosmosdb

会話履歴を保持する Chat を実装する
  • Loading branch information
ks6088ts authored Aug 1, 2024
2 parents c19311f + 6450eb6 commit 89f2939
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 0 deletions.
109 changes: 109 additions & 0 deletions apps/4_streamlit_chat_history/README.md
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
}
```
123 changes: 123 additions & 0 deletions apps/4_streamlit_chat_history/main.py
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)
Binary file added docs/images/4_streamlit_chat_history.png
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.

0 comments on commit 89f2939

Please sign in to comment.