Nebulixlabs commited on
Commit
0412d9e
Β·
verified Β·
1 Parent(s): 26f96ab

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -113
app.py CHANGED
@@ -1,7 +1,8 @@
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
  # strictly PRE-TRAINED MODELS ONLY (NO TRAINING)
 
5
  # ==============================================================================
6
  import json
7
  import uuid
@@ -10,7 +11,6 @@ import os
10
  import uvicorn
11
  import re
12
  import time
13
- import pickle
14
  import sqlite3
15
  import unicodedata
16
  import threading
@@ -24,9 +24,10 @@ from fastapi.middleware.cors import CORSMiddleware
24
  from rank_bm25 import BM25Okapi
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"
@@ -34,22 +35,19 @@ 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
 
42
  # πŸ”₯ STRICT NO-TRAINING LOCK πŸ”₯
43
  torch.set_num_threads(1)
44
  torch.set_grad_enabled(False)
45
-
46
  ML_AVAILABLE = True
47
  except ImportError:
48
  ML_AVAILABLE = False
49
  print("⚠️ ML packages missing. Check requirements.txt")
50
 
51
  # ==============================================================================
52
- # 22. SQLITE ANALYTICS SETUP
53
  # ==============================================================================
54
  DB_PATH = "analytics.db"
55
  def init_db():
@@ -76,11 +74,9 @@ url_to_data = {}
76
  bm25_index = None
77
  trie_root = {}
78
 
79
- SEARCH_CACHE = TTLCache(maxsize=10000, ttl=1800) # 30 Min TTL
80
  AI_ANSWER_CACHE = LRUCache(maxsize=2000)
81
 
82
- doc_embeddings = None
83
- text_embedder = None
84
  tokenizer = None
85
  pipe = None
86
  AI_SEMAPHORE = asyncio.Semaphore(1)
