Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/6_call_azure_ai_search/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,3 @@ python apps/6_call_azure_ai_search/search_index.py
## 参考資料

- [How to recursively split text by characters](https://python.langchain.com/v0.2/docs/how_to/recursive_text_splitter/)
- [青空文庫 > 吾輩は猫である](https://www.aozora.gr.jp/cards/000148/files/789_14547.html)
71 changes: 30 additions & 41 deletions apps/6_call_azure_ai_search/data/test.txt

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/6_call_azure_ai_search/search_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

# search for documents
results = vector_store.hybrid_search(
query="吾輩は猫である。名前はまだない",
query="すきやねん上鳥羽",
k=5,
)
pprint(results)
42 changes: 42 additions & 0 deletions apps/7_streamlit_chat_history_rag/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Streamlit のチャットアプリに RAG 機能を追加する

[4_streamlit_chat_history](../4_streamlit_chat_history/) で作成した履歴保存機能付きチャットアプリに、RAG (Retrieval Augmented Generation) 機能を追加します。

## 前提条件

- Python 3.11+ がインストールされていること
- Azure OpenAI Service が利用できること
- Azure OpenAI Service の API キーが取得できていること
- Azure Cosmos DB のアカウントが作成されていること
- Azure Cosmos DB の接続文字列が取得できていること
- Azure AI Search が利用できること
- Azure AI Search の API キーが取得できていること

## 手順

1. Azure OpenAI Service の API キーを取得する
1. Azure Cosmos DB の接続文字列を取得する
1. Azure AI Search の API キーを取得する
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/7_streamlit_chat_history_rag/main.py
```

### 実行例

http://localhost:8501 にアクセスすると、以下のような画面が表示されます。

![Streamlit Chat](../../docs/images/7_streamlit_chat_history_rag.png)
147 changes: 147 additions & 0 deletions apps/7_streamlit_chat_history_rag/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# GitHub: https://github.com/naotaka1128/llm_app_codes/chapter_010/main.py

from os import getenv

import streamlit as st
from dotenv import load_dotenv
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain.memory import ConversationBufferWindowMemory
from langchain_community.callbacks import StreamlitCallbackHandler
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableConfig

# models
from langchain_openai import AzureChatOpenAI
from streamlit.runtime.scriptrunner import get_script_run_ctx
from tools.fetch_contents import fetch_contents

CUSTOM_SYSTEM_PROMPT = """
あなたは上鳥羽製作所の総務係です。
社内からのお問い合わせに対して、誠実かつ正確な回答を心がけてください。

上鳥羽製作所の社内規則に関する一般的な知識についてのみ答えます。
それ以外のトピックに関する質問には、丁重にお断りしてください。

回答の正確性を保証するため「上鳥羽製作所」に関する質問を受けた際は、
必ずツールを使用して回答を見つけてください。

ユーザーが質問に使用した言語で回答してください。
例えば、ユーザーが英語で質問された場合は、必ず英語で回答してください。
スペイン語ならスペイン語で回答してください。

回答する際、不明な点がある場合は、ユーザーに確認しましょう。
それにより、ユーザーの意図を把握して、適切な回答を行えます。

例えば、ユーザーが「オフィスはどこにありますか?」と質問した場合、
まずユーザーの居住都道府県を尋ねてください。

日本全国のオフィスの場所を知りたいユーザーはほとんどいません。
自分の都道府県内のオフィスの場所を知りたいのです。
したがって、日本全国のオフィスを検索して回答するのではなく、
ユーザーの意図を本当に理解するまで回答しないでください。

あくまでこれは一例です。
その他のケースでもユーザーの意図を理解し、適切な回答を行ってください。
"""

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",
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)"

if not azure_openai_api_key or not azure_openai_endpoint or not azure_openai_api_version or not azure_openai_gpt_model:
st.warning("サイドバーに Azure OpenAI の設定を入力してください")
st.stop()


def get_session_id():
return get_script_run_ctx().session_id


def init_page():
st.title("Streamlit Chat")
st.write(f"Session ID: {get_session_id()}")


def init_messages():
clear_button = st.sidebar.button("Clear Conversation", key="clear")
if clear_button or "messages" not in st.session_state:
welcome_message = "ベアーモバイル カスタマーサポートへようこそ。ご質問をどうぞ🐻"
st.session_state.messages = [{"role": "assistant", "content": welcome_message}]
st.session_state["memory"] = ConversationBufferWindowMemory(
return_messages=True, memory_key="chat_history", k=10
)


def select_model():
return AzureChatOpenAI(
temperature=0,
api_key=azure_openai_api_key,
api_version=azure_openai_api_version,
azure_endpoint=azure_openai_endpoint,
model=azure_openai_gpt_model,
)


def create_agent():
## https://learn.deeplearning.ai/functions-tools-agents-langchain/lesson/7/conversational-agent
tools = [
fetch_contents,
]
prompt = ChatPromptTemplate.from_messages(
[
("system", CUSTOM_SYSTEM_PROMPT),
MessagesPlaceholder(variable_name="chat_history"),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)
llm = select_model()
agent = create_tool_calling_agent(llm, tools, prompt)
return AgentExecutor(agent=agent, tools=tools, verbose=True, memory=st.session_state["memory"])


def main():
init_page()
init_messages()
customer_support_agent = create_agent()

for msg in st.session_state["memory"].chat_memory.messages:
st.chat_message(msg.type).write(msg.content)

if prompt := st.chat_input(placeholder="法人で契約することはできるの?"):
st.chat_message("user").write(prompt)

with st.chat_message("assistant"):
st_cb = StreamlitCallbackHandler(st.container(), expand_new_thoughts=True)
response = customer_support_agent.invoke({"input": prompt}, config=RunnableConfig({"callbacks": [st_cb]}))
st.write(response["output"])


if __name__ == "__main__":
load_dotenv()

main()
59 changes: 59 additions & 0 deletions apps/7_streamlit_chat_history_rag/tools/fetch_contents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# GitHub: https://github.com/naotaka1128/llm_app_codes/chapter_010/tools/fetch_qa_content.py

from os import getenv

from dotenv import load_dotenv
from langchain_community.vectorstores.azuresearch import AzureSearch
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.tools import tool
from langchain_openai import AzureOpenAIEmbeddings

load_dotenv()
embeddings = AzureOpenAIEmbeddings(
api_key=getenv("AZURE_OPENAI_API_KEY"),
api_version=getenv("AZURE_OPENAI_API_VERSION"),
azure_endpoint=getenv("AZURE_OPENAI_ENDPOINT"),
model=getenv("AZURE_OPENAI_EMBEDDING_MODEL"),
)

vector_store = AzureSearch(
azure_search_endpoint=getenv("AZURE_AI_SEARCH_ENDPOINT"),
azure_search_key=getenv("AZURE_AI_SEARCH_API_KEY"),
index_name=getenv("AZURE_AI_SEARCH_INDEX_NAME"),
embedding_function=embeddings.embed_query,
additional_search_client_options={
"retry_total": 4,
},
)


class QueryInput(BaseModel):
"""型を指定するためのクラス"""

query: str = Field()
k: int = Field(default=5)


@tool(args_schema=QueryInput)
def fetch_contents(query, k=5):
"""
事前に登録されたドキュメントの中から、ユーザーの質問に関連するコンテンツを取得します。
"上鳥羽製作所"に関する社内規則など具体的な知識を得るのに役立ちます。

このツールは `content`(コンテンツ)を返します。
- 'content'は、質問に関連したテキストを提供します。

空のリストが返された場合、関連するコンテンツが見つからなかったことを意味します。
その場合、ユーザーに質問内容を明確にしてもらうのが良いでしょう。

Returns
-------
List[Dict[str, Any]]:
- page_content
- content: str
"""
docs = vector_store.hybrid_search(
query=query,
k=k,
)
return [{"content": doc.page_content} for doc in docs]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.