File size: 10,372 Bytes
5d9ae3d
ceac36f
 
60ec46b
 
325ad1b
60ec46b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9bb56bd
60ec46b
 
 
 
 
8772e58
ba31675
60ec46b
 
806c6c9
edea727
60ec46b
 
 
 
 
 
 
ceac36f
60ec46b
ceac36f
60ec46b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97c6c16
60ec46b
 
 
 
 
 
 
 
 
 
 
 
 
ceac36f
60ec46b
 
 
 
 
 
 
325ad1b
 
 
 
 
 
 
 
 
 
 
 
 
60ec46b
5d9ae3d
ceac36f
 
 
 
 
60ec46b
ceac36f
 
 
 
 
 
 
 
 
60ec46b
ceac36f
 
 
 
 
 
60ec46b
ceac36f
 
 
 
 
60ec46b
ceac36f
 
 
 
 
 
 
 
 
 
60ec46b
ceac36f
 
 
 
 
 
 
60ec46b
ceac36f
 
 
 
 
 
 
60ec46b
ceac36f
 
 
 
 
 
 
 
 
 
60ec46b
ceac36f
 
 
 
 
 
 
 
60ec46b
ceac36f
 
 
 
60ec46b
ceac36f
 
 
 
 
60ec46b
ceac36f
 
 
 
60ec46b
 
 
 
ceac36f
60ec46b
 
ceac36f
60ec46b
 
 
ceac36f
60ec46b
 
ceac36f
60ec46b
ceac36f
 
 
 
5d9ae3d
 
60ec46b
 
 
5d9ae3d
60ec46b
 
 
 
5d9ae3d
60ec46b
 
5d9ae3d
60ec46b
9bb56bd
60ec46b
9bb56bd
60ec46b
5d9ae3d
60ec46b
 
5d9ae3d
ceac36f
60ec46b
650764d
60ec46b
 
5d9ae3d
60ec46b
 
325ad1b
60ec46b
 
325ad1b
 
60ec46b
 
 
325ad1b
 
 
 
 
60ec46b
 
 
5d9ae3d
60ec46b
 
 
 
 
 
 
5d9ae3d
60ec46b
 
 
 
5d9ae3d
60ec46b
 
 
 
 
 
 
 
 
 
 
 
 
 
325ad1b
 
 
60ec46b
 
 
 
f543238
60ec46b
 
 
 
 
325ad1b
 
 
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
import gradio as gr
import os
import pickle
from sentence_transformers import SentenceTransformer, util
from transformers import AutoTokenizer, AutoModelForQuestionAnswering, pipeline
import random

# Function to load the saved embeddings
def load_embeddings(embedding_file_path):
    with open(embedding_file_path, 'rb') as f:
        return pickle.load(f)

