Spaces:
Sleeping
Sleeping
Upload 35 files
Browse files- .gitattributes +5 -0
- configs/app_config.yml +46 -0
- data/docs/Internal Medicine, Getachew Tizazu, Tadesse Anteneh.pdf +3 -0
- data/docs/cancer_and_cure__a_critical_analysis.27.pdf +0 -0
- data/docs/medical_oncology_handbook_june_2020_edition.pdf +0 -0
- data/docs_2/Attention_Is_All_You_Need.pdf +3 -0
- data/docs_2/stories.pdf +0 -0
- data/vectordb/processed/chroma/6987cbb9-f35b-4396-b0c0-a911c7c462df/data_level0.bin +3 -0
- data/vectordb/processed/chroma/6987cbb9-f35b-4396-b0c0-a911c7c462df/header.bin +3 -0
- data/vectordb/processed/chroma/6987cbb9-f35b-4396-b0c0-a911c7c462df/index_metadata.pickle +3 -0
- data/vectordb/processed/chroma/6987cbb9-f35b-4396-b0c0-a911c7c462df/length.bin +3 -0
- data/vectordb/processed/chroma/6987cbb9-f35b-4396-b0c0-a911c7c462df/link_lists.bin +3 -0
- data/vectordb/processed/chroma/chroma.sqlite3 +3 -0
- data/vectordb1/processed/chroma/chroma.sqlite3 +3 -0
- data/vectordb1/processed/chroma/d65c6700-15e7-4d1b-8a9c-16cefb1f4e1e/data_level0.bin +3 -0
- data/vectordb1/processed/chroma/d65c6700-15e7-4d1b-8a9c-16cefb1f4e1e/header.bin +3 -0
- data/vectordb1/processed/chroma/d65c6700-15e7-4d1b-8a9c-16cefb1f4e1e/index_metadata.pickle +3 -0
- data/vectordb1/processed/chroma/d65c6700-15e7-4d1b-8a9c-16cefb1f4e1e/length.bin +3 -0
- data/vectordb1/processed/chroma/d65c6700-15e7-4d1b-8a9c-16cefb1f4e1e/link_lists.bin +3 -0
- images/Gemma-logo.png +0 -0
- images/test.png +3 -0
- src/app.py +116 -0
- src/llm_serve.py +50 -0
- src/llm_service.py +58 -0
- src/reference_serve.py +76 -0
- src/upload_data_manually.py +35 -0
- src/utils/__pycache__/chatbot.cpython-39.pyc +0 -0
- src/utils/__pycache__/load_config.cpython-39.pyc +0 -0
- src/utils/__pycache__/prepare_vectordb.cpython-39.pyc +0 -0
- src/utils/__pycache__/ui_settings.cpython-39.pyc +0 -0
- src/utils/__pycache__/upload_file.cpython-39.pyc +0 -0
- src/utils/chatbot.py +171 -0
- src/utils/load_config.py +89 -0
- src/utils/prepare_vectordb.py +117 -0
- src/utils/ui_settings.py +35 -0
- src/utils/upload_file.py +39 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
data/docs_2/Attention_Is_All_You_Need.pdf filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
data/docs/Internal[[:space:]]Medicine,[[:space:]]Getachew[[:space:]]Tizazu,[[:space:]]Tadesse[[:space:]]Anteneh.pdf filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
data/vectordb/processed/chroma/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
data/vectordb1/processed/chroma/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
images/test.png filter=lfs diff=lfs merge=lfs -text
|
configs/app_config.yml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
directories:
|
| 2 |
+
data_directory: data/docs
|
| 3 |
+
data_directory_2: data/docs_2
|
| 4 |
+
persist_directory: data/vectordb/processed/chroma/
|
| 5 |
+
custom_persist_directory: data/vectordb/uploaded/chroma/
|
| 6 |
+
|
| 7 |
+
llm_config:
|
| 8 |
+
embedding_model: "NeuML/pubmedbert-base-embeddings"
|
| 9 |
+
llm_system_role_with_history: "Answer the question based on the given content without using on your own knowledge.
|
| 10 |
+
You will receive a prompt with the the following format:
|
| 11 |
+
|
| 12 |
+
# Chat history:\n
|
| 13 |
+
[user query, response]\n\n
|
| 14 |
+
|
| 15 |
+
# Retrieved content number:\n
|
| 16 |
+
Content\n\n
|
| 17 |
+
Source\n\n
|
| 18 |
+
|
| 19 |
+
# User question:\n
|
| 20 |
+
New question
|
| 21 |
+
"
|
| 22 |
+
llm_system_role_without_history: "In the following you recieve a prompt.
|
| 23 |
+
Answer it based on given content. Provide only the response, dont say 'Answer:'."
|
| 24 |
+
engine: "BioMistral/BioMistral-7B"
|
| 25 |
+
temperature: 0.1
|
| 26 |
+
device: "cuda"
|
| 27 |
+
max_new_tokens: 4096
|
| 28 |
+
do_sample: True
|
| 29 |
+
top_k: 10
|
| 30 |
+
top_p: 0.1
|
| 31 |
+
add_history: False
|
| 32 |
+
splitter_config:
|
| 33 |
+
chunk_size: 1500
|
| 34 |
+
chunk_overlap: 250
|
| 35 |
+
|
| 36 |
+
retrieval_config:
|
| 37 |
+
k: 2
|
| 38 |
+
|
| 39 |
+
serve:
|
| 40 |
+
port: 8000
|
| 41 |
+
|
| 42 |
+
memory:
|
| 43 |
+
number_of_q_a_pairs: 2
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
|
data/docs/Internal Medicine, Getachew Tizazu, Tadesse Anteneh.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0169c575e3a4a5e39326a18ad57f1c0f82d9e78d33d06fbb895d395394d75b0a
|
| 3 |
+
size 2035855
|
data/docs/cancer_and_cure__a_critical_analysis.27.pdf
ADDED
|
Binary file (226 kB). View file
|
|
|
data/docs/medical_oncology_handbook_june_2020_edition.pdf
ADDED
|
Binary file (818 kB). View file
|
|
|
data/docs_2/Attention_Is_All_You_Need.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b7d72988fd8107d07f7d278bf0ba6621adb6ed47df74be4014fa4a01f03aff6a
|
| 3 |
+
size 2215244
|
data/docs_2/stories.pdf
ADDED
|
Binary file (427 kB). View file
|
|
|
data/vectordb/processed/chroma/6987cbb9-f35b-4396-b0c0-a911c7c462df/data_level0.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9d17e4a33d5f6f07d497f04499ed371b0b9688fb5fd10be3295b8cd02c9bcd4e
|
| 3 |
+
size 3212000
|
data/vectordb/processed/chroma/6987cbb9-f35b-4396-b0c0-a911c7c462df/header.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9b27fdd8437a04493bf8a7d1f9a2e0fe99a426f9dd1cd05fc39e36645b2f4892
|
| 3 |
+
size 100
|
data/vectordb/processed/chroma/6987cbb9-f35b-4396-b0c0-a911c7c462df/index_metadata.pickle
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:aaabaf8bd5deb2ce724216d3d22742448e6bb36ad6fd47a8b7546943ac7bc2d9
|
| 3 |
+
size 55974
|
data/vectordb/processed/chroma/6987cbb9-f35b-4396-b0c0-a911c7c462df/length.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bd0e081293048e3304f94190eb77ea8a43b86ea1d325125dde693add12c583c0
|
| 3 |
+
size 4000
|
data/vectordb/processed/chroma/6987cbb9-f35b-4396-b0c0-a911c7c462df/link_lists.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d369861227544a7d964868dd4119f8d689bf3bad375dfbde0801806d57931359
|
| 3 |
+
size 8624
|
data/vectordb/processed/chroma/chroma.sqlite3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6b2e23cc1f35dd063d2cc3d566647382f22b7f130a53c30a82f65e7d57d22658
|
| 3 |
+
size 13520896
|
data/vectordb1/processed/chroma/chroma.sqlite3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5159675380044145b50f2c946dcb6015b30199c9475fc4d3763410a052770fca
|
| 3 |
+
size 14123008
|
data/vectordb1/processed/chroma/d65c6700-15e7-4d1b-8a9c-16cefb1f4e1e/data_level0.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d340a83df102dfb094b470e14917af51d9cc9b4d2899aac7e8d03f5281dae4af
|
| 3 |
+
size 4236000
|
data/vectordb1/processed/chroma/d65c6700-15e7-4d1b-8a9c-16cefb1f4e1e/header.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2a4f6ac52066e3cc07e4dbd3cf3ba996e47b2dc0f3f3b483d9667139b648464d
|
| 3 |
+
size 100
|
data/vectordb1/processed/chroma/d65c6700-15e7-4d1b-8a9c-16cefb1f4e1e/index_metadata.pickle
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6791402d0c0c55745efcc92b7b2b686b854ca13405ce507c8019494d5be482cb
|
| 3 |
+
size 55974
|
data/vectordb1/processed/chroma/d65c6700-15e7-4d1b-8a9c-16cefb1f4e1e/length.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4570a3076ed944f19171251810fe12aa927bf0befa0961c875f5838be34af681
|
| 3 |
+
size 4000
|
data/vectordb1/processed/chroma/d65c6700-15e7-4d1b-8a9c-16cefb1f4e1e/link_lists.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:15f5b49c9cc9564ead2b3490bfd89131af9469aec01ba17df0234f56400380aa
|
| 3 |
+
size 8624
|
images/Gemma-logo.png
ADDED
|
images/test.png
ADDED
|
Git LFS Details
|
src/app.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from utils.upload_file import UploadFile
|
| 3 |
+
from utils.chatbot import ChatBot
|
| 4 |
+
from utils.ui_settings import UISettings
|
| 5 |
+
from utils.load_config import LoadConfig
|
| 6 |
+
|
| 7 |
+
APPCFG = LoadConfig()
|
| 8 |
+
# # Prepare the LLm and Tokenizer
|
| 9 |
+
# tokenizer = AutoTokenizer.from_pretrained(
|
| 10 |
+
# APPCFG.llm_engine, token=APPCFG.gemma_token, device=APPCFG.device)
|
| 11 |
+
# model = model = AutoModelForCausalLM.from_pretrained(pretrained_model_name_or_path="google/gemma-7b-it",
|
| 12 |
+
# token=APPCFG.gemma_token,
|
| 13 |
+
# torch_dtype=torch.float16,
|
| 14 |
+
# device_map=APPCFG.device
|
| 15 |
+
# )
|
| 16 |
+
# app_pipeline = pipeline(
|
| 17 |
+
# "text-generation",
|
| 18 |
+
# model=model,
|
| 19 |
+
# tokenizer=tokenizer
|
| 20 |
+
# )
|
| 21 |
+
with gr.Blocks() as demo:
|
| 22 |
+
with gr.Tabs():
|
| 23 |
+
with gr.TabItem("Med-App"):
|
| 24 |
+
##############
|
| 25 |
+
# First ROW:
|
| 26 |
+
##############
|
| 27 |
+
with gr.Row() as row_one:
|
| 28 |
+
with gr.Column(visible=False) as reference_bar:
|
| 29 |
+
ref_output = gr.Markdown()
|
| 30 |
+
|
| 31 |
+
with gr.Column() as chatbot_output:
|
| 32 |
+
chatbot = gr.Chatbot(
|
| 33 |
+
[],
|
| 34 |
+
elem_id="chatbot",
|
| 35 |
+
bubble_full_width=False,
|
| 36 |
+
height=500,
|
| 37 |
+
avatar_images=(
|
| 38 |
+
("images/test.png"), "images/Gemma-logo.png"),
|
| 39 |
+
# render=False
|
| 40 |
+
)
|
| 41 |
+
# **Adding like/dislike icons
|
| 42 |
+
chatbot.like(UISettings.feedback, None, None)
|
| 43 |
+
##############
|
| 44 |
+
# SECOND ROW:
|
| 45 |
+
##############
|
| 46 |
+
with gr.Row():
|
| 47 |
+
input_txt = gr.Textbox(
|
| 48 |
+
lines=4,
|
| 49 |
+
scale=8,
|
| 50 |
+
placeholder="Enter text and press enter, or upload PDF files",
|
| 51 |
+
container=False,
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
##############
|
| 55 |
+
# Third ROW:
|
| 56 |
+
##############
|
| 57 |
+
with gr.Row() as row_two:
|
| 58 |
+
text_submit_btn = gr.Button(value="Submit text")
|
| 59 |
+
sidebar_state = gr.State(False)
|
| 60 |
+
btn_toggle_sidebar = gr.Button(
|
| 61 |
+
value="References")
|
| 62 |
+
btn_toggle_sidebar.click(UISettings.toggle_sidebar, [sidebar_state], [
|
| 63 |
+
reference_bar, sidebar_state])
|
| 64 |
+
upload_btn = gr.UploadButton(
|
| 65 |
+
"📁 Upload PDF or doc files", file_types=[
|
| 66 |
+
'.pdf',
|
| 67 |
+
'.doc'
|
| 68 |
+
],
|
| 69 |
+
file_count="multiple")
|
| 70 |
+
clear_button = gr.ClearButton([input_txt, chatbot])
|
| 71 |
+
rag_with_dropdown = gr.Dropdown(
|
| 72 |
+
label="RAG with", choices=["Preprocessed doc", "Upload doc: Process for RAG"], value="Preprocessed doc")
|
| 73 |
+
##############
|
| 74 |
+
# Fourth ROW:
|
| 75 |
+
##############
|
| 76 |
+
with gr.Row() as row_four:
|
| 77 |
+
temperature_bar = gr.Slider(minimum=0.1, maximum=1, value=0.1, step=0.1,
|
| 78 |
+
label="Temperature", info="Increasing the temperature will make the model answer more creatively.")
|
| 79 |
+
top_k = gr.Slider(minimum=0.0,
|
| 80 |
+
maximum=100.0,
|
| 81 |
+
step=1,
|
| 82 |
+
label="top_k",
|
| 83 |
+
value=50,
|
| 84 |
+
info="A lower value (e.g. 10) will result in more conservative answers.")
|
| 85 |
+
top_p = gr.Slider(minimum=0.0,
|
| 86 |
+
maximum=1.0,
|
| 87 |
+
step=0.01,
|
| 88 |
+
label="top_p",
|
| 89 |
+
value=0.95,
|
| 90 |
+
info=" Works together with top-k. lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.0)")
|
| 91 |
+
|
| 92 |
+
##############
|
| 93 |
+
# Process:
|
| 94 |
+
##############
|
| 95 |
+
file_msg = upload_btn.upload(fn=UploadFile.process_uploaded_files, inputs=[
|
| 96 |
+
upload_btn, chatbot, rag_with_dropdown], outputs=[input_txt, chatbot], queue=False)
|
| 97 |
+
|
| 98 |
+
txt_msg = input_txt.submit(fn=ChatBot.respond,
|
| 99 |
+
inputs=[chatbot, input_txt,
|
| 100 |
+
rag_with_dropdown, temperature_bar, top_k, top_p],
|
| 101 |
+
outputs=[input_txt,
|
| 102 |
+
chatbot, ref_output],
|
| 103 |
+
queue=False).then(lambda: gr.Textbox(interactive=True),
|
| 104 |
+
None, [input_txt], queue=False)
|
| 105 |
+
|
| 106 |
+
txt_msg = text_submit_btn.click(fn=ChatBot.respond,
|
| 107 |
+
inputs=[chatbot, input_txt,
|
| 108 |
+
rag_with_dropdown, temperature_bar, top_k, top_p],
|
| 109 |
+
outputs=[input_txt,
|
| 110 |
+
chatbot, ref_output],
|
| 111 |
+
queue=False).then(lambda: gr.Textbox(interactive=True),
|
| 112 |
+
None, [input_txt], queue=False)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
if __name__ == "__main__":
|
| 116 |
+
demo.launch()
|
src/llm_serve.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from flask import Flask, request, jsonify
|
| 3 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
|
| 4 |
+
from utils.load_config import LoadConfig
|
| 5 |
+
|
| 6 |
+
APPCFG = LoadConfig()
|
| 7 |
+
|
| 8 |
+
app = Flask(__name__)
|
| 9 |
+
|
| 10 |
+
# Load the LLM and Tokenizer
|
| 11 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 12 |
+
APPCFG.llm_engine, token=APPCFG.gemma_token, device=APPCFG.device)
|
| 13 |
+
model = AutoModelForCausalLM.from_pretrained(pretrained_model_name_or_path="BioMistral/BioMistral-7B",
|
| 14 |
+
token=APPCFG.gemma_token,
|
| 15 |
+
torch_dtype=torch.float16,
|
| 16 |
+
device_map=APPCFG.device
|
| 17 |
+
)
|
| 18 |
+
app_pipeline = pipeline(
|
| 19 |
+
"text-generation",
|
| 20 |
+
model=model,
|
| 21 |
+
tokenizer=tokenizer
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@app.route("/generate_text", methods=["POST"])
|
| 26 |
+
def generate_Text():
|
| 27 |
+
data = request.json
|
| 28 |
+
prompt = data.get("prompt", "")
|
| 29 |
+
max_new_tokens = data.get("max_new_tokens", 1000)
|
| 30 |
+
do_sample = data.get("do_sample", True)
|
| 31 |
+
temperature = data.get("temperature", 0.1)
|
| 32 |
+
top_k = data.get("top_k", 50)
|
| 33 |
+
top_p = data.get("top_p", 0.95)
|
| 34 |
+
|
| 35 |
+
tokenized_prompt = app_pipeline.tokenizer.apply_chat_template(
|
| 36 |
+
prompt, tokenize=False, add_generation_prompt=True)
|
| 37 |
+
outputs = app_pipeline(
|
| 38 |
+
tokenized_prompt,
|
| 39 |
+
max_new_tokens=max_new_tokens,
|
| 40 |
+
do_sample=do_sample,
|
| 41 |
+
temperature=temperature,
|
| 42 |
+
top_k=top_k,
|
| 43 |
+
top_p=top_p
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
return jsonify({"response": outputs[0]["generated_text"][len(tokenized_prompt):]})
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
if __name__ == "__main__":
|
| 50 |
+
app.run(debug=False, port=8888)
|
src/llm_service.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from flask import Flask, request, jsonify
|
| 3 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
|
| 4 |
+
from utils.load_config import LoadConfig
|
| 5 |
+
APPCFG = LoadConfig()
|
| 6 |
+
|
| 7 |
+
app = Flask(__name__)
|
| 8 |
+
|
| 9 |
+
# Load the LLM and Tokenizer
|
| 10 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 11 |
+
APPCFG.llm_engine, token=APPCFG.gemma_token, device=APPCFG.device)
|
| 12 |
+
model = AutoModelForCausalLM.from_pretrained(pretrained_model_name_or_path="BioMistral/BioMistral-7B",
|
| 13 |
+
token=APPCFG.gemma_token,
|
| 14 |
+
torch_dtype=torch.float16,
|
| 15 |
+
device_map=APPCFG.device
|
| 16 |
+
)
|
| 17 |
+
app_pipeline = pipeline(
|
| 18 |
+
"text-generation",
|
| 19 |
+
model=model,
|
| 20 |
+
tokenizer=tokenizer
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
# Endpoint to generate text
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@app.route("/generate_text", methods=["POST"])
|
| 27 |
+
def generate_text():
|
| 28 |
+
data = request.json
|
| 29 |
+
prompt = data.get("prompt", "")
|
| 30 |
+
max_new_tokens = data.get("max_new_tokens", 1000)
|
| 31 |
+
do_sample = data.get("do_sample", True)
|
| 32 |
+
temperature = data.get("temperature", 0.1)
|
| 33 |
+
top_k = data.get("top_k", 50)
|
| 34 |
+
top_p = data.get("top_p", 0.95)
|
| 35 |
+
tokenized_prompt = app_pipeline.tokenizer.apply_chat_template(
|
| 36 |
+
prompt, tokenize=False, add_generation_prompt=True)
|
| 37 |
+
|
| 38 |
+
# Generate text based on the prompt
|
| 39 |
+
response = app_pipeline(
|
| 40 |
+
tokenized_prompt,
|
| 41 |
+
max_new_tokens=max_new_tokens,
|
| 42 |
+
do_sample=do_sample,
|
| 43 |
+
temperature=temperature,
|
| 44 |
+
top_k=top_k,
|
| 45 |
+
top_p=top_p
|
| 46 |
+
)
|
| 47 |
+
print("==================")
|
| 48 |
+
print("top_k:", top_k, "top_p:", top_p, "temperature:",
|
| 49 |
+
temperature, "max_new_tokens:", max_new_tokens)
|
| 50 |
+
print("==================")
|
| 51 |
+
print(response[0]["generated_text"][len(tokenized_prompt):])
|
| 52 |
+
print("==================")
|
| 53 |
+
|
| 54 |
+
return jsonify({"response": response[0]["generated_text"][len(tokenized_prompt):]})
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
if __name__ == "__main__":
|
| 58 |
+
app.run(debug=False, port=8888)
|
src/reference_serve.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import http.server
|
| 2 |
+
import socketserver
|
| 3 |
+
import yaml
|
| 4 |
+
import os
|
| 5 |
+
from pyprojroot import here
|
| 6 |
+
|
| 7 |
+
with open(here("configs/app_config.yml")) as cfg:
|
| 8 |
+
app_config = yaml.load(cfg, Loader=yaml.FullLoader)
|
| 9 |
+
|
| 10 |
+
PORT = app_config["serve"]["port"]
|
| 11 |
+
DIRECTORY1 = app_config["directories"]["data_directory"]
|
| 12 |
+
DIRECTORY2 = app_config["directories"]["data_directory_2"]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class SingleDirectoryHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
| 16 |
+
"""
|
| 17 |
+
Custom HTTP request handler that serves files from a single directory.
|
| 18 |
+
|
| 19 |
+
This class extends the SimpleHTTPRequestHandler and sets the serving directory to DIRECTORY1.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
def __init__(self, *args, **kwargs):
|
| 23 |
+
"""
|
| 24 |
+
Initialize the SingleDirectoryHTTPRequestHandler.
|
| 25 |
+
|
| 26 |
+
Parameters:
|
| 27 |
+
args: Additional positional arguments for the base class.
|
| 28 |
+
kwargs: Additional keyword arguments for the base class.
|
| 29 |
+
"""
|
| 30 |
+
super().__init__(*args, directory=DIRECTORY1, **kwargs)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class MultiDirectoryHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
| 34 |
+
"""
|
| 35 |
+
Custom HTTP request handler that serves files from multiple directories.
|
| 36 |
+
|
| 37 |
+
This class extends the SimpleHTTPRequestHandler and allows serving files from DIRECTORY1 and DIRECTORY2
|
| 38 |
+
based on the first directory component in the requested path.
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
def translate_path(self, path):
|
| 42 |
+
"""
|
| 43 |
+
Translate the requested path to the actual file path.
|
| 44 |
+
|
| 45 |
+
Parameters:
|
| 46 |
+
path (str): The requested path.
|
| 47 |
+
|
| 48 |
+
Returns:
|
| 49 |
+
str: The translated file path.
|
| 50 |
+
"""
|
| 51 |
+
# Split the path to get the first directory component
|
| 52 |
+
parts = path.split('/', 2)
|
| 53 |
+
if len(parts) > 1:
|
| 54 |
+
first_directory = parts[1]
|
| 55 |
+
# Check if the first directory matches any of your target directories
|
| 56 |
+
if first_directory == os.path.basename(DIRECTORY1):
|
| 57 |
+
path = os.path.join(DIRECTORY1, *parts[2:])
|
| 58 |
+
|
| 59 |
+
elif first_directory == os.path.basename(DIRECTORY2):
|
| 60 |
+
path = os.path.join(DIRECTORY2, *parts[2:])
|
| 61 |
+
else:
|
| 62 |
+
# If the first part of the path is not a directory, check both directories for the file
|
| 63 |
+
file_path1 = os.path.join(DIRECTORY1, first_directory)
|
| 64 |
+
file_path2 = os.path.join(DIRECTORY2, first_directory)
|
| 65 |
+
if os.path.isfile(file_path1):
|
| 66 |
+
return file_path1
|
| 67 |
+
elif os.path.isfile(file_path2):
|
| 68 |
+
return file_path2
|
| 69 |
+
# If there's no match, use the default directory
|
| 70 |
+
return super().translate_path(path)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
if __name__ == "__main__":
|
| 74 |
+
with socketserver.TCPServer(("", PORT), MultiDirectoryHTTPRequestHandler) as httpd:
|
| 75 |
+
print(f"Serving at port {PORT}")
|
| 76 |
+
httpd.serve_forever()
|
src/upload_data_manually.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
from utils.prepare_vectordb import PrepareVectorDB
|
| 4 |
+
from utils.load_config import LoadConfig
|
| 5 |
+
CONFIG = LoadConfig()
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def upload_data_manually() -> None:
|
| 9 |
+
"""
|
| 10 |
+
Uploads data manually to the VectorDB.
|
| 11 |
+
|
| 12 |
+
This function initializes a PrepareVectorDB instance with configuration parameters
|
| 13 |
+
and chunk_overlap. It then checks if the VectorDB already exists in the specified
|
| 14 |
+
persist_directory. If not, it calls the prepare_and_save_vectordb method to
|
| 15 |
+
create and save the VectorDB. If the VectorDB already exists, a message is printed
|
| 16 |
+
indicating its presence.
|
| 17 |
+
|
| 18 |
+
Returns:
|
| 19 |
+
None
|
| 20 |
+
"""
|
| 21 |
+
prepare_vectordb_instance = PrepareVectorDB(
|
| 22 |
+
data_directory=CONFIG.data_directory,
|
| 23 |
+
persist_directory=CONFIG.persist_directory,
|
| 24 |
+
chunk_size=CONFIG.chunk_size,
|
| 25 |
+
chunk_overlap=CONFIG.chunk_overlap,
|
| 26 |
+
)
|
| 27 |
+
if not len(os.listdir(CONFIG.persist_directory)) != 0:
|
| 28 |
+
prepare_vectordb_instance.prepare_and_save_vectordb()
|
| 29 |
+
else:
|
| 30 |
+
print(f"VectorDB already exists in {CONFIG.persist_directory}")
|
| 31 |
+
return None
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
if __name__ == "__main__":
|
| 35 |
+
upload_data_manually()
|
src/utils/__pycache__/chatbot.cpython-39.pyc
ADDED
|
Binary file (5.05 kB). View file
|
|
|
src/utils/__pycache__/load_config.cpython-39.pyc
ADDED
|
Binary file (3.17 kB). View file
|
|
|
src/utils/__pycache__/prepare_vectordb.cpython-39.pyc
ADDED
|
Binary file (4.12 kB). View file
|
|
|
src/utils/__pycache__/ui_settings.cpython-39.pyc
ADDED
|
Binary file (1.4 kB). View file
|
|
|
src/utils/__pycache__/upload_file.cpython-39.pyc
ADDED
|
Binary file (1.75 kB). View file
|
|
|
src/utils/chatbot.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import time
|
| 3 |
+
import os
|
| 4 |
+
from langchain.vectorstores import Chroma
|
| 5 |
+
from typing import List, Tuple
|
| 6 |
+
import re
|
| 7 |
+
import ast
|
| 8 |
+
import html
|
| 9 |
+
from utils.load_config import LoadConfig
|
| 10 |
+
from langchain.embeddings import HuggingFaceEmbeddings
|
| 11 |
+
import requests
|
| 12 |
+
import torch
|
| 13 |
+
FLASK_APP_ENDPOINT = "http://127.0.0.1:8888/generate_text"
|
| 14 |
+
|
| 15 |
+
APPCFG = LoadConfig()
|
| 16 |
+
URL = ""
|
| 17 |
+
hyperlink = f"[RAG]({URL})"
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class ChatBot:
|
| 21 |
+
"""
|
| 22 |
+
Class representing a chatbot with document retrieval and response generation capabilities.
|
| 23 |
+
|
| 24 |
+
This class provides static methods for responding to user queries, handling feedback, and
|
| 25 |
+
cleaning references from retrieved documents.
|
| 26 |
+
"""
|
| 27 |
+
@staticmethod
|
| 28 |
+
def respond(chatbot: List,
|
| 29 |
+
message: str,
|
| 30 |
+
data_type: str = "Preprocessed doc",
|
| 31 |
+
temperature: float = 0.1,
|
| 32 |
+
top_k: int = 10,
|
| 33 |
+
top_p: float = 0.1) -> Tuple:
|
| 34 |
+
"""
|
| 35 |
+
Generate a response to a user query using document retrieval and language model completion.
|
| 36 |
+
|
| 37 |
+
Parameters:
|
| 38 |
+
chatbot (List): List representing the chatbot's conversation history.
|
| 39 |
+
message (str): The user's query.
|
| 40 |
+
data_type (str): Type of data used for document retrieval ("Preprocessed doc" or "Upload doc: Process for RAG").
|
| 41 |
+
temperature (float): Temperature parameter for language model completion.
|
| 42 |
+
|
| 43 |
+
Returns:
|
| 44 |
+
Tuple: A tuple containing an empty string, the updated chat history, and references from retrieved documents.
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
# Retrieve embedding function from code env resources
|
| 48 |
+
# emb_model = "sentence-transformers/all-MiniLM-L6-v2"
|
| 49 |
+
embedding_function = HuggingFaceEmbeddings(
|
| 50 |
+
model_name="NeuML/pubmedbert-base-embeddings",
|
| 51 |
+
# cache_folder=os.getenv('SENTENCE_TRANSFORMERS_HOME')
|
| 52 |
+
)
|
| 53 |
+
if data_type == "Preprocessed doc":
|
| 54 |
+
# directories
|
| 55 |
+
if os.path.exists(APPCFG.persist_directory):
|
| 56 |
+
vectordb = Chroma(persist_directory=APPCFG.persist_directory,
|
| 57 |
+
embedding_function=embedding_function)
|
| 58 |
+
else:
|
| 59 |
+
chatbot.append(
|
| 60 |
+
(message, f"VectorDB does not exist. Please first execute the 'upload_data_manually.py' module. For further information please visit {hyperlink}."))
|
| 61 |
+
return "", chatbot, None
|
| 62 |
+
|
| 63 |
+
elif data_type == "Upload doc: Process for RAG":
|
| 64 |
+
if os.path.exists(APPCFG.custom_persist_directory):
|
| 65 |
+
vectordb = Chroma(persist_directory=APPCFG.custom_persist_directory,
|
| 66 |
+
embedding_function=embedding_function)
|
| 67 |
+
else:
|
| 68 |
+
chatbot.append(
|
| 69 |
+
(message, f"No file was uploaded. Please first upload your files using the 'upload' button."))
|
| 70 |
+
return "", chatbot, None
|
| 71 |
+
|
| 72 |
+
docs = vectordb.similarity_search(message, k=APPCFG.k)
|
| 73 |
+
question = "# Prompt that you have to answer:\n" + message
|
| 74 |
+
retrieved_content, markdown_documents = ChatBot.clean_references(docs)
|
| 75 |
+
# Memory: previous two Q&A pairs
|
| 76 |
+
chat_history = f"Chat history:\n {str(chatbot[-APPCFG.number_of_q_a_pairs:])}\n\n"
|
| 77 |
+
if APPCFG.add_history:
|
| 78 |
+
prompt_wrapper = f"{APPCFG.llm_system_role_with_history}\n\n{chat_history}\n\n{retrieved_content}{question}"
|
| 79 |
+
else:
|
| 80 |
+
prompt_wrapper = f"{APPCFG.llm_system_role_without_history}\n\n{question}\n\n{retrieved_content}"
|
| 81 |
+
|
| 82 |
+
print("========================")
|
| 83 |
+
print(prompt_wrapper)
|
| 84 |
+
print("========================")
|
| 85 |
+
messages = [
|
| 86 |
+
{"role": "user", "content": prompt_wrapper},
|
| 87 |
+
]
|
| 88 |
+
data = {
|
| 89 |
+
"prompt": messages,
|
| 90 |
+
"max_new_tokens": APPCFG.max_new_tokens,
|
| 91 |
+
"do_sample": APPCFG.do_sample,
|
| 92 |
+
"temperature": temperature,
|
| 93 |
+
"top_k": top_k,
|
| 94 |
+
"top_p": top_p
|
| 95 |
+
}
|
| 96 |
+
response = requests.post(FLASK_APP_ENDPOINT, json=data)
|
| 97 |
+
# print(response.text)
|
| 98 |
+
response_json = response.json()
|
| 99 |
+
|
| 100 |
+
chatbot.append(
|
| 101 |
+
(message, response_json["response"]))
|
| 102 |
+
# Clean up GPU memory
|
| 103 |
+
del vectordb
|
| 104 |
+
del docs
|
| 105 |
+
torch.cuda.empty_cache()
|
| 106 |
+
return "", chatbot, markdown_documents
|
| 107 |
+
|
| 108 |
+
@staticmethod
|
| 109 |
+
def clean_references(documents: List) -> str:
|
| 110 |
+
"""
|
| 111 |
+
Clean and format references from retrieved documents.
|
| 112 |
+
|
| 113 |
+
Parameters:
|
| 114 |
+
documents (List): List of retrieved documents.
|
| 115 |
+
|
| 116 |
+
Returns:
|
| 117 |
+
str: A string containing cleaned and formatted references.
|
| 118 |
+
"""
|
| 119 |
+
server_url = "http://localhost:8000"
|
| 120 |
+
documents = [str(x)+"\n\n" for x in documents]
|
| 121 |
+
markdown_documents = ""
|
| 122 |
+
retrieved_content = ""
|
| 123 |
+
counter = 1
|
| 124 |
+
for doc in documents:
|
| 125 |
+
# Extract content and metadata
|
| 126 |
+
content, metadata = re.match(
|
| 127 |
+
r"page_content=(.*?)( metadata=\{.*\})", doc).groups()
|
| 128 |
+
metadata = metadata.split('=', 1)[1]
|
| 129 |
+
metadata_dict = ast.literal_eval(metadata)
|
| 130 |
+
|
| 131 |
+
# Decode newlines and other escape sequences
|
| 132 |
+
content = bytes(content, "utf-8").decode("unicode_escape")
|
| 133 |
+
|
| 134 |
+
# Replace escaped newlines with actual newlines
|
| 135 |
+
content = re.sub(r'\\n', '\n', content)
|
| 136 |
+
content = re.sub(r'\s*<EOS>\s*<pad>\s*', ' ', content)
|
| 137 |
+
content = re.sub(r'\s+', ' ', content).strip()
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
# Decode HTML entities
|
| 141 |
+
content = html.unescape(content)
|
| 142 |
+
|
| 143 |
+
# Replace incorrect unicode characters with correct ones
|
| 144 |
+
#content = content.encode('utf-8').decode('utf-8', 'ignore')
|
| 145 |
+
# Use UTF-8 encoding instead of latin-1 to avoid encoding issues
|
| 146 |
+
content = content.encode('utf-8', 'ignore').decode('utf-8', 'ignore')
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
# Remove or replace special characters and mathematical symbols
|
| 151 |
+
# This step may need to be customized based on the specific symbols in your documents
|
| 152 |
+
content = re.sub(r'â', '-', content)
|
| 153 |
+
content = re.sub(r'â', '∈', content)
|
| 154 |
+
content = re.sub(r'Ã', '×', content)
|
| 155 |
+
content = re.sub(r'ï¬', 'fi', content)
|
| 156 |
+
content = re.sub(r'â', '∈', content)
|
| 157 |
+
content = re.sub(r'·', '·', content)
|
| 158 |
+
content = re.sub(r'ï¬', 'fl', content)
|
| 159 |
+
|
| 160 |
+
pdf_url = f"{server_url}/{os.path.basename(metadata_dict['source'])}"
|
| 161 |
+
retrieved_content += f"# Content {counter}:\n" + \
|
| 162 |
+
content + "\n\n"
|
| 163 |
+
|
| 164 |
+
# Append cleaned content to the markdown string with two newlines between documents
|
| 165 |
+
markdown_documents += f"# Retrieved content {counter}:\n" + content + "\n\n" + \
|
| 166 |
+
f"Source: {os.path.basename(metadata_dict['source'])}" + " | " +\
|
| 167 |
+
f"Page number: {str(metadata_dict['page'])}" + " | " +\
|
| 168 |
+
f"[View PDF]({pdf_url})" "\n\n"
|
| 169 |
+
counter += 1
|
| 170 |
+
|
| 171 |
+
return retrieved_content, markdown_documents
|
src/utils/load_config.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
import yaml
|
| 5 |
+
from pyprojroot import here
|
| 6 |
+
import shutil
|
| 7 |
+
|
| 8 |
+
load_dotenv()
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class LoadConfig:
|
| 12 |
+
"""
|
| 13 |
+
A class for loading configuration settings and managing directories.
|
| 14 |
+
|
| 15 |
+
This class loads various configuration settings from the 'app_config.yml' file,
|
| 16 |
+
including language model (LLM) configurations, retrieval configurations, summarizer
|
| 17 |
+
configurations, and memory configurations. It also sets up OpenAI API credentials
|
| 18 |
+
and performs directory-related operations such as creating and removing directories.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
def __init__(self) -> None:
|
| 22 |
+
with open(here("configs/app_config.yml")) as cfg:
|
| 23 |
+
app_config = yaml.load(cfg, Loader=yaml.FullLoader)
|
| 24 |
+
|
| 25 |
+
# LLM configs
|
| 26 |
+
self.llm_engine = app_config["llm_config"]["engine"]
|
| 27 |
+
self.llm_system_role_with_history = app_config["llm_config"]["llm_system_role_with_history"]
|
| 28 |
+
self.llm_system_role_without_history = app_config[
|
| 29 |
+
"llm_config"]["llm_system_role_without_history"]
|
| 30 |
+
self.persist_directory = str(here(
|
| 31 |
+
app_config["directories"]["persist_directory"])) # needs to be strin for summation in chromadb backend: self._settings.require("persist_directory") + "/chroma.sqlite3"
|
| 32 |
+
self.custom_persist_directory = str(here(
|
| 33 |
+
app_config["directories"]["custom_persist_directory"]))
|
| 34 |
+
self.gemma_token = os.getenv("GEMMA_TOKEN")
|
| 35 |
+
self.device = app_config["llm_config"]["device"]
|
| 36 |
+
# Retrieval configs
|
| 37 |
+
self.data_directory = app_config["directories"]["data_directory"]
|
| 38 |
+
self.k = app_config["retrieval_config"]["k"]
|
| 39 |
+
self.chunk_size = int(app_config["splitter_config"]["chunk_size"])
|
| 40 |
+
self.chunk_overlap = int(
|
| 41 |
+
app_config["splitter_config"]["chunk_overlap"])
|
| 42 |
+
self.temperature = float(app_config["llm_config"]["temperature"])
|
| 43 |
+
self.add_history = bool(app_config["llm_config"]["add_history"])
|
| 44 |
+
self.top_k = int(app_config["llm_config"]["top_k"])
|
| 45 |
+
self.top_p = float(app_config["llm_config"]["top_p"])
|
| 46 |
+
self.max_new_tokens = int(app_config["llm_config"]["max_new_tokens"])
|
| 47 |
+
self.do_sample = bool(app_config["llm_config"]["do_sample"])
|
| 48 |
+
self.embedding_model = app_config["llm_config"]["embedding_model"]
|
| 49 |
+
|
| 50 |
+
# Memory
|
| 51 |
+
self.number_of_q_a_pairs = int(
|
| 52 |
+
app_config["memory"]["number_of_q_a_pairs"])
|
| 53 |
+
|
| 54 |
+
# clean up the upload doc vectordb if it exists
|
| 55 |
+
self.create_directory(self.persist_directory)
|
| 56 |
+
self.remove_directory(self.custom_persist_directory)
|
| 57 |
+
|
| 58 |
+
def create_directory(self, directory_path: str):
|
| 59 |
+
"""
|
| 60 |
+
Create a directory if it does not exist.
|
| 61 |
+
|
| 62 |
+
Parameters:
|
| 63 |
+
directory_path (str): The path of the directory to be created.
|
| 64 |
+
"""
|
| 65 |
+
if not os.path.exists(directory_path):
|
| 66 |
+
os.makedirs(directory_path)
|
| 67 |
+
|
| 68 |
+
def remove_directory(self, directory_path: str):
|
| 69 |
+
"""
|
| 70 |
+
Removes the specified directory.
|
| 71 |
+
|
| 72 |
+
Parameters:
|
| 73 |
+
directory_path (str): The path of the directory to be removed.
|
| 74 |
+
|
| 75 |
+
Raises:
|
| 76 |
+
OSError: If an error occurs during the directory removal process.
|
| 77 |
+
|
| 78 |
+
Returns:
|
| 79 |
+
None
|
| 80 |
+
"""
|
| 81 |
+
if os.path.exists(directory_path):
|
| 82 |
+
try:
|
| 83 |
+
shutil.rmtree(directory_path)
|
| 84 |
+
print(
|
| 85 |
+
f"The directory '{directory_path}' has been successfully removed.")
|
| 86 |
+
except OSError as e:
|
| 87 |
+
print(f"Error: {e}")
|
| 88 |
+
else:
|
| 89 |
+
print(f"The directory '{directory_path}' does not exist.")
|
src/utils/prepare_vectordb.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain.vectorstores import Chroma
|
| 2 |
+
from langchain.document_loaders import PyPDFLoader
|
| 3 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 4 |
+
import os
|
| 5 |
+
from typing import List
|
| 6 |
+
from langchain.embeddings import HuggingFaceEmbeddings
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class PrepareVectorDB:
|
| 10 |
+
"""
|
| 11 |
+
A class for preparing and saving a VectorDB using OpenAI embeddings.
|
| 12 |
+
|
| 13 |
+
This class facilitates the process of loading documents, chunking them, and creating a VectorDB
|
| 14 |
+
with OpenAI embeddings. It provides methods to prepare and save the VectorDB.
|
| 15 |
+
|
| 16 |
+
Parameters:
|
| 17 |
+
data_directory (str or List[str]): The directory or list of directories containing the documents.
|
| 18 |
+
persist_directory (str): The directory to save the VectorDB.
|
| 19 |
+
chunk_size (int): The size of the chunks for document processing.
|
| 20 |
+
chunk_overlap (int): The overlap between chunks.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
def __init__(
|
| 24 |
+
self,
|
| 25 |
+
data_directory: str,
|
| 26 |
+
persist_directory: str,
|
| 27 |
+
chunk_size: int,
|
| 28 |
+
chunk_overlap: int
|
| 29 |
+
) -> None:
|
| 30 |
+
"""
|
| 31 |
+
Initialize the PrepareVectorDB instance.
|
| 32 |
+
|
| 33 |
+
Parameters:
|
| 34 |
+
data_directory (str or List[str]): The directory or list of directories containing the documents.
|
| 35 |
+
persist_directory (str): The directory to save the VectorDB.
|
| 36 |
+
chunk_size (int): The size of the chunks for document processing.
|
| 37 |
+
chunk_overlap (int): The overlap between chunks.
|
| 38 |
+
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
self.text_splitter = RecursiveCharacterTextSplitter(
|
| 42 |
+
chunk_size=chunk_size,
|
| 43 |
+
chunk_overlap=chunk_overlap,
|
| 44 |
+
separators=["\n\n", "\n", " ", ""]
|
| 45 |
+
)
|
| 46 |
+
"""Other options: CharacterTextSplitter, TokenTextSplitter, etc."""
|
| 47 |
+
self.data_directory = data_directory
|
| 48 |
+
self.persist_directory = persist_directory
|
| 49 |
+
self.embedding_function = HuggingFaceEmbeddings(
|
| 50 |
+
model_name="NeuML/pubmedbert-base-embeddings",
|
| 51 |
+
# cache_folder=os.getenv('SENTENCE_TRANSFORMERS_HOME')
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
def __load_all_documents(self) -> List:
|
| 55 |
+
"""
|
| 56 |
+
Load all documents from the specified directory or directories.
|
| 57 |
+
|
| 58 |
+
Returns:
|
| 59 |
+
List: A list of loaded documents.
|
| 60 |
+
"""
|
| 61 |
+
doc_counter = 0
|
| 62 |
+
if isinstance(self.data_directory, list):
|
| 63 |
+
print("Loading the uploaded documents...")
|
| 64 |
+
docs = []
|
| 65 |
+
for doc_dir in self.data_directory:
|
| 66 |
+
docs.extend(PyPDFLoader(doc_dir).load())
|
| 67 |
+
doc_counter += 1
|
| 68 |
+
print("Number of loaded documents:", doc_counter)
|
| 69 |
+
print("Number of pages:", len(docs), "\n\n")
|
| 70 |
+
else:
|
| 71 |
+
print("Loading documents manually...")
|
| 72 |
+
document_list = os.listdir(self.data_directory)
|
| 73 |
+
docs = []
|
| 74 |
+
for doc_name in document_list:
|
| 75 |
+
docs.extend(PyPDFLoader(os.path.join(
|
| 76 |
+
self.data_directory, doc_name)).load())
|
| 77 |
+
doc_counter += 1
|
| 78 |
+
print("Number of loaded documents:", doc_counter)
|
| 79 |
+
print("Number of pages:", len(docs), "\n\n")
|
| 80 |
+
|
| 81 |
+
return docs
|
| 82 |
+
|
| 83 |
+
def __chunk_documents(self, docs: List) -> List:
|
| 84 |
+
"""
|
| 85 |
+
Chunk the loaded documents using the specified text splitter.
|
| 86 |
+
|
| 87 |
+
Parameters:
|
| 88 |
+
docs (List): The list of loaded documents.
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
List: A list of chunked documents.
|
| 92 |
+
|
| 93 |
+
"""
|
| 94 |
+
print("Chunking documents...")
|
| 95 |
+
chunked_documents = self.text_splitter.split_documents(docs)
|
| 96 |
+
print("Number of chunks:", len(chunked_documents), "\n\n")
|
| 97 |
+
return chunked_documents
|
| 98 |
+
|
| 99 |
+
def prepare_and_save_vectordb(self):
|
| 100 |
+
"""
|
| 101 |
+
Load, chunk, and create a VectorDB with OpenAI embeddings, and save it.
|
| 102 |
+
|
| 103 |
+
Returns:
|
| 104 |
+
Chroma: The created VectorDB.
|
| 105 |
+
"""
|
| 106 |
+
docs = self.__load_all_documents()
|
| 107 |
+
chunked_documents = self.__chunk_documents(docs)
|
| 108 |
+
print("Preparing vectordb...")
|
| 109 |
+
vectordb = Chroma.from_documents(
|
| 110 |
+
documents=chunked_documents,
|
| 111 |
+
embedding=self.embedding_function,
|
| 112 |
+
persist_directory=self.persist_directory
|
| 113 |
+
)
|
| 114 |
+
print("VectorDB is created and saved.")
|
| 115 |
+
print("Number of vectors in vectordb:",
|
| 116 |
+
vectordb._collection.count(), "\n\n")
|
| 117 |
+
return vectordb
|
src/utils/ui_settings.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class UISettings:
|
| 5 |
+
"""
|
| 6 |
+
Utility class for managing UI settings.
|
| 7 |
+
|
| 8 |
+
This class provides static methods for toggling UI components, such as a sidebar.
|
| 9 |
+
"""
|
| 10 |
+
@staticmethod
|
| 11 |
+
def toggle_sidebar(state):
|
| 12 |
+
"""
|
| 13 |
+
Toggle the visibility state of a UI component.
|
| 14 |
+
|
| 15 |
+
Parameters:
|
| 16 |
+
state: The current state of the UI component.
|
| 17 |
+
|
| 18 |
+
Returns:
|
| 19 |
+
Tuple: A tuple containing the updated UI component state and the new state.
|
| 20 |
+
"""
|
| 21 |
+
state = not state
|
| 22 |
+
return gr.update(visible=state), state
|
| 23 |
+
|
| 24 |
+
@staticmethod
|
| 25 |
+
def feedback(data: gr.LikeData):
|
| 26 |
+
"""
|
| 27 |
+
Process user feedback on the generated response.
|
| 28 |
+
|
| 29 |
+
Parameters:
|
| 30 |
+
data (gr.LikeData): Gradio LikeData object containing user feedback.
|
| 31 |
+
"""
|
| 32 |
+
if data.liked:
|
| 33 |
+
print("You upvoted this response: " + data.value)
|
| 34 |
+
else:
|
| 35 |
+
print("You downvoted this response: " + data.value)
|
src/utils/upload_file.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from utils.prepare_vectordb import PrepareVectorDB
|
| 2 |
+
from typing import List, Tuple
|
| 3 |
+
from utils.load_config import LoadConfig
|
| 4 |
+
|
| 5 |
+
APPCFG = LoadConfig()
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class UploadFile:
|
| 9 |
+
"""
|
| 10 |
+
Utility class for handling file uploads and processing.
|
| 11 |
+
|
| 12 |
+
This class provides static methods for checking directories and processing uploaded files
|
| 13 |
+
to prepare a VectorDB.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
@staticmethod
|
| 17 |
+
def process_uploaded_files(files_dir: List, chatbot: List, rag_with_dropdown: str) -> Tuple:
|
| 18 |
+
"""
|
| 19 |
+
Process uploaded files to prepare a VectorDB.
|
| 20 |
+
|
| 21 |
+
Parameters:
|
| 22 |
+
files_dir (List): List of paths to the uploaded files.
|
| 23 |
+
chatbot: An instance of the chatbot for communication.
|
| 24 |
+
|
| 25 |
+
Returns:
|
| 26 |
+
Tuple: A tuple containing an empty string and the updated chatbot instance.
|
| 27 |
+
"""
|
| 28 |
+
if rag_with_dropdown == "Upload doc: Process for RAG":
|
| 29 |
+
prepare_vectordb_instance = PrepareVectorDB(data_directory=files_dir,
|
| 30 |
+
persist_directory=APPCFG.custom_persist_directory,
|
| 31 |
+
chunk_size=APPCFG.chunk_size,
|
| 32 |
+
chunk_overlap=APPCFG.chunk_overlap)
|
| 33 |
+
prepare_vectordb_instance.prepare_and_save_vectordb()
|
| 34 |
+
chatbot.append(
|
| 35 |
+
(" ", "Uploaded files are ready. Please ask your question"))
|
| 36 |
+
else:
|
| 37 |
+
chatbot.append(
|
| 38 |
+
(" ", "If you would like to upload a PDF, please select your desired action in 'rag_with' dropdown."))
|
| 39 |
+
return "", chatbot
|