Nebulixlabs commited on
Commit
cb79449
·
verified ·
1 Parent(s): fa965cf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +60 -51
app.py CHANGED
@@ -1,7 +1,7 @@
1
  # ==============================================================================
2
  # NEBULIX SEARCH ENGINE v6 FINAL (PRODUCTION READY)
3
- # Target: 2 vCPU • 16 GB RAM • True Agentic RAG • AI Streaming • SQLite Analytics
4
- # Includes Hugging Face Dataset Integration & SmolLM2 (Million Params) integration
5
  # ==============================================================================
6
  import json
7
  import uuid
@@ -13,7 +13,7 @@ import time
13
  import pickle
14
  import sqlite3
15
  import unicodedata
16
- import multiprocessing
17
  import numpy as np
18
  import psutil
19
  from datetime import datetime
@@ -25,22 +25,24 @@ from rank_bm25 import BM25Okapi
25
  from cachetools import TTLCache, LRUCache
26
  import hashlib
27
 
 
 
 
 
 
 
 
28
  # --- ML & Data Imports ---
29
  try:
30
  from datasets import load_dataset
31
- from llama_cpp import Llama
32
  from sentence_transformers import SentenceTransformer
 
 
 
33
  ML_AVAILABLE = True
34
  except ImportError:
35
  ML_AVAILABLE = False
36
- print("⚠️ ML packages missing. Run: pip install llama-cpp-python sentence-transformers psutil numpy datasets")
37
-
38
- # ==============================================================================
39
- # 25. CPU ALLOCATION (60% Search, 40% AI on 2 vCPUs)
40
- # ==============================================================================
41
- os.environ["OMP_NUM_THREADS"] = "1"
42
- os.environ["OPENBLAS_NUM_THREADS"] = "1"
43
- os.environ["MKL_NUM_THREADS"] = "1"
44
 
45
  # ==============================================================================
46
  # 22. SQLITE ANALYTICS SETUP
@@ -75,7 +77,8 @@ AI_ANSWER_CACHE = LRUCache(maxsize=2000)
75
 
76
  doc_embeddings = None
77
  text_embedder = None
78
- llm = None
 
79
  AI_SEMAPHORE = asyncio.Semaphore(1)
80
 
81
  CLICK_TRACKER = {}
@@ -108,9 +111,6 @@ def update_dynamic_fields(doc):
108
 
109
  doc["special_tags"] = list(tags)
110
 
111
- # ==============================================================================
112
- # TOKENIZER & UTILS
113
- # ==============================================================================
114
  def normalize_text(text: str) -> str:
115
  if not text: return ""
116
  text = unicodedata.normalize('NFKD', str(text)).encode('ASCII', 'ignore').decode('utf-8')
@@ -134,24 +134,16 @@ def cosine_similarity(vec_a, mat_b):
134
  def detect_language(text):
135
  return "English"
136
 
137
- # ==============================================================================
138
- # DATASET LOADING & NEW SCHEMA MAPPING
139
- # ==============================================================================
140
  def build_searchable_text(doc):
141
- """Combines title, description, tag, and string values from meta/og tags into one text."""
142
  parts = [doc.get("title", ""), doc.get("description", ""), doc.get("tag", "")]
143
-
144
  for k, v in doc.get("meta_tags", {}).items():
145
  if isinstance(v, str): parts.append(v)
146
-
147
  for k, v in doc.get("og_tags", {}).items():
148
  if isinstance(v, str): parts.append(v)
149
-
150
  return " ".join(p for p in parts if p).strip()
151
 
152
  def build_runtime_record(doc):
153
  r = doc.copy()
154
-
155
  r["meta_tags"] = doc.get("meta_tags", {})
156
  r["og_tags"] = doc.get("og_tags", {})
157
  r["images"] = doc.get("images", [])
@@ -179,7 +171,7 @@ def build_runtime_record(doc):
179
  return r
180
 
181
  def init_system():
182
- global bm25_index, doc_embeddings, text_embedder, llm, raw_data, url_to_data
183
  init_db()
184
  print("🚀 [1/4] Fetching Dataset & Building Indexes...")
185
 
