Nebulixlabs commited on
Commit
8a41c29
Β·
verified Β·
1 Parent(s): 15b74e9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -50
app.py CHANGED
@@ -14,6 +14,7 @@ from cryptography.fernet import Fernet
14
  # AI Models Imports
15
  import torch
16
  from transformers import pipeline
 
17
 
18
  # --- 1. SETUP & CONFIGURATION ---
19
  SECRET_KEY = Fernet.generate_key()
@@ -25,14 +26,17 @@ os.makedirs(LOG_DIR, exist_ok=True)
25
 
26
  raw_data = []
27
  search_corpus = []
 
 
28
 
29
  # AI Concurrency Limit
30
  MAX_CONCURRENT_AI = 2
31
  ai_semaphore = asyncio.Semaphore(MAX_CONCURRENT_AI)
32
  ai_generator = None
 
33
 
34
 
35
- # --- 2. LOAD DATASET & AI MODEL ---
36
  def load_dataset():
37
  print("πŸ”„ Loading Search Dataset...")
38
  if os.path.exists("dataset.jsonl"):
@@ -42,49 +46,65 @@ def load_dataset():
42
  try:
43
  item = json.loads(line)
44
  raw_data.append(item)
 
 
45
  search_corpus.append({
46
- "url": item.get("url", "").lower(),
47
- "title": item.get("title", "").lower(),
48
- "description": item.get("description", "").lower()
49
  })
 
50
  except: pass
51
  print(f"βœ… Loaded {len(raw_data)} records into memory.")
52
 
53
- def load_ai_model():
54
- global ai_generator
55
- # 🌟 NAYA MODEL: SmolLM2-360M (Chota hai par bohot smart hai)
56
- print("πŸ€– Loading 360M Parameter AI Model (SmolLM2-360M-Instruct)...")
 
 
 
 
 
 
 
 
 
 
 
 
57
  try:
58
- model_id = "HuggingFaceTB/SmolLM2-360M-Instruct"
59
  ai_generator = pipeline(
60
  "text-generation",
61
  model=model_id,
62
- device=-1, # -1 means CPU
63
  torch_dtype=torch.float32
64
  )
65
- print("βœ… AI Model Loaded Successfully!")
66
  except Exception as e:
67
- print("❌ AI Model Loading Failed:", e)
68
 
69
 
70
  # --- 3. ASYNC AI INFERENCE HELPERS ---
71
  def _generate_text(prompt: str, max_tokens: int):
 
72
  messages = [{"role": "user", "content": prompt}]
73
  result = ai_generator(
74
  messages,
75
  max_new_tokens=max_tokens,
76
  do_sample=True,
77
- temperature=0.5, # Thoda kam temperature for precise answers
78
- top_p=0.9
79
  )
80
  return result[0]['generated_text'][-1]['content']
81
 
82
- async def run_ai_task(prompt: str, max_tokens: int = 150):
83
  if not ai_generator:
84
  return "AI model is not initialized."
85
 
86
  if ai_semaphore.locked():
87
- return "⚠️ AI Servers are currently at full capacity handling other users. Please try again in a few seconds."
88
 
89
  async with ai_semaphore:
90
  loop = asyncio.get_event_loop()
@@ -105,43 +125,69 @@ async def log_query_encrypted(req_id: str, query: str):
105
  @asynccontextmanager
106
  async def lifespan(app: FastAPI):
107
  load_dataset()
108
- asyncio.get_event_loop().run_in_executor(None, load_ai_model)
 
109
  yield
110
 
111
  app = FastAPI(title="Nebulix AI Search Engine", lifespan=lifespan)
112
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
113
 
114
 
115
- # --- 5. SEARCH LOGIC & SMART PREDICTION ---
116
  def perform_core_search(query: str):
 
 
 
117
  query_lower = query.lower()
118
  scored_results = []
119
 
 
 
 
 
 
 
 
 
 
 
 
120
  for index, data in enumerate(search_corpus):
 
