ِAkramtaha98 commited on
Commit
51168f3
·
1 Parent(s): 8daf33e

Add multilingual RAG chatbot

Browse files
Files changed (6) hide show
  1. app.py +174 -0
  2. data/sample_docs.txt +43 -0
  3. ingest.py +49 -0
  4. packages.txt +1 -0
  5. rag_pipeline.py +94 -0
  6. requirements.txt +8 -0
app.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Gradio Blocks UI for the multilingual RAG chatbot.
3
+ Run: python3 app.py
4
+ """
5
+
6
+ import subprocess
7
+ import pathlib
8
+ import gradio as gr
9
+ from rag_pipeline import RAGPipeline
10
+
11
+ if not pathlib.Path("faiss_index").exists():
12
+ print("FAISS index not found — running ingest.py...")
13
+ subprocess.run(["python3", "ingest.py"], check=True)
14
+
15
+ pipeline = RAGPipeline()
16
+
17
+ DESCRIPTION = """
18
+ # 🌐 Multilingual RAG Chatbot
19
+ Ask questions in **English**, **العربية (Arabic)**, or **Bahasa Melayu (Malay)**.
20
+ Answers are grounded in the knowledge base. Retrieved source passages appear below each answer.
21
+ """
22
+
23
+ EXAMPLES = [
24
+ ["What is retrieval-augmented generation?"],
25
+ ["How does machine learning differ from deep learning?"],
26
+ ["What are vector databases used for?"],
27
+ ["ما هو الذكاء الاصطناعي؟"],
28
+ ["كيف يعمل التعلم الآلي؟"],
29
+ ["ما هي قواعد البيانات المتجهية؟"],
30
+ ["Apakah itu kecerdasan buatan?"],
31
+ ["Bagaimana pembelajaran mesin berfungsi?"],
32
+ ["Apakah RAG dan kegunaannya?"],
33
+ ]
34
+
35
+ RTL_CSS = """
36
+ .message-bubble p, .message-bubble span, .prose p {
37
+ unicode-bidi: plaintext;
38
+ text-align: start;
39
+ }
40
+ [lang="ar"], .rtl-text {
41
+ direction: rtl;
42
+ text-align: right;
43
+ font-family: 'Segoe UI', Tahoma, Arial, sans-serif;
44
+ }
45
+ .source-box {
46
+ border-left: 3px solid #6366f1;
47
+ padding-left: 0.75rem;
48
+ margin: 0.5rem 0;
49
+ font-size: 0.875rem;
50
+ }
51
+ .example-btn { font-size: 0.82rem !important; }
52
+ """
53
+
54
+ JS_RTL = """
55
+ function applyRTL() {
56
+ document.querySelectorAll('.message-bubble p, .prose p').forEach(el => {
57
+ if (/[؀-ۿ]/.test(el.innerText || '')) {
58
+ el.setAttribute('dir', 'rtl');
59
+ el.style.textAlign = 'right';
60
+ }
61
+ });
62
+ }
63
+ const observer = new MutationObserver(applyRTL);
64
+ observer.observe(document.body, { childList: true, subtree: true });
65
+ applyRTL();
66
+ """
67
+
68
+
69
+ def format_sources(sources: list[dict]) -> str:
70
+ if not sources:
71
+ return "_No sources retrieved._"
72
+ lines = []
73
+ for i, s in enumerate(sources, 1):
74
+ snippet = s["content"].replace("\n", " ").strip()
75
+ if len(snippet) > 300:
76
+ snippet = snippet[:300] + "…"
77
+ file_name = s["source"].split("/")[-1]
78
+ lines.append(f"**[{i}] `{file_name}`**\n> {snippet}")
79
+ return "\n\n".join(lines)
80
+
81
+
82
+ def respond(message: str, history: list):
83
+ if not message.strip():
84
+ yield history, "_Please enter a question._"
85
+ return
86
+
87
+ result = pipeline.ask(message)
88
+ answer = result["answer"]
89
+ sources_md = format_sources(result["sources"])
90
+
91
+ history = history + [
92
+ {"role": "user", "content": message},
93
+ {"role": "assistant", "content": answer},
94
+ ]
95
+ yield history, sources_md
96
+
97
+
98
+ def clear_all():
99
+ pipeline.chain.memory.clear()
100
+ return [], "_Sources will appear here after your first question._"
101
+
102
+
103
+ with gr.Blocks(title="Multilingual RAG Chatbot") as demo:
104
+ gr.Markdown(DESCRIPTION)
105
+
106
+ with gr.Row():
107
+ with gr.Column(scale=3):
108
+ chatbot = gr.Chatbot(
109
+ elem_id="chatbot",
110
+ label="Chat",
111
+ height=480,
112
+ )
113
+
114
+ with gr.Row():
115
+ msg_box = gr.Textbox(
116
+ placeholder="Type your question in English, العربية, or Bahasa Melayu…",
117
+ show_label=False,
118
+ scale=8,
119
+ autofocus=True,
120
+ )
121
+ send_btn = gr.Button("Send ➤", variant="primary", scale=1)
122
+
123
+ with gr.Row():
124
+ clear_btn = gr.Button("🗑 Clear Chat", variant="secondary", size="sm")
125
+
126
+ with gr.Column(scale=2):
127
+ gr.Markdown("### 📄 Retrieved Sources")
128
+ sources_box = gr.Markdown(
129
+ value="_Sources will appear here after your first question._",
130
+ )
131
+
132
+ with gr.Accordion("💡 Example Questions", open=True):
133
+ with gr.Row():
134
+ with gr.Column():
135
+ gr.Markdown("**🇬🇧 English**")
136
+ for ex in EXAMPLES[:3]:
137
+ gr.Button(ex[0], elem_classes="example-btn").click(
138
+ fn=lambda q=ex[0]: q,
139
+ outputs=msg_box,
140
+ )
141
+ with gr.Column():
142
+ gr.Markdown("**🇸🇦 العربية**")
143
+ for ex in EXAMPLES[3:6]:
144
+ gr.Button(ex[0], elem_classes="example-btn").click(
145
+ fn=lambda q=ex[0]: q,
146
+ outputs=msg_box,
147
+ )
148
+ with gr.Column():
149
+ gr.Markdown("**🇲🇾 Bahasa Melayu**")
150
+ for ex in EXAMPLES[6:]:
151
+ gr.Button(ex[0], elem_classes="example-btn").click(
152
+ fn=lambda q=ex[0]: q,
153
+ outputs=msg_box,
154
+ )
155
+
156
+ submit_inputs = [msg_box, chatbot]
157
+ submit_outputs = [chatbot, sources_box]
158
+
159
+ msg_box.submit(respond, submit_inputs, submit_outputs).then(
160
+ fn=lambda: "", outputs=msg_box
161
+ )
162
+ send_btn.click(respond, submit_inputs, submit_outputs).then(
163
+ fn=lambda: "", outputs=msg_box
164
+ )
165
+ clear_btn.click(clear_all, outputs=[chatbot, sources_box])
166
+
167
+ if __name__ == "__main__":
168
+ demo.launch(
169
+ server_name="0.0.0.0",
170
+ server_port=7860,
171
+ show_error=True,
172
+ css=RTL_CSS,
173
+ js=JS_RTL,
174
+ )
data/sample_docs.txt ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ===== ENGLISH =====
2
+
3
+ Artificial Intelligence (AI) is the simulation of human intelligence processes by machines, especially computer systems. These processes include learning, reasoning, and self-correction. AI applications include expert systems, natural language processing, speech recognition, and machine vision.
4
+
5
+ Machine learning is a subset of AI that provides systems the ability to automatically learn and improve from experience without being explicitly programmed. It focuses on the development of computer programs that can access data and use it to learn for themselves.
6
+
7
+ Deep learning is part of a broader family of machine learning methods based on artificial neural networks with representation learning. Learning can be supervised, semi-supervised or unsupervised.
8
+
9
+ Natural Language Processing (NLP) is a subfield of linguistics, computer science, and artificial intelligence concerned with the interactions between computers and human language, in particular how to program computers to process and analyze large amounts of natural language data.
10
+
11
+ Vector databases store data as high-dimensional vectors and are optimized for similarity search. They are widely used in AI applications for semantic search, recommendation systems, and retrieval-augmented generation (RAG).
12
+
13
+ Retrieval-Augmented Generation (RAG) is an AI framework that retrieves relevant information from a knowledge base before generating a response. It combines the power of retrieval systems with generative language models to produce accurate, grounded answers.
14
+
15
+ ===== ARABIC =====
16
+
17
+ الذكاء الاصطناعي هو محاكاة عمليات الذكاء البشري بواسطة الآلات، وخاصة أنظمة الكمبيوتر. تشمل هذه العمليات التعلم والتفكير والتصحيح الذاتي. تطبيقات الذكاء الاصطناعي تشمل الأنظمة الخبيرة ومعالجة اللغة الطبيعية والتعرف على الكلام والرؤية الآلية.
18
+
19
+ التعلم الآلي هو فرع من الذكاء الاصطناعي يوفر للأنظمة القدرة على التعلم التلقائي والتحسن من الخبرة دون أن تتم برمجتها بشكل صريح. يركز على تطوير برامج الكمبيوتر التي يمكنها الوصول إلى البيانات واستخدامها للتعلم بمفردها.
20
+
21
+ معالجة اللغة الطبيعية هي مجال فرعي من اللغويات وعلوم الكمبيوتر والذكاء الاصطناعي يهتم بالتفاعلات بين الحواسيب واللغة البشرية. يهدف إلى تمكين الحواسيب من فهم النصوص والكلام البشري وتحليلها.
22
+
23
+ قواعد البيانات المتجهية تخزن البيانات كمتجهات عالية الأبعاد وهي محسّنة للبحث عن التشابه. تُستخدم على نطاق واسع في تطبيقات الذكاء الاصطناعي للبحث الدلالي وأنظمة التوصية والتوليد المعزز بالاسترجاع.
24
+
25
+ التوليد المعزز بالاسترجاع هو إطار عمل يجمع بين استرجاع المعلومات ذات الصلة من قاعدة المعرفة وتوليد الاستجابات. يجمع قوة أنظمة الاسترجاع مع نماذج اللغة التوليدية لإنتاج إجابات دقيقة ومؤسسة.
26
+
27
+ الشبكات العصبية الاصطناعية هي نماذج حوسبية مستوحاة من الشبكات العصبية البيولوجية في الدماغ البشري. تتكون من طبقات من العقد أو الخلايا العصبية الاصطناعية المترابطة التي تعالج المعلومات.
28
+
29
+ ===== MALAY =====
30
+
31
+ Kecerdasan buatan (AI) ialah simulasi proses kecerdasan manusia oleh mesin, terutamanya sistem komputer. Proses-proses ini termasuk pembelajaran, penaakulan, dan pembetulan diri. Aplikasi AI termasuk sistem pakar, pemprosesan bahasa semula jadi, pengecaman pertuturan, dan penglihatan mesin.
32
+
33
+ Pembelajaran mesin adalah subset AI yang memberikan sistem keupayaan untuk belajar secara automatik dan bertambah baik daripada pengalaman tanpa diprogramkan secara eksplisit. Ia memberi tumpuan kepada pembangunan program komputer yang boleh mengakses data dan menggunakannya untuk belajar sendiri.
34
+
35
+ Pembelajaran mendalam adalah sebahagian daripada keluarga kaedah pembelajaran mesin yang lebih luas berdasarkan rangkaian neural buatan dengan pembelajaran perwakilan. Pembelajaran boleh diselia, separuh diselia atau tidak diselia.
36
+
37
+ Pemprosesan Bahasa Semula Jadi (NLP) ialah subbidang linguistik, sains komputer, dan kecerdasan buatan yang berkaitan dengan interaksi antara komputer dan bahasa manusia. Ia bertujuan untuk membolehkan komputer memahami dan menganalisis data bahasa semula jadi dalam jumlah yang besar.
38
+
39
+ Pangkalan data vektor menyimpan data sebagai vektor berdimensi tinggi dan dioptimumkan untuk carian persamaan. Ia digunakan secara meluas dalam aplikasi AI untuk carian semantik, sistem cadangan, dan penjanaan yang ditambah dengan pengambilan semula.
40
+
41
+ Penjanaan Bertambah Pengambilan Semula (RAG) ialah rangka kerja AI yang mengambil semula maklumat yang relevan daripada pangkalan pengetahuan sebelum menjana respons. Ia menggabungkan kuasa sistem pengambilan semula dengan model bahasa generatif untuk menghasilkan jawapan yang tepat dan berasas.
42
+
43
+ Model bahasa besar (LLM) ialah model kecerdasan buatan yang dilatih pada set data teks yang besar. Ia boleh menjana teks seperti manusia, menjawab soalan, meringkaskan dokumen, menterjemah bahasa, dan melaksanakan banyak tugas bahasa yang lain.
ingest.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Loads data/sample_docs.txt, chunks it, embeds with multilingual HuggingFace
3
+ model, and saves a FAISS index to faiss_index/.
4
+ """
5
+
6
+ from pathlib import Path
7
+
8
+ from dotenv import load_dotenv
9
+ from langchain_community.document_loaders import TextLoader
10
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
11
+ from langchain_huggingface import HuggingFaceEmbeddings
12
+ from langchain_community.vectorstores import FAISS
13
+
14
+ load_dotenv()
15
+
16
+ EMBEDDING_MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
17
+ SOURCE_FILE = Path("data/sample_docs.txt")
18
+ INDEX_DIR = Path("faiss_index")
19
+ CHUNK_SIZE = 500
20
+ CHUNK_OVERLAP = 100
21
+
22
+
23
+ def main():
24
+ if not SOURCE_FILE.exists():
25
+ raise FileNotFoundError(f"{SOURCE_FILE} not found.")
26
+
27
+ print(f"Loading {SOURCE_FILE}...")
28
+ loader = TextLoader(str(SOURCE_FILE), encoding="utf-8")
29
+ docs = loader.load()
30
+
31
+ splitter = RecursiveCharacterTextSplitter(
32
+ chunk_size=CHUNK_SIZE,
33
+ chunk_overlap=CHUNK_OVERLAP,
34
+ separators=["\n\n", "\n", ".", "،", " ", ""],
35
+ )
36
+ chunks = splitter.split_documents(docs)
37
+ print(f"Created {len(chunks)} chunks.")
38
+
39
+ print("Embedding chunks (this may take a moment)...")
40
+ embeddings = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL)
41
+ vectorstore = FAISS.from_documents(chunks, embeddings)
42
+
43
+ INDEX_DIR.mkdir(exist_ok=True)
44
+ vectorstore.save_local(str(INDEX_DIR))
45
+ print(f"FAISS index saved to {INDEX_DIR}/")
46
+
47
+
48
+ if __name__ == "__main__":
49
+ main()
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ libgomp1
rag_pipeline.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ RAG pipeline: loads the FAISS index and builds a ConversationalRetrievalChain
3
+ backed by Groq (llama-3.3-70b-versatile). Returns the answer and source chunks.
4
+ """
5
+
6
+ import os
7
+ from pathlib import Path
8
+
9
+ from dotenv import load_dotenv
10
+ from langchain_huggingface import HuggingFaceEmbeddings
11
+ from langchain_community.vectorstores import FAISS
12
+ from langchain_classic.chains import ConversationalRetrievalChain
13
+ from langchain_classic.memory import ConversationBufferMemory
14
+ from langchain_groq import ChatGroq
15
+
16
+ load_dotenv()
17
+
18
+ EMBEDDING_MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
19
+ INDEX_DIR = Path("faiss_index")
20
+ GROQ_MODEL = "llama-3.3-70b-versatile"
21
+ TOP_K = 4
22
+
23
+ SYSTEM_PROMPT = (
24
+ "You are a helpful multilingual assistant that supports English, Arabic, and Malay. "
25
+ "Always reply in the same language the user used. "
26
+ "Base your answer strictly on the provided context. "
27
+ "If the context does not contain enough information, say so honestly."
28
+ )
29
+
30
+
31
+ def load_vectorstore() -> FAISS:
32
+ if not INDEX_DIR.exists():
33
+ raise FileNotFoundError(
34
+ "FAISS index not found. Run `python ingest.py` first."
35
+ )
36
+ embeddings = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL)
37
+ return FAISS.load_local(
38
+ str(INDEX_DIR), embeddings, allow_dangerous_deserialization=True
39
+ )
40
+
41
+
42
+ class RAGPipeline:
43
+ def __init__(self):
44
+ vectorstore = load_vectorstore()
45
+ retriever = vectorstore.as_retriever(search_kwargs={"k": TOP_K})
46
+
47
+ llm = ChatGroq(
48
+ model=GROQ_MODEL,
49
+ api_key=os.environ["GROQ_API_KEY"],
50
+ max_tokens=1024,
51
+ )
52
+
53
+ memory = ConversationBufferMemory(
54
+ memory_key="chat_history",
55
+ return_messages=True,
56
+ output_key="answer",
57
+ )
58
+
59
+ self.chain = ConversationalRetrievalChain.from_llm(
60
+ llm=llm,
61
+ retriever=retriever,
62
+ memory=memory,
63
+ return_source_documents=True,
64
+ output_key="answer",
65
+ combine_docs_chain_kwargs={"prompt": _build_prompt()},
66
+ )
67
+
68
+ def ask(self, question: str) -> dict:
69
+ """
70
+ Returns:
71
+ {
72
+ "answer": str,
73
+ "sources": [{"content": str, "source": str}, ...]
74
+ }
75
+ """
76
+ result = self.chain.invoke({"question": question})
77
+ sources = [
78
+ {
79
+ "content": doc.page_content,
80
+ "source": doc.metadata.get("source", "unknown"),
81
+ }
82
+ for doc in result.get("source_documents", [])
83
+ ]
84
+ return {"answer": result["answer"], "sources": sources}
85
+
86
+
87
+ def _build_prompt():
88
+ from langchain_core.prompts import PromptTemplate
89
+
90
+ template = (
91
+ SYSTEM_PROMPT
92
+ + "\n\nContext:\n{context}\n\nQuestion: {question}\n\nAnswer:"
93
+ )
94
+ return PromptTemplate(input_variables=["context", "question"], template=template)
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ langchain>=0.3.0
2
+ langchain-community>=0.3.0
3
+ langchain-huggingface>=0.1.0
4
+ langchain-groq>=0.2.0
5
+ faiss-cpu>=1.8.0
6
+ sentence-transformers>=3.0.0
7
+ gradio>=4.44.0
8
+ python-dotenv>=1.0.0