import streamlit as st from openai import OpenAI import glob import time import pickle from langchain_community.vectorstores import Chroma from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langchain_text_splitters import RecursiveCharacterTextSplitter, CharacterTextSplitter from langchain.callbacks import get_openai_callback from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnableParallel from langchain import VectorDBQAWithSourcesChain from langchain.chains import RetrievalQA import json from documents import read_documents_from_file, create_documents, store_documents, create_faq_documents, html_to_chunks #store_documents(html_to_chunks(), path="./docs/langchain_semantic_documents.json") #store_documents(create_documents()) #create_faq_documents() OPENAI_API_KEY = st.secrets["OPENAI_API_KEY"] #vectorstore = Chroma(persist_directory=directory, embedding_function=OpenAIEmbeddings()) st.set_page_config(initial_sidebar_state="collapsed") data_source = st.radio("Data source", options=['FAQ', 'Blog articles']) if data_source == 'FAQ': docs=read_documents_from_file("./docs/faq_docs.json") def_model = "gpt-3.5-turbo" def_temperature = 0.0 def_k = 1 def_chunk_size = 500 def_chunk_overlap = 0 directory = "./chroma_db" elif data_source == 'Blog articles': docs=read_documents_from_file("./docs/langchain_semantic_documents.json") def_model = "gpt-3.5-turbo" def_temperature = 0.0 def_k = 3 def_chunk_size = 500 def_chunk_overlap = 0 directory = "./chroma_db" with st.sidebar: if st.toggle("Experimental"): disabled = False else: disabled = True model = def_model temperature = st.number_input("Temperature", value=def_temperature, min_value=0.0, step=0.2, max_value=1.0, placeholder=def_temperature, disabled=disabled) k = st.number_input("Number of documents to include", value=def_k, min_value=1, step=1, placeholder=def_k, disabled=disabled) if st.toggle("Splitting", value=True, disabled=disabled): chunk_size = st.number_input("Chunk size", value=def_chunk_size, step=250, placeholder=def_chunk_size, disabled=disabled) # Defines the chunks in amount of tokens in which the files are split. Also defines the amount of tokens that are feeded into the context. chunk_overlap = st.number_input("Chunk overlap", value=def_chunk_overlap, step=10, placeholder=def_chunk_overlap, disabled=disabled) text_splitter = RecursiveCharacterTextSplitter( chunk_size=chunk_size, chunk_overlap=chunk_overlap, separators=[ "\n\n", "\n", " ", ". " ] ) splits = text_splitter.split_documents(docs) vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings()) if chunk_size != def_chunk_size | chunk_overlap != def_chunk_overlap: splits = text_splitter.split_documents(docs) vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings()) print("Created new vectordb for this session.") else: vectorstore = Chroma.from_documents(documents=docs, embedding=OpenAIEmbeddings()) print("Used vectordb with all blog articles.") if data_source == 'FAQ': prompt_template =""" Du bist Volker. Du beantwortest häufig gestellte Fragen. Beantworte nur die Fragen zu denen du einen Kontext hast. Wenn du die Antwort nicht weißt, sag dass du es nicht weißt. Antworte immer in ganzen Sätzen und verwende korrekte Grammatik und Rechtschreibung. Antworte nur auf Deutsch. Antworte kurz und nur im Sinne des vorhandenen Kontext. Frage: {question} Kontext: {context} Antwort: """ else: prompt_template =""" ### Wenn du gefragt wirst wer du bist: Du heißt "Volker". Du bist ein Assistent für die Beantwortung von Fragen zu Long-Covid (Post-Covid synonym verwenden). Deine Quellen sind Blogartikel von Fimo Health. ### Du weißt nichts über Krankheiten wie 'tumorbedingte Fatigue', 'Multiple Sklerose', 'Hashimoto-Thyreoditis' oder 'Krebs'. Werden Fragen zu diesen Erkrankungen gestellt, beantworte sie mit "Dazu fehlen mir Informationen". ### Du beantwortest keine Fragen zu 'Tod', 'Suizid', 'Selbstverletzung', oder anderen potenziell schädigenden Themen. Werden Fragen zum 'Tod' gestellt, verweise auf den behandelnden Arzt. Bei Fragen zu Suizid verweise auf die Telefonseelsorge: 0800 1110111 ### Du gibst keine Ratschläge zur Diagnose, Behandlung oder Therapie. Wenn du die Antwort nicht weißt oder du keinen Kontext hast, sage dass du es nicht weißt. Wenn du allgemeine unspezifische Fragen gestellt bekommst, antworte, dass du die Frage nicht verstehst und frage nach einer präziseren Fragestellung. Antworte immer in ganzen Sätzen und verwende korrekte Grammatik und Rechtschreibung. Antworte nur auf Deutsch. Antworte kurz mit maximal fünf Sätzen außer es wird von dir eine ausführlichere Antwort verlangt. Verwende zur Beantwortung der Frage nur den vorhandenen Kontext. Frage: {question} Kontext: {context} Antwort: """ # Source: hub.pull("rlm/rag-prompt") # (1) Retriever retriever = vectorstore.as_retriever(search_type="similarity_score_threshold", search_kwargs={"score_threshold": 0.75, "k": k}) # (2) Prompt prompt = ChatPromptTemplate.from_template(prompt_template) # (3) LLM # Define the LLM we want to use. Default is "gpt-3.5-turbo" with temperature 0. # Temperature is a number between 0 and 1. With 0.8 it generates more random answers, with 0.2 it is more focused on the retrieved content. With temperature = 0 it uses log-probabilities depending on the content. llm = ChatOpenAI(model_name=model, temperature=temperature) def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) rag_chain_from_docs = ( RunnablePassthrough.assign(context=(lambda x: format_docs(x["context"]))) | prompt | llm | StrOutputParser() ) rag_chain = RunnableParallel( {"context": retriever, "question": RunnablePassthrough()} ).assign(answer=rag_chain_from_docs) st.title("🐔 Volker-Chat") def click_button(prompt): st.session_state.clicked = True st.session_state['prompt'] = prompt c = st.container() c.write("Beispielfragen") if data_source == 'Blog articles': examples = ['Was ist Pacing?', 'Wie funktioniert die Wiedereingliederung?', 'Sollte ich eine Reha machen?'] for i, col in enumerate(c.columns(len(examples))): question = examples[i] col.button(question, on_click=click_button, args=[question]) elif data_source == 'FAQ': examples = ['Wie komme ich an meinen PDF-Report?', 'Wer steckt hinter den Kurs-Inhalten?', 'Wozu dient der Check-Out?'] for i, col in enumerate(c.columns(len(examples))): question = examples[i] col.button(question, on_click=click_button, args=[question]) if 'clicked' not in st.session_state: st.session_state.clicked = False if "messages" not in st.session_state: st.session_state["messages"] = [{"role": "assistant", "content": "Ahoi! Ich bin Volker. Wie kann ich dir helfen?"}] for msg in st.session_state.messages: st.chat_message(msg["role"]).write(msg["content"]) # Streamed response emulator def response_generator(response): for word in response.split(): yield word + " " time.sleep(0.05) if st.session_state.clicked: prompt = st.session_state['prompt'] st.chat_message("user").write(prompt) with get_openai_callback() as cb: response = rag_chain.invoke(prompt) print(response) if response['context'] != []: response_stream = response_generator(response['answer']) st.chat_message("assistant").write_stream(response_stream) else: response_stream = response_generator("Dazu kann ich dir leider keine Antwort geben. Bitte versuche eine andere Frage.") st.chat_message("assistant").write_stream(response_stream) with st.expander("Kontext ansehen"): if len(response['context'][0].page_content) > 50: for i, citation in enumerate(response["context"]): print(citation.metadata) st.write(f"[{i+1}] ", str(citation.page_content)) st.write(str(citation.metadata['source'])) section = "" if (len(list(citation.metadata.values())) > 1) & (data_source=='Blog articles'): for chapter in list(citation.metadata.values())[:-1]: section += f"{chapter} " st.write(f"Abschnitt: '{section}'") st.write(str("---")*20) with st.sidebar: sidebar_c = st.container() sidebar_c.success(cb) if prompt := st.chat_input(): st.chat_message("user").write(prompt) with get_openai_callback() as cb: response = rag_chain.invoke(prompt) if response['context'] != []: response_stream = response_generator(response['answer']) st.chat_message("assistant").write_stream(response_stream) else: response_stream = response_generator("Dazu kann ich dir leider keine Antwort geben. Bitte versuche eine andere Frage.") st.chat_message("assistant").write_stream(response_stream) with st.expander("Kontext ansehen"): if len(response['context'][0].page_content) > 50: for i, citation in enumerate(response["context"]): print(citation.metadata) st.write(f"[{i+1}] ", str(citation.page_content)) st.write(str(citation.metadata['source'])) section = "" if (len(list(citation.metadata.values())) > 1) & (data_source=='Blog articles'): for chapter in list(citation.metadata.values())[:-1]: section += f"{chapter} " st.write(f"Abschnitt: '{section}'") st.write(str("---")*20) with st.sidebar: sidebar_c = st.container() sidebar_c.success(cb) # cleanup st.session_state.clicked = False vectorstore.delete_collection()