@@ -188,7 +180,7 @@ def init_system():
188
  if not os.path.exists(CACHE_FILE):
189
  hf_token = os.getenv("HF_TOKEN")
190
  if not hf_token:
191
- raise ValueError("❌ HF_TOKEN environment variable is missing. Cannot fetch dataset.")
192
  print("☁️ Downloading dataset from Hugging Face...")
193
  dataset = load_dataset("Nebulixlabs/Ultimate-Search-Engine-Dataset", split="train", token=hf_token)
194
  dataset.to_json(CACHE_FILE)
@@ -224,17 +216,16 @@ def init_system():
224
  with open("embeddings.bin", "wb") as fb: pickle.dump(doc_embeddings, fb)
225
  with open("metadata.json", "w") as fm: json.dump({"count": len(doc_embeddings)}, fm)
226
 
227
- # =============== CHANGED TO SMOLLM2 (360 Million Params) ===============
228
- print("🔥 [4/4] Loading Llama.cpp SmolLM2-360M GGUF (Lightweight CPU Model)...")
229
  try:
230
- llm = Llama(
231
- model_path="./models/smollm2-360m-instruct-q8_0.gguf", # Update filename if downloaded differently
232
- n_threads=1,
233
- n_ctx=3000,
234
- verbose=False
235
- )
236
- except:
237
- print("⚠️ LLM not found locally in ./models/, mocked mode active.")
238
  print("✅ Nebulix v6 Ready.")
239
 
240
  # ==============================================================================
@@ -283,9 +274,6 @@ def get_ranked_results(query: str):
283
  })
284
  return formatted
285
 
286
- # ==============================================================================
287
- # CHUNKING
288
- # ==============================================================================
289
  def get_best_chunks(query, full_text):
290
  if not full_text: return []
291
  words = full_text.split()
@@ -308,9 +296,12 @@ def get_best_chunks(query, full_text):
308
  # TRUE AGENTIC RAG PLANNER (Max 10 Hops)
309
  # ==============================================================================
310
  async def agent_planner_call(prompt):
311
- if not llm: return "ACTION: ANSWER"
312
- res = await asyncio.to_thread(llm, prompt, max_tokens=20, temperature=0.1, stop=["\n"])
313
- return res["choices"][0]["text"].strip()
 
 
 
314
 
315
  async def execute_agentic_rag(query, candidates, debug=False):
316
  context_memory = ""
@@ -322,7 +313,6 @@ async def execute_agentic_rag(query, candidates, debug=False):
322
 
323
  async with AI_SEMAPHORE:
324
  for hop in range(10):