# Function to load the ground truth answers from a file
def load_ground_truth_answers(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        answers = file.readlines()
    return [answer.strip() for answer in answers]

# Function to generate embeddings and find the most relevant answers
def perform_embeddings(question_embedding, answer_embeddings):
    # Compute cosine similarity between the question and answers
    similarities = util.pytorch_cos_sim(question_embedding, answer_embeddings)
    
    # Find the index of the most similar answer
    most_similar_index = similarities.argmax()
    
    # Retrieve the most relevant answer
    return most_similar_index

# Initialize models and data (load once at startup)
try:
    # Load the embedding model for retrieval
    retrieval_model = SentenceTransformer("all-MiniLM-L6-v2")
    
    # Load DistilBERT for extractive QA (NOT GENERATION)
    qa_tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased-distilled-squad")
    qa_model = AutoModelForQuestionAnswering.from_pretrained("distilbert-base-uncased-distilled-squad")
    qa_pipeline = pipeline("question-answering", model=qa_model, tokenizer=qa_tokenizer)
    
    # Load Hull data
    hull_embeddings = load_embeddings('./data/MiniLM_embeddings_hull.pkl')
    hull_ground_truth = load_ground_truth_answers('./data/msc_ai_hullonline_short.txt')
    
    # Load Keele data
    keele_embeddings = load_embeddings('./data/MiniLM_embeddings_keele.pkl')
    keele_ground_truth = load_ground_truth_answers('./data/msc_ai_keeleonline_short.txt') 
    
    data_loaded = True
except Exception as e:
    data_loaded = False
    error_message = f"Error loading data: {str(e)}"

def get_answer(question):
    """
    Process user question and return the most relevant answer using retrieval and extractive QA.
    """
    if not data_loaded:
        return f" {error_message}"
    
    if not question or question.strip() == "":
        return " Please enter a question."
    
    # Determine which university's data to use
    question_lower = question.lower()
    if "hull" in question_lower:
        current_embeddings = hull_embeddings
        current_ground_truth = hull_ground_truth
        university_name = "University of Hull"
    elif "keele" in question_lower:
        current_embeddings = keele_embeddings
        current_ground_truth = keele_ground_truth
        university_name = "Keele University"
    else:
        return "Please specify either **Hull** or **Keele** in your question."

    try:
        # Retrieval: Generate embedding for the user's question
        question_embedding = retrieval_model.encode([question], convert_to_tensor=True)
        
        # Find the most relevant document/answer for retrieval
        most_similar_index = perform_embeddings(question_embedding, current_embeddings)
        retrieved_document = current_ground_truth[most_similar_index]
        
        # Extractive QA: Use DistilBERT to find the answer within the retrieved document
        qa_input = {
            'question': question,
            'context': retrieved_document
        }
        extractive_answer = qa_pipeline(qa_input)['answer']
        
        return f"**Q:** {question}\n\n**A (from {university_name}):** {extractive_answer}\n\n**Retrieved Document:** {retrieved_document}"
        
    except Exception as e:
        return f" An error occurred while processing your question: {str(e)}"

# List of example questions
example_questions = [
    "How long does the MSc Artificial Intelligence course at the University of Hull take to complete?",
    "When are the start dates for the MSc Computer Science with Artificial Intelligence at Keele University?",
    "What is the phone number to reach the course advisers at the University of Hull?",
    "How long does the MSc Computer Science with Artificial Intelligence at Keele University take to complete?",
    "What is the total cost of the MSc Artificial Intelligence Online program at the University of Hull?",
    "What is the total cost of the MSc Computer Science with Artificial Intelligence at Keele University?"
]

def get_random_example():
    return random.choice(example_questions)

# Custom CSS for better styling
custom_css = """
.gradio-container {
    max-width: 900px !important;
    margin: auto !important;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

.main-header {
    text-align: center;
    margin-bottom: 2rem;
    padding: 2rem;
    background: linear-gradient(135deg, #fbcfe8 0%, #f472b6 100%);
    border-radius: 15px;
    color: white;
    box-shadow: 0 8px 32px rgba(0,0,0,0.1);
}

.main-title {
    font-size: 2.2rem;
    font-weight: 700;
    margin-bottom: 0.5rem;
    line-height: 1.2;
}

.sub-title {
    font-size: 1.1rem;
    opacity: 0.9;
    margin-bottom: 0;
}

.description {
    font-size: 1rem;
    color: #34495e;
    margin: 2rem 0;
    padding: 1.5rem;
    background: linear-gradient(135deg, #fce7f3 0%, #fbcfe8 100%);
    border-radius: 12px;
    border-left: 5px solid #f472b6;
    box-shadow: 0 4px 16px rgba(0,0,0,0.05);
}

.input-section {
    margin-bottom: 1.5rem;
    background: linear-gradient(135deg, #fff0f5 0%, #ffe4e1 100%);
    padding: 1.5rem;
    border-radius: 12px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.05);
}

.output-section {
    margin-top: 1.5rem;
    background: linear-gradient(135deg, #fff0f5 0%, #ffe4e1 100%);
    padding: 1.5rem;
    border-radius: 12px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.05);
}

.footer-info {
    text-align: center;
    margin-top: 2rem;
    padding: 1.5rem;
    background: linear-gradient(135deg, #fce7f3 0%, #fbcfe8 100%);
    border-radius: 12px;
    font-size: 0.9rem;
    color: #7f8c8d;
    box-shadow: 0 4px 16px rgba(0,0,0,0.05);
}

.gr-button-primary {
    background: linear-gradient(135deg, #f472b6 0%, #ec4899 100%) !important;
    border: none !important;
    border-radius: 8px !important;
    font-weight: 600 !important;
    padding: 12px 24px !important;
    transition: all 0.3s ease !important;
}

.gr-button-primary:hover {
    transform: translateY(-2px) !important;
    box-shadow: 0 8px 25px rgba(244, 114, 182, 0.4) !important;
}

.gr-textbox {
    border-radius: 8px !important;
    border: 2px solid #e9ecef !important;
    transition: all 0.3s ease !important;
}

.gr-textbox:focus {
    border-color: #f472b6 !important;
    box-shadow: 0 0 0 3px rgba(244, 114, 182, 0.1) !important;
}

.gr-button-secondary {
    background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%) !important;
    border: 2px solid #dee2e6 !important;
    border-radius: 8px !important;
    font-weight: 500 !important;
    padding: 8px 16px !important;
    transition: all 0.3s ease !important;
    color: #495057 !important;
    font-size: 0.9rem !important;
    margin: 2px !important;
}

.gr-button-secondary:hover {
    background: linear-gradient(135deg, #f472b6 0%, #ec4899 100%) !important;
    border-color: #f472b6 !important;
    color: white !important;
    transform: translateY(-1px) !important;
    box-shadow: 0 4px 12px rgba(244, 114, 182, 0.3) !important;
}
"""

# Create the Gradio interface
with gr.Blocks(css=custom_css, title="Retrieval-Based Chatbot", theme=gr.themes.Soft()) as demo:
    # Header section
    gr.HTML("""
        <div class="main-header">
            <h1 class="main-title">Retrieval-Based Methods with Embeddings and Extractive QA</h1>
            <p class="sub-title">about the MSc AI Online Programmes at University of Hull or Keele University</p>
        </div>
    """)
    
    # Description section
    gr.HTML("""
        <div class="description">
            <p>Please ask questions about MSc Artificial Intelligence Online program at the University of Hull or Keele University. 
            This chatbot uses MiniLM embeddings for retrieval and DistilBERT for extractive answers. 
            Please include the word <b>Hull</b> or <b>Keele</b> in your question.</p>
        </div>
    """)
    
    # Main interface
    with gr.Group(elem_classes=["input-section"]):
        question_input = gr.Textbox(
            label="Enter your question:",
            placeholder="e.g., How long does the MSc Artificial Intelligence course at the University of Hull take to complete?",
            lines=2,
            show_label=True
        )
        
        # Example questions section
        gr.HTML("<h3 style='margin-top: 1rem; margin-bottom: 0.5rem; color: #f472b6;'> Try these example questions:</h3>")
        
        with gr.Row():
            example_btn = gr.Button(
                "Examples",
                size="sm",
                variant="secondary"
            )
            submit_btn = gr.Button(
                "Get Answer", 
                variant="primary", 
                size="lg",
                scale=1
            )
    
    # Output section
    with gr.Group(elem_classes=["output-section"]):
        answer_output = gr.Markdown(
            label="Answer:",
            show_label=True,
            visible=True
        )
    
    # Footer information
    gr.HTML("""
        <div class="footer-info">
            <p> <strong>How it works:</strong> This system uses MiniLM sentence embeddings for retrieval and DistilBERT for extractive answers.</p>
            <p> <strong>About:</strong> MSc Artificial Intelligence Online Programmes - University of Hull & Keele University</p>
        </div>
    """)
    
    # Event handlers
    submit_btn.click(
        fn=get_answer,
        inputs=[question_input],
        outputs=[answer_output]
    )
    
    question_input.submit(
        fn=get_answer,
        inputs=[question_input],
        outputs=[answer_output]
    )
    
    # Example button event handler
    example_btn.click(
        fn=get_random_example,
        outputs=[question_input]
    )

# Launch the app
if __name__ == "__main__":
    demo.launch(
        server_name="0.0.0.0",
        server_port=7860,
        share=False,
        show_error=True
    )