File size: 5,849 Bytes
4a0c158
 
 
 
 
 
 
 
 
 
 
 
 
 
31e2acf
4a0c158
 
 
 
 
31e2acf
 
 
eb58fc5
 
31e2acf
e33920b
eb58fc5
e33920b
4a0c158
e33920b
4a0c158
 
 
 
 
 
eb58fc5
4a0c158
e33920b
4a0c158
 
 
 
 
 
eb58fc5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4a0c158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eb58fc5
e33920b
 
4a0c158
 
 
eb58fc5
 
4a0c158
eb58fc5
 
 
4a0c158
eb58fc5
4a0c158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31e2acf
4a0c158
 
 
 
 
 
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
153
154
155
156
157
import os
from typing import List
from operator import itemgetter
from Chunking import ChunkingStrategy, TextLoaderAndSplitterWrapper

from langchain.schema.runnable import RunnablePassthrough
from langchain_openai import ChatOpenAI
from langchain_openai.embeddings import OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.vectorstores import Qdrant

import chainlit as cl
from chainlit.types import AskFileResponse
from chainlit.cli import run_chainlit
from uuid import uuid4
import tempfile

OPENAI_API_KEY = os.environ["OPENAI_API_KEY"] 
GPT_MODEL = "gpt-4o-mini"

# Used for Langsmith
unique_id = uuid4().hex[0:8]
os.environ["LANGCHAIN_TRACING_V2"] = "true"
if os.environ.get("LANGCHAIN_PROJECT") is None:
    os.environ["LANGCHAIN_PROJECT"] = f"LangSmith LCEL RAG - {unique_id}"

is_azure = False if os.environ.get("AZURE_DEPLOYMENT") is None else True
is_azure_qdrant_inmem = True if os.environ.get("AZURE_QDRANT_INMEM") else False

# Utility functions
def save_file(file: AskFileResponse,file_ext:str,is_azure:bool) -> str:
    if file_ext == "application/pdf":
        file_ext = ".pdf"
    elif file_ext == "text/plain":
        file_ext = ".txt"
    else:
        raise ValueError(f"Unknown file type: {file_ext}")
    dir = "/tmp" if is_azure_qdrant_inmem else None
    with tempfile.NamedTemporaryFile(
        mode="wb", delete=False, suffix=file_ext,dir=dir
    ) as temp_file:
        temp_file_path = temp_file.name
        temp_file.write(file.content)
    return temp_file_path


def setup_vectorstore(documents: List[str], embedding_model: OpenAIEmbeddings,is_azure:bool) -> Qdrant:
    if is_azure:
        if is_azure_qdrant_inmem: 
            qdrant_vectorstore = Qdrant.from_documents(
                documents=documents,
                embedding=embedding_model,
                location=":memory:"
            )
        else:
            qdrant_vectorstore = Qdrant.from_documents(
                documents=documents,
                embedding=embedding_model,
                url="http://qdrant:6333", # Docker compose setup
            )
    else:
        qdrant_vectorstore = Qdrant.from_documents(
            documents=documents,
            embedding=embedding_model,
            location=":memory:"
        )
    return qdrant_vectorstore

# Prepare the components that will form the chain

## Step 1: Create a prompt template
base_rag_prompt_template = """\
You are a helpful assistant that can answer questions related to the provided context. Repond I don't have that information if outside context.

Context:
{context}

Question:
{question}
"""

base_rag_prompt = ChatPromptTemplate.from_template(base_rag_prompt_template)

## Step 2: Create Embeddings model instance for creating embeddings
embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")

## Step 2: Create the OpenAI chat model
base_llm = ChatOpenAI(model="gpt-4o-mini", tags=["base_llm"])


@cl.on_chat_start
async def on_chat_start():

    msg = cl.Message(content="Welcome to the Chat with Files app powered by LCEL and OpenAI - RAG!")
    await msg.send()

    files = None
    documents = None
    # Wait for the user to upload a file
    while files == None:
        files = await cl.AskFileMessage(
            content="Please upload a text or a pdf file to begin!",
            accept=["text/plain", "application/pdf"],
            max_size_mb=10,
            max_files=1,
            timeout=180,
        ).send()
    
    ## Load file and split into chunks
    await cl.Message(content=f"Processing `{files[0].name}`...").send()

    current_file_path = save_file(files[0], files[0].type,is_azure)
    loader_splitter = TextLoaderAndSplitterWrapper(ChunkingStrategy.RECURSIVE_CHARACTER_CHAR_SPLITTER, current_file_path)
    documents = loader_splitter.load_documents() 

    await cl.Message(content="    Data Chunked...").send()

    ## Vectorising the documents
    
    qdrant_vectorstore = setup_vectorstore(documents, embedding_model,is_azure)
    
    qdrant_retriever = qdrant_vectorstore.as_retriever()
    await cl.Message(content="    Created Vector store").send()

    # create the chain on new chart session
    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": base_rag_prompt | base_llm, "context": itemgetter("context")}
    )
    
    # Let the user know that the system is ready
    msg = cl.Message(content=f"Processing `{files[0].name}` done. You can now ask questions!")
    await msg.send()
    
    cl.user_session.set("chain", retrieval_augmented_qa_chain)
    

@cl.on_message
async def main(message: cl.Message):
    chain = cl.user_session.get("chain")
    msg = cl.Message(content="")
    response = chain.invoke({"question": message.content}, {"tags" : ["Demo Run"]})
    msg.content= response["response"].content
    await msg.send()
    cl.user_session.set("chain", chain)

if __name__ == "__main__":
    run_chainlit(__file__)