325
- # Prompt simplified slightly for million-parameter models to ensure strict output
326
  sys_prompt = f"""System: You are a Planner. Output ONLY the action command.
327
  Query: {query}
328
  URLs:
@@ -416,11 +406,13 @@ async def api_aimode(q: str = Query(...), debug: bool = False):
416
  results = get_ranked_results(q)
417
  final_prompt, sources, debug_log = await execute_agentic_rag(q, results, debug)
418
 
419
- if not llm: return {"Answer": "Mocked", "Sources": sources}
420
 
421
  async with AI_SEMAPHORE:
422
- res = await asyncio.to_thread(llm, final_prompt, max_tokens=1024, temperature=0.2)
423
- answer = res["choices"][0]["text"].strip()
 
 
424
 
425
  resp = {
426
  "Answer": answer[:150] + "...",
@@ -438,13 +430,30 @@ async def api_aimodechat(q: str = Query(...)):
438
  final_prompt, sources, _ = await execute_agentic_rag(q, results)
439
 
440
  async def true_event_stream():
441
- if llm:
442
  async with AI_SEMAPHORE:
443
- for chunk in llm(final_prompt, max_tokens=1024, stream=True, temperature=0.2):
444
- text = chunk["choices"][0]["text"]
445
- if text: yield f"data: {text}\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
  else:
447
  yield "data: [Mocked stream due to missing LLM model]\n\n"
 
448
  yield f"data: [SOURCES_JSON] {json.dumps(sources)}\n\n"
449
 
450
  return StreamingResponse(true_event_stream(), media_type="text/event-stream")
@@ -485,7 +494,7 @@ async def api_suggestions(q: str = Query(...)):
485
  async def api_stats():
486
  mem = psutil.virtual_memory()
487
  return {
488
- "engine": "Nebulix v6 HF Production (SmolLM2 360M)",
489
  "documents": len(raw_data),
490
  "cpu_usage_percent": psutil.cpu_percent(),
491
  "ram_usage_percent": mem.percent,
 
1
  # ==============================================================================
2
  # NEBULIX SEARCH ENGINE v6 FINAL (PRODUCTION READY)
3
+ # Target: Hugging Face Spaces | 2 vCPU • 16 GB RAM • True Agentic RAG • AI Streaming
4
+ # Includes Hugging Face Dataset Integration & SmolLM2 (Transformers/PyTorch)
5
  # ==============================================================================
6
  import json
7
  import uuid
 
13
  import pickle
14
  import sqlite3
15
  import unicodedata
16
+ import threading
17
  import numpy as np
18
  import psutil
19
  from datetime import datetime
 
25
  from cachetools import TTLCache, LRUCache
26
  import hashlib
27
 
28
+ # ==============================================================================
29
+ # 25. CPU ALLOCATION (60% Search, 40% AI on 2 vCPUs)
30
+ # ==============================================================================
31
+ os.environ["OMP_NUM_THREADS"] = "1"
32
+ os.environ["OPENBLAS_NUM_THREADS"] = "1"
33
+ os.environ["MKL_NUM_THREADS"] = "1"
34
+
35
  # --- ML & Data Imports ---
36
  try:
37
  from datasets import load_dataset
 
38
  from sentence_transformers import SentenceTransformer
39
+ import torch
40
+ from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, TextIteratorStreamer
41
+ torch.set_num_threads(1) # CPU optimization for Hugging Face Spaces
42
  ML_AVAILABLE = True
43
  except ImportError:
44
  ML_AVAILABLE = False
45
+ print("⚠️ ML packages missing. Check requirements.txt")
 
 
 
 
 
 
 
46
 
47
  # ==============================================================================
48
  # 22. SQLITE ANALYTICS SETUP
 
77
 
78
  doc_embeddings = None
79
  text_embedder = None
80
+ tokenizer = None
81
+ pipe = None
82
  AI_SEMAPHORE = asyncio.Semaphore(1)
83
 
84
  CLICK_TRACKER = {}
 
111
 
112
  doc["special_tags"] = list(tags)
113
 
 
 
 
114
  def normalize_text(text: str) -> str:
115
  if not text: return ""
116
  text = unicodedata.normalize('NFKD', str(text)).encode('ASCII', 'ignore').decode('utf-8')
 
134
  def detect_language(text):
135
  return "English"
136
 
 
 
 
137
  def build_searchable_text(doc):
 
138
  parts = [doc.get("title", ""), doc.get("description", ""), doc.get("tag", "")]
 
139
  for k, v in doc.get("meta_tags", {}).items():
140
  if isinstance(v, str): parts.append(v)
 
141
  for k, v in doc.get("og_tags", {}).items():
142
  if isinstance(v, str): parts.append(v)
 
143
  return " ".join(p for p in parts if p).strip()
144
 
145
  def build_runtime_record(doc):
146
  r = doc.copy()
 
147
  r["meta_tags"] = doc.get("meta_tags", {})
148
  r["og_tags"] = doc.get("og_tags", {})
149
  r["images"] = doc.get("images", [])
 
171
  return r
172
 
173
  def init_system():
174
+ global bm25_index, doc_embeddings, text_embedder, tokenizer, pipe, raw_data, url_to_data
175
  init_db()
176
  print("🚀 [1/4] Fetching Dataset & Building Indexes...")
177
 
 
180
  if not os.path.exists(CACHE_FILE):
181
  hf_token = os.getenv("HF_TOKEN")
182
  if not hf_token:
183
+ print("⚠️ HF_TOKEN environment variable is missing. It's needed if the dataset is private.")
184
  print("☁️ Downloading dataset from Hugging Face...")
185
  dataset = load_dataset("Nebulixlabs/Ultimate-Search-Engine-Dataset", split="train", token=hf_token)
186
  dataset.to_json(CACHE_FILE)
 
216
  with open("embeddings.bin", "wb") as fb: pickle.dump(doc_embeddings, fb)
217
  with open("metadata.json", "w") as fm: json.dump({"count": len(doc_embeddings)}, fm)
218
 
219
+ # =============== TRANSFORMERS BASED SMOLLM2 ===============
220
+ print("🔥 [4/4] Loading SmolLM2 (360 Million Params) via Transformers & PyTorch...")
221
  try:
222
+ model_id = "HuggingFaceTB/SmolLM2-360M-Instruct"
223
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
224
+ model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.float32)
225
+ pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, device=-1)
226
+ print("✅ Model successfully loaded into memory.")
227
+ except Exception as e:
228
+ print(f"⚠️ LLM loading failed: {e}. Mocked mode active.")
 
229
  print("✅ Nebulix v6 Ready.")
230
 
231
  # ==============================================================================
 
274
  })
275
  return formatted
276
 
 
 
 
277
  def get_best_chunks(query, full_text):
278
  if not full_text: return []
279
  words = full_text.split()
 
296
  # TRUE AGENTIC RAG PLANNER (Max 10 Hops)
297
  # ==============================================================================
298
  async def agent_planner_call(prompt):
299
+ if not pipe: return "ACTION: ANSWER"
300
+ res = await asyncio.to_thread(
301
+ pipe, prompt, max_new_tokens=20, temperature=0.1, do_sample=True, return_full_text=False
302
+ )
303
+ generated = res[0]["generated_text"].strip()
304
+ return generated.split('\n')[0]
305
 
306
  async def execute_agentic_rag(query, candidates, debug=False):
307
  context_memory = ""
 
313
 
314
  async with AI_SEMAPHORE:
315
  for hop in range(10):
 
316
  sys_prompt = f"""System: You are a Planner. Output ONLY the action command.
