Nebulixlabs commited on
Commit
2f030ff
Β·
verified Β·
1 Parent(s): 0fd0e86

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -62
app.py CHANGED
@@ -16,11 +16,13 @@ from rank_bm25 import BM25Okapi
16
  import torch
17
  import aiohttp
18
  from bs4 import BeautifulSoup
19
- from transformers import pipeline
20
 
21
- # --- 1. SETUP & CPU ALLOCATION (MAX SPEED) ---
 
 
 
22
  TOTAL_CORES = multiprocessing.cpu_count()
23
- AI_CORES = max(1, int(TOTAL_CORES * 0.7)) # Give 70% CPU to AI for max speed
24
  torch.set_num_threads(AI_CORES)
25
  print(f"πŸ–₯️ Total Cores: {TOTAL_CORES} | Dedicated AI Cores: {AI_CORES}")
26
 
@@ -113,29 +115,89 @@ def load_and_index_dataset():
113
  bm25_index = BM25Okapi(tokenized_corpus)
114
  print(f"πŸš€ Indexing Complete! {len(raw_data)} records ready.")
115
 
 
116
  def load_ai_models():
117
  global ai_generator
118
- print("πŸ€– Loading LiquidAI/LFM2.5-350M for BLAZING FAST inference...")
119
  try:
120
- # Optimization: use float32 on CPU but pipeline optimized
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  ai_generator = pipeline(
122
  "text-generation",
123
- model="LiquidAI/LFM2.5-350M",
124
- device=-1,
125
- torch_dtype=torch.float32,
126
- trust_remote_code=True
127
  )
128
- print("βœ… LiquidAI Model Loaded! Superfast mode activated.")
129
  except Exception as e:
130
  print(f"❌ Model Loading Failed: {e}")
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  def chunk_text(text, chunk_size=80):
133
  words = text.split()
134
  return [" ".join(words[i:i+chunk_size]) for i in range(0, len(words), chunk_size)]
135
 
136
  async def scrape_and_find_best_chunk(url, query):
137
  try:
138
- # πŸ”₯ REDUCED TIMEOUT TO 1.5 SECONDS FOR SPEED
139
  async with aiohttp.ClientSession() as session:
140
  async with session.get(url, timeout=1.5) as resp:
141
  if resp.status == 200:
@@ -204,55 +266,13 @@ async def perform_core_search(raw_query: str):
204
 
205
  return [{"match_score": round(s, 2), **doc} for s, doc in scored_results[:100]], prediction, best_score
206
 
207
- def _generate_text(messages, max_tokens: int):
208
- tokenizer = ai_generator.tokenizer
209
-
210
- # 1. Use the model's Native Chat Template (Prevents random autocomplete/echoing)
211
- try:
212
- prompt = tokenizer.apply_chat_template(
213
- messages,
214
- tokenize=False,
215
- add_generation_prompt=True
216
- )
217
- except Exception:
218
- # Fallback if model doesn't have a strict template
219
- prompt = "\n".join([f"{m['role'].capitalize()}: {m['content']}" for m in messages]) + "\nAssistant: "
220
-
221
- # 2. return_full_text=False ensures the prompt isn't returned in the output!
222
- result = ai_generator(
223
- prompt,
224
- max_new_tokens=max_tokens,
225
- do_sample=False,
226
- truncation=True,
227
- return_full_text=False, # πŸ”₯ THIS FIXES THE REPEATING QUERY ISSUE
228
- pad_token_id=tokenizer.eos_token_id if tokenizer.eos_token_id is not None else 0
229
- )
230
-
231
- text_out = result[0]['generated_text'].strip()
232
-
233
- # 3. Extra safety cleanup for small AI models
234
- if text_out.lower().startswith("assistant:"):
235
- text_out = text_out[10:].strip()
236
-
237
- return text_out
238
-
239
- async def run_ai_task(messages, max_tokens: int, query_key: str):
240
- if not ai_generator: return "⚠️ AI Model is starting up."
241
- if query_key in AI_RESPONSE_CACHE: return AI_RESPONSE_CACHE[query_key]
242
-
243
- async with ai_semaphore:
244
- response = await asyncio.to_thread(_generate_text, messages, max_tokens)
245
- if len(AI_RESPONSE_CACHE) > MAX_CACHE_SIZE: AI_RESPONSE_CACHE.clear()
246
- AI_RESPONSE_CACHE[query_key] = response
247
- return response
248
-
249
  @asynccontextmanager
250
  async def lifespan(app: FastAPI):
251
  asyncio.create_task(asyncio.to_thread(load_and_index_dataset))
252
  asyncio.create_task(asyncio.to_thread(load_ai_models))
253
  yield
254
 
255
- app = FastAPI(title="Nebulix Fast AI Search", lifespan=lifespan)
256
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
257
 
258
  @app.get("/suggest")
