Nebulixlabs commited on
Commit
c8d62e8
·
verified ·
1 Parent(s): b23f97a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -223
app.py CHANGED
@@ -16,29 +16,27 @@ from collections import Counter, defaultdict
16
  from contextlib import asynccontextmanager
17
  from typing import List, Dict, Any, Tuple, Set
18
 
19
- from fastapi import FastAPI, Query, Form
20
  from fastapi.middleware.cors import CORSMiddleware
21
  from huggingface_hub import snapshot_download
22
  import torch
23
  import torch.nn.functional as F
24
  import faiss
25
 
26
- # Hugging Face imports
27
- from transformers import AutoModelForCausalLM, AutoTokenizer, AutoModel, pipeline, BitsAndBytesConfig
28
 
29
  # --- CONFIGURATION & MULTIPROCESSING ---
30
  TOTAL_CORES = multiprocessing.cpu_count()
31
- AI_CORES = max(1, int(TOTAL_CORES * 0.50))
32
- torch.set_num_threads(AI_CORES)
33
- print(f"🖥️ Total Cores: {TOTAL_CORES} | Dedicated AI Cores (50%): {AI_CORES} | Dedicated Search Cores: {TOTAL_CORES - AI_CORES}")
34
 
35
- MAX_CONCURRENT_AI = max(1, AI_CORES)
36
- ai_semaphore = asyncio.Semaphore(MAX_CONCURRENT_AI)
37
- ai_generator = None
38
  HF_TOKEN = os.getenv("HF_TOKEN")
39
 
40
- AI_RESPONSE_CACHE = {}
41
- MAX_AI_CACHE_SIZE = 10000
 
42
 
43
  # --- DENSE RETRIEVAL GLOBALS (FAISS + E5) ---
44
  embed_tokenizer = None
@@ -67,16 +65,16 @@ EXPANSION_DICT = {
67
  "js": ["javascript"]
68
  }
69
 
70
- # Added W_DENSE for FAISS score fusion
71
  WEIGHTS = {
72
  "W_BM25": 1.0, "W_TITLE": 4.0, "W_HEADINGS": 3.0, "W_DESCRIPTION": 2.0, "W_BODY": 1.0,
73
  "W_URL": 1.5, "W_DOMAIN": 1.5, "W_PHRASE": 2.5, "W_EXACT": 3.0, "W_FRESHNESS": 1.0,
74
  "W_QUALITY": 1.0, "W_POPULARITY": 0.5, "W_PROXIMITY": 1.5,
75
- "W_ROOT_DOMAIN": 8.0, "W_DENSE": 25.0
76
  }
77
 
78
- MAX_RESULTS_PER_DOMAIN = 3
79
- STRICT_RESULT_LIMIT = 20
80
 
81
  def normalize_text(text: str) -> str:
82
  if not text: return ""
@@ -93,42 +91,26 @@ def tokenize(text: str, remove_stopwords: bool = True) -> List[str]:
93
 
94
  # --- E5 POOLING FUNCTION ---
95
  def average_pool(last_hidden_states, attention_mask):
96
- """Mean pooling for E5 dense embeddings"""
97
  last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
98
  return last_hidden.sum(dim=1) / attention_mask.sum(dim=1)[..., None]
99
 
100
  # --- 2. QUERY INTENT CLASSIFIER ---
101
  def detect_query_intent(query_tokens: List[str]) -> Dict[str, Any]:
102
  intents = []
103
- news_terms = {"latest", "news", "today", "breaking", "recent", "updates", "current", "aaj"}
104
  if any(term in query_tokens for term in news_terms): intents.append("NEWS")
105
  def_terms = {"definition", "meaning", "kya", "matlab"}
106
  if any(term in query_tokens for term in def_terms): intents.append("DEFINITION")
107
  how_terms = {"tutorial", "guide", "steps", "learn", "how", "kaise"}
108
  if any(term in query_tokens for term in how_terms): intents.append("HOW_TO")
109
- comp_terms = {"vs", "versus", "compare", "comparison", "difference", "best", "accha"}
110
  if any(term in query_tokens for term in comp_terms): intents.append("COMPARISON")
111
- local_terms = {"near", "restaurant", "restaurants", "location", "weather", "paas"}
112
  if any(term in query_tokens for term in local_terms): intents.append("LOCAL")
113
  if len(query_tokens) <= 2 and not intents: intents.append("NAVIGATIONAL")
114
  return {"intent": intents[0] if intents else "GENERAL", "signals": intents}
115
 
116
- # --- 3. INTERNAL RAG CHUNKING LOGIC ---
117
- def rank_chunks_for_rag(text: str, query_tokens: List[str]) -> List[str]:
118
- if not text: return []
119
- chunks = text.split("\n\n")
120
- q_set = set(query_tokens)
121
- scored_chunks = []
122
- for c in chunks:
123
- c_toks = set(tokenize(c))
124
- score = len(q_set.intersection(c_toks))
125
- if score > 0: scored_chunks.append((score, c))
126
- if not scored_chunks and chunks: scored_chunks = [(0, c) for c in chunks[:3]]
127
- scored_chunks.sort(reverse=True, key=lambda x: x[0])
128
- return [c for score, c in scored_chunks[:2]]
129
-
130
-
131
- # --- 4. CORE DETERMINISTIC + DENSE HYBRID INDEX ---
132
  class HybridSearchIndex:
