Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -23,7 +23,7 @@ import aiohttp
|
|
| 23 |
from bs4 import BeautifulSoup
|
| 24 |
import torch
|
| 25 |
|
| 26 |
-
# Hugging Face imports
|
| 27 |
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, BitsAndBytesConfig
|
| 28 |
|
| 29 |
# --- CONFIGURATION & MULTIPROCESSING ---
|
|
@@ -37,9 +37,9 @@ ai_semaphore = asyncio.Semaphore(MAX_CONCURRENT_AI)
|
|
| 37 |
ai_generator = None
|
| 38 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 39 |
|
| 40 |
-
# Cache Structures
|
| 41 |
AI_RESPONSE_CACHE = {}
|
| 42 |
-
MAX_AI_CACHE_SIZE =
|
| 43 |
|
| 44 |
CONTENT_CACHE = {}
|
| 45 |
MAX_CONTENT_CONCURRENCY = 10
|
|
@@ -113,31 +113,27 @@ def detect_query_intent(query_tokens: List[str]) -> Dict[str, Any]:
|
|
| 113 |
|
| 114 |
# --- 3. DEDICATED WEB CONTENT EXTRACTION ---
|
| 115 |
async def extract_page_content(url: str) -> Dict[str, Any]:
|
| 116 |
-
"""Downloads webpage safely, extracts pure text, and caches it."""
|
| 117 |
if url in CONTENT_CACHE:
|
| 118 |
return CONTENT_CACHE[url]
|
| 119 |
|
| 120 |
async with content_semaphore:
|
| 121 |
try:
|
| 122 |
async with aiohttp.ClientSession() as session:
|
| 123 |
-
async with session.get(url, timeout=
|
| 124 |
if resp.status != 200:
|
| 125 |
return {"url": url, "title": "", "content": "", "success": False, "error": f"HTTP {resp.status}"}
|
| 126 |
|
| 127 |
html = await resp.text()
|
| 128 |
soup = BeautifulSoup(html, "html.parser")
|
| 129 |
|
| 130 |
-
# Clean irrelevant elements
|
| 131 |
for tag in soup(['script', 'style', 'nav', 'footer', 'header', 'aside', 'noscript', 'form', 'button', 'iframe']):
|
| 132 |
tag.decompose()
|
| 133 |
|
| 134 |
blocks = []
|
| 135 |
for tag in soup.find_all(['h1', 'h2', 'h3', 'p', 'article', 'main', 'li']):
|
| 136 |
text = tag.get_text(separator=' ', strip=True)
|
| 137 |
-
if len(text) > 30:
|
| 138 |
-
blocks.append(text)
|
| 139 |
|
| 140 |
-
# Deduplicate contiguous identical text
|
| 141 |
clean_blocks = []
|
| 142 |
seen_blocks = set()
|
| 143 |
for b in blocks:
|
|
@@ -148,22 +144,13 @@ async def extract_page_content(url: str) -> Dict[str, Any]:
|
|
| 148 |
content_str = "\n\n".join(clean_blocks)
|
| 149 |
page_title = soup.title.string.strip() if soup.title and soup.title.string else ""
|
| 150 |
|
| 151 |
-
result = {
|
| 152 |
-
"url": url,
|
| 153 |
-
"title": page_title,
|
| 154 |
-
"content": content_str,
|
| 155 |
-
"success": True,
|
| 156 |
-
"timestamp": time.time()
|
| 157 |
-
}
|
| 158 |
CONTENT_CACHE[url] = result
|
| 159 |
return result
|
| 160 |
-
except asyncio.TimeoutError:
|
| 161 |
-
return {"url": url, "title": "", "content": "", "success": False, "error": "Timeout"}
|
| 162 |
except Exception as e:
|
| 163 |
return {"url": url, "title": "", "content": "", "success": False, "error": str(e)}
|
| 164 |
|
| 165 |
def rank_chunks_for_rag(text: str, query_tokens: List[str]) -> List[str]:
|
| 166 |
-
"""Deterministically extracts the most relevant chunks from an article for context."""
|
| 167 |
if not text: return []
|
| 168 |
chunks = text.split("\n\n")
|
| 169 |
q_set = set(query_tokens)
|
|
@@ -172,14 +159,11 @@ def rank_chunks_for_rag(text: str, query_tokens: List[str]) -> List[str]:
|
|
| 172 |
for c in chunks:
|
| 173 |
c_toks = set(tokenize(c))
|
| 174 |
score = len(q_set.intersection(c_toks))
|
| 175 |
-
if score > 0:
|
| 176 |
-
scored_chunks.append((score, c))
|
| 177 |
|
| 178 |
-
if not scored_chunks and chunks:
|
| 179 |
-
scored_chunks = [(0, c) for c in chunks[:3]] # Fallback to top paragraphs
|
| 180 |
-
|
| 181 |
scored_chunks.sort(reverse=True, key=lambda x: x[0])
|
| 182 |
-
return [c for score, c in scored_chunks[:
|
| 183 |
|
| 184 |
|
| 185 |
# --- 4. CORE DETERMINISTIC SEARCH INDEX ---
|
|
@@ -290,8 +274,7 @@ class HybridSearchIndex:
|
|
| 290 |
scores["proximity"] = 1.0 / (1.0 + (max(positions) - min(positions)))
|
| 291 |
|
| 292 |
year_match = re.search(r'\b(202[0-6])\b', url + " " + desc)
|
| 293 |
-
if year_match:
|
| 294 |
-
scores["freshness"] = math.exp(-(datetime.now().year - int(year_match.group(1))) / 2.0)
|
| 295 |
else: scores["freshness"] = 0.3
|
| 296 |
|
| 297 |
q_score = 0.0
|
|
@@ -367,12 +350,9 @@ class HybridSearchIndex:
|
|
| 367 |
diversified_results.append(doc_info)
|
| 368 |
if len(diversified_results) >= STRICT_RESULT_LIMIT: break # STRICT LIMIT
|
| 369 |
|
| 370 |
-
# Assign final rank
|
| 371 |
for idx, item in enumerate(diversified_results): item["rank"] = idx + 1
|
| 372 |
-
|
| 373 |
return diversified_results, intent_info
|
| 374 |
|
| 375 |
-
# --- INITIALIZATION ---
|
| 376 |
search_index = HybridSearchIndex()
|
| 377 |
|
| 378 |
def load_and_index_dataset():
|
|
@@ -403,12 +383,12 @@ def load_and_index_dataset():
|
|
| 403 |
except: pass
|
| 404 |
search_index.build(raw_data)
|
| 405 |
|
| 406 |
-
# ---
|
| 407 |
def load_ai_models():
|
| 408 |
global ai_generator
|
| 409 |
-
print("🤖 Loading
|
| 410 |
try:
|
| 411 |
-
model_id = "
|
| 412 |
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
| 413 |
|
| 414 |
if torch.cuda.is_available():
|
|
@@ -418,13 +398,16 @@ def load_ai_models():
|
|
| 418 |
model = AutoModelForCausalLM.from_pretrained(model_id, device_map="cpu", torch_dtype=torch.float32)
|
| 419 |
|
| 420 |
ai_generator = pipeline("text-generation", model=model, tokenizer=tokenizer)
|
| 421 |
-
print("✅
|
| 422 |
except Exception as e:
|
| 423 |
print(f"❌ Model Loading Failed: {e}")
|
| 424 |
|
| 425 |
-
async def run_ai_task(messages, max_tokens: int, query_key: str
|
| 426 |
if not ai_generator: return "⚠️ AI Model is starting up."
|
| 427 |
-
|
|
|
|
|
|
|
|
|
|
| 428 |
|
| 429 |
async with ai_semaphore:
|
| 430 |
def _gen():
|
|
@@ -436,9 +419,11 @@ async def run_ai_task(messages, max_tokens: int, query_key: str, skip_cache: boo
|
|
| 436 |
return result[0]['generated_text'].strip()
|
| 437 |
|
| 438 |
response = await asyncio.to_thread(_gen)
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
|
|
|
|
|
|
| 442 |
return response
|
| 443 |
|
| 444 |
# --- FASTAPI APP ENDPOINTS ---
|
|
@@ -459,7 +444,6 @@ async def get_suggestions(q: str = Query(..., min_length=1)):
|
|
| 459 |
|
| 460 |
@app.get("/content")
|
| 461 |
async def get_content_endpoint(url: str = Query(...)):
|
| 462 |
-
"""Dedicated endpoint to just extract webpage content."""
|
| 463 |
res = await extract_page_content(url)
|
| 464 |
return res
|
| 465 |
|
|
@@ -467,8 +451,6 @@ async def get_content_endpoint(url: str = Query(...)):
|
|
| 467 |
async def pure_search(q: str = Query(...), content: bool = Query(False), debug: bool = Query(False)):
|
| 468 |
req_id = str(uuid.uuid4())
|
| 469 |
results, intent_info = await asyncio.to_thread(search_index.search, q, debug)
|
| 470 |
-
|
| 471 |
-
# Strictly limit to 20 results output
|
| 472 |
top_20 = results[:STRICT_RESULT_LIMIT]
|
| 473 |
|
| 474 |
if content and top_20:
|
|
@@ -479,52 +461,64 @@ async def pure_search(q: str = Query(...), content: bool = Query(False), debug:
|
|
| 479 |
|
| 480 |
return {"request_id": req_id, "query": q, "results_count": len(top_20), "results": top_20}
|
| 481 |
|
| 482 |
-
async def process_aimode(q: str, include_content: bool, debug: bool):
|
| 483 |
req_id = str(uuid.uuid4())
|
|
|
|
| 484 |
|
| 485 |
-
# 1.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 486 |
results, intent_info = await asyncio.to_thread(search_index.search, q, debug)
|
| 487 |
top_20 = results[:STRICT_RESULT_LIMIT]
|
| 488 |
query_tokens = tokenize(q)
|
| 489 |
|
| 490 |
-
# Bypass AI cache if it's a News/Latest query
|
| 491 |
-
is_news = "NEWS" in intent_info["signals"]
|
| 492 |
-
|
| 493 |
rag_context_parts = []
|
| 494 |
used_sources = []
|
| 495 |
|
| 496 |
if top_20:
|
| 497 |
-
#
|
| 498 |
-
tasks = [extract_page_content(r["url"]) for r in top_20]
|
| 499 |
extracted_data = await asyncio.gather(*tasks)
|
| 500 |
|
| 501 |
-
# 3. Deterministic Chunk Selection for Context
|
| 502 |
valid_sources_count = 0
|
| 503 |
-
for i, (r, ext) in enumerate(zip(top_20, extracted_data)):
|
| 504 |
-
if include_content:
|
| 505 |
-
r["content"] = ext.get("content", "") if ext.get("success") else ""
|
| 506 |
|
| 507 |
-
if ext.get("success") and ext.get("content") and valid_sources_count <
|
| 508 |
best_chunks = rank_chunks_for_rag(ext["content"], query_tokens)
|
| 509 |
if best_chunks:
|
| 510 |
-
rag_context_parts.append(f"[
|
| 511 |
used_sources.append({"id": valid_sources_count+1, "title": r["title"], "url": r["url"]})
|
| 512 |
valid_sources_count += 1
|
| 513 |
|
| 514 |
-
|
| 515 |
-
system_prompt =
|
| 516 |
-
|
| 517 |
-
"Do not invent facts. If the context contains the answer, summarize it clearly and cite the sources used (e.g., [Source 1]). "
|
| 518 |
-
"If the context does not contain the answer, state that you do not have enough information."
|
| 519 |
-
)
|
| 520 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 521 |
messages = [
|
| 522 |
{"role": "system", "content": system_prompt},
|
| 523 |
-
{"role": "user", "content":
|
| 524 |
]
|
| 525 |
|
| 526 |
-
# 4. Generate
|
| 527 |
-
ai_reply = await run_ai_task(messages, max_tokens=
|
| 528 |
|
| 529 |
return {
|
| 530 |
"request_id": req_id,
|
|
@@ -532,16 +526,17 @@ async def process_aimode(q: str, include_content: bool, debug: bool):
|
|
| 532 |
"ai_response": ai_reply,
|
| 533 |
"results_count": len(top_20),
|
| 534 |
"results": top_20,
|
| 535 |
-
"sources": used_sources
|
|
|
|
| 536 |
}
|
| 537 |
|
| 538 |
@app.get("/aimode")
|
| 539 |
async def ai_mode_get(q: str = Query(...), content: bool = Query(False), debug: bool = Query(False)):
|
| 540 |
-
return await process_aimode(q, content, debug)
|
| 541 |
|
| 542 |
@app.post("/aimode")
|
| 543 |
async def ai_mode_post(q: str = Form(...), content: bool = Form(False), debug: bool = Form(False)):
|
| 544 |
-
return await process_aimode(q, content, debug)
|
| 545 |
|
| 546 |
if __name__ == "__main__":
|
| 547 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
| 23 |
from bs4 import BeautifulSoup
|
| 24 |
import torch
|
| 25 |
|
| 26 |
+
# Hugging Face imports
|
| 27 |
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, BitsAndBytesConfig
|
| 28 |
|
| 29 |
# --- CONFIGURATION & MULTIPROCESSING ---
|
|
|
|
| 37 |
ai_generator = None
|
| 38 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 39 |
|
| 40 |
+
# Strict Cache Structures (Guarantees no re-runs for identical queries)
|
| 41 |
AI_RESPONSE_CACHE = {}
|
| 42 |
+
MAX_AI_CACHE_SIZE = 10000
|
| 43 |
|
| 44 |
CONTENT_CACHE = {}
|
| 45 |
MAX_CONTENT_CONCURRENCY = 10
|
|
|
|
| 113 |
|
| 114 |
# --- 3. DEDICATED WEB CONTENT EXTRACTION ---
|
| 115 |
async def extract_page_content(url: str) -> Dict[str, Any]:
|
|
|
|
| 116 |
if url in CONTENT_CACHE:
|
| 117 |
return CONTENT_CACHE[url]
|
| 118 |
|
| 119 |
async with content_semaphore:
|
| 120 |
try:
|
| 121 |
async with aiohttp.ClientSession() as session:
|
| 122 |
+
async with session.get(url, timeout=3.0, headers={"User-Agent": "Mozilla/5.0 NebulixBot"}) as resp:
|
| 123 |
if resp.status != 200:
|
| 124 |
return {"url": url, "title": "", "content": "", "success": False, "error": f"HTTP {resp.status}"}
|
| 125 |
|
| 126 |
html = await resp.text()
|
| 127 |
soup = BeautifulSoup(html, "html.parser")
|
| 128 |
|
|
|
|
| 129 |
for tag in soup(['script', 'style', 'nav', 'footer', 'header', 'aside', 'noscript', 'form', 'button', 'iframe']):
|
| 130 |
tag.decompose()
|
| 131 |
|
| 132 |
blocks = []
|
| 133 |
for tag in soup.find_all(['h1', 'h2', 'h3', 'p', 'article', 'main', 'li']):
|
| 134 |
text = tag.get_text(separator=' ', strip=True)
|
| 135 |
+
if len(text) > 30: blocks.append(text)
|
|
|
|
| 136 |
|
|
|
|
| 137 |
clean_blocks = []
|
| 138 |
seen_blocks = set()
|
| 139 |
for b in blocks:
|
|
|
|
| 144 |
content_str = "\n\n".join(clean_blocks)
|
| 145 |
page_title = soup.title.string.strip() if soup.title and soup.title.string else ""
|
| 146 |
|
| 147 |
+
result = {"url": url, "title": page_title, "content": content_str, "success": True}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
CONTENT_CACHE[url] = result
|
| 149 |
return result
|
|
|
|
|
|
|
| 150 |
except Exception as e:
|
| 151 |
return {"url": url, "title": "", "content": "", "success": False, "error": str(e)}
|
| 152 |
|
| 153 |
def rank_chunks_for_rag(text: str, query_tokens: List[str]) -> List[str]:
|
|
|
|
| 154 |
if not text: return []
|
| 155 |
chunks = text.split("\n\n")
|
| 156 |
q_set = set(query_tokens)
|
|
|
|
| 159 |
for c in chunks:
|
| 160 |
c_toks = set(tokenize(c))
|
| 161 |
score = len(q_set.intersection(c_toks))
|
| 162 |
+
if score > 0: scored_chunks.append((score, c))
|
|
|
|
| 163 |
|
| 164 |
+
if not scored_chunks and chunks: scored_chunks = [(0, c) for c in chunks[:3]]
|
|
|
|
|
|
|
| 165 |
scored_chunks.sort(reverse=True, key=lambda x: x[0])
|
| 166 |
+
return [c for score, c in scored_chunks[:2]] # Strictly reduced for token limits
|
| 167 |
|
| 168 |
|
| 169 |
# --- 4. CORE DETERMINISTIC SEARCH INDEX ---
|
|
|
|
| 274 |
scores["proximity"] = 1.0 / (1.0 + (max(positions) - min(positions)))
|
| 275 |
|
| 276 |
year_match = re.search(r'\b(202[0-6])\b', url + " " + desc)
|
| 277 |
+
if year_match: scores["freshness"] = math.exp(-(datetime.now().year - int(year_match.group(1))) / 2.0)
|
|
|
|
| 278 |
else: scores["freshness"] = 0.3
|
| 279 |
|
| 280 |
q_score = 0.0
|
|
|
|
| 350 |
diversified_results.append(doc_info)
|
| 351 |
if len(diversified_results) >= STRICT_RESULT_LIMIT: break # STRICT LIMIT
|
| 352 |
|
|
|
|
| 353 |
for idx, item in enumerate(diversified_results): item["rank"] = idx + 1
|
|
|
|
| 354 |
return diversified_results, intent_info
|
| 355 |
|
|
|
|
| 356 |
search_index = HybridSearchIndex()
|
| 357 |
|
| 358 |
def load_and_index_dataset():
|
|
|
|
| 383 |
except: pass
|
| 384 |
search_index.build(raw_data)
|
| 385 |
|
| 386 |
+
# --- AI GENERATION MODULE (SMOLLM 135M) ---
|
| 387 |
def load_ai_models():
|
| 388 |
global ai_generator
|
| 389 |
+
print("🤖 Loading HuggingFaceTB/SmolLM-135M-Instruct (Ultra Fast)...")
|
| 390 |
try:
|
| 391 |
+
model_id = "HuggingFaceTB/SmolLM-135M-Instruct"
|
| 392 |
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
| 393 |
|
| 394 |
if torch.cuda.is_available():
|
|
|
|
| 398 |
model = AutoModelForCausalLM.from_pretrained(model_id, device_map="cpu", torch_dtype=torch.float32)
|
| 399 |
|
| 400 |
ai_generator = pipeline("text-generation", model=model, tokenizer=tokenizer)
|
| 401 |
+
print("✅ SmolLM AI Ready!")
|
| 402 |
except Exception as e:
|
| 403 |
print(f"❌ Model Loading Failed: {e}")
|
| 404 |
|
| 405 |
+
async def run_ai_task(messages, max_tokens: int, query_key: str):
|
| 406 |
if not ai_generator: return "⚠️ AI Model is starting up."
|
| 407 |
+
|
| 408 |
+
# EXACT CACHE MATCH - Guarantees User 1 and User 2 get identical cached response
|
| 409 |
+
if query_key in AI_RESPONSE_CACHE:
|
| 410 |
+
return AI_RESPONSE_CACHE[query_key]
|
| 411 |
|
| 412 |
async with ai_semaphore:
|
| 413 |
def _gen():
|
|
|
|
| 419 |
return result[0]['generated_text'].strip()
|
| 420 |
|
| 421 |
response = await asyncio.to_thread(_gen)
|
| 422 |
+
|
| 423 |
+
# Unconditionally cache to save computing power
|
| 424 |
+
if len(AI_RESPONSE_CACHE) > MAX_AI_CACHE_SIZE: AI_RESPONSE_CACHE.clear()
|
| 425 |
+
AI_RESPONSE_CACHE[query_key] = response
|
| 426 |
+
|
| 427 |
return response
|
| 428 |
|
| 429 |
# --- FASTAPI APP ENDPOINTS ---
|
|
|
|
| 444 |
|
| 445 |
@app.get("/content")
|
| 446 |
async def get_content_endpoint(url: str = Query(...)):
|
|
|
|
| 447 |
res = await extract_page_content(url)
|
| 448 |
return res
|
| 449 |
|
|
|
|
| 451 |
async def pure_search(q: str = Query(...), content: bool = Query(False), debug: bool = Query(False)):
|
| 452 |
req_id = str(uuid.uuid4())
|
| 453 |
results, intent_info = await asyncio.to_thread(search_index.search, q, debug)
|
|
|
|
|
|
|
| 454 |
top_20 = results[:STRICT_RESULT_LIMIT]
|
| 455 |
|
| 456 |
if content and top_20:
|
|
|
|
| 461 |
|
| 462 |
return {"request_id": req_id, "query": q, "results_count": len(top_20), "results": top_20}
|
| 463 |
|
| 464 |
+
async def process_aimode(q: str, include_content: bool, debug: bool, max_ai_tokens: int):
|
| 465 |
req_id = str(uuid.uuid4())
|
| 466 |
+
cache_key = f"ai_{normalize_text(q)}_{max_ai_tokens}"
|
| 467 |
|
| 468 |
+
# 1. IMMEDIATE CACHE CHECK (0s latency if previously asked)
|
| 469 |
+
if cache_key in AI_RESPONSE_CACHE:
|
| 470 |
+
results, _ = await asyncio.to_thread(search_index.search, q, debug)
|
| 471 |
+
return {
|
| 472 |
+
"request_id": req_id,
|
| 473 |
+
"query": q,
|
| 474 |
+
"ai_response": AI_RESPONSE_CACHE[cache_key],
|
| 475 |
+
"results_count": len(results[:STRICT_RESULT_LIMIT]),
|
| 476 |
+
"results": results[:STRICT_RESULT_LIMIT],
|
| 477 |
+
"sources": [],
|
| 478 |
+
"cached": True
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
# 2. Deterministic Search
|
| 482 |
results, intent_info = await asyncio.to_thread(search_index.search, q, debug)
|
| 483 |
top_20 = results[:STRICT_RESULT_LIMIT]
|
| 484 |
query_tokens = tokenize(q)
|
| 485 |
|
|
|
|
|
|
|
|
|
|
| 486 |
rag_context_parts = []
|
| 487 |
used_sources = []
|
| 488 |
|
| 489 |
if top_20:
|
| 490 |
+
tasks = [extract_page_content(r["url"]) for r in top_20[:3]] # Only fetch top 3 to keep it fast
|
|
|
|
| 491 |
extracted_data = await asyncio.gather(*tasks)
|
| 492 |
|
|
|
|
| 493 |
valid_sources_count = 0
|
| 494 |
+
for i, (r, ext) in enumerate(zip(top_20[:3], extracted_data)):
|
| 495 |
+
if include_content: r["content"] = ext.get("content", "") if ext.get("success") else ""
|
|
|
|
| 496 |
|
| 497 |
+
if ext.get("success") and ext.get("content") and valid_sources_count < 3:
|
| 498 |
best_chunks = rank_chunks_for_rag(ext["content"], query_tokens)
|
| 499 |
if best_chunks:
|
| 500 |
+
rag_context_parts.append(f"[{valid_sources_count+1}]: {' ... '.join(best_chunks)}")
|
| 501 |
used_sources.append({"id": valid_sources_count+1, "title": r["title"], "url": r["url"]})
|
| 502 |
valid_sources_count += 1
|
| 503 |
|
| 504 |
+
# 3. STRICT 100 TOKEN INPUT LIMIT (User Query + RAG)
|
| 505 |
+
system_prompt = "Answer directly based on context. Cite sources."
|
| 506 |
+
combined_input = f"Context:\n{chr(10).join(rag_context_parts)}\n\nQuery: {q}"
|
|
|
|
|
|
|
|
|
|
| 507 |
|
| 508 |
+
if ai_generator:
|
| 509 |
+
tokenizer = ai_generator.tokenizer
|
| 510 |
+
encoded_input = tokenizer.encode(combined_input, add_special_tokens=False)
|
| 511 |
+
if len(encoded_input) > 90: # 90 + 10 for system prompt = 100 max tokens input
|
| 512 |
+
encoded_input = encoded_input[:90]
|
| 513 |
+
combined_input = tokenizer.decode(encoded_input, skip_special_tokens=True)
|
| 514 |
+
|
| 515 |
messages = [
|
| 516 |
{"role": "system", "content": system_prompt},
|
| 517 |
+
{"role": "user", "content": combined_input}
|
| 518 |
]
|
| 519 |
|
| 520 |
+
# 4. Generate Answer (Restricted to 20 or 50 tokens based on GET/POST)
|
| 521 |
+
ai_reply = await run_ai_task(messages, max_tokens=max_ai_tokens, query_key=cache_key)
|
| 522 |
|
| 523 |
return {
|
| 524 |
"request_id": req_id,
|
|
|
|
| 526 |
"ai_response": ai_reply,
|
| 527 |
"results_count": len(top_20),
|
| 528 |
"results": top_20,
|
| 529 |
+
"sources": used_sources,
|
| 530 |
+
"cached": False
|
| 531 |
}
|
| 532 |
|
| 533 |
@app.get("/aimode")
|
| 534 |
async def ai_mode_get(q: str = Query(...), content: bool = Query(False), debug: bool = Query(False)):
|
| 535 |
+
return await process_aimode(q, content, debug, max_ai_tokens=20) # Strictly 20 Tokens
|
| 536 |
|
| 537 |
@app.post("/aimode")
|
| 538 |
async def ai_mode_post(q: str = Form(...), content: bool = Form(False), debug: bool = Form(False)):
|
| 539 |
+
return await process_aimode(q, content, debug, max_ai_tokens=50) # Strictly 50 Tokens
|
| 540 |
|
| 541 |
if __name__ == "__main__":
|
| 542 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|