@@ -275,23 +295,21 @@ async def ai_mode_short(q: str = Query(...)):
275
 
276
  context = " ".join([f"{r.get('title', '')} - {r.get('description', '')}" for r in results[:2]])
277
  messages = [
278
- {"role": "system", "content": "You are a concise AI. Answer in 1 short sentence. Put important keywords in (parentheses)."},
279
- {"role": "user", "content": f"Query: {q}\nContext: {context}"}
280
  ]
281
- # Reduce token generation for lightning speed
282
  ai_reply = await run_ai_task(messages, 50, query_key=f"short_{q}")
283
  return {"request_id": req_id, "ai_response": ai_reply, "results": results}
284
 
285
  async def process_rag_chat(q: str):
286
  req_id = str(uuid.uuid4())
287
- needs_rag = any(w in q.lower() for w in ["what", "who", "where", "how", "why", "tell me", "explain", "latest", "best"])
288
  results, _, _ = await perform_core_search(q)
289
 
290
  rag_context = ""
291
  used_sources = []
292
 
293
  if needs_rag and results:
294
- # πŸ”₯ REDUCED FROM 10 TO TOP 3 URLs FOR FASTER RAG
295
  top_urls = results[:3]
296
  tasks = [scrape_and_find_best_chunk(doc["url"], q) for doc in top_urls]
297
  scraped_chunks = await asyncio.gather(*tasks)
@@ -309,13 +327,12 @@ async def process_rag_chat(q: str):
309
 
310
  rag_context = "\n".join(context_parts)
311
 
312
- system_prompt = "You are a Smart AI Search Assistant. Provide a short, direct answer. ENCLOSE IMPORTANT KEYWORDS IN (PARENTHESES). Mention Source URLs if used."
313
  messages = [
314
  {"role": "system", "content": system_prompt},
315
- {"role": "user", "content": f"Query: {q}\nContext:\n{rag_context}" if needs_rag else f"Query: {q}"}
316
  ]
317
 
318
- # Max tokens 120 so it generates answers faster
319
  ai_reply = await run_ai_task(messages, 120, query_key=f"chat_{q}_{needs_rag}")
320
  return {"request_id": req_id, "ai_response": ai_reply, "rag_used": needs_rag, "sources": used_sources, "results": results[:15]}
321
 
 
16
  import torch
17
  import aiohttp
18
  from bs4 import BeautifulSoup
 
19
 
20
+ # Hugging Face imports for Q4 Quantization
21
+ from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, BitsAndBytesConfig
22
+
23
+ # --- 1. SETUP & CPU ALLOCATION ---
24
  TOTAL_CORES = multiprocessing.cpu_count()
25
+ AI_CORES = max(1, int(TOTAL_CORES * 0.7))
26
  torch.set_num_threads(AI_CORES)
27
  print(f"πŸ–₯️ Total Cores: {TOTAL_CORES} | Dedicated AI Cores: {AI_CORES}")
28
 
 
115
  bm25_index = BM25Okapi(tokenized_corpus)
116
  print(f"πŸš€ Indexing Complete! {len(raw_data)} records ready.")
117
 
118
+ # --- QWEN 2.5 (0.5B) + Q4 OPTIMIZATION ---
119
  def load_ai_models():
120
  global ai_generator
121
+ print("πŸ€– Loading Qwen/Qwen2.5-0.5B-Instruct...")
122
  try:
123
+ model_id = "Qwen/Qwen2.5-0.5B-Instruct"
124
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
125
+
126
+ # Smart Hardware Check for 4-bit (Q4) Quantization
127
+ if torch.cuda.is_available():
128
+ print("⚑ GPU detected! Loading in 4-bit (Q4) using BitsAndBytes for Extreme Speed...")
129
+ quant_config = BitsAndBytesConfig(
130
+ load_in_4bit=True,
131
+ bnb_4bit_compute_dtype=torch.float16,
132
+ bnb_4bit_quant_type="nf4",
133
+ )
134
+ model = AutoModelForCausalLM.from_pretrained(
135
+ model_id,
136
+ quantization_config=quant_config,
137
+ device_map="auto"
138
+ )
139
+ else:
140
+ print("πŸ–₯️ CPU detected! Loading optimized model (PyTorch execution)...")
141
+ # For CPU, 4-bit BitsAndBytes isn't natively optimal, so we load in standard float32
142
+ # Qwen 0.5B is already tiny (~1GB) so it runs perfectly fast on CPU too.
143
+ model = AutoModelForCausalLM.from_pretrained(
144
+ model_id,
145
+ device_map="cpu",
146
+ torch_dtype=torch.float32
147
+ )
148
+
149
  ai_generator = pipeline(
150
  "text-generation",
151
+ model=model,
152
+ tokenizer=tokenizer
 
 
153
  )
154
+ print("βœ… Qwen2.5-0.5B-Instruct (Superfast Mode) Ready!")
155
  except Exception as e:
156
  print(f"❌ Model Loading Failed: {e}")
157
 
158
+ # --- AI TEXT GENERATION FIX ---
159
+ def _generate_text(messages, max_tokens: int):
160
+ tokenizer = ai_generator.tokenizer
161
+
162
+ # 1. Use Native Chat Template for Qwen (Makes answers highly accurate & stops hallucinations)
163
+ try:
164
+ prompt = tokenizer.apply_chat_template(
165
+ messages,
166
+ tokenize=False,
167
+ add_generation_prompt=True
168
+ )
169
+ except Exception:
170
+ prompt = "\n".join([f"{m['role'].capitalize()}: {m['content']}" for m in messages]) + "\nAssistant: "
171
+
172
+ # 2. Greedy Decoding (do_sample=False) + Return Setup
173
+ result = ai_generator(
174
+ prompt,
175
+ max_new_tokens=max_tokens,
176
+ do_sample=False,
177
+ truncation=True,
178
+ return_full_text=False, # ❌ FIX: No more repeating the query in answers!
179
+ pad_token_id=tokenizer.eos_token_id if tokenizer.eos_token_id is not None else 0
180
+ )
181
+
182
+ text_out = result[0]['generated_text'].strip()
183
+ return text_out
184
+
185
+ async def run_ai_task(messages, max_tokens: int, query_key: str):
186
+ if not ai_generator: return "⚠️ AI Model is starting up."
187
+ if query_key in AI_RESPONSE_CACHE: return AI_RESPONSE_CACHE[query_key]
188
+
189
+ async with ai_semaphore:
190
+ response = await asyncio.to_thread(_generate_text, messages, max_tokens)
191
+ if len(AI_RESPONSE_CACHE) > MAX_CACHE_SIZE: AI_RESPONSE_CACHE.clear()
192
+ AI_RESPONSE_CACHE[query_key] = response
193
+ return response
194
+
195
  def chunk_text(text, chunk_size=80):
196
  words = text.split()
197
  return [" ".join(words[i:i+chunk_size]) for i in range(0, len(words), chunk_size)]
198
 
199
  async def scrape_and_find_best_chunk(url, query):
200
  try:
 
201
  async with aiohttp.ClientSession() as session:
202
  async with session.get(url, timeout=1.5) as resp:
203
  if resp.status == 200:
 
266
 
267
  return [{"match_score": round(s, 2), **doc} for s, doc in scored_results[:100]], prediction, best_score
268
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  @asynccontextmanager
270
  async def lifespan(app: FastAPI):
271
  asyncio.create_task(asyncio.to_thread(load_and_index_dataset))
272
  asyncio.create_task(asyncio.to_thread(load_ai_models))
273
  yield
274
 
275
+ app = FastAPI(title="Nebulix Fast Qwen AI Search", lifespan=lifespan)
276
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
277
 
278
  @app.get("/suggest")
 
295
 
296
  context = " ".join([f"{r.get('title', '')} - {r.get('description', '')}" for r in results[:2]])
297
  messages = [
298
+ {"role": "system", "content": "You are a concise search AI. Provide a direct, 1-sentence answer based on the context. Highlight keywords in (parentheses)."},
299
+ {"role": "user", "content": f"Context: {context}\n\nQuestion: {q}"}
300
  ]
 
301
  ai_reply = await run_ai_task(messages, 50, query_key=f"short_{q}")
302
  return {"request_id": req_id, "ai_response": ai_reply, "results": results}
303
 
304
  async def process_rag_chat(q: str):
305
  req_id = str(uuid.uuid4())
306
+ needs_rag = any(w in q.lower() for w in ["what", "who", "where", "how", "why", "tell me", "explain", "latest", "best", "news"])
307
  results, _, _ = await perform_core_search(q)
308
 
309
  rag_context = ""
310
  used_sources = []
311
 
312
  if needs_rag and results:
 
313
  top_urls = results[:3]
314
  tasks = [scrape_and_find_best_chunk(doc["url"], q) for doc in top_urls]
315
  scraped_chunks = await asyncio.gather(*tasks)
 
327
 
328
  rag_context = "\n".join(context_parts)
329
 
330
+ system_prompt = "You are a Smart AI Search Assistant. Provide a short, direct answer. ENCLOSE IMPORTANT KEYWORDS IN (PARENTHESES). If you use information from a Source, cite it."
331
  messages = [
332
  {"role": "system", "content": system_prompt},
333
+ {"role": "user", "content": f"Live Context Data:\n{rag_context}\n\nQuestion: {q}" if needs_rag else f"Question: {q}"}
334
  ]
335
 
 
336
  ai_reply = await run_ai_task(messages, 120, query_key=f"chat_{q}_{needs_rag}")
337
  return {"request_id": req_id, "ai_response": ai_reply, "rag_used": needs_rag, "sources": used_sources, "results": results[:15]}
338