133
  def __init__(self):
134
  self.docs = []
@@ -139,9 +121,7 @@ class HybridSearchIndex:
139
  self.df = defaultdict(int)
140
  self.avg_doc_len = 0.0
141
 
142
- # Link dataset doc_id -> Internal List Index for FAISS matching
143
  self.dataset_id_to_internal = {}
144
-
145
  self.inverted_index = defaultdict(lambda: array.array('I'))
146
  self.suggestion_counter = Counter()
147
  self.suggestions = []
@@ -151,7 +131,6 @@ class HybridSearchIndex:
151
  def add_document(self, doc_json: dict):
152
  doc_id = len(self.docs)
153
 
154
- # Map original dataset ID for FAISS metadata linking
155
  dataset_doc_id = doc_json.get("doc_id", "")
156
  if dataset_doc_id:
157
  self.dataset_id_to_internal[dataset_doc_id] = doc_id
@@ -170,7 +149,6 @@ class HybridSearchIndex:
170
 
171
  url = doc_json.get("url", "")
172
  parsed_url = urllib.parse.urlparse(url)
173
-
174
  domain_str = sys.intern(parsed_url.netloc.lower().replace("www.", ""))
175
 
176
  clean_doc = {
@@ -192,7 +170,6 @@ class HybridSearchIndex:
192
  content_tokens = tokenize(clean_doc["_fast_content"])[:250]
193
 
194
  all_tokens = title_tokens + desc_tokens + domain_tokens + headings_tokens + snippet_tokens + content_tokens
195
-
196
  clean_doc["_token_len"] = len(all_tokens)
197
  self.total_len += len(all_tokens)
198
 
@@ -217,10 +194,9 @@ class HybridSearchIndex:
217
  self.avg_doc_len = self.total_len / max(1, self.total_docs)
218
  self.suggestions = self.suggestion_counter.most_common(20000)
219
  self.high_freq_vocab = [word for word, count in Counter(self.df).most_common(15000)]
220
-
221
  self.suggestion_counter.clear()
222
  gc.collect()
223
- print(f"🚀 Indexing Complete! Exactly {self.total_docs} records fully embedded inside Safe RAM Limits.")
224
 
225
  def idf(self, term: str) -> float:
226
  df = self.df.get(term, 0)
@@ -257,17 +233,11 @@ class HybridSearchIndex:
257
 
258
  doc_len = doc.get("_token_len", 1)
259
  avg_dl = max(1.0, self.avg_doc_len)
260
-
261
  content_lower = content.lower()
262
  snippet_lower = snippet.lower()
263
 
264
  for term in query_tokens_sorted:
265
- tf = (title_toks.count(term) * 4 +
266
- headings_toks.count(term) * 3 +
267
- desc_toks.count(term) * 2 +
268
- snippet_lower.count(term) * 2 +
269
- content_lower.count(term))
270
-
271
  if tf > 0:
272
  tf_num = tf * (self.k1 + 1)
273
  tf_den = tf + self.k1 * (1 - self.b + self.b * (doc_len / avg_dl))
@@ -370,24 +340,23 @@ class HybridSearchIndex:
370
  intent = intent_info["intent"]
371
  user_wants_adult = any(aw in query_norm for aw in ADULT_KEYWORDS)
372
 
373
- # 1. Lexical Retrieval (BM25)
374
  candidates_ids = self._get_candidates(sorted_tokens)
375
 
376
- # 2. Dense Semantic Retrieval (FAISS) - True Hybrid Merging
377
  dense_scores_map = {}
378
  if faiss_index is not None and embed_model is not None and embed_tokenizer is not None:
379
  try:
380
- # E5 Requires 'query: ' prefix
381
- embed_inputs = embed_tokenizer(
382
- f"query: {query_norm}", return_tensors="pt", padding=True, truncation=True, max_length=512
383
- )
384
- with torch.no_grad():
385
  embed_outputs = embed_model(**embed_inputs)
386
  xq = average_pool(embed_outputs.last_hidden_state, embed_inputs["attention_mask"])
387
  xq = F.normalize(xq, p=2, dim=1).numpy()
388
 
389
- # Fetch Top 50 Contextual Matches
390
- D, I = faiss_index.search(xq, k=50)
391
 
392
  for score, idx in zip(D[0], I[0]):
393
  if 0 <= idx < len(faiss_metadata):
@@ -395,9 +364,9 @@ class HybridSearchIndex:
395
  if ds_id in self.dataset_id_to_internal:
396
  internal_id = self.dataset_id_to_internal[ds_id]
397
  dense_scores_map[internal_id] = float(score)
398
- candidates_ids.add(internal_id) # Inject Semantic Results into Candidate Pool
399
  except Exception as e:
400
- print(f"⚠️ FAISS Dense Search Error: {e}")
401
 
402
  scored_results = []
403
  seen_domains = defaultdict(int)
@@ -418,11 +387,10 @@ class HybridSearchIndex:
418
  w_title = WEIGHTS["W_TITLE"] + (2.0 if intent == "DEFINITION" else 0.0)
419
  w_phrase = WEIGHTS["W_PHRASE"] + (1.5 if intent in ("DEFINITION", "COMPARISON") else 0.0)
420
 
421
- # Fetch Dense Score (Cosine Sim typically 0.70 - 1.0)
422
  dense_score = dense_scores_map.get(doc_id, 0.0)
423
  features["dense_score"] = dense_score
424
 
425
- # Fusion: Give high boost only if semantic similarity is significantly strong (>0.75)
426
  dense_boost = max(0.0, dense_score - 0.75) * WEIGHTS["W_DENSE"]
427
 
428
  final_score = (
@@ -438,11 +406,10 @@ class HybridSearchIndex:
438
  WEIGHTS["W_ROOT_DOMAIN"] * features["root_domain"] +
439
  WEIGHTS["W_HEADINGS"] * features["headings"] +
440
  WEIGHTS["W_BODY"] * features["body"] +
441
- dense_boost # HYBRID BOOST APPLIED
442
  )
443
 
444
- # True Semantic Safety Check: Don't heavily penalize missing exact keywords if dense similarity is very high!
445
- is_semantic_match = dense_score >= 0.82
446
 
447
  if len(sorted_tokens) > 0:
448
  top_term = sorted_tokens[0]
@@ -452,16 +419,16 @@ class HybridSearchIndex:
452
  doc_headings = tokenize(doc.get("_fast_headings", ""), False)
453
 
454
  if top_term not in doc_title and top_term not in doc_desc and top_term not in doc.get("_fast_domain", "") and top_term not in doc_content and top_term not in doc_headings:
455
- if is_semantic_match: final_score *= 0.6 # Soft semantic penalty
456
- else: final_score *= 0.1 # Harsh Lexical penalty
457
 
458
  if len(sorted_tokens) > 1:
459
  second_term = sorted_tokens[1]
460
  if second_term not in doc_title and second_term not in doc_desc and second_term not in doc.get("_fast_domain", "") and second_term not in doc_content and second_term not in doc_headings:
461
- if is_semantic_match: final_score *= 0.8
462
  else: final_score *= 0.4
463
 
464
- if final_score > 0.05:
465
  doc_out = {
466
  "doc_id": str(doc_id),
467
  "title": doc.get("_fast_title", ""),
@@ -486,7 +453,7 @@ class HybridSearchIndex:
486
  seen_domains[domain] += 1
487
  seen_urls.add(b_url)
488
  diversified_results.append(doc_info)
489
- if len(diversified_results) >= STRICT_RESULT_LIMIT: break
490
 
491
  for idx, item in enumerate(diversified_results): item["rank"] = idx + 1
492
  return diversified_results, intent_info, corrected_query_str
@@ -496,22 +463,18 @@ search_index = HybridSearchIndex()
496
  # --- DATA LOADER (FAISS & LEXICAL) ---
497
  def load_dense_retriever():
498
  global embed_tokenizer, embed_model, faiss_index, faiss_metadata
499
- print("🧠 Loading E5-Small-v2 Embedder for Dense Semantic Search...")
500
  try:
501
  embed_tokenizer = AutoTokenizer.from_pretrained("intfloat/e5-small-v2")
502
- embed_model = AutoModel.from_pretrained("intfloat/e5-small-v2")
503
- embed_model.eval()
504
  print("✅ E5 Embedder Ready!")
505
  except Exception as e:
506
  print(f"❌ E5 Loading Failed: {e}")
507
 
508
- print("📥 Downloading Secured FAISS Index from Nebulixlabs/embedded-data...")
509
  try:
510
- dense_path = snapshot_download(
511
- repo_id="Nebulixlabs/embedded-data",
512
- repo_type="dataset",
513
- token=HF_TOKEN
514
- )
515
  index_file = os.path.join(dense_path, "dataset_embeddings.index")
516
  meta_file = os.path.join(dense_path, "metadata.json")
517
 
@@ -519,23 +482,15 @@ def load_dense_retriever():
519
  faiss_index = faiss.read_index(index_file)
520
  with open(meta_file, "r", encoding="utf-8") as f:
521
  faiss_metadata = json.load(f)
522
- print(f"✅ True Hybrid Enabled! FAISS Index Loaded with {faiss_index.ntotal} vectors.")
523
- else:
524
- print("⚠️ FAISS index files not found in repo.")
525
  except Exception as e:
526
  print(f"❌ FAISS Loading Failed: {e}")
527
 
528
  def load_and_index_dataset():
529
- print("📦 Downloading and Streaming 'web-search-dataset' ...")
530
  try:
531
- dataset_path = snapshot_download(
532
- repo_id="Nebulixlabs/web-search-dataset",
533
- repo_type="dataset",
534
- allow_patterns="data/*.jsonl",
535
- token=HF_TOKEN
536
- )
537
  except Exception as e:
538
- print("⚠️ Direct HF Hub fetch failed, attempting local fallback. Error:", e)
539
  dataset_path = "."
540
 
541
  data_folder = os.path.join(dataset_path, "data")
@@ -544,175 +499,69 @@ def load_and_index_dataset():
544
  if os.path.exists(data_folder):
545
  jsonl_files = [f for f in os.listdir(data_folder) if f.endswith(".jsonl")]
546
  docs_processed = 0
547
-
548
  for file in jsonl_files:
549
- file_path = os.path.join(data_folder, file)
550
- with open(file_path, "r", encoding="utf-8") as f:
551
  for line in f:
552
  if not line.strip(): continue
553
  try:
554
- doc = json.loads(line)
555
- search_index.add_document(doc)
556
  docs_processed += 1
557
-
558
  if docs_processed % 50000 == 0:
559
  gc.collect()
560
- print(f"🔄 Streamed & Indexed {docs_processed} docs so far...")
561
- except Exception:
562
- pass
563
-
564
  search_index.finalize_build()
565
 
566
- # --- AI GENERATION MODULE ---
567
- def load_ai_models():
568
- global ai_generator
569
- print("🤖 Loading HuggingFaceTB/SmolLM2-360M-Instruct (Ultra Fast 4-Bit)...")
570
- try:
571
- model_id = "HuggingFaceTB/SmolLM2-360M-Instruct"
572
- tokenizer = AutoTokenizer.from_pretrained(model_id)
573
- quant_config = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_compute_dtype=torch.float16, bnb_4bit_quant_type="nf4")
574
-
575
- if torch.cuda.is_available():
576
- model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=quant_config, device_map="auto")
577
- else:
578
- model = AutoModelForCausalLM.from_pretrained(model_id, device_map="cpu", torch_dtype=torch.float32)
579
-
580
- ai_generator = pipeline("text-generation", model=model, tokenizer=tokenizer)
581
- print("✅ Fast 4-Bit AI Ready!")
582
- except Exception as e:
583
- print(f"❌ Model Loading Failed: {e}")
584
-
585
- async def run_ai_task(messages, max_tokens: int, query_key: str):
586
- if not ai_generator: return "⚠️ AI Model is starting up."
587
- if query_key in AI_RESPONSE_CACHE: return AI_RESPONSE_CACHE[query_key]
588
-
589
- try: await asyncio.wait_for(ai_semaphore.acquire(), timeout=0.1)
590
- except asyncio.TimeoutError:
591
- return "⚠️ AI Servers are fully occupied right now. Displaying your ultra-fast search results below!"
592
-
593
- try:
594
- def _gen():
595
- tokenizer = ai_generator.tokenizer
596
- try: prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
597
- except: prompt = "\n".join([f"{m['role'].capitalize()}: {m['content']}" for m in messages]) + "\nAssistant: "
598
- result = ai_generator(prompt, max_new_tokens=max_tokens, do_sample=False, truncation=True, return_full_text=False)
599
- return result[0]['generated_text'].strip()
600
-
601
- response = await asyncio.to_thread(_gen)
602
- if len(AI_RESPONSE_CACHE) > MAX_AI_CACHE_SIZE: AI_RESPONSE_CACHE.clear()
603
- AI_RESPONSE_CACHE[query_key] = response
604
- return response
605
- finally:
606
- ai_semaphore.release()
607
-
608
-
609
  # --- FASTAPI APP ENDPOINTS ---
