Nebulixlabs commited on
Commit
a668a4f
·
verified ·
1 Parent(s): 7733e04

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -183
app.py CHANGED
@@ -5,21 +5,20 @@ import os
5
  import uvicorn
6
  import re
7
  import math
8
- import time
9
  import multiprocessing
10
  import urllib.parse
11
  import difflib
 
 
 
12
  from datetime import datetime
13
  from collections import Counter, defaultdict
14
  from contextlib import asynccontextmanager
15
  from typing import List, Dict, Any, Tuple, Set
16
 
17
  from fastapi import FastAPI, Query, Form
18
- from fastapi.responses import JSONResponse
19
  from fastapi.middleware.cors import CORSMiddleware
20
  from huggingface_hub import snapshot_download
21
- import aiohttp
22
- from bs4 import BeautifulSoup
23
  import torch
24
 
25
  # Hugging Face imports
@@ -39,10 +38,6 @@ HF_TOKEN = os.getenv("HF_TOKEN")
39
  AI_RESPONSE_CACHE = {}
40
  MAX_AI_CACHE_SIZE = 10000
41
 
42
- CONTENT_CACHE = {}
43
- MAX_CONTENT_CONCURRENCY = 10
44
- content_semaphore = asyncio.Semaphore(MAX_CONTENT_CONCURRENCY)
45
-
46
  # --- 1. DETERMINISTIC PREPROCESSING & CONSTANTS ---
