File size: 4,737 Bytes
4a0c158
 
 
 
 
 
 
 
 
 
 
 
 
 
31e2acf
4a0c158
 
 
 
 
31e2acf
 
 
 
 
4a0c158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31e2acf
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
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"
os.environ["LANGCHAIN_PROJECT"] = f"LangSmith LCEL RAG - {unique_id}"

# Utility functions
def save_file(file: AskFileResponse,file_ext:str) -> 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}")
    
    with tempfile.NamedTemporaryFile(
        mode="wb", delete=False, suffix=file_ext
    ) as temp_file:
        temp_file_path = temp_file.name
        temp_file.write(file.content)
    return temp_file_path


# 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
    msg = cl.Message(content=f"Processing `{files[0].name}`...")
    await msg.send()
    
    current_file_path = save_file(files[0], files[0].type)
    loader_splitter = TextLoaderAndSplitterWrapper(ChunkingStrategy.RECURSIVE_CHARACTER_CHAR_SPLITTER, current_file_path)
    documents = loader_splitter.load_documents() 

    ## Vectorising the documents
    qdrant_vectorstore = Qdrant.from_documents(
        documents=documents,
        embedding=embedding_model,
        location=":memory:"
    )
    qdrant_retriever = qdrant_vectorstore.as_retriever()

    # 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__)