610
  @asynccontextmanager
611
  async def lifespan(app: FastAPI):
612
- # Added Dense Retriever Loader here
613
  asyncio.create_task(asyncio.to_thread(load_dense_retriever))
614
  asyncio.create_task(asyncio.to_thread(load_and_index_dataset))
615
- asyncio.create_task(asyncio.to_thread(load_ai_models))
616
  yield
617
 
618
- app = FastAPI(title="Nebulix Fast Hybrid Search Engine", lifespan=lifespan)
619
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
620
 
621
  @app.get("/suggest")
622
  async def get_suggestions(q: str = Query(..., min_length=1)):
623
  q = normalize_text(q).strip()
624
- matches = [word for word, count in search_index.suggestions if word.startswith(q)][:7]
625
  return {"query": q, "suggestions": matches}
626
 
627
  @app.get("/search")
628
  async def pure_search(q: str = Query(...), content: bool = Query(False), debug: bool = Query(False)):
629
  req_id = str(uuid.uuid4())
 
 
 
 
 
 
 
 
 
630
  results, intent_info, corrected_query = await asyncio.to_thread(search_index.search, q, debug)
631
- top_20 = results[:STRICT_RESULT_LIMIT]
632
 
633
- for r in top_20:
 
 
 
634
  if content: r["content"] = r.get("_internal_content", "")