@@ -89,7 +85,7 @@ CLICK_TRACKER = {}
89
  STOP_WORDS = {"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "is"}
90
 
91
  # ==============================================================================
92
- # MATH & SPECIAL TAGS
93
  # ==============================================================================
94
  def update_dynamic_fields(doc):
95
  human = doc.get("human_opens", 0)
@@ -112,7 +108,6 @@ def update_dynamic_fields(doc):
112
 
113
  if ai >= 25: tags.add("Opened by AI")
114
  if total_interactive > 10 and ai_percent >= 80: tags.add("AI Favorite")
115
-
116
  doc["special_tags"] = list(tags)
117
 
118
  def normalize_text(text: str) -> str:
@@ -131,22 +126,11 @@ def add_to_trie(word):
131
  node = node[char]
132
  node['_end'] = True
133
 
134
- def cosine_similarity(vec_a, mat_b):
135
- if len(mat_b) == 0: return np.array([])
136
- return np.dot(mat_b, vec_a) / (np.linalg.norm(mat_b, axis=1) * np.linalg.norm(vec_a) + 1e-10)
137
-
138
- def detect_language(text):
139
- return "English"
140
-
141
  def parse_json_field(val):
142
- """Safely converts stringified JSONs to dicts if dataset schema changed."""
143
- if isinstance(val, dict):
144
- return val
145
  if isinstance(val, str):
146
- try:
147
- return json.loads(val)
148
- except:
149
- return {}
150
  return {}
151
 
152
  def build_searchable_text(doc):
@@ -159,8 +143,6 @@ def build_searchable_text(doc):
159
 
160
  def build_runtime_record(doc):
161
  r = doc.copy()
162
-
163
- # Safe JSON parsing to prevent crashes from schema changes
164
  r["meta_tags"] = parse_json_field(doc.get("meta_tags"))
165
  r["og_tags"] = parse_json_field(doc.get("og_tags"))
166
  r["specific_data"] = parse_json_field(doc.get("specific_data"))
@@ -173,56 +155,40 @@ def build_runtime_record(doc):
173
 
174
  r["human_opens"] = r.get("human_opens", 0)
175
  r["ai_opens"] = r.get("ai_opens", 0)
176
- r["crawler_opens"] = r.get("crawler_opens", 0)
177
- r["bot_opens"] = r.get("bot_opens", 0)
178
  r["trust_score"] = r.get("trust_score", 50.0)
179
  r["quality_score"] = r.get("quality_score", 50.0)
180
  r["learning_score"] = r.get("learning_score", 50.0)
181
  r["search_boost"] = r.get("search_boost", 0.0)
182
  r["spam_score"] = r.get("spam_score", 0.0)
183
- r["domain_quality"] = r.get("domain_quality", 50.0)
184
  r["last_opened"] = r.get("last_opened", datetime.utcnow().isoformat())
185
- r["language"] = r.get("language", detect_language(r.get("title", "")))
186
  r["country"] = r.get("country", "Global")
187
 
188
  update_dynamic_fields(r)
189
  return r
190
 
191
- def init_system():
192
- global bm25_index, doc_embeddings, text_embedder, tokenizer, pipe, raw_data, url_to_data
193
- init_db()
194
- print("πŸš€ [1/4] Fetching Dataset & Building Indexes...")
195
-
196
- CACHE_FILE = "cached_dataset.jsonl"
 
197
 
198
- if not os.path.exists(CACHE_FILE):
199
- hf_token = os.getenv("HF_TOKEN")
200
- print("☁️ Downloading dataset from Hugging Face...")
 
 
201
 
202
- try:
203
- # Attempt 1: Standard load
204
- dataset = load_dataset("Nebulixlabs/Ultimate-Search-Engine-Dataset", split="train", token=hf_token)
205
- dataset.to_json(CACHE_FILE)
206
- except Exception as e:
207
- print(f"⚠️ Standard load failed due to schema mismatch ({e}). Trying fallback raw JSON loader...")
208
- try:
209
- # Attempt 2: Force raw JSON inference (bypasses broken schemas)
210
- dataset = load_dataset("json", data_files="hf://datasets/Nebulixlabs/Ultimate-Search-Engine-Dataset/**/*.json*", split="train", token=hf_token)
211
- dataset.to_json(CACHE_FILE)
212
- except Exception as e2:
213
- print(f"⚠️ Raw JSON load failed. Trying Parquet loader... ({e2})")
214
- # Attempt 3: Parquet fallback
215
- dataset = load_dataset("parquet", data_files="hf://datasets/Nebulixlabs/Ultimate-Search-Engine-Dataset/**/*.parquet", split="train", token=hf_token)
216
- dataset.to_json(CACHE_FILE)
217
-
218
- print("βœ… Dataset successfully cached locally.")
219
-
220
- if os.path.exists(CACHE_FILE):
221
  corpus_tokens = []
222
- texts_to_embed = []
223
- with open(CACHE_FILE, "r", encoding="utf-8") as f:
224
- for line in f:
225
- if line.strip():
 
 
 
226
  try:
227
  doc = build_runtime_record(json.loads(line))
228
  raw_data.append(doc)
@@ -230,63 +196,56 @@ def init_system():
230
 
231
  tokens = tokenize(doc["searchable_text"])
232
  corpus_tokens.append(tokens)
233
- texts_to_embed.append(doc["searchable_text"])
234
  for t in tokens: add_to_trie(t)
235
- except Exception as parse_e:
236
- # Ignore highly corrupted single lines
237
- pass
238
-
239
  bm25_index = BM25Okapi(corpus_tokens)
 
240
 
241
- if ML_AVAILABLE:
242
- print("🧠 [2/4] Loading BAAI/bge-small-en-v1.5 (Pre-trained Inference Only)...")
243
- text_embedder = SentenceTransformer('BAAI/bge-small-en-v1.5', device='cpu')
244
 
245
- if os.path.exists("embeddings.bin"):
246
- print("πŸ’Ύ [3/4] Loading Precomputed Embeddings...")
247
- with open("embeddings.bin", "rb") as fb: doc_embeddings = pickle.load(fb)
248
- else:
249
- print("βš™οΈ [3/4] Converting Text to Vector Embeddings (One-Time Inference)...")
250
- if texts_to_embed:
251
- doc_embeddings = text_embedder.encode(texts_to_embed, convert_to_numpy=True)
252
- with open("embeddings.bin", "wb") as fb: pickle.dump(doc_embeddings, fb)
253
- with open("metadata.json", "w") as fm: json.dump({"count": len(doc_embeddings)}, fm)
254
-
255
- # =============== PRE-TRAINED TRANSFORMERS BASED SMOLLM2 ===============
256
- print("πŸ”₯ [4/4] Loading Pre-trained SmolLM2 (360M Params)...")
257
- try:
258
- model_id = "HuggingFaceTB/SmolLM2-360M-Instruct"
259
- tokenizer = AutoTokenizer.from_pretrained(model_id)
260
- model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.float32)
261
- model.eval() # Puts the model in strict inference mode
262
- pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, device=-1)
263
- print("βœ… Model successfully loaded in INFERENCE mode.")
264
- except Exception as e:
265
- print(f"⚠️ LLM loading failed: {e}. Mocked mode active.")
266
- print("βœ… Nebulix v6 Ready. (AI Training Disabled)")
267
 
