Skip to content

add frontend apps #134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 27, 2024
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
15 changes: 13 additions & 2 deletions frontend/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,16 @@ BACKEND_URL = "http://localhost:8888"
AZURE_OPENAI_ENDPOINT = "https://<aoai-name>.openai.azure.com"
AZURE_OPENAI_API_KEY = "<aoai-api-key>"
AZURE_OPENAI_API_VERSION = "2024-05-01-preview"
AZURE_OPENAI_WHISPER_MODEL = "whisper"
AZURE_OPENAI_GPT_MODEL = "gpt-4o"
AZURE_OPENAI_MODEL_WHISPER = "whisper"
AZURE_OPENAI_MODEL_CHAT = "gpt-4o"
AZURE_OPENAI_MODEL_EMBEDDING = "text-embedding-3-large"

# Azure AI Search
AZURE_AI_SEARCH_ENDPOINT = "https://<your-aisearch-name>.search.windows.net"
AZURE_AI_SEARCH_API_KEY = "<api-key>"

# LangSmith
LANGCHAIN_TRACING_V2 = "false" # set to "true" to enable tracing
LANGCHAIN_API_KEY = "<api-key>"
LANGCHAIN_ENDPOINT = "https://api.smith.langchain.com"
LANGCHAIN_PROJECT = "default"
2 changes: 1 addition & 1 deletion frontend/pages/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def main(
st.markdown(prompt)

response = client.chat.completions.create(
model=getenv("AZURE_OPENAI_GPT_MODEL"),
model=getenv("AZURE_OPENAI_MODEL_CHAT"),
messages=[{"role": m["role"], "content": m["content"]} for m in st.session_state.messages],
stream=True,
)
Expand Down
139 changes: 139 additions & 0 deletions frontend/pages/summarize_youtube.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import logging
import traceback
from os import getenv
from urllib.parse import urlparse

import streamlit as st
import tiktoken
from dotenv import load_dotenv
from langchain_community.document_loaders import YoutubeLoader # Youtube用
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda
from langchain_openai import AzureChatOpenAI
from langchain_text_splitters import RecursiveCharacterTextSplitter

logger = logging.getLogger(__name__)
load_dotenv()


SUMMARIZE_PROMPT = """Please provide a clear 300 word summary of the following content in Japanese.

========

{content}

========
"""


def init_page():
st.set_page_config(page_title="Summarize YouTube", page_icon="💻")
st.header("Summarize YouTube")
st.sidebar.title("Options")


def select_model(temperature=0):
return AzureChatOpenAI(
temperature=temperature,
api_key=getenv("AZURE_OPENAI_API_KEY"),
api_version=getenv("AZURE_OPENAI_API_VERSION"),
azure_endpoint=getenv("AZURE_OPENAI_ENDPOINT"),
model=getenv("AZURE_OPENAI_MODEL_CHAT"),
)


def init_summarize_chain():
llm = select_model()
prompt = ChatPromptTemplate.from_messages(
[
("user", SUMMARIZE_PROMPT),
]
)
output_parser = StrOutputParser()
return prompt | llm | output_parser


def init_map_reduce_chain():
summarize_chain = init_summarize_chain()

text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
model_name="gpt-4o", # hard-coded for now
chunk_size=16000,
chunk_overlap=0,
)
text_split = RunnableLambda(lambda x: [{"content": doc} for doc in text_splitter.split_text(x["content"])])
text_concat = RunnableLambda(lambda x: {"content": "\n".join(x)})
return text_split | summarize_chain.map() | text_concat | summarize_chain


def init_chain():
summarize_chain = init_summarize_chain()
map_reduce_chain = init_map_reduce_chain()

def route(x):
encoding = tiktoken.encoding_for_model("gpt-4o")
token_count = len(encoding.encode(x["content"]))
if token_count > 16000:
return map_reduce_chain
else:
return summarize_chain

chain = RunnableLambda(route)

return chain


def validate_url(url):
"""URLが有効かどうかを判定する関数"""
try:
result = urlparse(url)
if result.netloc != "www.youtube.com":
return False
if not result.path.startswith("/watch"):
return False
return all([result.scheme, result.netloc])
except ValueError:
return False


def get_content(url):
with st.spinner("Fetching Youtube ..."):
loader = YoutubeLoader.from_youtube_url(
url,
add_video_info=True, # タイトルや再生数も取得できる
language=["en", "ja"], # 英語→日本語の優先順位で字幕を取得
)
res = loader.load() # list of `Document` (page_content, metadata)
try:
if res:
content = res[0].page_content
title = res[0].metadata["title"]
return f"Title: {title}\n\n{content}"
else:
return None
except Exception as e:
logger.error(f"An error occurred: {e}")
st.write(traceback.format_exc())
return None


def main():
init_page()
chain = init_chain()
if url := st.text_input("URL: ", key="input"):
# clear text input
is_valid_url = validate_url(url)
if not is_valid_url:
st.write("Please input valid url")
else:
if content := get_content(url):
st.markdown("## Summary")
st.write_stream(chain.stream({"content": content}))
st.markdown("---")
st.markdown("## Original Text")
st.write(content)


if __name__ == "__main__":
main()
117 changes: 117 additions & 0 deletions frontend/pages/tool_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import logging
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
from langchain_openai import AzureChatOpenAI
from tools.fetch_contoso_rules import fetch_contoso_rules
from tools.search_ddg import search_ddg

logger = logging.getLogger(__name__)
load_dotenv()