317
  Query: {query}
318
  URLs:
 
406
  results = get_ranked_results(q)
407
  final_prompt, sources, debug_log = await execute_agentic_rag(q, results, debug)
408
 
409
+ if not pipe: return {"Answer": "Mocked", "Sources": sources}
410
 
411
  async with AI_SEMAPHORE:
412
+ res = await asyncio.to_thread(
413
+ pipe, final_prompt, max_new_tokens=1024, temperature=0.2, do_sample=True, return_full_text=False
414
+ )
415
+ answer = res[0]["generated_text"].strip()
416
 
417
  resp = {
418
  "Answer": answer[:150] + "...",
 
430
  final_prompt, sources, _ = await execute_agentic_rag(q, results)
431
 
432
  async def true_event_stream():
433
+ if pipe and tokenizer:
434
  async with AI_SEMAPHORE:
435
+ streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
436
+
437
+ def generate():
438
+ try:
439
+ pipe(final_prompt, max_new_tokens=1024, temperature=0.2, do_sample=True, streamer=streamer, return_full_text=False)
440
+ except Exception as e:
441
+ print(f"Generation error: {e}")
442
+
443
+ # Model ko background thread mein start karo taaki event loop block na ho
444
+ thread = threading.Thread(target=generate)
445
+ thread.start()
446
+
447
+ while True:
448
+ try:
449
+ # asyncio.to_thread ka use iterator se text fetch karne ke liye
450
+ text = await asyncio.to_thread(next, streamer)
451
+ if text: yield f"data: {text}\n\n"
452
+ except StopIteration:
453
+ break
454
  else:
455
  yield "data: [Mocked stream due to missing LLM model]\n\n"
456
+
457
  yield f"data: [SOURCES_JSON] {json.dumps(sources)}\n\n"
458
 
459
  return StreamingResponse(true_event_stream(), media_type="text/event-stream")
 
494
  async def api_stats():
495
  mem = psutil.virtual_memory()
496
  return {
497
+ "engine": "Nebulix v6 HF Production (SmolLM2 360M via Transformers)",
498
  "documents": len(raw_data),
499
  "cpu_usage_percent": psutil.cpu_percent(),
500
  "ram_usage_percent": mem.percent,