268
  # ==============================================================================
269
- # RANKING FORMULA
270
  # ==============================================================================
271
  def get_ranked_results(query: str):
272
  q_tokens = tokenize(query)
273
  if not q_tokens: return []
274
 
275
  bm25_scores = bm25_index.get_scores(q_tokens) if bm25_index else np.zeros(len(raw_data))
276
- semantic_scores = np.zeros(len(raw_data))
277
-
278
- if text_embedder and doc_embeddings is not None and len(doc_embeddings) > 0:
279
- q_vec = text_embedder.encode([query])[0]
280
- semantic_scores = cosine_similarity(q_vec, doc_embeddings)
281
 
282
  scored_results = []
283
-
284
  for i, doc in enumerate(raw_data):
285
- score = (bm25_scores[i] * 0.35) + \
286
- (semantic_scores[i] * 0.45) + \
287
  (doc["trust_score"] * 0.05) + \
288
  (doc["quality_score"] * 0.05) + \
289
- (doc["learning_score"] * 0.03) + \
290
  (doc["popularity"] * 0.05) + \
291
  doc["search_boost"] - \
292
  doc["spam_score"]
@@ -321,16 +280,20 @@ def get_best_chunks(query, full_text):
321
  i += (512 - 64)
322
 
323
  if not chunks: return []
324
- if text_embedder:
325
- q_emb = text_embedder.encode([query], convert_to_numpy=True)[0]
326
- c_embs = text_embedder.encode(chunks, convert_to_numpy=True)
327
- scores = cosine_similarity(q_emb, c_embs)
328
- best_idx = np.argsort(scores)[::-1][:5]
329
- return [chunks[idx] for idx in best_idx]
330
- return chunks[:5]
 
 
 
 
331
 
332
  # ==============================================================================
333
- # TRUE AGENTIC RAG PLANNER (Max 10 Hops)
334
  # ==============================================================================
335
  async def agent_planner_call(prompt):
336
  if not pipe: return "ACTION: ANSWER"
@@ -529,7 +492,7 @@ async def api_suggestions(q: str = Query(...)):
529
  async def api_stats():
530
  mem = psutil.virtual_memory()