635
  r.pop("_internal_content", None)
636
 
637
- return {
638
  "request_id": req_id,
639
  "query": q,
640
  "corrected_query": corrected_query,
641
- "results_count": len(top_20),
642
- "results": top_20
643
- }
644
-
645
- async def process_aimode(q: str, include_content: bool, debug: bool, max_ai_tokens: int):
646
- req_id = str(uuid.uuid4())
647
- cache_key = f"ai_{normalize_text(q)}_{max_ai_tokens}"
648
-
649
- results, intent_info, corrected_query = await asyncio.to_thread(search_index.search, q, debug)
650
- top_20 = results[:STRICT_RESULT_LIMIT]
651
-
652
- if cache_key in AI_RESPONSE_CACHE:
653
- for r in top_20:
654
- if include_content: r["content"] = r.get("_internal_content", "")
655
- r.pop("_internal_content", None)
656
-
657
- return {
658
- "request_id": req_id, "query": q, "corrected_query": corrected_query,
659
- "ai_response": AI_RESPONSE_CACHE[cache_key], "results_count": len(top_20),
660
- "results": top_20, "sources": [], "cached": True
661
- }
662
-
663
- query_tokens = tokenize(q)
664
- rag_context_parts = []
665
- used_sources = []
666
-
667
- if top_20:
668
- valid_sources_count = 0
669
- for r in top_20[:3]:
670
- doc_content = r.get("_internal_content", "")
671
- if include_content: r["content"] = doc_content
672
-
673
- if doc_content and valid_sources_count < 3:
674
- best_chunks = rank_chunks_for_rag(doc_content, query_tokens)
675
- if best_chunks:
676
- rag_context_parts.append(f"[{valid_sources_count+1}]: {' ... '.join(best_chunks)}")
677
- used_sources.append({"id": valid_sources_count+1, "title": r["title"], "url": r["url"]})
678
- valid_sources_count += 1
679
-
680
- for r in top_20: r.pop("_internal_content", None)
681
-
682
- system_prompt = (
683
- "You are a helpful AI. Answer directly using the context. "
684
- "CRITICAL RULE: You MUST write EXACTLY one short, complete sentence. "
685
- "Keep it under 15 words. End your sentence with a full stop (.)."
686
- )
687
- combined_input = f"Context:\n{chr(10).join(rag_context_parts)}\n\nQuery: {q}"
688
-
689
- if ai_generator:
690
- tokenizer = ai_generator.tokenizer
691
- encoded_input = tokenizer.encode(combined_input, add_special_tokens=False)
692
- if len(encoded_input) > 90:
693
- encoded_input = encoded_input[:90]
694
- combined_input = tokenizer.decode(encoded_input, skip_special_tokens=True)
695
-
696
- messages = [
697
- {"role": "system", "content": system_prompt},
698
- {"role": "user", "content": combined_input}
699
- ]
700
-
701
- ai_reply = await run_ai_task(messages, max_tokens=max_ai_tokens, query_key=cache_key)
702
-
703
- return {
704
- "request_id": req_id, "query": q, "corrected_query": corrected_query,
705
- "ai_response": ai_reply, "results_count": len(top_20),
706
- "results": top_20, "sources": used_sources, "cached": False
707
  }
