File size: 4,753 Bytes
70c9066 5ba15b9 70c9066 5ba15b9 70c9066 5ba15b9 f2f6041 5ba15b9 0813263 5ba15b9 f2f6041 5ba15b9 ca09033 5ba15b9 329e509 5ba15b9 329e509 5ba15b9 9e13bc6 5ba15b9 329e509 70c9066 5ba15b9 70c9066 5ba15b9 70c9066 5ba15b9 70c9066 5ba15b9 70c9066 5ba15b9 70c9066 5ba15b9 293906c 0813263 70c9066 5ba15b9 70c9066 0813263 70c9066 5ba15b9 6b9da7e b9507ee 70c9066 b9507ee 70c9066 b9507ee 70c9066 5ba15b9 70c9066 5ba15b9 70c9066 5ba15b9 70c9066 5ba15b9 70c9066 5ba15b9 70c9066 5ba15b9 70c9066 5ba15b9 70c9066 5ba15b9 70c9066 5ba15b9 70c9066 938cc20 70c9066 938cc20 5ba15b9 329e509 | 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 | import os
import zipfile
import torch
import faiss
import numpy as np
import gradio as gr
from transformers import GPT2Tokenizer, AutoTokenizer, AutoModelForCausalLM, pipeline
from sentence_transformers import SentenceTransformer
from langchain.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS as LangChainFAISS
from langchain.docstore import InMemoryDocstore
from langchain.schema import Document
from langchain.llms import HuggingFacePipeline
# === 1. Extract ZIP Knowledge Base ===
KB_DIR = "md_GCU_Chatbot_KnowledgeBase"
if os.path.exists("md_GCU_Chatbot_KnowledgeBase.zip"):
with zipfile.ZipFile("md_GCU_Chatbot_KnowledgeBase.zip", "r") as zip_ref:
zip_ref.extractall(KB_DIR)
print("✅ Knowledge base extracted.")
# === 2. Load Markdown Files ===
KB_PATH = "md_GCU_Chatbot_KnowledgeBase"
files = [os.path.join(dp, f) for dp, _, fn in os.walk(KB_PATH) for f in fn if f.endswith(".md")]
docs = [doc for f in files for doc in TextLoader(f, encoding="utf-8").load()]
print(f"✅ Loaded {len(docs)} documents.")
# === 3. Split into Chunks ===
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
def get_dynamic_chunk_size(text):
if len(text) < 1000: return 300
elif len(text) < 5000: return 500
else: return 1000
chunks = []
for doc in docs:
chunk_size = get_dynamic_chunk_size(doc.page_content)
chunk_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=100)
chunks.extend(chunk_splitter.split_documents([doc]))
texts = [chunk.page_content for chunk in chunks]
# === 4. Build Vectorstore ===
embed_model_id = "distilbert-base-uncased"
embedder = SentenceTransformer(embed_model_id)
embeddings = embedder.encode(texts, show_progress_bar=False)
dim = embeddings.shape[1]
index = faiss.IndexFlatL2(dim)
index.add(np.array(embeddings, dtype="float32"))
docs = [Document(page_content=t) for t in texts]
docstore = InMemoryDocstore({str(i): docs[i] for i in range(len(docs))})
id_map = {i: str(i) for i in range(len(docs))}
embed_fn = HuggingFaceEmbeddings(model_name=embed_model_id)
vectorstore = LangChainFAISS(
index=index,
docstore=docstore,
index_to_docstore_id=id_map,
embedding_function=embed_fn
)
print("✅ FAISS vectorstore ready.")
# === 5. Load GPT-2 for Generation ===
#tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
#model = AutoModelForCausalLM.from_pretrained("gpt2").to("cuda" if torch.cuda.is_available() else "cpu")
tokenizer = AutoTokenizer.from_pretrained("tiiuae/falcon-rw-1b")
model = AutoModelForCausalLM.from_pretrained("tiiuae/falcon-rw-1b")
text_gen_pipeline = pipeline(
"text-generation",
model=model,
tokenizer=tokenizer,
device=0 if torch.cuda.is_available() else -1,
return_full_text=False,
do_sample=False,
max_new_tokens=200,
pad_token_id=tokenizer.eos_token_id
)
llm = HuggingFacePipeline(pipeline=text_gen_pipeline)
#print("✅ GPT-2 loaded.")
# === 6. Prompt Formatting and Answer Logic ===
def truncate_context(context, max_length=1024):
tokens = tokenizer.encode(context)
if len(tokens) > max_length:
tokens = tokens[:max_length]
return tokenizer.decode(tokens, skip_special_tokens=True)
def format_prompt(context, question):
return (
"You are the Glasgow Caledonian University Course Assistant—helping students with questions about courses, admissions, fees, etc. "
"Only use the information in the context below to answer the question.\n\n"
f"Context:\n{truncate_context(context)}\n\n"
f"Student Question: {question}\n"
"Assistant Answer:"
)
def answer_fn(question):
docs = vectorstore.similarity_search(question, k=5)
if not docs:
return "I'm sorry, I couldn't find any relevant information for your query."
context = "\n\n".join(d.page_content for d in docs)
prompt = format_prompt(context, question)
try:
response = llm.invoke(prompt).strip()
return response
except Exception as e:
return f"An error occurred: {e}"
# === 7. Gradio UI ===
def chat_fn(user_message, history):
bot_response = answer_fn(user_message)
history = history + [(user_message, bot_response)]
return history, history
with gr.Blocks() as demo:
gr.Markdown("## Glasgow Caledonian University Postgraduate Course Assistant")
chatbot = gr.Chatbot()
state = gr.State([])
user_input = gr.Textbox(placeholder="Ask a question about GCU Postgraduate Courses...", show_label=False)
user_input.submit(fn=chat_fn, inputs=[user_input, state], outputs=[chatbot, state])
demo.launch()
|