CUSTOM_SYSTEM_PROMPT = """
あなたは、ユーザーのリクエストに基づいてインターネットで調べ物を行うアシスタントです。
利用可能なツールを使用して、調査した情報を説明してください。
既に知っていることだけに基づいて答えないでください。回答する前にできる限り検索を行ってください。
(ユーザーが読むページを指定するなど、特別な場合は、検索する必要はありません。)

検索結果ページを見ただけでは情報があまりないと思われる場合は、次の2つのオプションを検討して試してみてください。

- 検索結果のリンクをクリックして、各ページのコンテンツにアクセスし、読んでみてください。
- 1ページが長すぎる場合は、3回以上ページ送りしないでください(メモリの負荷がかかるため)。
- 検索クエリを変更して、新しい検索を実行してください。
- 検索する内容に応じて検索に利用する言語を適切に変更してください。
- 例えば、プログラミング関連の質問については英語で検索するのがいいでしょう。

ユーザーは非常に忙しく、あなたほど自由ではありません。
そのため、ユーザーの労力を節約するために、直接的な回答を提供してください。

=== 悪い回答の例 ===
- これらのページを参照してください。
- これらのページを参照してコードを書くことができます。
- 次のページが役立つでしょう。

=== 良い回答の例 ===
- これはサンプルコードです。 -- サンプルコードをここに --
- あなたの質問の答えは -- 回答をここに --

回答の最後には、参照したページのURLを**必ず**記載してください。(これにより、ユーザーは回答を検証することができます)

ユーザーが使用している言語で回答するようにしてください。
ユーザーが日本語で質問した場合は、日本語で回答してください。ユーザーがスペイン語で質問した場合は、スペイン語で回答してください。
"""


def init_page():
st.set_page_config(page_title="Web Browsing Agent", page_icon="🤗")
st.header("Web Browsing Agent 🤗")
st.sidebar.title("Options")


def init_messages():
clear_button = st.sidebar.button("Clear Conversation", key="clear")
if clear_button or "messages" not in st.session_state:
st.session_state.messages = [{"role": "assistant", "content": "Please ask me any questions you may have."}]
st.session_state["memory"] = ConversationBufferWindowMemory(
return_messages=True, memory_key="chat_history", k=10
)


def create_agent():
tools = [
search_ddg,
fetch_contoso_rules,
]
prompt = ChatPromptTemplate.from_messages(
[
("system", CUSTOM_SYSTEM_PROMPT),
MessagesPlaceholder(variable_name="chat_history"),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)
llm = AzureChatOpenAI(
temperature=0,
api_key=getenv("AZURE_OPENAI_API_KEY"),
api_version=getenv("AZURE_OPENAI_API_VERSION"),
azure_endpoint=getenv("AZURE_OPENAI_ENDPOINT"),
model=getenv("AZURE_OPENAI_MODEL_CHAT"),
)
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()
web_browsing_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="Type your question here..."):
st.chat_message("user").write(prompt)

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


if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion frontend/pages/transcription.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def get_transcription(file_path: str) -> Transcription:

return client.audio.transcriptions.create(
file=open(file=file_path, mode="rb"),
model=getenv("AZURE_OPENAI_WHISPER_MODEL"),
model=getenv("AZURE_OPENAI_MODEL_WHISPER"),
)


Expand Down
Empty file added frontend/tools/__init__.py
Empty file.
88 changes: 88 additions & 0 deletions frontend/tools/fetch_contoso_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# GitHub: https://github.com/naotaka1128/llm_app_codes/chapter_010/tools/fetch_qa_content.py

from os import getenv

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


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

query: str = Field()


def get_embeddings():
return AzureOpenAIEmbeddings(
api_key=getenv("AZURE_OPENAI_API_KEY"),
api_version=getenv("AZURE_OPENAI_API_VERSION"),
azure_endpoint=getenv("AZURE_OPENAI_ENDPOINT"),
azure_deployment=getenv("AZURE_OPENAI_MODEL_EMBEDDING"),
)


def create_azure_search(index_name: str) -> AzureSearch:
return AzureSearch(
azure_search_endpoint=getenv("AZURE_AI_SEARCH_ENDPOINT"),
azure_search_key=getenv("AZURE_AI_SEARCH_API_KEY"),
index_name=index_name,
embedding_function=get_embeddings().embed_query,
additional_search_client_options={"retry_total": 4},
)


@tool(args_schema=FetchContentInput)
def fetch_contoso_rules(query):
"""
Contoso 社の就業規則情報から、関連するコンテンツを見つけるツールです。
Contoso 社に関する具体的な知識を得るのに役立ちます。

このツールは `similarity`(類似度)と `content`(コンテンツ)を返します。
- 'similarity'は、回答が質問にどの程度関連しているかを示します。
値が高いほど、質問との関連性が高いことを意味します。
'similarity'値が0.5未満のドキュメントは返されません。
- 'content'は、質問に対する回答のテキストを提供します。
通常、よくある質問とその対応する回答で構成されています。

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

Returns
-------
List[Dict[str, Any]]:
- page_content
- similarity: float
- content: str
"""
db = create_azure_search("contoso_rules")
docs = db.similarity_search_with_relevance_scores(
query=query,
k=3,
score_threshold=0.5,
)
return [
{
"similarity": similarity,
"content": i.page_content,
}
for i, similarity in docs
]


if __name__ == "__main__":
import logging

from dotenv import load_dotenv

logging.basicConfig(
format="[%(asctime)s] %(levelname)7s from %(name)s in %(pathname)s:%(lineno)d: " "%(message)s",
level=logging.DEBUG,
force=True,
)

load_dotenv()
docs = fetch_contoso_rules("ドレスコード")
for doc in docs:
print(doc)
Loading