121
  title_score = fuzz.WRatio(query_lower, data["title"])
122
  desc_score = fuzz.WRatio(query_lower, data["description"])
123
- total_score = max(title_score, desc_score)
 
 
 
 
 
 
124
 
125
- if total_score > 40:
126
- scored_results.append((total_score, index))
127
 
 
128
  scored_results.sort(key=lambda x: x[0], reverse=True)
129
 
130
  best_score = scored_results[0][0] if scored_results else 0
131
- if best_score > 85:
132
- prediction = "High confidence: We found exact matches for your query."
133
- elif best_score > 55:
134
- prediction = "Medium confidence: We found related content, but it might not be exact."
135
  else:
136
- prediction = "Low confidence: Results are poor. Try changing your keywords."
137
 
 
138
  top_30 = scored_results[:30]
139
  final_results = []
 
140
  for score, index in top_30:
141
  item = raw_data[index].copy()
142
  item["match_score"] = round(score, 2)
143
  final_results.append(item)
144
 
 
 
145
  return final_results, prediction, best_score
146
 
147
 
@@ -152,42 +198,43 @@ async def serve_ui():
152
  if os.path.exists("index.html"): return FileResponse("index.html")
153
  return {"message": "API Running!"}
154
 
155
- # 🌟 SMART AI OVERVIEW MODE (Max 100 Tokens + () Highlighting + Conditional Trigger) 🌟
 
156
  @app.get("/aimode")
157
  async def ai_mode_short(q: str = Query(...), background_tasks: BackgroundTasks = BackgroundTasks()):
158
  req_id = str(uuid.uuid4())
159
  background_tasks.add_task(log_query_encrypted, req_id, q)
160
 
 
161
  results, prediction, best_score = perform_core_search(q)
162
 
163
- # πŸ›‘ CONDITIONAL AI TRIGGER: Agar query faltu hai ya result nahi mila, toh AI skip karo
164
- if not results or best_score < 45 or len(q.strip()) < 3:
165
  return {
166
  "request_id": req_id,
167
- "ai_response": None, # Frontend isko dekh kar AI box hide kar dega
168
  "smart_prediction": prediction,
169
- "results": results[:5]
170
  }
171
 
172
- context = " ".join([f"{r.get('title', '')} - {r.get('description', '')}" for r in results[:2]])
173
 
174
  prompt = f"""You are a smart Search Assistant. Answer the user's query based ONLY on the context.
175
  Rules:
176
- 1. Keep the answer under 100 words.
177
- 2. You MUST enclose the most important keywords, names, or main concepts in parentheses, like (this).
178
 
179
  User Query: {q}
180
  Context: {context}
181
  Answer:"""
182
 
183
- # Max tokens 120 (approx 90-100 words)
184
- ai_reply = await run_ai_task(prompt, max_tokens=120)
185
 
186
  return {
187
  "request_id": req_id,
188
  "ai_response": ai_reply,
189
  "smart_prediction": prediction,
190
- "results": results[:5]
191
  }
192
 
193
 