708
 
709
- @app.get("/aimode")
710
- async def ai_mode_get(q: str = Query(...), content: bool = Query(False), debug: bool = Query(False)):
711
- return await process_aimode(q, content, debug, max_ai_tokens=20)
712
 
713
- @app.post("/aimode")
714
- async def ai_mode_post(q: str = Form(...), content: bool = Form(False), debug: bool = Form(False)):
715
- return await process_aimode(q, content, debug, max_ai_tokens=20)
716
 
717
  if __name__ == "__main__":
718
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
16
  from contextlib import asynccontextmanager
17
  from typing import List, Dict, Any, Tuple, Set
18
 
19
+ from fastapi import FastAPI, Query
20
  from fastapi.middleware.cors import CORSMiddleware
21
  from huggingface_hub import snapshot_download
22
  import torch
23
  import torch.nn.functional as F
24
  import faiss
25
 
26
+ # Hugging Face imports (Only for embeddings now)
27
+ from transformers import AutoTokenizer, AutoModel
28
 
29
  # --- CONFIGURATION & MULTIPROCESSING ---
30
  TOTAL_CORES = multiprocessing.cpu_count()
31
+ torch.set_num_threads(TOTAL_CORES)
32
+ faiss.omp_set_num_threads(TOTAL_CORES)
33
+ print(f"🖥️ Total Cores Assigned for High-Speed Search: {TOTAL_CORES}")
34
 
 
 
 
35
  HF_TOKEN = os.getenv("HF_TOKEN")
