Spaces:
Sleeping
Sleeping
File size: 5,308 Bytes
0f23ed7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | """
Bharat Tech Atlas β Chat Engine Core
Implements lazy model loading, keyword fallbacks, web search, LLM generation,
and safety checks (prompt injection + XSS sanitization).
"""
import logging
from typing import Optional, List, Tuple
from ..security import (
validate_chat_message,
detect_prompt_injection,
sanitize_response_text,
escape_html,
audit_log,
)
from .config import (
MODEL_ID,
MAX_NEW_TOKENS,
TEMPERATURE,
TOP_P,
DEVICE_GPU,
DEVICE_CPU,
KEYWORD_RESPONSES,
NEEDS_SEARCH_TRIGGERS,
WEB_SEARCH_MAX_RESULTS,
WEB_SEARCH_QUERY_PREFIX,
SYSTEM_PROMPT,
SYSTEM_PROMPT_WITH_WEB,
)
logger = logging.getLogger(__name__)
# βββ Lazy-loaded pipeline βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
_chat_pipeline = None
def _get_chat_pipeline():
"""Lazy-load Qwen2.5-0.5B-Instruct. Returns None if transformers unavailable."""
global _chat_pipeline
if _chat_pipeline is not None:
return _chat_pipeline
try:
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
import torch
device = DEVICE_GPU if torch.cuda.is_available() else DEVICE_CPU
dtype = torch.float16 if device == 0 else torch.float32
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID, trust_remote_code=True, torch_dtype=dtype,
device_map="auto" if device == 0 else None,
)
_chat_pipeline = pipeline(
"text-generation", model=model, tokenizer=tokenizer,
device=device, do_sample=True, temperature=TEMPERATURE,
top_p=TOP_P, max_new_tokens=MAX_NEW_TOKENS,
)
logger.info("Chat model loaded: %s", MODEL_ID)
return _chat_pipeline
except Exception as e:
logger.warning("Could not load chat model: %s", e)
_chat_pipeline = False
return None
def keyword_response(user_text: str) -> Optional[str]:
"""Return a keyword-match answer without loading the LLM."""
lowered = user_text.lower()
for kw, resp in KEYWORD_RESPONSES.items():
if kw in lowered:
return resp
return None
def needs_web_search(text: str) -> bool:
lowered = text.lower()
return any(t in lowered for t in NEEDS_SEARCH_TRIGGERS)
async def web_search(query: str, max_results: int = WEB_SEARCH_MAX_RESULTS) -> List[dict]:
"""Search DuckDuckGo for fresh news/articles."""
results = []
try:
from duckduckgo_search import DDGS
with DDGS() as ddgs:
for r in ddgs.text(query, max_results=max_results):
title = escape_html(r.get("title", ""))[:200]
url = r.get("href", "")
snippet = escape_html(r.get("body", ""))[:400]
results.append({"title": title, "url": url, "snippet": snippet})
except Exception as e:
logger.warning("Web search failed: %s", e)
return results
def generate_with_model(
messages: List[dict],
web_results: Optional[List[dict]] = None,
req_id: str = "unknown",
) -> Tuple[str, dict]:
"""Generate a response via Qwen. Returns (text, safety_info)."""
pipeline = _get_chat_pipeline()
safety = {
"model_used": False,
"xss_detected": False,
"injection_score": 0.0,
}
if not pipeline:
if web_results:
lines = ["Here are the latest search results:"]
for r in web_results[:5]:
lines.append(f"- {r['title']}: {r['snippet'][:200]}...")
return "\n".join(lines), safety
return ("I'm running in lightweight mode. Ask about unicorns, fintech, SaaS,",
safety)
if web_results:
search_ctx = "\n\n".join([
f"[{i+1}] {r['title']}\n{r['snippet']}\nSource: {r['url']}"
for i, r in enumerate(web_results[:6])
])
chat = [
{"role": "system", "content": SYSTEM_PROMPT_WITH_WEB + f"\n\nSearch results:\n{search_ctx}\n"},
]
else:
chat = [{"role": "system", "content": SYSTEM_PROMPT}]
for m in messages:
chat.append({"role": m["role"], "content": m["content"]})
try:
prompt = pipeline.tokenizer.apply_chat_template(
chat, tokenize=False, add_generation_prompt=True
)
outputs = pipeline(prompt, return_full_text=False, max_new_tokens=MAX_NEW_TOKENS)
raw = outputs[0]["generated_text"].strip()
safety["model_used"] = True
safety["injection_score"] = detect_prompt_injection(raw)
text = sanitize_response_text(raw)
safety["xss_detected"] = text != raw
return text, safety
except Exception as e:
logger.error("Chat generation failed: %s", e)
if web_results:
lines = ["I found these results but couldn't process them fully:"]
for r in web_results[:5]:
lines.append(f"- {r['title']}: {r['snippet'][:200]}...")
return "\n".join(lines), safety
return "I'm having trouble processing that. Try asking about Indian startups or sectors.", safety
|