File size: 4,503 Bytes
b36ae4a
 
 
 
 
10402f5
b36ae4a
 
 
 
 
2e4622d
 
b36ae4a
 
 
 
 
 
 
 
 
 
 
 
 
 
2e4622d
b36ae4a
2e4622d
 
 
b36ae4a
 
 
10402f5
2e4622d
b36ae4a
 
 
 
 
 
 
 
2e4622d
b36ae4a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2e4622d
b36ae4a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2e4622d
b36ae4a
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# You can find this code for Chainlit python streaming here (https://docs.chainlit.io/concepts/streaming/python)

import chainlit as cl  # importing chainlit for our app
from dotenv import load_dotenv

from langchain_community.document_loaders import PyPDFLoader
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
import tiktoken
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai.embeddings import OpenAIEmbeddings
from utils.custom_retriver import CustomQDrant
from starters import set_starters


load_dotenv()




RAG_PROMPT = """
CONTEXT:
{context}

QUERY:
{question}

Answer questions first based on provided context and if you can't find answer in provided context, use your previous knowledge. 
In your answer never mention phrases like Based on provided context, From the context etc.

At the end of each answer add CONTEXT CONFIDENCE tag -> answer vs. context similarity score -> faithfulness - answer in percent e.g. 85%.
Also add CONTEXT vs PRIOR tag: break answer to what you find in provided context and what you build from your prior knowledge.
"""

data_path = "data/airbnb_midterm.pdf"
docs = PyPDFLoader(data_path).load()
openai_chat_model = ChatOpenAI(model="gpt-4o") #gpt-4o

def tiktoken_len(text):
    tokens = tiktoken.encoding_for_model("gpt-4o").encode(
        text,
    )
    return len(tokens)

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size = 200,
    chunk_overlap = 10,
    length_function = tiktoken_len,
)

split_chunks = text_splitter.split_documents(docs)

rag_prompt = ChatPromptTemplate.from_template(RAG_PROMPT)

embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")


qdrant_vectorstore = CustomQDrant.from_documents(
    split_chunks,
    embedding_model,
    location=":memory:",
    collection_name="air bnb data",
    score_threshold=0.3
    
)

qdrant_retriever = qdrant_vectorstore.as_retriever()

from operator import itemgetter
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough

retrieval_augmented_qa_chain = (
    # INVOKE CHAIN WITH: {"question" : "<<SOME USER QUESTION>>"}
    # "question" : populated by getting the value of the "question" key
    # "context"  : populated by getting the value of the "question" key and chaining it into the base_retriever
    {"context": itemgetter("question") | qdrant_retriever, "question": itemgetter("question")}
    # "context"  : is assigned to a RunnablePassthrough object (will not be called or considered in the next step)
    #              by getting the value of the "context" key from the previous step
    | RunnablePassthrough.assign(context=itemgetter("context"))
    # "response" : the "context" and "question" values are used to format our prompt object and then piped
    #              into the LLM and stored in a key called "response"
    # "context"  : populated by getting the value of the "context" key from the previous step
    | {"response": rag_prompt | openai_chat_model, "context": itemgetter("context")}
)


@cl.author_rename
def rename(orig_author: str):
    rename_dict = {"User": "You", "Chatbot": "Airbnb"}
    return rename_dict.get(orig_author, orig_author)



@cl.on_chat_start  # marks a function that will be executed at the start of a user session
async def start_chat():



    cl.user_session.set("chain", retrieval_augmented_qa_chain, )





@cl.on_message  # marks a function that should be run each time the chatbot receives a message from a user
async def main(message: cl.Message):
    chain = cl.user_session.get("chain")

    resp = chain.invoke({"question" : message.content})
    source_documents = resp["context"] 

    text_elements = []  # type: List[cl.Text]

    resp_msg = resp["response"].content

    if source_documents:
        for source_idx, source_doc in enumerate(source_documents):
            source_name = f"source_{source_idx}"

            # Create the text element referenced in the message
            text_elements.append(
                cl.Text(content=source_doc[0].page_content, name="{} (scr: {})".format(source_name, round(source_doc[1],2)), display="side")
            )
        source_names = [text_el.name for text_el in text_elements]

    if source_names:
        resp_msg += f"\n\nSources: {', '.join(source_names)}"
    else:
        resp_msg += "\nNo sources found"

    msg = cl.Message(content=resp_msg, elements=text_elements)

    await msg.send()