531
  return {
532
- "engine": "Nebulix v6 HF Production (SmolLM2 360M Inference-Only)",
533
  "documents": len(raw_data),
534
  "cpu_usage_percent": psutil.cpu_percent(),
535
  "ram_usage_percent": mem.percent,
 
1
  # ==============================================================================
2
+ # NEBULIX SEARCH ENGINE v6 FINAL
3
  # Target: Hugging Face Spaces | 2 vCPU β€’ 16 GB RAM β€’ True Agentic RAG β€’ AI Streaming
4
  # strictly PRE-TRAINED MODELS ONLY (NO TRAINING)
5
+ # REMOVED: Sentence-Transformers & Datasets library (No more train split errors)
6
  # ==============================================================================
7
  import json
8
  import uuid
 
11
  import uvicorn
12
  import re
13
  import time
 
14
  import sqlite3
15
  import unicodedata
16
  import threading
 
24
  from rank_bm25 import BM25Okapi
25
  from cachetools import TTLCache, LRUCache
26
  import hashlib
27
+ from huggingface_hub import HfApi, hf_hub_download
28
 
29
  # ==============================================================================
30
+ # 25. CPU ALLOCATION
31
  # ==============================================================================
32
  os.environ["OMP_NUM_THREADS"] = "1"
33
  os.environ["OPENBLAS_NUM_THREADS"] = "1"
 
35
 
36
  # --- ML & Data Imports ---
37
  try:
 
 
38
  import torch
39
  from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, TextIteratorStreamer
40
 
41
  # πŸ”₯ STRICT NO-TRAINING LOCK πŸ”₯
42
  torch.set_num_threads(1)
43
  torch.set_grad_enabled(False)
 
44
  ML_AVAILABLE = True
45
  except ImportError:
46
  ML_AVAILABLE = False
47
  print("⚠️ ML packages missing. Check requirements.txt")
48
 
49
  # ==============================================================================
50
+ # SQLITE ANALYTICS SETUP
51
  # ==============================================================================
52
  DB_PATH = "analytics.db"
53
  def init_db():
 
74
  bm25_index = None
75
  trie_root = {}
76
 
77
+ SEARCH_CACHE = TTLCache(maxsize=10000, ttl=1800)
78
  AI_ANSWER_CACHE = LRUCache(maxsize=2000)
79
 
 
 
80
  tokenizer = None
81
  pipe = None
82
  AI_SEMAPHORE = asyncio.Semaphore(1)
 
85
  STOP_WORDS = {"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "is"}
86
 
87
  # ==============================================================================
88
+ # UTILS & TEXT PROCESSING
89
  # ==============================================================================
90
  def update_dynamic_fields(doc):
91
  human = doc.get("human_opens", 0)
 
108
 
109
  if ai >= 25: tags.add("Opened by AI")
110
  if total_interactive > 10 and ai_percent >= 80: tags.add("AI Favorite")
 
111
  doc["special_tags"] = list(tags)
112
 
113
  def normalize_text(text: str) -> str:
 
126
  node = node[char]
127
  node['_end'] = True
128
 
 
 
 
 
 
 
 
129
  def parse_json_field(val):
130
+ if isinstance(val, dict): return val
 
 
131
  if isinstance(val, str):
132
+ try: return json.loads(val)
133
+ except: return {}
 
 
134
  return {}
135
 
136
  def build_searchable_text(doc):
 
143
 
144
  def build_runtime_record(doc):
145
  r = doc.copy()
 
 
146
  r["meta_tags"] = parse_json_field(doc.get("meta_tags"))
147
  r["og_tags"] = parse_json_field(doc.get("og_tags"))
148
  r["specific_data"] = parse_json_field(doc.get("specific_data"))
 
155
 
156
  r["human_opens"] = r.get("human_opens", 0)
157
  r["ai_opens"] = r.get("ai_opens", 0)
 
 
158
  r["trust_score"] = r.get("trust_score", 50.0)
159
  r["quality_score"] = r.get("quality_score", 50.0)
160
  r["learning_score"] = r.get("learning_score", 50.0)
161
  r["search_boost"] = r.get("search_boost", 0.0)
162
  r["spam_score"] = r.get("spam_score", 0.0)
 
163
  r["last_opened"] = r.get("last_opened", datetime.utcnow().isoformat())
164
+ r["language"] = r.get("language", "English")
165
  r["country"] = r.get("country", "Global")
166
 
167
  update_dynamic_fields(r)
168
  return r
169
 
170
+ # ==============================================================================
171
+ # DATASET FETCHING (Direct Raw Method, Bypassing "Train Split" Schema Errors)
172
+ # ==============================================================================
173
+ def load_dataset_direct():
174
+ global bm25_index, raw_data, url_to_data
175
+ hf_token = os.getenv("HF_TOKEN")
176
+ repo_id = "Nebulixlabs/Ultimate-Search-Engine-Dataset"
177
 
178
+ try:
179
+ print("☁️ Fetching raw data files directly from Hugging Face Hub (No datasets library)...")
180
+ api = HfApi()
181
+ files_in_repo = api.list_repo_files(repo_id, repo_type="dataset", token=hf_token)
182
+ jsonl_files = [f for f in files_in_repo if f.endswith(".jsonl") or f.endswith(".json")]
183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  corpus_tokens = []
185
+ for file_name in jsonl_files:
186
+ print(f"πŸ“₯ Downloading {file_name}...")
187
+ local_path = hf_hub_download(repo_id, file_name, repo_type="dataset", token=hf_token)
188
+
189
+ with open(local_path, "r", encoding="utf-8") as f:
190
+ for line in f:
191
+ if not line.strip(): continue
192
  try:
193
  doc = build_runtime_record(json.loads(line))
194
  raw_data.append(doc)
 
196
 
197
  tokens = tokenize(doc["searchable_text"])
198
  corpus_tokens.append(tokens)
 
199
  for t in tokens: add_to_trie(t)
200
+ except Exception as e:
201
+ pass # Ignore broken lines
202
+
203
+ print(f"βœ… Loaded {len(raw_data)} documents safely. Building Search Index...")
204
  bm25_index = BM25Okapi(corpus_tokens)
205
+ print("βœ… Fast BM25 Search Index Ready.")
206
 
207
+ except Exception as e:
208
+ print(f"❌ Failed to load dataset: {e}")
 
209
 
210
+ def init_system():
211
+ global tokenizer, pipe
212
+ init_db()
213
+ print("πŸš€ [1/3] Initializing System...")
214
+
215
+ # 1. Load Data
216
+ load_dataset_direct()
217
+
218
+ # 2. Load GenAI Model
219
+ if ML_AVAILABLE:
220
+ print("πŸ”₯ [2/3] Loading Pre-trained SmolLM2 (360M Params)...")
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
+ model.eval() # STRICT INFERENCE MODE
226
+ pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, device=-1)
227
+ print("βœ… Model successfully loaded in INFERENCE mode.")
228
+ except Exception as e:
229
+ print(f"⚠️ LLM loading failed: {e}. Mocked mode active.")
230
+
231
+ print("βœ… Nebulix v6 Ready. (Vector Embeddings Removed, 100% BM25 Mode)")
232
 
233
  # ==============================================================================
234
+ # RANKING FORMULA (PURE BM25 + SIGNALS)
235
  # ==============================================================================
236
  def get_ranked_results(query: str):
237
  q_tokens = tokenize(query)
238
  if not q_tokens: return []
239
 
240
  bm25_scores = bm25_index.get_scores(q_tokens) if bm25_index else np.zeros(len(raw_data))
 
 
 
 
 
241
 
242
  scored_results = []
 
243
  for i, doc in enumerate(raw_data):
244
+ # Only Keyword Match & Signals (No semantic similarity)
245
+ score = (bm25_scores[i] * 0.80) + \
246
  (doc["trust_score"] * 0.05) + \
247
  (doc["quality_score"] * 0.05) + \
248
+ (doc["learning_score"] * 0.05) + \
249
  (doc["popularity"] * 0.05) + \
250
  doc["search_boost"] - \
251
  doc["spam_score"]
 
280
  i += (512 - 64)
281
 
282
  if not chunks: return []
283
+
284
+ # Simple Keyword Overlap Ranking (Since vector model is removed)
285
+ q_terms = set(query.lower().split())
286
+ scored_chunks = []
287
+ for c in chunks:
288
+ c_lower = c.lower()
289
+ score = sum(1 for q in q_terms if q in c_lower)
290
+ scored_chunks.append((score, c))
291
+
292
+ scored_chunks.sort(key=lambda x: x[0], reverse=True)
293
+ return [c for score, c in scored_chunks[:5]]
294
 
295
  # ==============================================================================
296
+ # TRUE AGENTIC RAG PLANNER
297
  # ==============================================================================
298
  async def agent_planner_call(prompt):
299
  if not pipe: return "ACTION: ANSWER"
 
492
  async def api_stats():
493
  mem = psutil.virtual_memory()
494
  return {
495
+ "engine": "Nebulix v6 HF Production (No Vectors, BM25 Only, No Dataset split='train' error)",
496
  "documents": len(raw_data),
497
  "cpu_usage_percent": psutil.cpu_percent(),
498
  "ram_usage_percent": mem.percent,