File size: 6,165 Bytes
77ce43a
 
d9fbbaf
77ce43a
 
 
 
d9fbbaf
77ce43a
d9fbbaf
 
 
0c0b010
d9fbbaf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77ce43a
 
 
d9fbbaf
77ce43a
 
 
 
d9fbbaf
 
 
78ebcff
d9fbbaf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0c0b010
 
 
 
 
 
 
 
 
 
 
d9fbbaf
0c0b010
d9fbbaf
 
16a577a
c2f2d7f
70900f3
31c1c2f
 
 
 
9e2df4e
c2f2d7f
d9fbbaf
 
 
 
 
 
 
c2f2d7f
d9fbbaf
 
 
16a577a
d9fbbaf
 
 
 
 
 
 
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
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_chroma import Chroma
# Importing RunnableLambda for wrapping functions as runnable components.
from langchain_core.runnables import RunnableLambda
from langchain_core.prompts import PromptTemplate                                    # To format prompts
from langchain_core.output_parsers import StrOutputParser                            # to transform the output of an LLM into a more usable format
from langchain.schema.runnable import RunnableParallel, RunnablePassthrough 
# importing HuggingFace model abstraction class from langchain
from langchain_huggingface import HuggingFaceEndpoint

import torch
import gradio
import re

# Initializing a Hugging Face endpoint for text generation using a specific model.
llm = HuggingFaceEndpoint(
    repo_id="HuggingFaceH4/zephyr-7b-beta",  # Specifies the model to be used (Zephyr-7B Beta), available on Hugging Face.

    task="text-generation",  # Defines the task type as text generation (commonly used for chatbots, summarization, etc.).

    max_new_tokens=512,  # Limits the maximum number of new tokens the model can generate in a response.

    top_k=30,  # Controls the diversity of the generated text by restricting the number of top probable tokens considered at each step.

    temperature=0.1,  # A low temperature value makes the model’s output more deterministic and focused.

    repetition_penalty=1.03,  # Slightly penalizes repeated tokens to reduce redundancy in responses.
)

# Initializing the Hugging Face embedding model.
embedding =  HuggingFaceEmbeddings(
    model_name="mixedbread-ai/mxbai-embed-large-v1",  # Using a pre-trained embedding model for text vectorization.

    model_kwargs={'device': "cuda" if torch.cuda.is_available() else "cpu"},  # Configuring the model to use GPU if available.

    encode_kwargs={'normalize_embeddings': False}  # Disabling normalization to preserve the original embedding scale.
)

# Creating a Chroma vector database instance.
vectordb = Chroma(
    persist_directory=  './chroma/',   #'RAG_Implementation/chroma/',  # Setting the directory where embeddings will be stored.
    embedding_function=embedding  # Specifying the embedding function to convert text into vector representations.
)

# Function to retrieve relevant document chunks based on a given question.
def get_context_info(question):
    # Creating a retriever using the Chroma vector database with Maximum Marginal Relevance (MMR).
    # `search_type="mmr"` ensures diversity in retrieved results.
    # `fetch_k=5` first fetches the top 5 most relevant chunks.
    # `k=3` selects the 3 most diverse and relevant chunks from the 5 fetched.
    retriever = vectordb.as_retriever(search_type="mmr", search_kwargs={"k": 3, "fetch_k": 5})

    # Retrieving the document chunks using the retriever.
    docs = retriever.invoke(question)

    # Returning the retrieved document chunks.
    return docs

# Creating a retrieval pipeline using RunnableParallel.
retrieval = RunnableParallel(
    {
        # Retrieving relevant document chunks based on the input question.
        # RunnableLambda ensures that `get_context_info` runs dynamically when called.
        "context": RunnableLambda(lambda x: get_context_info(x["question"])),

        # Simply passing the question as is.
        "question": RunnableLambda(lambda x: x["question"])
    }
)

# Defining a prompt template for answering questions based on retrieved context.
template = """Use the following pieces of context to answer the question at the end.
If you don't know the answer, that is if the answer is not in the context, then just say that you don't know, don't try to make up an answer.
Always say "thanks for asking!" at the end of the answer.

{context}
Question: {question}
Helpful Answer:"""

# Creating a PromptTemplate object using the defined template.
# The template takes two input variables: 'context' (retrieved documents) and 'question' (user query).
QA_PROMPT = PromptTemplate(input_variables=["context", "question"], template=template)

# Constructing a Retrieval-Augmented Generation (RAG) chain.

rag_chain = (retrieval       # Step 1: Retrieve relevant document chunks based on the user's question.
             | QA_PROMPT     # Step 2: Format the retrieved context and question into a structured prompt.
             | llm           # Step 3: Pass the prompt to the language model (LLM) for answer generation.
             | StrOutputParser()  # Step 4: Convert the LLM's output into a plain string format.
             )

def clean_text(question):
    question=re.sub(r'\s+', ' ', question).strip()
    question=re.sub(r'\A\d+', '', question).strip()
    question = re.sub(r'#\S+', '', question)
    question = re.sub(r'@\S+', '', question)
    question.replace('\n', ' ')
    re.sub(r'\s+(?=(\n+$))', '', question)
    pattern = r'[^a-zA-Z0-9\s]'
    question=re.sub(pattern, '', question)
    return question
    
def get_rag_chain_response(user_question):
    user_question = clean_text(user_question)
    return rag_chain.invoke({"question": user_question})

#Below function is used only for testing. Otherwise it is not actually used
def get_context_string_testing(user_question):
    ret_arr = get_context_info(user_question)
    context_str = ''
    for i in range(len(ret_arr)):
        # Printing the content of each document chunk (page content).
        context_str += ret_arr[i].page_content + '\n  '    
    return context_str

# Gradio elements

# Input from user
in_max_length = 200 # YOUR CODE HERE
in_question = gradio.Textbox(type='text', label='Question', max_length=in_max_length)

# Output response
out_response = gradio.Textbox(type='text', label='Response to question')

# Gradio interface to generate UI link
iface = gradio.Interface( # YOUR CODE HERE
                            fn=get_rag_chain_response, #get_context_string_testing,
                            inputs=in_question,
                            outputs=out_response,
                            title="RAG implementation",
                            description="via gradio",)

# YOUR CODE HERE to launch the interface
iface.launch()