36
 
37
+ # Ultra-fast Cache for handling multiple users seamlessly
38
+ QUERY_CACHE = {}
39
+ MAX_CACHE_SIZE = 50000
40
 
41
  # --- DENSE RETRIEVAL GLOBALS (FAISS + E5) ---
42
  embed_tokenizer = None
 
65
  "js": ["javascript"]
66
  }
67
 
68
+ # Fine-tuned Weights for best Dense + Lexical Fusion
69
  WEIGHTS = {
70
  "W_BM25": 1.0, "W_TITLE": 4.0, "W_HEADINGS": 3.0, "W_DESCRIPTION": 2.0, "W_BODY": 1.0,
71
  "W_URL": 1.5, "W_DOMAIN": 1.5, "W_PHRASE": 2.5, "W_EXACT": 3.0, "W_FRESHNESS": 1.0,
72
  "W_QUALITY": 1.0, "W_POPULARITY": 0.5, "W_PROXIMITY": 1.5,
73
+ "W_ROOT_DOMAIN": 8.0, "W_DENSE": 35.0 # Increased priority for Semantic Matching
74
  }
75
 
76
+ MAX_RESULTS_PER_DOMAIN = 2 # Diversity constraint
77
+ STRICT_RESULT_LIMIT = 5 # ✨ AS REQUESTED: Only return Top 5 URLs to boost speed
78
 
79
  def normalize_text(text: str) -> str:
80
  if not text: return ""
 
91
 
92
  # --- E5 POOLING FUNCTION ---
93
  def average_pool(last_hidden_states, attention_mask):
 
94
  last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
95
  return last_hidden.sum(dim=1) / attention_mask.sum(dim=1)[..., None]
96
 
97
  # --- 2. QUERY INTENT CLASSIFIER ---
98
  def detect_query_intent(query_tokens: List[str]) -> Dict[str, Any]:
99
  intents = []
100
+ news_terms = {"latest", "news", "today", "breaking", "recent", "updates", "current"}
101
  if any(term in query_tokens for term in news_terms): intents.append("NEWS")
102
  def_terms = {"definition", "meaning", "kya", "matlab"}
103
  if any(term in query_tokens for term in def_terms): intents.append("DEFINITION")
104
  how_terms = {"tutorial", "guide", "steps", "learn", "how", "kaise"}
105
  if any(term in query_tokens for term in how_terms): intents.append("HOW_TO")
106
+ comp_terms = {"vs", "versus", "compare", "comparison", "difference", "best"}
107
  if any(term in query_tokens for term in comp_terms): intents.append("COMPARISON")
108
+ local_terms = {"near", "restaurant", "restaurants", "location", "weather"}
109
  if any(term in query_tokens for term in local_terms): intents.append("LOCAL")
110
  if len(query_tokens) <= 2 and not intents: intents.append("NAVIGATIONAL")
111
  return {"intent": intents[0] if intents else "GENERAL", "signals": intents}
112
 
113
+ # --- 3. CORE DETERMINISTIC + DENSE HYBRID INDEX ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  class HybridSearchIndex:
115
  def __init__(self):
116
  self.docs = []
 
121
  self.df = defaultdict(int)
122
  self.avg_doc_len = 0.0
123
 
 
124
  self.dataset_id_to_internal = {}
 
125
  self.inverted_index = defaultdict(lambda: array.array('I'))
126
  self.suggestion_counter = Counter()
127
  self.suggestions = []
 
131
  def add_document(self, doc_json: dict):
132
  doc_id = len(self.docs)
133
 
 
134
  dataset_doc_id = doc_json.get("doc_id", "")
135
  if dataset_doc_id:
136
  self.dataset_id_to_internal[dataset_doc_id] = doc_id
 
149
 
150
  url = doc_json.get("url", "")
151
  parsed_url = urllib.parse.urlparse(url)
 
152
  domain_str = sys.intern(parsed_url.netloc.lower().replace("www.", ""))
153
 
