File size: 4,973 Bytes
8fd4dfb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# 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", streaming=True) #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
async 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 = await chain.ainvoke({"question" : message.content})
    source_documents = resp["context"] 

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

    resp_msg = resp["response"].content

    #print(source_documents)

    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.page_content, name="{}".format(source_name), display="side")
            #)
            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)

    #print(msg.content)
    await msg.send()


    """async for chunk in msg.content:
    
        if token := chunk.choices[0].delta.content or "":
            await msg.stream_token(token)

    await msg.update()"""

    #async for chunk in chain:
    #    if token:=