47
  STOP_WORDS = {"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "is", "are", "by", "this", "that", "it", "what", "who", "where", "when", "why", "how"}
48
  ADULT_KEYWORDS = {
@@ -78,7 +73,6 @@ def normalize_text(text: str) -> str:
78
  if not text: return ""
79
  text = str(text).lower()
80
  text = re.sub(r'[\'’]', '', text)
81
- # \w allows words from any language (Hindi, Marathi, etc.)
82
  text = re.sub(r'[^\w\s\-]', ' ', text)
83
  return " ".join(text.split())
84
 
@@ -91,146 +85,126 @@ def tokenize(text: str, remove_stopwords: bool = True) -> List[str]:
91
  # --- 2. DETERMINISTIC QUERY INTENT CLASSIFIER ---
92
  def detect_query_intent(query_tokens: List[str]) -> Dict[str, Any]:
93
  intents = []
94
-
95
  news_terms = {"latest", "news", "today", "breaking", "recent", "updates", "current", "aaj"}
96
  if any(term in query_tokens for term in news_terms): intents.append("NEWS")
97
-
98
  def_terms = {"definition", "meaning", "kya", "matlab"}
99
  if any(term in query_tokens for term in def_terms): intents.append("DEFINITION")
100
-
101
  how_terms = {"tutorial", "guide", "steps", "learn", "how", "kaise"}
102
  if any(term in query_tokens for term in how_terms): intents.append("HOW_TO")
103
-
104
  comp_terms = {"vs", "versus", "compare", "comparison", "difference", "best", "accha"}
105
  if any(term in query_tokens for term in comp_terms): intents.append("COMPARISON")
106
-
107
  local_terms = {"near", "restaurant", "restaurants", "location", "weather", "paas"}
108
  if any(term in query_tokens for term in local_terms): intents.append("LOCAL")
109
-
110
  if len(query_tokens) <= 2 and not intents: intents.append("NAVIGATIONAL")
111
-
112
  return {"intent": intents[0] if intents else "GENERAL", "signals": intents}
113
 
114
- # --- 3. DEDICATED WEB CONTENT EXTRACTION ---
115
- async def extract_page_content(url: str) -> Dict[str, Any]:
116
- if url in search_index.url_to_doc:
117
- doc = search_index.url_to_doc[url]
118
- if doc.get("_fast_content"):
119
- return {"url": url, "title": doc.get("_fast_title", ""), "content": doc.get("_fast_content", ""), "success": True}
120
-
121
- if url in CONTENT_CACHE:
122
- return CONTENT_CACHE[url]
123
-
124
- async with content_semaphore:
125
- try:
126
- async with aiohttp.ClientSession() as session:
127
- async with session.get(url, timeout=3.0, headers={"User-Agent": "Mozilla/5.0 NebulixBot"}) as resp:
128
- if resp.status != 200:
129
- return {"url": url, "title": "", "content": "", "success": False, "error": f"HTTP {resp.status}"}
130
-
131
- html = await resp.text()
132
- soup = BeautifulSoup(html, "html.parser")
133
-
134
- for tag in soup(['script', 'style', 'nav', 'footer', 'header', 'aside', 'noscript', 'form', 'button', 'iframe']):
135
- tag.decompose()
136
-
137
- blocks = []
138
- for tag in soup.find_all(['h1', 'h2', 'h3', 'p', 'article', 'main', 'li']):
139
- text = tag.get_text(separator=' ', strip=True)
140
- if len(text) > 30: blocks.append(text)
141
-
142
- clean_blocks = []
143
- seen_blocks = set()
144
- for b in blocks:
145
- if b not in seen_blocks:
146
- seen_blocks.add(b)
147
- clean_blocks.append(b)
148
-
149
- content_str = "\n\n".join(clean_blocks)
150
- page_title = soup.title.string.strip() if soup.title and soup.title.string else ""
151
-
152
- result = {"url": url, "title": page_title, "content": content_str, "success": True}
153
- CONTENT_CACHE[url] = result
154
- return result
155
- except Exception as e:
156
- return {"url": url, "title": "", "content": "", "success": False, "error": str(e)}
157
-
158
  def rank_chunks_for_rag(text: str, query_tokens: List[str]) -> List[str]:
159
  if not text: return []
160
  chunks = text.split("\n\n")
161
  q_set = set(query_tokens)
162
  scored_chunks = []
163
-
164
  for c in chunks:
165
  c_toks = set(tokenize(c))
166
  score = len(q_set.intersection(c_toks))
167
  if score > 0: scored_chunks.append((score, c))
168
-
169
  if not scored_chunks and chunks: scored_chunks = [(0, c) for c in chunks[:3]]
170
  scored_chunks.sort(reverse=True, key=lambda x: x[0])
171
  return [c for score, c in scored_chunks[:2]]
172
 
173
- # --- 4. CORE DETERMINISTIC SEARCH INDEX ---
 
174
  class HybridSearchIndex:
175
  def __init__(self):
176
  self.docs = []
177
- self.url_to_doc = {}
178
  self.total_docs = 0
 
179
  self.vocab = set()
180
  self.high_freq_vocab = []
181
  self.df = defaultdict(int)
182
  self.avg_doc_len = 0.0
183
- self.inverted_index = defaultdict(set)
 
 
 
 
184
  self.suggestions = []
185
  self.k1 = 1.5
186
  self.b = 0.75
187
 
188
- def build(self, raw_data: List[dict]):
189
- print(f"📥 Building Advanced Deterministic Search Index from {len(raw_data)} documents...")
190
- total_len = 0
191
- suggestion_counter = Counter()
192
-
193
- for doc_id, doc in enumerate(raw_data):
194
- self.docs.append(doc)
195
- self.url_to_doc[doc.get("url", "")] = doc
196
-
197
- # Using partial content tokens to save memory while maximizing recall
198
- title_tokens = tokenize(doc.get("_fast_title", ""))
199
- desc_tokens = tokenize(doc.get("_fast_desc", ""))
200
- domain_tokens = tokenize(doc.get("_fast_domain", ""))
201
- headings_tokens = tokenize(doc.get("_fast_headings", ""))
202
- snippet_tokens = tokenize(doc.get("_fast_snippet", ""))
203
- content_tokens = tokenize(doc.get("_fast_content", ""))[:400]
204
-
205
- all_tokens = title_tokens + desc_tokens + domain_tokens + headings_tokens + snippet_tokens + content_tokens
206
-
207
- doc["_token_len"] = len(all_tokens)
208
- total_len += len(all_tokens)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
 
210
- # Use distinct tokens to speed up df counting
211
- distinct_tokens = set(all_tokens)
212
- for term in distinct_tokens:
213
- self.inverted_index[term].add(doc_id)
214
- self.df[term] += 1
215
- self.vocab.add(term)
216
-
217
- if not doc.get("_is_adult", False):
218
- clean_title = [w for w in title_tokens if w not in ADULT_KEYWORDS and len(w) > 2]
219
- for w in clean_title: suggestion_counter[w] += 1
220
-
221
- d_clean = doc.get("_fast_domain", "").split('.')[0]
222
- if len(d_clean) > 2 and d_clean not in ADULT_KEYWORDS: suggestion_counter[d_clean] += 5
223
 
 
224
  self.total_docs = len(self.docs)
225
- self.avg_doc_len = total_len / max(1, self.total_docs)
226
- self.suggestions = suggestion_counter.most_common(20000)
227
- # Store high freq words for fast spell correction (Did you mean)
228
  self.high_freq_vocab = [word for word, count in Counter(self.df).most_common(15000)]
229
- print(f"🚀 Indexing Complete! {self.total_docs} records fully embedded.")
 
 
 
230
 
231
  def idf(self, term: str) -> float:
232
  df = self.df.get(term, 0)
233
- total = max(1, self.total_docs) # Prevents division by zero
234
  return math.log(1.0 + (total - df + 0.5) / (df + 0.5))
235
 
236
  def _get_candidates(self, query_tokens: List[str]) -> Set[int]:
@@ -238,7 +212,6 @@ class HybridSearchIndex:
238
  for term in query_tokens:
239
  if term in self.inverted_index: candidates.update(self.inverted_index[term])
240
 
241
- # Fast fallback for slight misspellings during retrieval
242
  if len(candidates) < 10 and self.high_freq_vocab:
243
  for term in query_tokens:
244
  if term not in self.inverted_index:
@@ -263,7 +236,7 @@ class HybridSearchIndex:
263
  scores = {"bm25": 0.0, "title": 0.0, "phrase": 0.0, "exact": 0.0, "url": 0.0, "domain": 0.0, "quality": 0.0, "freshness": 0.0, "proximity": 0.0, "root_domain": 0.0, "headings": 0.0, "body": 0.0}
264
 
265
  doc_len = doc.get("_token_len", 1)
266
- avg_dl = max(1.0, self.avg_doc_len) # Fixed ZeroDivisionError
267
 
268
  content_lower = content.lower()
269
  snippet_lower = snippet.lower()
@@ -330,27 +303,23 @@ class HybridSearchIndex:
330
  if len(desc_toks) >= 10: q_score += 0.2
331
  if doc.get("url", "").startswith("https"): q_score += 0.2
332
 
333
- # Code-based Domain Authority Boost
334
  if domain.endswith(".edu") or domain.endswith(".gov"): q_score += 0.4
335
  elif domain.endswith(".org"): q_score += 0.15
336
 
337
  if len(title_toks) > 0 and (len(set(title_toks)) / len(title_toks)) < 0.5: q_score -= 0.3
338
  scores["quality"] = max(0.0, min(1.0, q_score))
339
 
340
- if url == "" or url == "/":
341
- scores["root_domain"] = 1.0
342
 
343
  return scores
344
 
345
  def search(self, query: str, debug: bool = False) -> Tuple[List[dict], Dict[str, Any], str]:
346
- # Handle search request if indexing is still loading or totally empty
347
  if self.total_docs == 0:
348
  return [], {"intent": "GENERAL", "signals": []}, None
349
 
350
  query_norm = normalize_text(query)
351
  raw_tokens = query_norm.split()
352
 
353
- # Spell Correction ("Did you mean / Showing results for")
354
  corrected_tokens = []
355
  is_corrected = False
356
 
@@ -359,7 +328,6 @@ class HybridSearchIndex:
359
  if t in self.vocab or t in EXPANSION_DICT or t in ADULT_KEYWORDS:
360
  corrected_tokens.append(t)
361
  else:
362
- # Fast correction using pre-computed high frequency words
363
  matches = difflib.get_close_matches(t, self.high_freq_vocab, n=1, cutoff=0.85)
364
  if matches:
365
  corrected_tokens.append(matches[0])
@@ -377,11 +345,9 @@ class HybridSearchIndex:
377
  if t in EXPANSION_DICT: expanded_tokens.extend(EXPANSION_DICT[t])
378
 
379
  sorted_tokens = list(dict.fromkeys(expanded_tokens))
380
- if not sorted_tokens:
381
- sorted_tokens = corrected_tokens
382
 
383
  sorted_tokens.sort(key=lambda t: self.idf(t), reverse=True)
384
-
385
  intent_info = detect_query_intent(sorted_tokens)
386
  intent = intent_info["intent"]
387
  user_wants_adult = any(aw in query_norm for aw in ADULT_KEYWORDS)
@@ -438,7 +404,7 @@ class HybridSearchIndex:
438
 
439
  if final_score > 0.05:
440
  doc_out = {
441
- "doc_id": doc.get("doc_id", ""),
442
  "title": doc.get("_fast_title", ""),
443
  "description": doc.get("_fast_desc", ""),
444
  "snippet": doc.get("_fast_snippet", ""),
@@ -469,8 +435,7 @@ class HybridSearchIndex:
469
  search_index = HybridSearchIndex()
470
 
471
  def load_and_index_dataset():
472
- print("📦 Automatically Downloading 'Nebulixlabs/web-search-dataset' ...")
473
- dataset_path = "."
474
  try:
475
  dataset_path = snapshot_download(
476
  repo_id="Nebulixlabs/web-search-dataset",
@@ -480,14 +445,15 @@ def load_and_index_dataset():
480
  )
481
  except Exception as e:
482
  print("⚠️ Direct HF Hub fetch failed, attempting local 'data' folder mapping. Error:", e)
 
483
 
484
- raw_data = []
485
  data_folder = os.path.join(dataset_path, "data")
486
- if not os.path.exists(data_folder):
487
- data_folder = "data"
488
 
489
  if os.path.exists(data_folder):
490
  jsonl_files = [f for f in os.listdir(data_folder) if f.endswith(".jsonl")]
 
 
491
  for file in jsonl_files:
492
  file_path = os.path.join(data_folder, file)
493
  with open(file_path, "r", encoding="utf-8") as f:
@@ -495,39 +461,16 @@ def load_and_index_dataset():
495
  if not line.strip(): continue
496
  try:
497
  doc = json.loads(line)
498
- url = doc.get("url", "")
499
-
500
- metadata = doc.get("metadata") or {}
501
- content_dict = doc.get("content") or {}
502
-
503
- title_str = metadata.get("title") or ""
504
- desc_str = metadata.get("description") or ""
505
- snippet_str = content_dict.get("snippet") or ""
506
- main_text = content_dict.get("main_text") or ""
507
-
508
- headings = content_dict.get("headings") or {}
509
- h1_str = " ".join(headings.get("h1") or [])
510
- h2_str = " ".join(headings.get("h2") or [])
511
-
512
- parsed_url = urllib.parse.urlparse(url)
513
 
514
- doc["_fast_domain"] = doc.get("domain") or parsed_url.netloc.lower().replace("www.", "")
515
- doc["_fast_path"] = parsed_url.path.strip("/")
516
- doc["_fast_title"] = title_str
517
- doc["_fast_desc"] = desc_str
518
- doc["_fast_snippet"] = snippet_str
519
- doc["_fast_content"] = main_text
520
- doc["_fast_headings"] = h1_str + " " + h2_str
521
-
522
- combined_adult_check = f"{title_str} {desc_str} {url} {snippet_str} {main_text[:200]}".lower()
523
- # Fast Regex Adult Match
524
- doc["_is_adult"] = bool(ADULT_REGEX.search(combined_adult_check))
525
-
526
- raw_data.append(doc)
527
  except Exception:
528
  pass
529
-
530
- search_index.build(raw_data)
531
 
532
  # --- AI GENERATION MODULE ---
533
  def load_ai_models():
@@ -554,22 +497,19 @@ async def run_ai_task(messages, max_tokens: int, query_key: str):
554
 
555
  try: await asyncio.wait_for(ai_semaphore.acquire(), timeout=0.1)
556
  except asyncio.TimeoutError:
557
- return "⚠️ AI Servers are fully occupied right now. Displaying your ultra-fast search results below! (Please try AI Mode again in a few seconds)"
558
 
559
  try:
560
  def _gen():
561
  tokenizer = ai_generator.tokenizer
562
  try: prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
563
  except: prompt = "\n".join([f"{m['role'].capitalize()}: {m['content']}" for m in messages]) + "\nAssistant: "
564
-
565
  result = ai_generator(prompt, max_new_tokens=max_tokens, do_sample=False, truncation=True, return_full_text=False)
566
  return result[0]['generated_text'].strip()
567
 
568
  response = await asyncio.to_thread(_gen)
569
-
570
  if len(AI_RESPONSE_CACHE) > MAX_AI_CACHE_SIZE: AI_RESPONSE_CACHE.clear()
571
  AI_RESPONSE_CACHE[query_key] = response
572
-
573
  return response
574
  finally:
575
  ai_semaphore.release()
@@ -591,11 +531,6 @@ async def get_suggestions(q: str = Query(..., min_length=1)):
591
  matches = [word for word, count in search_index.suggestions if word.startswith(q)][:7]
592
  return {"query": q, "suggestions": matches}
593
 
594
- @app.get("/content")
595
- async def get_content_endpoint(url: str = Query(...)):
596
- res = await extract_page_content(url)
597
- return res
598
-
599
  @app.get("/search")
600
  async def pure_search(q: str = Query(...), content: bool = Query(False), debug: bool = Query(False)):
601
  req_id = str(uuid.uuid4())
@@ -603,8 +538,7 @@ async def pure_search(q: str = Query(...), content: bool = Query(False), debug:
603
  top_20 = results[:STRICT_RESULT_LIMIT]
604
 
605
  for r in top_20:
606
- if content:
607
- r["content"] = r.get("_internal_content", "")
608
  r.pop("_internal_content", None)
609
 
610
  return {
@@ -628,14 +562,9 @@ async def process_aimode(q: str, include_content: bool, debug: bool, max_ai_toke
628
  r.pop("_internal_content", None)
629
 
630
  return {
631
- "request_id": req_id,
632
- "query": q,
633
- "corrected_query": corrected_query,
634
- "ai_response": AI_RESPONSE_CACHE[cache_key],
635
- "results_count": len(top_20),
636
- "results": top_20,
637
- "sources": [],
638
- "cached": True
639
  }
640
 
641
  query_tokens = tokenize(q)
@@ -646,9 +575,7 @@ async def process_aimode(q: str, include_content: bool, debug: bool, max_ai_toke
646
  valid_sources_count = 0
647
  for r in top_20[:3]:
648
  doc_content = r.get("_internal_content", "")
649
-
650
- if include_content:
651
- r["content"] = doc_content
652
 
653
  if doc_content and valid_sources_count < 3:
654
  best_chunks = rank_chunks_for_rag(doc_content, query_tokens)
@@ -657,8 +584,7 @@ async def process_aimode(q: str, include_content: bool, debug: bool, max_ai_toke
657
  used_sources.append({"id": valid_sources_count+1, "title": r["title"], "url": r["url"]})
658
  valid_sources_count += 1
659
 
660
- for r in top_20:
661
- r.pop("_internal_content", None)
662
 
663
  system_prompt = (
664
  "You are a helpful AI. Answer directly using the context. "
@@ -682,14 +608,9 @@ async def process_aimode(q: str, include_content: bool, debug: bool, max_ai_toke
682
  ai_reply = await run_ai_task(messages, max_tokens=max_ai_tokens, query_key=cache_key)
683
 
684
  return {
685
- "request_id": req_id,
686
- "query": q,
687
- "corrected_query": corrected_query,
688
- "ai_response": ai_reply,
689
- "results_count": len(top_20),
690
- "results": top_20,
691
- "sources": used_sources,
692
- "cached": False
693
  }
694
 
695
  @app.get("/aimode")
 
5
  import uvicorn
6
  import re
7
  import math
 
8
  import multiprocessing
9
  import urllib.parse
10
  import difflib
11
+ import gc
12
+ import sys
13
+ import array
14
  from datetime import datetime
15
  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
 
24
  # Hugging Face imports
 
38
  AI_RESPONSE_CACHE = {}
39
  MAX_AI_CACHE_SIZE = 10000
40
 
 
 
 
 
41
  # --- 1. DETERMINISTIC PREPROCESSING & CONSTANTS ---
42
  STOP_WORDS = {"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "is", "are", "by", "this", "that", "it", "what", "who", "where", "when", "why", "how"}
43
  ADULT_KEYWORDS = {
 
73
  if not text: return ""
74
  text = str(text).lower()
75
  text = re.sub(r'[\'’]', '', text)
 
76
  text = re.sub(r'[^\w\s\-]', ' ', text)
77
  return " ".join(text.split())
78
 
 
85
  # --- 2. DETERMINISTIC QUERY INTENT CLASSIFIER ---
86
  def detect_query_intent(query_tokens: List[str]) -> Dict[str, Any]:
87
  intents = []
 
88
  news_terms = {"latest", "news", "today", "breaking", "recent", "updates", "current", "aaj"}
89
  if any(term in query_tokens for term in news_terms): intents.append("NEWS")
 
90
  def_terms = {"definition", "meaning", "kya", "matlab"}
91
  if any(term in query_tokens for term in def_terms): intents.append("DEFINITION")
 
92
  how_terms = {"tutorial", "guide", "steps", "learn", "how", "kaise"}
93
  if any(term in query_tokens for term in how_terms): intents.append("HOW_TO")
 
94
  comp_terms = {"vs", "versus", "compare", "comparison", "difference", "best", "accha"}
95
  if any(term in query_tokens for term in comp_terms): intents.append("COMPARISON")
 
96
  local_terms = {"near", "restaurant", "restaurants", "location", "weather", "paas"}
97
  if any(term in query_tokens for term in local_terms): intents.append("LOCAL")
 
98
  if len(query_tokens) <= 2 and not intents: intents.append("NAVIGATIONAL")
 
99
  return {"intent": intents[0] if intents else "GENERAL", "signals": intents}
100
 
101
+ # --- 3. INTERNAL RAG CHUNKING LOGIC ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  def rank_chunks_for_rag(text: str, query_tokens: List[str]) -> List[str]:
103
  if not text: return []
104
  chunks = text.split("\n\n")
105
  q_set = set(query_tokens)
106
  scored_chunks = []
 
107
  for c in chunks:
108
  c_toks = set(tokenize(c))
109
  score = len(q_set.intersection(c_toks))
110
  if score > 0: scored_chunks.append((score, c))
 
111
  if not scored_chunks and chunks: scored_chunks = [(0, c) for c in chunks[:3]]
112
  scored_chunks.sort(reverse=True, key=lambda x: x[0])
113
  return [c for score, c in scored_chunks[:2]]
114
 
115
+
116
+ # --- 4. CORE DETERMINISTIC SEARCH INDEX (STREAMING ARCHITECTURE) ---
117
  class HybridSearchIndex:
118
  def __init__(self):
119
  self.docs = []
 
120
  self.total_docs = 0
121
+ self.total_len = 0
122
  self.vocab = set()
123
  self.high_freq_vocab = []
124
  self.df = defaultdict(int)
125
  self.avg_doc_len = 0.0
126
+
127
+ # MEMORY SAVER: C-style array index, saves >90% memory over sets
128
+ self.inverted_index = defaultdict(lambda: array.array('I'))
129
+
130
+ self.suggestion_counter = Counter()
131
  self.suggestions = []
132
  self.k1 = 1.5
133
  self.b = 0.75
134
 
135
+ def add_document(self, doc_json: dict):
136
+ doc_id = len(self.docs)
137
+
138
+ metadata = doc_json.get("metadata") or {}
139
+ content_dict = doc_json.get("content") or {}
140
+
141
+ title_str = metadata.get("title") or ""
142
+ desc_str = metadata.get("description") or ""
143
+ snippet_str = content_dict.get("snippet") or ""
144
+ main_text = (content_dict.get("main_text") or "")[:800] # Safe crop for 16GB
145
+
146
+ headings = content_dict.get("headings") or {}
147
+ h1_str = " ".join(headings.get("h1") or [])
148
+ h2_str = " ".join(headings.get("h2") or [])
149
+
150
+ url = doc_json.get("url", "")
151
+ parsed_url = urllib.parse.urlparse(url)
152
+
153
+ # MEMORY SAVER: sys.intern deduplicates domain strings globally in RAM
154
+ domain_str = sys.intern(parsed_url.netloc.lower().replace("www.", ""))
155
+
156
+ clean_doc = {
157
+ "url": url,
158
+ "_fast_domain": domain_str,
159
+ "_fast_path": parsed_url.path.strip("/"),
160
+ "_fast_title": title_str[:150],
161
+ "_fast_desc": desc_str[:250],
162
+ "_fast_snippet": snippet_str[:300],
163
+ "_fast_content": main_text,
164
+ "_fast_headings": (h1_str + " " + h2_str)[:200]
165
+ }
166
+
167
+ title_tokens = tokenize(clean_doc["_fast_title"])
168
+ desc_tokens = tokenize(clean_doc["_fast_desc"])
169
+ domain_tokens = tokenize(clean_doc["_fast_domain"])
170
+ headings_tokens = tokenize(clean_doc["_fast_headings"])
171
+ snippet_tokens = tokenize(clean_doc["_fast_snippet"])
172
+ content_tokens = tokenize(clean_doc["_fast_content"])[:250]
173
+
174
+ all_tokens = title_tokens + desc_tokens + domain_tokens + headings_tokens + snippet_tokens + content_tokens
175
+
176
+ clean_doc["_token_len"] = len(all_tokens)
177
+ self.total_len += len(all_tokens)
178
+
179
+ combined_adult_check = f"{title_str} {desc_str} {url} {snippet_str} {main_text[:200]}".lower()
180
+ clean_doc["_is_adult"] = bool(ADULT_REGEX.search(combined_adult_check))
181
+
182
+ self.docs.append(clean_doc)
183
+
184
+ distinct_tokens = set(all_tokens)
185
+ for term in distinct_tokens:
186
+ self.inverted_index[term].append(doc_id)
187
+ self.df[term] += 1
188
+ self.vocab.add(term)
189
 
190
+ if not clean_doc["_is_adult"]:
191
+ clean_title = [w for w in title_tokens if w not in ADULT_KEYWORDS and len(w) > 2]
192
+ for w in clean_title: self.suggestion_counter[w] += 1
193
+ if len(domain_str) > 2 and domain_str not in ADULT_KEYWORDS: self.suggestion_counter[domain_str] += 5
 
 
 
 
 
 
 
 
 
194
 
195
+ def finalize_build(self):
196
  self.total_docs = len(self.docs)
197
+ self.avg_doc_len = self.total_len / max(1, self.total_docs)
198
+ self.suggestions = self.suggestion_counter.most_common(20000)
 
199
  self.high_freq_vocab = [word for word, count in Counter(self.df).most_common(15000)]
200
+
201
+ self.suggestion_counter.clear()
202
+ gc.collect()
203
+ print(f"🚀 Indexing Complete! Exactly {self.total_docs} records fully embedded inside Safe RAM Limits.")
204
 
205
  def idf(self, term: str) -> float:
206
  df = self.df.get(term, 0)
207
+ total = max(1, self.total_docs)
208
  return math.log(1.0 + (total - df + 0.5) / (df + 0.5))
209
 
210
  def _get_candidates(self, query_tokens: List[str]) -> Set[int]:
 
212
  for term in query_tokens:
213
  if term in self.inverted_index: candidates.update(self.inverted_index[term])
214
 
 
215
  if len(candidates) < 10 and self.high_freq_vocab:
216
  for term in query_tokens:
217
  if term not in self.inverted_index:
 
236
  scores = {"bm25": 0.0, "title": 0.0, "phrase": 0.0, "exact": 0.0, "url": 0.0, "domain": 0.0, "quality": 0.0, "freshness": 0.0, "proximity": 0.0, "root_domain": 0.0, "headings": 0.0, "body": 0.0}
237
 
238
  doc_len = doc.get("_token_len", 1)
239
+ avg_dl = max(1.0, self.avg_doc_len)
240
 
241
  content_lower = content.lower()
242
  snippet_lower = snippet.lower()
 
303
  if len(desc_toks) >= 10: q_score += 0.2
304
  if doc.get("url", "").startswith("https"): q_score += 0.2
305
 
 
306
  if domain.endswith(".edu") or domain.endswith(".gov"): q_score += 0.4
307
  elif domain.endswith(".org"): q_score += 0.15
308
 
309
  if len(title_toks) > 0 and (len(set(title_toks)) / len(title_toks)) < 0.5: q_score -= 0.3
310
  scores["quality"] = max(0.0, min(1.0, q_score))
311
 
312
+ if url == "" or url == "/": scores["root_domain"] = 1.0
 
313
 
314
  return scores
315
 
316
  def search(self, query: str, debug: bool = False) -> Tuple[List[dict], Dict[str, Any], str]:
 
317
  if self.total_docs == 0:
318
  return [], {"intent": "GENERAL", "signals": []}, None
319
 
320
  query_norm = normalize_text(query)
321
  raw_tokens = query_norm.split()
322
 
 
323
  corrected_tokens = []
324
  is_corrected = False
325
 
 
328
  if t in self.vocab or t in EXPANSION_DICT or t in ADULT_KEYWORDS:
329
  corrected_tokens.append(t)
330
  else:
 
331
  matches = difflib.get_close_matches(t, self.high_freq_vocab, n=1, cutoff=0.85)
332
  if matches:
333
  corrected_tokens.append(matches[0])
 
345
  if t in EXPANSION_DICT: expanded_tokens.extend(EXPANSION_DICT[t])
346
 
347
  sorted_tokens = list(dict.fromkeys(expanded_tokens))
348
+ if not sorted_tokens: sorted_tokens = corrected_tokens
 
349
 
350
  sorted_tokens.sort(key=lambda t: self.idf(t), reverse=True)
 
351
  intent_info = detect_query_intent(sorted_tokens)
352
  intent = intent_info["intent"]
353
  user_wants_adult = any(aw in query_norm for aw in ADULT_KEYWORDS)
 
404
 
405
  if final_score > 0.05:
406
  doc_out = {
407
+ "doc_id": str(doc_id),
408
  "title": doc.get("_fast_title", ""),
409
  "description": doc.get("_fast_desc", ""),
410
  "snippet": doc.get("_fast_snippet", ""),
 
435
  search_index = HybridSearchIndex()
436
 
437
  def load_and_index_dataset():
438
+ print("📦 Automatically Downloading and Streaming 'web-search-dataset' ...")
 
439
  try:
440
  dataset_path = snapshot_download(
441
  repo_id="Nebulixlabs/web-search-dataset",
 
445
  )
446
  except Exception as e:
447
  print("⚠️ Direct HF Hub fetch failed, attempting local 'data' folder mapping. Error:", e)
448
+ dataset_path = "."
449
 
 
450
  data_folder = os.path.join(dataset_path, "data")
451
+ if not os.path.exists(data_folder): data_folder = "data"
 
452
 
453
  if os.path.exists(data_folder):
454
  jsonl_files = [f for f in os.listdir(data_folder) if f.endswith(".jsonl")]
455
+ docs_processed = 0
456
+
457
  for file in jsonl_files:
458
  file_path = os.path.join(data_folder, file)
459
  with open(file_path, "r", encoding="utf-8") as f:
 
461
  if not line.strip(): continue
462
  try:
463
  doc = json.loads(line)
464
+ search_index.add_document(doc)
465
+ docs_processed += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
466
 
467
+ if docs_processed % 50000 == 0:
468
+ gc.collect()
469
+ print(f"🔄 Streamed & Indexed {docs_processed} docs so far...")
 
 
 
 
 
 
 
 
 
 
470
  except Exception:
471
  pass
472
+
473
+ search_index.finalize_build()
474
 
475
  # --- AI GENERATION MODULE ---
476
  def load_ai_models():
 
497
 
498
  try: await asyncio.wait_for(ai_semaphore.acquire(), timeout=0.1)
499
  except asyncio.TimeoutError:
500
+ return "⚠️ AI Servers are fully occupied right now. Displaying your ultra-fast search results below!"
501
 
502
  try:
503
  def _gen():
504
  tokenizer = ai_generator.tokenizer
505
  try: prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
506
  except: prompt = "\n".join([f"{m['role'].capitalize()}: {m['content']}" for m in messages]) + "\nAssistant: "
 
507
  result = ai_generator(prompt, max_new_tokens=max_tokens, do_sample=False, truncation=True, return_full_text=False)
508
  return result[0]['generated_text'].strip()
509
 
510
  response = await asyncio.to_thread(_gen)
 
511
  if len(AI_RESPONSE_CACHE) > MAX_AI_CACHE_SIZE: AI_RESPONSE_CACHE.clear()
512
  AI_RESPONSE_CACHE[query_key] = response
 
513
  return response
514
  finally:
515
  ai_semaphore.release()
 
531
  matches = [word for word, count in search_index.suggestions if word.startswith(q)][:7]
532
  return {"query": q, "suggestions": matches}
533
 
 
 
 
 
 
534
  @app.get("/search")
535
  async def pure_search(q: str = Query(...), content: bool = Query(False), debug: bool = Query(False)):
536
  req_id = str(uuid.uuid4())
 
538
  top_20 = results[:STRICT_RESULT_LIMIT]
539
 
540
  for r in top_20:
541
+ if content: r["content"] = r.get("_internal_content", "")
 
542
  r.pop("_internal_content", None)
543
 
544
  return {
 
562
  r.pop("_internal_content", None)
563
 
564
  return {
565
+ "request_id": req_id, "query": q, "corrected_query": corrected_query,
566
+ "ai_response": AI_RESPONSE_CACHE[cache_key], "results_count": len(top_20),
567
+ "results": top_20, "sources": [], "cached": True
 
 
 
 
 
568
  }
569
 
570
  query_tokens = tokenize(q)
 
575
  valid_sources_count = 0
576
  for r in top_20[:3]:
577
  doc_content = r.get("_internal_content", "")
578
+ if include_content: r["content"] = doc_content
 
 
579
 
580
  if doc_content and valid_sources_count < 3:
581
  best_chunks = rank_chunks_for_rag(doc_content, query_tokens)
 
584
  used_sources.append({"id": valid_sources_count+1, "title": r["title"], "url": r["url"]})
585
  valid_sources_count += 1
586
 
587
+ for r in top_20: r.pop("_internal_content", None)
 
588
 
589
  system_prompt = (
590
  "You are a helpful AI. Answer directly using the context. "
 
608
  ai_reply = await run_ai_task(messages, max_tokens=max_ai_tokens, query_key=cache_key)
609
 
610
  return {
611
+ "request_id": req_id, "query": q, "corrected_query": corrected_query,
612
+ "ai_response": ai_reply, "results_count": len(top_20),
613
+ "results": top_20, "sources": used_sources, "cached": False
 
 
 
 
 
614
  }
615
 
616
  @app.get("/aimode")