154
  clean_doc = {
 
170
  content_tokens = tokenize(clean_doc["_fast_content"])[:250]
171
 
172
  all_tokens = title_tokens + desc_tokens + domain_tokens + headings_tokens + snippet_tokens + content_tokens
 
173
  clean_doc["_token_len"] = len(all_tokens)
174
  self.total_len += len(all_tokens)
175
 
 
194
  self.avg_doc_len = self.total_len / max(1, self.total_docs)
195
  self.suggestions = self.suggestion_counter.most_common(20000)
196
  self.high_freq_vocab = [word for word, count in Counter(self.df).most_common(15000)]
 
197
  self.suggestion_counter.clear()
198
  gc.collect()
199
+ print(f"🚀 Core Search Engine Indexing Complete! Total Docs: {self.total_docs}")
200
 
201
  def idf(self, term: str) -> float:
202
  df = self.df.get(term, 0)
 
233
 
234
  doc_len = doc.get("_token_len", 1)
235
  avg_dl = max(1.0, self.avg_doc_len)
 
236
  content_lower = content.lower()
237
  snippet_lower = snippet.lower()
238
 
239
  for term in query_tokens_sorted:
240
+ tf = (title_toks.count(term) * 4 + headings_toks.count(term) * 3 + desc_toks.count(term) * 2 + snippet_lower.count(term) * 2 + content_lower.count(term))
 
 
 
 
 
241
  if tf > 0:
242
  tf_num = tf * (self.k1 + 1)
243
  tf_den = tf + self.k1 * (1 - self.b + self.b * (doc_len / avg_dl))
 
340
  intent = intent_info["intent"]
341
  user_wants_adult = any(aw in query_norm for aw in ADULT_KEYWORDS)
342
 
343
+ # 1. Lexical Retrieval (Fast BM25 Base Candidates)
344
  candidates_ids = self._get_candidates(sorted_tokens)
345
 
346
+ # 2. Dense Semantic Retrieval (FAISS - Top Priority)
347
  dense_scores_map = {}
348
  if faiss_index is not None and embed_model is not None and embed_tokenizer is not None:
349
  try:
350
+ embed_inputs = embed_tokenizer(f"query: {query_norm}", return_tensors="pt", padding=True, truncation=True, max_length=512)
351
+
352
+ # Use inference_mode for maximum PyTorch speed
353
+ with torch.inference_mode():
 
354
  embed_outputs = embed_model(**embed_inputs)
355
  xq = average_pool(embed_outputs.last_hidden_state, embed_inputs["attention_mask"])
356
  xq = F.normalize(xq, p=2, dim=1).numpy()
357
 
358
+ # Fetch Top 20 Contextual Matches via FAISS
359
+ D, I = faiss_index.search(xq, k=20)
360
 
361
  for score, idx in zip(D[0], I[0]):
362
  if 0 <= idx < len(faiss_metadata):
 
364
  if ds_id in self.dataset_id_to_internal:
365
  internal_id = self.dataset_id_to_internal[ds_id]
366
  dense_scores_map[internal_id] = float(score)
367
+ candidates_ids.add(internal_id) # Merge into Hybrid Pool
368
  except Exception as e:
369
+ print(f"⚠️ FAISS Search Warning: {e}")
370
 
371
  scored_results = []
372
  seen_domains = defaultdict(int)
 
387
  w_title = WEIGHTS["W_TITLE"] + (2.0 if intent == "DEFINITION" else 0.0)
388
  w_phrase = WEIGHTS["W_PHRASE"] + (1.5 if intent in ("DEFINITION", "COMPARISON") else 0.0)
389
 
 
390
  dense_score = dense_scores_map.get(doc_id, 0.0)
391
  features["dense_score"] = dense_score
392
 
393
+ # Massive Dense Boost (Threshold >= 0.75 gets heavily rewarded)
394
  dense_boost = max(0.0, dense_score - 0.75) * WEIGHTS["W_DENSE"]
395
 
396
  final_score = (
 
406
  WEIGHTS["W_ROOT_DOMAIN"] * features["root_domain"] +
407
  WEIGHTS["W_HEADINGS"] * features["headings"] +
408
  WEIGHTS["W_BODY"] * features["body"] +
409
+ dense_boost
410
  )
411
 
412
+ is_semantic_match = dense_score >= 0.81
 
413
 
414
  if len(sorted_tokens) > 0:
415
  top_term = sorted_tokens[0]
 
419
  doc_headings = tokenize(doc.get("_fast_headings", ""), False)
420
 
421
  if top_term not in doc_title and top_term not in doc_desc and top_term not in doc.get("_fast_domain", "") and top_term not in doc_content and top_term not in doc_headings:
422
+ if is_semantic_match: final_score *= 0.7
423
+ else: final_score *= 0.1
424
 
425
  if len(sorted_tokens) > 1:
426
  second_term = sorted_tokens[1]
427
  if second_term not in doc_title and second_term not in doc_desc and second_term not in doc.get("_fast_domain", "") and second_term not in doc_content and second_term not in doc_headings:
428
+ if is_semantic_match: final_score *= 0.85
429
  else: final_score *= 0.4
430
 
431
+ if final_score > 0.1:
432
  doc_out = {
433
  "doc_id": str(doc_id),
434
  "title": doc.get("_fast_title", ""),
 
453
  seen_domains[domain] += 1
454
  seen_urls.add(b_url)
455
  diversified_results.append(doc_info)
456
+ if len(diversified_results) >= STRICT_RESULT_LIMIT: break # 🔥 Only 5 URLs
457
 
458
  for idx, item in enumerate(diversified_results): item["rank"] = idx + 1
459
  return diversified_results, intent_info, corrected_query_str
 
463
  # --- DATA LOADER (FAISS & LEXICAL) ---
464
  def load_dense_retriever():
465
  global embed_tokenizer, embed_model, faiss_index, faiss_metadata
466
+ print("🧠 Loading Lightning Fast E5-Small-v2 Embedder (CPU Optimized)...")
467
  try:
468
  embed_tokenizer = AutoTokenizer.from_pretrained("intfloat/e5-small-v2")
469
+ # Load directly in eval mode for speed
470
+ embed_model = AutoModel.from_pretrained("intfloat/e5-small-v2").eval()
471
  print("✅ E5 Embedder Ready!")
472
  except Exception as e:
473
  print(f"❌ E5 Loading Failed: {e}")
474
 
475
+ print("📥 Downloading FAISS Index via HF_TOKEN...")
476
  try:
477
+ dense_path = snapshot_download(repo_id="Nebulixlabs/embedded-data", repo_type="dataset", token=HF_TOKEN)
 
 
 
 
478
  index_file = os.path.join(dense_path, "dataset_embeddings.index")
479
  meta_file = os.path.join(dense_path, "metadata.json")
480
 
 
482
  faiss_index = faiss.read_index(index_file)
483
  with open(meta_file, "r", encoding="utf-8") as f:
484
  faiss_metadata = json.load(f)
485
+ print(f"✅ FAISS High-Speed Index Loaded! ({faiss_index.ntotal} vectors)")
 
 
486
  except Exception as e:
487
  print(f"❌ FAISS Loading Failed: {e}")
488
 
489
  def load_and_index_dataset():
490
+ print("📦 Streaming 'web-search-dataset' ...")
491
  try:
492
+ dataset_path = snapshot_download(repo_id="Nebulixlabs/web-search-dataset", repo_type="dataset", allow_patterns="data/*.jsonl", token=HF_TOKEN)
 
 
 
 
 
493
  except Exception as e:
 
494
  dataset_path = "."
495
 
496
  data_folder = os.path.join(dataset_path, "data")
 
499
  if os.path.exists(data_folder):
500
  jsonl_files = [f for f in os.listdir(data_folder) if f.endswith(".jsonl")]
501
  docs_processed = 0
 
502
  for file in jsonl_files:
503
+ with open(os.path.join(data_folder, file), "r", encoding="utf-8") as f:
 
504
  for line in f:
505
  if not line.strip(): continue
506
  try:
507
+ search_index.add_document(json.loads(line))
 
508
  docs_processed += 1
 
509
  if docs_processed % 50000 == 0:
510
  gc.collect()
511
+ print(f"🔄 Indexed {docs_processed} docs...")
512
+ except Exception: pass
 
 
513
  search_index.finalize_build()
514
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
  # --- FASTAPI APP ENDPOINTS ---
516
  @asynccontextmanager
517
  async def lifespan(app: FastAPI):
 
518
  asyncio.create_task(asyncio.to_thread(load_dense_retriever))
519
  asyncio.create_task(asyncio.to_thread(load_and_index_dataset))
 
520
  yield
521
 
522
+ app = FastAPI(title="Nebulix Ultra-Fast Hybrid Engine", lifespan=lifespan)
523
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
524
 
525
  @app.get("/suggest")
526
  async def get_suggestions(q: str = Query(..., min_length=1)):
527
  q = normalize_text(q).strip()
528
+ matches = [word for word, count in search_index.suggestions if word.startswith(q)][:5]
529
  return {"query": q, "suggestions": matches}
530
 
531
  @app.get("/search")
532
  async def pure_search(q: str = Query(...), content: bool = Query(False), debug: bool = Query(False)):
533
  req_id = str(uuid.uuid4())
534
+ cache_key = f"{normalize_text(q)}_{content}_{debug}"
535
+
536
+ # ⚡ Cache Hit: Instant response for concurrent/repeated queries (0.001s)
537
+ if cache_key in QUERY_CACHE:
538
+ cached_response = QUERY_CACHE[cache_key]
539
+ cached_response["request_id"] = req_id
540
+ return cached_response
541
+
542
+ # ⚡ asyncio.to_thread prevents Event Loop blocking so multiple users can search simultaneously
543
  results, intent_info, corrected_query = await asyncio.to_thread(search_index.search, q, debug)
 
544
 
545
+ # ⚡ Strictly Top 5 limits for ultra-fast network transfer
546
+ top_5 = results[:STRICT_RESULT_LIMIT]
547
+
548
+ for r in top_5:
549
  if content: r["content"] = r.get("_internal_content", "")
550
  r.pop("_internal_content", None)
551
 
552
+ final_response = {
553
  "request_id": req_id,
554
  "query": q,
555
  "corrected_query": corrected_query,
556
+ "results_count": len(top_5),
557
+ "results": top_5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558
  }
559
 
560
+ # Save to Cache
561
+ if len(QUERY_CACHE) > MAX_CACHE_SIZE: QUERY_CACHE.clear()
562
+ QUERY_CACHE[cache_key] = final_response
563
 
564
+ return final_response
 
 
565
 
566
  if __name__ == "__main__":
567
+ uvicorn.run(app, host="0.0.0.0", port=7860, log_level="warning")