Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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
|
| 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", "")
|
| 47 |
-
"title":
|
| 48 |
-
"description":
|
| 49 |
})
|
|
|
|
| 50 |
except: pass
|
| 51 |
print(f"β
Loaded {len(raw_data)} records into memory.")
|
| 52 |
|
| 53 |
-
def
|
| 54 |
-
global ai_generator
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
try:
|
| 58 |
-
model_id = "
|
| 59 |
ai_generator = pipeline(
|
| 60 |
"text-generation",
|
| 61 |
model=model_id,
|
| 62 |
-
device=-1, #
|
| 63 |
torch_dtype=torch.float32
|
| 64 |
)
|
| 65 |
-
print("β
AI Model Loaded Successfully!")
|
| 66 |
except Exception as e:
|
| 67 |
-
print("β AI Model
|
| 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.
|
| 78 |
-
top_p=0.
|
| 79 |
)
|
| 80 |
return result[0]['generated_text'][-1]['content']
|
| 81 |
|
| 82 |
-
async def run_ai_task(prompt: str, max_tokens: int
|
| 83 |
if not ai_generator:
|
| 84 |
return "AI model is not initialized."
|
| 85 |
|
| 86 |
if ai_semaphore.locked():
|
| 87 |
-
return "β οΈ AI Servers are currently
|
| 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 |
-
|
|
|
|
| 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
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
-
|
| 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 >
|
| 132 |
-
prediction = "High confidence: We found
|
| 133 |
-
elif best_score >
|
| 134 |
-
prediction = "Medium confidence: We found related
|
| 135 |
else:
|
| 136 |
-
prediction = "Low confidence:
|
| 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 |
-
|
|
|
|
| 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 |
-
|
| 164 |
-
if not results or best_score < 45 or len(q.strip()) < 3:
|
| 165 |
return {
|
| 166 |
"request_id": req_id,
|
| 167 |
-
"ai_response": None,
|
| 168 |
"smart_prediction": prediction,
|
| 169 |
-
"results": results
|
| 170 |
}
|
| 171 |
|
| 172 |
-
context = " ".join([f"{r.get('title', '')} - {r.get('description', '')}" for r in results[:
|
| 173 |
|
| 174 |
prompt = f"""You are a smart Search Assistant. Answer the user's query based ONLY on the context.
|
| 175 |
Rules:
|
| 176 |
-
1.
|
| 177 |
-
2. You MUST enclose the most important keywords
|
| 178 |
|
| 179 |
User Query: {q}
|
| 180 |
Context: {context}
|
| 181 |
Answer:"""
|
| 182 |
|
| 183 |
-
#
|
| 184 |
-
ai_reply = await run_ai_task(prompt, max_tokens=
|
| 185 |
|
| 186 |
return {
|
| 187 |
"request_id": req_id,
|
| 188 |
"ai_response": ai_reply,
|
| 189 |
"smart_prediction": prediction,
|
| 190 |
-
"results": results
|
| 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 |
-
#
|
| 203 |
-
if not results or best_score <
|
| 204 |
prompt = f"""You are Nebulix AI. The user said: "{q}".
|
| 205 |
-
|
| 206 |
|
| 207 |
-
ai_reply = await run_ai_task(prompt, max_tokens=
|
| 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 |
-
#
|
| 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
|
| 229 |
-
Provide a comprehensive
|
| 230 |
|
| 231 |
User Query: {q}
|
| 232 |
|
| 233 |
-
|
| 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=
|
| 240 |
|
| 241 |
return {
|
| 242 |
"request_id": req_id,
|
| 243 |
"ai_response": ai_reply,
|
| 244 |
"smart_prediction": prediction,
|
| 245 |
"sources": sources_used,
|
| 246 |
-
"results": results
|
| 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__":
|