From 8921deb6ffb0e36350760605798e2362bc6852cc Mon Sep 17 00:00:00 2001 From: alxspiker Date: Fri, 12 May 2023 23:44:02 -0600 Subject: [PATCH 1/8] GUI SUPPORT First working edition! Ill continue adding to this PR to make it more complete. I want to see if I can get an ingest page to ingest using the GUI too. Run `streamlit run .\gui.py` to use it. --- gui.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 +++ startLLM.py | 7 ++++-- 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 gui.py diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..f6253a1 --- /dev/null +++ b/gui.py @@ -0,0 +1,63 @@ +import streamlit as st +from streamlit_chat import message +from streamlit_extras.colored_header import colored_header +from streamlit_extras.add_vertical_space import add_vertical_space + +st.set_page_config(page_title="CASALIOY") + +# Sidebar contents +with st.sidebar: + st.title('CASALIOY') + st.markdown(''' + ## About + This app is an LLM-powered chatbot built using: + - [Streamlit](https://streamlit.io/) + - [su77ungr/CASALIOY](https://github.com/alxspiker/CASALIOY) LLM Toolkit + + 💡 Note: No API key required! + + GUI does not support live response yet, so you have to wait for the tokens to process. + ''') + add_vertical_space(5) + st.write('Made with ❤️ by [su77ungr/CASALIOY](https://github.com/alxspiker/CASALIOY)') + +# Generate empty lists for generated and past. +## generated stores AI generated responses +if 'generated' not in st.session_state: + st.session_state['generated'] = ["I can help you answer questions about the documents you have ingested into the vector store."] +## past stores User's questions +if 'past' not in st.session_state: + st.session_state['past'] = ['Hi, what can you help me with!'] + +# Layout of input/response containers +input_container = st.container() +colored_header(label='', description='', color_name='blue-30') +response_container = st.container() + +# User input +## Function for taking user provided prompt as input +def get_text(): + input_text = st.text_input("You: ", "", key="input") + return input_text +## Applying the user input box +with input_container: + user_input = get_text() + +# Response output +## Function for taking user prompt as input followed by producing AI generated responses +def generate_response(prompt): + import startLLM + response = startLLM.main(prompt, True) + return response + +## Conditional display of AI generated responses as a function of user provided prompts +with response_container: + if user_input: + response = generate_response(user_input) + st.session_state.past.append(user_input) + st.session_state.generated.append(response) + + if st.session_state['generated']: + for i in range(len(st.session_state['generated'])): + message(st.session_state['past'][i], is_user=True, key=str(i) + '_user') + message(st.session_state["generated"][i], key=str(i)) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 344b43a..96f6850 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,6 @@ qdrant-client==1.1.7 llama-cpp-python==0.1.49 pdfminer.six==20221105 python-dotenv==1.0.0 +steamlit +streamlit-chat +streamlit-extras \ No newline at end of file diff --git a/startLLM.py b/startLLM.py index c71dee5..dfb6001 100644 --- a/startLLM.py +++ b/startLLM.py @@ -15,7 +15,7 @@ model_temp = os.environ.get('MODEL_TEMP') model_stop = os.environ.get('MODEL_STOP').split(",") -def main(): +def main(prompt="", gui=False): # Load stored vectorstore llama = LlamaCppEmbeddings(model_path=llama_embeddings_model, n_ctx=model_n_ctx) # Load ggml-formatted model @@ -44,7 +44,7 @@ def main(): # Interactive questions and answers while True: - query = input("\nEnter a query: ") + query = prompt if prompt.strip() != "" else input("\nEnter a query: ") if query == "exit": break @@ -62,6 +62,9 @@ def main(): for document in docs: print("\n> " + document.metadata["source"] + ":") print(document.page_content) + + if gui: + return answer if __name__ == "__main__": main() From 8f40781eb9879c007e75e2988f62be9d05077e7c Mon Sep 17 00:00:00 2001 From: alxspiker Date: Sat, 13 May 2023 00:47:26 -0600 Subject: [PATCH 2/8] Preload models only once instead of every query. Text input doesnt disable like I tried to do after a query. --- gui.py | 19 +++++++++++++++--- startLLM.py | 56 ++++++++++++++++++++++++++++++++--------------------- 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/gui.py b/gui.py index f6253a1..0e45ca5 100644 --- a/gui.py +++ b/gui.py @@ -2,8 +2,10 @@ from streamlit_chat import message from streamlit_extras.colored_header import colored_header from streamlit_extras.add_vertical_space import add_vertical_space +from startLLM import main as startLLM st.set_page_config(page_title="CASALIOY") +input_text = None # Sidebar contents with st.sidebar: @@ -34,10 +36,20 @@ colored_header(label='', description='', color_name='blue-30') response_container = st.container() +if "disabled" not in st.session_state: + st.session_state["disabled"] = False + +def enable(): + st.session_state["disabled"] = False + +def disable(): + st.session_state["disabled"] = True + # User input ## Function for taking user provided prompt as input def get_text(): - input_text = st.text_input("You: ", "", key="input") + global input_text + input_text = st.text_input("You: ", "", key="input", disabled=st.session_state["disabled"]) return input_text ## Applying the user input box with input_container: @@ -46,8 +58,9 @@ def get_text(): # Response output ## Function for taking user prompt as input followed by producing AI generated responses def generate_response(prompt): - import startLLM - response = startLLM.main(prompt, True) + #input_text.disabled = True + disable() + response = startLLM(prompt, True) return response ## Conditional display of AI generated responses as a function of user provided prompts diff --git a/startLLM.py b/startLLM.py index dfb6001..4a36e7b 100644 --- a/startLLM.py +++ b/startLLM.py @@ -15,7 +15,11 @@ model_temp = os.environ.get('MODEL_TEMP') model_stop = os.environ.get('MODEL_STOP').split(",") -def main(prompt="", gui=False): +qa_system=None +llm=None +qdrant=None + +def initialize_qa_system(): # Load stored vectorstore llama = LlamaCppEmbeddings(model_path=llama_embeddings_model, n_ctx=model_n_ctx) # Load ggml-formatted model @@ -24,6 +28,7 @@ def main(prompt="", gui=False): client = qdrant_client.QdrantClient( path=persist_directory, prefer_grpc=True ) + global qdrant qdrant = Qdrant( client=client, collection_name="test", embeddings=llama @@ -31,6 +36,7 @@ def main(prompt="", gui=False): # Prepare the LLM chain callbacks = [StreamingStdOutCallbackHandler()] + global llm match model_type: case "LlamaCpp": from langchain.llms import LlamaCpp @@ -41,30 +47,36 @@ def main(prompt="", gui=False): case _default: print("Only LlamaCpp or GPT4All supported right now. Make sure you set up your .env correctly.") qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=qdrant.as_retriever(search_type="mmr"), return_source_documents=True) + return qa +def main(prompt="", gui=False): + global qa_system + if qa_system is None: + qa_system = initialize_qa_system() # Interactive questions and answers - while True: - query = prompt if prompt.strip() != "" else input("\nEnter a query: ") - if query == "exit": - break - - # Get the answer from the chain - res = qa(query) - answer, docs = res['result'], res['source_documents'] + if prompt.strip() != "": + while True: + query = prompt if prompt.strip() != "" else input("\nEnter a query: ") + if query == "exit": + break + + # Get the answer from the chain + res = qa_system(query) + answer, docs = res['result'], res['source_documents'] - # Print the result - print("\n\n> Question:") - print(query) - print("\n> Answer:") - print(answer) - - # Print the relevant sources used for the answer - for document in docs: - print("\n> " + document.metadata["source"] + ":") - print(document.page_content) - - if gui: - return answer + # Print the result + print("\n\n> Question:") + print(query) + print("\n> Answer:") + print(answer) + + # Print the relevant sources used for the answer + for document in docs: + print("\n> " + document.metadata["source"] + ":") + print(document.page_content) + + if gui: + return answer if __name__ == "__main__": main() From d5499e6d30b501e600786e6daea25859b0d573ea Mon Sep 17 00:00:00 2001 From: alxspiker Date: Sat, 13 May 2023 13:21:37 -0600 Subject: [PATCH 3/8] Preload models + better interface Still needs some work. --- gui.py | 59 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/gui.py b/gui.py index 0e45ca5..250170b 100644 --- a/gui.py +++ b/gui.py @@ -4,8 +4,12 @@ from streamlit_extras.add_vertical_space import add_vertical_space from startLLM import main as startLLM +# Initialization +if "visibility" not in st.session_state: + st.session_state.visibility = "visible" + st.session_state.running = False + st.set_page_config(page_title="CASALIOY") -input_text = None # Sidebar contents with st.sidebar: @@ -36,41 +40,36 @@ colored_header(label='', description='', color_name='blue-30') response_container = st.container() -if "disabled" not in st.session_state: - st.session_state["disabled"] = False - -def enable(): - st.session_state["disabled"] = False - -def disable(): - st.session_state["disabled"] = True - # User input ## Function for taking user provided prompt as input -def get_text(): - global input_text - input_text = st.text_input("You: ", "", key="input", disabled=st.session_state["disabled"]) - return input_text +#def get_text(): + #return input_text + ## Applying the user input box -with input_container: - user_input = get_text() +#with input_container: + #user_input = get_text() # Response output ## Function for taking user prompt as input followed by producing AI generated responses def generate_response(prompt): - #input_text.disabled = True - disable() - response = startLLM(prompt, True) - return response + if prompt.strip() != "": + st.session_state.running = True + response = startLLM(prompt, True) + st.session_state.running = False + st.session_state.past.append(prompt) + st.session_state.generated.append(response) + return response +with st.form("my_form", clear_on_submit=True): ## Conditional display of AI generated responses as a function of user provided prompts -with response_container: - if user_input: - response = generate_response(user_input) - st.session_state.past.append(user_input) - st.session_state.generated.append(response) - - if st.session_state['generated']: - for i in range(len(st.session_state['generated'])): - message(st.session_state['past'][i], is_user=True, key=str(i) + '_user') - message(st.session_state["generated"][i], key=str(i)) \ No newline at end of file + user_input = st.text_input("You: ", "", key="input", disabled=st.session_state.running) + st.form_submit_button('SUBMIT', on_click=generate_response(user_input), disabled=st.session_state.running) +#if user_input: + #response = generate_response(user_input) + #st.session_state.past.append(user_input) + #st.session_state.generated.append(response) + +if st.session_state['generated']: + for i in range(len(st.session_state['generated'])): + message(st.session_state['past'][i], is_user=True, key=str(i) + '_user') + message(st.session_state["generated"][i], key=str(i)) \ No newline at end of file From 004cc797f544cb3383f225b2092d8a05410a98b7 Mon Sep 17 00:00:00 2001 From: su77ungr <69374354+su77ungr@users.noreply.github.com> Date: Sat, 13 May 2023 22:03:42 +0200 Subject: [PATCH 4/8] specified version of streamlit else None --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 50254b2..3734f00 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,8 +4,8 @@ qdrant-client==1.1.7 llama-cpp-python==0.1.49 pdfminer.six==20221105 python-dotenv==1.0.0 -steamlit -streamlit-chat -streamlit-extras +streamlit==1.22.0 +streamlit-chat==0.0.2.2 +streamlit-extras==0.2.7 pandoc==2.3 unstructured==0.6.6 From c8bbe2b40f22c0b61fb98d1946a5850c58624c88 Mon Sep 17 00:00:00 2001 From: alxspiker Date: Sat, 13 May 2023 14:50:02 -0600 Subject: [PATCH 5/8] Working version: Added loading message and improved UI Still need to do cleanup, but should perform properly. --- gui.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/gui.py b/gui.py index 250170b..e6b451e 100644 --- a/gui.py +++ b/gui.py @@ -5,8 +5,8 @@ from startLLM import main as startLLM # Initialization -if "visibility" not in st.session_state: - st.session_state.visibility = "visible" +if "input" not in st.session_state: + st.session_state.input = "" st.session_state.running = False st.set_page_config(page_title="CASALIOY") @@ -52,24 +52,29 @@ # Response output ## Function for taking user prompt as input followed by producing AI generated responses def generate_response(prompt): - if prompt.strip() != "": + if st.session_state['generated']: + for i in range(len(st.session_state['generated'])): + message(st.session_state["past"][i], is_user=True, key=str(i) + '_user') + message(st.session_state["generated"][i], key=str(i)) + #if prompt.strip() != "": + + if st.session_state.running==False and prompt.strip() != "": st.session_state.running = True - response = startLLM(prompt, True) - st.session_state.running = False st.session_state.past.append(prompt) + message(prompt, is_user=True) + message("Loading response. Please wait...", key="rmessage") + response = startLLM(prompt, True) st.session_state.generated.append(response) - return response - -with st.form("my_form", clear_on_submit=True): -## Conditional display of AI generated responses as a function of user provided prompts - user_input = st.text_input("You: ", "", key="input", disabled=st.session_state.running) - st.form_submit_button('SUBMIT', on_click=generate_response(user_input), disabled=st.session_state.running) + message(response) + st.session_state.running = False + #st.session_state.rmessage = response + #return response + st.text_input("You: ", "", key="input", disabled=st.session_state.running) #if user_input: #response = generate_response(user_input) #st.session_state.past.append(user_input) #st.session_state.generated.append(response) - -if st.session_state['generated']: - for i in range(len(st.session_state['generated'])): - message(st.session_state['past'][i], is_user=True, key=str(i) + '_user') - message(st.session_state["generated"][i], key=str(i)) \ No newline at end of file +with st.container(): + with st.form("my_form", clear_on_submit=True): + ## Conditional display of AI generated responses as a function of user provided prompts + st.form_submit_button('SUBMIT', on_click=generate_response(st.session_state.input), disabled=st.session_state.running) \ No newline at end of file From 4a146ce5b03041b53260e61b9e32edab246e05e3 Mon Sep 17 00:00:00 2001 From: alxspiker Date: Sat, 13 May 2023 14:53:58 -0600 Subject: [PATCH 6/8] Code cleanup for commit --- gui.py | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/gui.py b/gui.py index e6b451e..9807cd1 100644 --- a/gui.py +++ b/gui.py @@ -27,37 +27,21 @@ add_vertical_space(5) st.write('Made with ❤️ by [su77ungr/CASALIOY](https://github.com/alxspiker/CASALIOY)') -# Generate empty lists for generated and past. -## generated stores AI generated responses if 'generated' not in st.session_state: st.session_state['generated'] = ["I can help you answer questions about the documents you have ingested into the vector store."] -## past stores User's questions + if 'past' not in st.session_state: st.session_state['past'] = ['Hi, what can you help me with!'] -# Layout of input/response containers input_container = st.container() colored_header(label='', description='', color_name='blue-30') response_container = st.container() -# User input -## Function for taking user provided prompt as input -#def get_text(): - #return input_text - -## Applying the user input box -#with input_container: - #user_input = get_text() - -# Response output -## Function for taking user prompt as input followed by producing AI generated responses def generate_response(prompt): if st.session_state['generated']: for i in range(len(st.session_state['generated'])): message(st.session_state["past"][i], is_user=True, key=str(i) + '_user') message(st.session_state["generated"][i], key=str(i)) - #if prompt.strip() != "": - if st.session_state.running==False and prompt.strip() != "": st.session_state.running = True st.session_state.past.append(prompt) @@ -67,14 +51,8 @@ def generate_response(prompt): st.session_state.generated.append(response) message(response) st.session_state.running = False - #st.session_state.rmessage = response - #return response st.text_input("You: ", "", key="input", disabled=st.session_state.running) -#if user_input: - #response = generate_response(user_input) - #st.session_state.past.append(user_input) - #st.session_state.generated.append(response) + with st.container(): with st.form("my_form", clear_on_submit=True): - ## Conditional display of AI generated responses as a function of user provided prompts st.form_submit_button('SUBMIT', on_click=generate_response(st.session_state.input), disabled=st.session_state.running) \ No newline at end of file From 98f38841cbee99403c4d3f2d673233a255fc8585 Mon Sep 17 00:00:00 2001 From: alxspiker Date: Sat, 13 May 2023 15:34:30 -0600 Subject: [PATCH 7/8] Faster responses + GUI | Run: streamlit run .\gui.py --- gui.py | 7 +++++-- startLLM.py | 8 ++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/gui.py b/gui.py index 9807cd1..92a9452 100644 --- a/gui.py +++ b/gui.py @@ -2,7 +2,7 @@ from streamlit_chat import message from streamlit_extras.colored_header import colored_header from streamlit_extras.add_vertical_space import add_vertical_space -from startLLM import main as startLLM +import startLLM # Initialization if "input" not in st.session_state: @@ -21,6 +21,8 @@ - [su77ungr/CASALIOY](https://github.com/alxspiker/CASALIOY) LLM Toolkit 💡 Note: No API key required! + Refreshing the page will restart gui.py with a fresh chat history. + CASALIOY will not remember previous questions as of yet. GUI does not support live response yet, so you have to wait for the tokens to process. ''') @@ -47,7 +49,8 @@ def generate_response(prompt): st.session_state.past.append(prompt) message(prompt, is_user=True) message("Loading response. Please wait...", key="rmessage") - response = startLLM(prompt, True) + response = startLLM.main(prompt, True) + #startLLM.qdrant = None st.session_state.generated.append(response) message(response) st.session_state.running = False diff --git a/startLLM.py b/startLLM.py index 4a36e7b..aabfc81 100644 --- a/startLLM.py +++ b/startLLM.py @@ -16,8 +16,6 @@ model_stop = os.environ.get('MODEL_STOP').split(",") qa_system=None -llm=None -qdrant=None def initialize_qa_system(): # Load stored vectorstore @@ -28,7 +26,6 @@ def initialize_qa_system(): client = qdrant_client.QdrantClient( path=persist_directory, prefer_grpc=True ) - global qdrant qdrant = Qdrant( client=client, collection_name="test", embeddings=llama @@ -36,7 +33,6 @@ def initialize_qa_system(): # Prepare the LLM chain callbacks = [StreamingStdOutCallbackHandler()] - global llm match model_type: case "LlamaCpp": from langchain.llms import LlamaCpp @@ -54,9 +50,9 @@ def main(prompt="", gui=False): if qa_system is None: qa_system = initialize_qa_system() # Interactive questions and answers - if prompt.strip() != "": + if (prompt.strip() != "" and gui) or gui==False: while True: - query = prompt if prompt.strip() != "" else input("\nEnter a query: ") + query = prompt if gui else input("\nEnter a query: ") if query == "exit": break From 3fa8687efb148a090ea1bfe924c03643986e0d9b Mon Sep 17 00:00:00 2001 From: alxspiker Date: Sat, 13 May 2023 17:49:37 -0600 Subject: [PATCH 8/8] Edit .env directly in GUI --- gui.py | 72 +++++++++++++++++++++++++++++++++++++---------------- startLLM.py | 4 +-- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/gui.py b/gui.py index 92a9452..fd3e2a0 100644 --- a/gui.py +++ b/gui.py @@ -1,8 +1,20 @@ +import dotenv import streamlit as st from streamlit_chat import message from streamlit_extras.colored_header import colored_header from streamlit_extras.add_vertical_space import add_vertical_space import startLLM +import os + +dotenv_file = dotenv.find_dotenv(".env") +dotenv.load_dotenv() +llama_embeddings_model = os.environ.get("LLAMA_EMBEDDINGS_MODEL") +persist_directory = os.environ.get('PERSIST_DIRECTORY') +model_type = os.environ.get('MODEL_TYPE') +model_path = os.environ.get('MODEL_PATH') +model_n_ctx = int(os.environ.get('MODEL_N_CTX')) +model_temp = float(os.environ.get('MODEL_TEMP')) +model_stop = os.environ.get('MODEL_STOP') # Initialization if "input" not in st.session_state: @@ -35,27 +47,45 @@ if 'past' not in st.session_state: st.session_state['past'] = ['Hi, what can you help me with!'] -input_container = st.container() colored_header(label='', description='', color_name='blue-30') response_container = st.container() -def generate_response(prompt): - if st.session_state['generated']: - for i in range(len(st.session_state['generated'])): - message(st.session_state["past"][i], is_user=True, key=str(i) + '_user') - message(st.session_state["generated"][i], key=str(i)) - if st.session_state.running==False and prompt.strip() != "": - st.session_state.running = True - st.session_state.past.append(prompt) - message(prompt, is_user=True) - message("Loading response. Please wait...", key="rmessage") - response = startLLM.main(prompt, True) - #startLLM.qdrant = None - st.session_state.generated.append(response) - message(response) - st.session_state.running = False - st.text_input("You: ", "", key="input", disabled=st.session_state.running) - -with st.container(): - with st.form("my_form", clear_on_submit=True): - st.form_submit_button('SUBMIT', on_click=generate_response(st.session_state.input), disabled=st.session_state.running) \ No newline at end of file + + +def generate_response(input=""): + with response_container: + col1, col2, col3 = st.columns(3) + with col1: + if st.number_input('Temperature', key="temp_input", value=float(model_temp), step=float(0.05), min_value=float(0), max_value=float(1)): + os.environ["MODEL_TEMP"] = str(st.session_state.temp_input) + dotenv.set_key(dotenv_file, "MODEL_TEMP", os.environ["MODEL_TEMP"]) + with col2: + if st.number_input('Context', key="ctx_input", value=int(model_n_ctx), step=int(512), min_value=int(512), max_value=int(9000)): + os.environ["MODEL_N_CTX"] = str(st.session_state.ctx_input) + dotenv.set_key(dotenv_file, "MODEL_N_CTX", os.environ["MODEL_N_CTX"]) + with col3: + if st.text_input('Stops', key="stops_input", value=str(model_stop)): + os.environ["MODEL_STOP"] = str(st.session_state.stops_input) + dotenv.set_key(dotenv_file, "MODEL_STOP", os.environ["MODEL_STOP"]) + #with st.form("my_form", clear_on_submit=True): + if st.session_state['generated']: + for i in range(len(st.session_state['generated'])): + message(st.session_state["past"][i], is_user=True, key=str(i) + '_user') + message(st.session_state["generated"][i], key=str(i)) + if input.strip() != "": + st.session_state.running=True + st.session_state.past.append(st.session_state.input) + if st.session_state.running: + message(st.session_state.input, is_user=True) + message("Loading response. Please wait for me to finish before refreshing the page...", key="rmessage") + #startLLM.qdrant = None #Not sure why this fixes db error + response = startLLM.main(st.session_state.input, True) + st.session_state.generated.append(response) + message(response) + st.session_state.running = False + st.text_input("You: ", "", key="input", disabled=st.session_state.running) + + +with st.form("my_form", clear_on_submit=True): + st.form_submit_button('SUBMIT', on_click=generate_response(st.session_state.input), disabled=st.session_state.running) + \ No newline at end of file diff --git a/startLLM.py b/startLLM.py index aabfc81..503a1b2 100644 --- a/startLLM.py +++ b/startLLM.py @@ -11,8 +11,8 @@ persist_directory = os.environ.get('PERSIST_DIRECTORY') model_type = os.environ.get('MODEL_TYPE') model_path = os.environ.get('MODEL_PATH') -model_n_ctx = os.environ.get('MODEL_N_CTX') -model_temp = os.environ.get('MODEL_TEMP') +model_n_ctx = int(os.environ.get('MODEL_N_CTX')) +model_temp = float(os.environ.get('MODEL_TEMP')) model_stop = os.environ.get('MODEL_STOP').split(",") qa_system=None