@@ -199,21 +246,21 @@ async def ai_mode_chat(q: str = Query(...), background_tasks: BackgroundTasks =
199
 
200
  results, prediction, best_score = perform_core_search(q)
201
 
202
- # Agar search me kuch nahi mila (Faltu query), toh normal chat karo bina sources ke
203
- if not results or best_score < 45:
204
  prompt = f"""You are Nebulix AI. The user said: "{q}".
205
- You do not have any web search results for this. Answer politely as a helpful AI assistant."""
206
 
207
- ai_reply = await run_ai_task(prompt, max_tokens=150)
208
  return {
209
  "request_id": req_id,
210
  "ai_response": ai_reply,
211
- "smart_prediction": "No search results. General AI chat activated.",
212
  "sources": [],
213
- "results": []
214
  }
215
 
216
- # Build Deep RAG Context & Extract Sources
217
  context = ""
218
  sources_used = []
219
 
@@ -221,29 +268,28 @@ You do not have any web search results for this. Answer politely as a helpful AI
221
  url = r.get("url", "#")
222
  title = r.get("title", "Unknown Title")
223
  desc = r.get("description", "")
224
-
225
  context += f"Source {i+1} [{title}]: {desc}\n"
226
  sources_used.append({"id": i+1, "title": title, "url": url})
227
 
228
- prompt = f"""You are Nebulix AI, a highly intelligent and helpful search assistant.
229
- Provide a comprehensive and accurate answer to the user's query using ONLY the provided search results.
230
 
231
  User Query: {q}
232
 
233
- Search Results Data:
234
  {context}
235
 
236
  Provide a detailed response. Do not include the prompt in your output.
237
  Answer:"""
238
 
239
- ai_reply = await run_ai_task(prompt, max_tokens=250)
240
 
241
  return {
242
  "request_id": req_id,
243
  "ai_response": ai_reply,
244
  "smart_prediction": prediction,
245
  "sources": sources_used,
246
- "results": results[:5]
247
  }
248
 
249
  if __name__ == "__main__":
 
14
  # AI Models Imports
15
  import torch
16
  from transformers import pipeline
17
+ from sentence_transformers import SentenceTransformer, util
18
 
19
  # --- 1. SETUP & CONFIGURATION ---
20
  SECRET_KEY = Fernet.generate_key()
 
26
 
27
  raw_data = []
28
  search_corpus = []
29
+ corpus_texts = [] # AI embedding ke liye
30
+ corpus_embeddings = None
31
 
32
  # AI Concurrency Limit
33
  MAX_CONCURRENT_AI = 2
34
  ai_semaphore = asyncio.Semaphore(MAX_CONCURRENT_AI)
35
  ai_generator = None
36
+ embedding_model = None
37
 
38
 
39
+ # --- 2. LOAD DATASET & AI MODELS ---
40
  def load_dataset():
41
  print("πŸ”„ Loading Search Dataset...")
42
  if os.path.exists("dataset.jsonl"):
 
46
  try:
47
  item = json.loads(line)
48
  raw_data.append(item)
49
+ title = item.get("title", "").lower()
50
+ desc = item.get("description", "").lower()
51
  search_corpus.append({
52
+ "url": item.get("url", ""),
53
+ "title": title,
54
+ "description": desc
55
  })
56
+ corpus_texts.append(f"{title} {desc}")
57
  except: pass
58
  print(f"βœ… Loaded {len(raw_data)} records into memory.")
59
 
60
+ def load_ai_models():
61
+ global ai_generator, embedding_model, corpus_embeddings
62
+
63
+ # 🌟 1. AI SEMANTIC SEARCH MODEL (Accurate URLs ke liye)
64
+ print("πŸ” Loading Semantic Search AI (all-MiniLM-L6-v2)...")
65
+ try:
66
+ embedding_model = SentenceTransformer('all-MiniLM-L6-v2', device='cpu')
67
+ if corpus_texts:
68
+ print("🧠 Encoding dataset into AI Brain... This may take a minute.")
69
+ corpus_embeddings = embedding_model.encode(corpus_texts, convert_to_tensor=True)
70
+ print("βœ… Semantic Search AI Loaded!")
71
+ except Exception as e:
72
+ print("❌ Semantic Search AI Failed:", e)
73
+
74
+ # 🌟 2. BETTER RAG MODEL: Qwen 2.5 (Chota lekin bohot jyada smart)
75
+ print("πŸ€– Loading RAG AI Model (Qwen2.5-0.5B-Instruct)...")
76
  try:
77
+ model_id = "Qwen/Qwen2.5-0.5B-Instruct"
78
  ai_generator = pipeline(
79
  "text-generation",
80
  model=model_id,
81
+ device=-1, # CPU
82
  torch_dtype=torch.float32
83
  )
84
+ print("βœ… AI Generator Model Loaded Successfully!")
85
  except Exception as e:
86
+ print("❌ AI Generator Model Failed:", e)
87
 
88
 
89
  # --- 3. ASYNC AI INFERENCE HELPERS ---
90
  def _generate_text(prompt: str, max_tokens: int):
91
+ # Chat Template for Models
92
  messages = [{"role": "user", "content": prompt}]
93
  result = ai_generator(
94
  messages,
95
  max_new_tokens=max_tokens,
96
  do_sample=True,
97
+ temperature=0.3, # Low temperature for accurate answers
98
+ top_p=0.85
99
  )
100
  return result[0]['generated_text'][-1]['content']
101
 
102
+ async def run_ai_task(prompt: str, max_tokens: int):
103
  if not ai_generator:
104
  return "AI model is not initialized."
105
 
106
  if ai_semaphore.locked():
107
+ return "⚠️ AI Servers are currently full. Please wait a second."
108
 
109
  async with ai_semaphore:
110
  loop = asyncio.get_event_loop()
 
125
  @asynccontextmanager
126
  async def lifespan(app: FastAPI):
127
  load_dataset()
128
+ # Loading models in background thread so server starts immediately
129
+ asyncio.get_event_loop().run_in_executor(None, load_ai_models)
130
  yield
131
 
132
  app = FastAPI(title="Nebulix AI Search Engine", lifespan=lifespan)
133
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
134
 
135
 
136
+ # --- 5. HYBRID SEARCH LOGIC (Fuzzy + AI Semantic) ---
137
  def perform_core_search(query: str):
138
+ if not raw_data:
139
+ return [], "No dataset found.", 0
140
+
141
  query_lower = query.lower()
142
  scored_results = []
143
 
144
+ # 🧠 Calculate AI Semantic Scores
145
+ ai_scores = []
146
+ if embedding_model and corpus_embeddings is not None:
147
+ query_embedding = embedding_model.encode(query, convert_to_tensor=True)
148
+ # Cosine Similarity gives result between -1 to 1 (scaled to 0-100 below)
149
+ cos_scores = util.cos_sim(query_embedding, corpus_embeddings)[0].tolist()
150
+ ai_scores = cos_scores
151
+ else:
152
+ ai_scores = [0] * len(raw_data)
153
+
154
+ # πŸ”€ Combine AI Score + Fuzzy Score
155
  for index, data in enumerate(search_corpus):
156
+ # 1. Fuzzy Match (Keywords)
157
  title_score = fuzz.WRatio(query_lower, data["title"])
158
  desc_score = fuzz.WRatio(query_lower, data["description"])
159
+ fuzzy_max = max(title_score, desc_score)
160
+
161
+ # 2. AI Score (Meaning)
162
+ ai_score = max(0, ai_scores[index] * 100)
163
+
164
+ # 3. Hybrid Score (60% AI + 40% Fuzzy)
165
+ total_score = (ai_score * 0.6) + (fuzzy_max * 0.4)
166
 
167
+ scored_results.append((total_score, index))
 
168
 
169
+ # Sort from best to worst match
170
  scored_results.sort(key=lambda x: x[0], reverse=True)
171
 
172
  best_score = scored_results[0][0] if scored_results else 0
173
+ if best_score > 75:
174
+ prediction = "High confidence: We found strong semantic and exact matches."
175
+ elif best_score > 40:
176
+ prediction = "Medium confidence: We found related context for your query."
177
  else:
178
+ prediction = "Low confidence: Forcing best available results."
179
 
180
+ # πŸ“Œ EXACTLY 30 RESULTS (Yahan har baar top 30 hi return honge)
181
  top_30 = scored_results[:30]
182
  final_results = []
183
+
184
  for score, index in top_30:
185
  item = raw_data[index].copy()
186
  item["match_score"] = round(score, 2)
187
  final_results.append(item)
188
 
189
+ # Agar dataset mein 30 se kam data hai, toh jo hai utna return hoga.
190
+ # Agar 30+ hai, toh EXACTLY 30 return hoga har baar.
191
  return final_results, prediction, best_score
192
 
193
 
 
198
  if os.path.exists("index.html"): return FileResponse("index.html")
199
  return {"message": "API Running!"}
200
 
201
+
202
+ # 🌟 AI OVERVIEW MODE (40 Tokens = ~160 Words Logic) 🌟
203
  @app.get("/aimode")
204
  async def ai_mode_short(q: str = Query(...), background_tasks: BackgroundTasks = BackgroundTasks()):
205
  req_id = str(uuid.uuid4())
206
  background_tasks.add_task(log_query_encrypted, req_id, q)
207
 
208
+ # Har baar exactly 30 aayenge backend se
209
  results, prediction, best_score = perform_core_search(q)
210
 
211
+ if not results or best_score < 30 or len(q.strip()) < 3:
 
212
  return {
213
  "request_id": req_id,
214
+ "ai_response": None,
215
  "smart_prediction": prediction,
216
+ "results": results # Exactly top 30 list
217
  }
218
 
219
+ context = " ".join([f"{r.get('title', '')} - {r.get('description', '')}" for r in results[:3]])
220
 
221
  prompt = f"""You are a smart Search Assistant. Answer the user's query based ONLY on the context.
222
  Rules:
223
+ 1. Provide your answer in approximately 150-160 words (Equivalent to what the user calls 40 extended tokens).
224
+ 2. You MUST enclose the most important keywords or concepts in parentheses, like (this).
225
 
226
  User Query: {q}
227
  Context: {context}
228
  Answer:"""
229
 
230
+ # max_new_tokens set to 200 to easily accommodate the ~160 words constraint.
231
+ ai_reply = await run_ai_task(prompt, max_tokens=200)
232
 
233
  return {
234
  "request_id": req_id,
235
  "ai_response": ai_reply,
236
  "smart_prediction": prediction,
237
+ "results": results # Har baar exactly 30 pass honge
238
  }
239
 
240
 
 
246
 
247
  results, prediction, best_score = perform_core_search(q)
248
 
249
+ # AI Chat Fallback if no logical context found
250
+ if not results or best_score < 30:
251
  prompt = f"""You are Nebulix AI. The user said: "{q}".
252
+ Answer politely as a helpful AI assistant. Answer in detailed format."""
253
 
254
+ ai_reply = await run_ai_task(prompt, max_tokens=250)
255
  return {
256
  "request_id": req_id,
257
  "ai_response": ai_reply,
258
+ "smart_prediction": "No reliable search results. General AI chat activated.",
259
  "sources": [],
260
+ "results": results
261
  }
262
 
263
+ # Deep RAG Context
264
  context = ""
265
  sources_used = []
266
 
 
268
  url = r.get("url", "#")
269
  title = r.get("title", "Unknown Title")
270
  desc = r.get("description", "")
 
271
  context += f"Source {i+1} [{title}]: {desc}\n"
272
  sources_used.append({"id": i+1, "title": title, "url": url})
273
 
274
+ prompt = f"""You are Nebulix AI, a highly intelligent search assistant.
275
+ Provide a comprehensive answer using ONLY the provided search results.
276
 
277
  User Query: {q}
278
 
279
+ Context:
280
  {context}
281
 
282
  Provide a detailed response. Do not include the prompt in your output.
283
  Answer:"""
284
 
285
+ ai_reply = await run_ai_task(prompt, max_tokens=300)
286
 
287
  return {
288
  "request_id": req_id,
289
  "ai_response": ai_reply,
290
  "smart_prediction": prediction,
291
  "sources": sources_used,
292
+ "results": results # Har baar exactly 30 results!
293
  }
294
 
295
  if __name__ == "__main__":