Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# ============================================================
|
| 2 |
# X-RUDRA CHAT
|
| 3 |
-
#
|
| 4 |
# ============================================================
|
| 5 |
|
| 6 |
from __future__ import annotations
|
|
@@ -21,24 +21,19 @@ from gradio_client import Client
|
|
| 21 |
# ============================================================
|
| 22 |
|
| 23 |
APP_NAME = "X-RUDRA"
|
| 24 |
-
VERSION = "3.
|
| 25 |
|
| 26 |
-
# M1 and M2 Spaces – change these to your own if needed
|
| 27 |
M1_REPO = os.getenv("M1_REPO", "Shrijanagain/M1")
|
| 28 |
M2_REPO = os.getenv("M2_REPO", "Shrijanagain/M2")
|
| 29 |
PORT = int(os.getenv("PORT", "7860"))
|
| 30 |
|
| 31 |
-
# ------------------------------------------------------------
|
| 32 |
-
# HF_TOKEN is recommended to avoid rate limits
|
| 33 |
-
# Set it as a Secret in your Space settings.
|
| 34 |
-
# ------------------------------------------------------------
|
| 35 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 36 |
if HF_TOKEN:
|
| 37 |
-
os.environ["HF_TOKEN"] = HF_TOKEN
|
| 38 |
|
| 39 |
|
| 40 |
# ============================================================
|
| 41 |
-
# GRADIO CLIENTS FOR M1 / M2
|
| 42 |
# ============================================================
|
| 43 |
|
| 44 |
_M1_CLIENT = None
|
|
@@ -48,7 +43,6 @@ def get_m1_client():
|
|
| 48 |
global _M1_CLIENT
|
| 49 |
if _M1_CLIENT is None:
|
| 50 |
try:
|
| 51 |
-
# Construct the public URL of the Space
|
| 52 |
url = f"https://{M1_REPO.replace('/', '-')}.hf.space"
|
| 53 |
_M1_CLIENT = Client(url)
|
| 54 |
except Exception as e:
|
|
@@ -73,10 +67,14 @@ def get_m2_client():
|
|
| 73 |
# ============================================================
|
| 74 |
|
| 75 |
def is_casual_query(text: str) -> bool:
|
| 76 |
-
"""Return True if the query is casual/timepass."""
|
| 77 |
text = text.lower().strip()
|
| 78 |
-
|
|
|
|
| 79 |
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
casual_patterns = [
|
| 81 |
"hey", "hi", "hello", "yo", "what's up", "how are you",
|
| 82 |
"good morning", "good evening", "good night",
|
|
@@ -85,13 +83,13 @@ def is_casual_query(text: str) -> bool:
|
|
| 85 |
"who are you", "what can you do", "help", "thanks"
|
| 86 |
]
|
| 87 |
for pattern in casual_patterns:
|
| 88 |
-
if text
|
| 89 |
return True
|
| 90 |
return False
|
| 91 |
|
| 92 |
|
| 93 |
# ============================================================
|
| 94 |
-
# LAZY ENGINE (
|
| 95 |
# ============================================================
|
| 96 |
|
| 97 |
_ENGINE = None
|
|
@@ -104,6 +102,129 @@ def get_engine():
|
|
| 104 |
return _ENGINE
|
| 105 |
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
# ============================================================
|
| 108 |
# FORMATTERS (Sources, Evidence, Verification)
|
| 109 |
# ============================================================
|
|
@@ -174,14 +295,6 @@ def format_verification(contradictions):
|
|
| 174 |
return "\n\n".join(output)
|
| 175 |
|
| 176 |
|
| 177 |
-
def extract_answer(data):
|
| 178 |
-
for key in ("final_answer", "answer", "response", "final", "synthesis", "summary"):
|
| 179 |
-
val = data.get(key)
|
| 180 |
-
if isinstance(val, str) and val.strip():
|
| 181 |
-
return val.strip()
|
| 182 |
-
return None
|
| 183 |
-
|
| 184 |
-
|
| 185 |
def build_activity(data, elapsed_ms):
|
| 186 |
sources = data.get("sources", []) or data.get("results", [])
|
| 187 |
claims = data.get("claims", [])
|
|
@@ -193,13 +306,13 @@ def build_activity(data, elapsed_ms):
|
|
| 193 |
| Stage | Status |
|
| 194 |
|---|---|
|
| 195 |
| Task analysis | ✅ Complete |
|
| 196 |
-
| M1 research |
|
| 197 |
-
| M2 research |
|
| 198 |
| Web discovery | ✅ Complete |
|
| 199 |
-
| Evidence extraction | {"✅" if
|
| 200 |
| Source verification | ✅ Complete |
|
| 201 |
| Contradiction check | {"⚠️ Found" if contradictions else "✅ Clear"} |
|
| 202 |
-
| Final synthesis |
|
| 203 |
|
| 204 |
**Sources:** `{len(sources)}`
|
| 205 |
**Claims:** `{len(claims)}`
|
|
@@ -240,50 +353,6 @@ def safe_dict(value):
|
|
| 240 |
return {"result": str(value)}
|
| 241 |
|
| 242 |
|
| 243 |
-
# ============================================================
|
| 244 |
-
# GET MODEL RESPONSE FOR CASUAL QUERIES
|
| 245 |
-
# ============================================================
|
| 246 |
-
|
| 247 |
-
def get_casual_model_response(query: str) -> str:
|
| 248 |
-
"""Call M1 (or M2) to generate a friendly reply for casual queries."""
|
| 249 |
-
# Try M1 first
|
| 250 |
-
client = get_m1_client()
|
| 251 |
-
if client is not None:
|
| 252 |
-
try:
|
| 253 |
-
# Assuming the endpoint is /generate with inputs: prompt, max_tokens, temperature
|
| 254 |
-
result = client.predict(
|
| 255 |
-
prompt=f"User: {query}\nAssistant:",
|
| 256 |
-
max_tokens=64,
|
| 257 |
-
temperature=0.7,
|
| 258 |
-
api_name="/generate"
|
| 259 |
-
)
|
| 260 |
-
if result and isinstance(result, str) and result.strip():
|
| 261 |
-
return result.strip()
|
| 262 |
-
except Exception as e:
|
| 263 |
-
print(f"M1 casual call failed: {e}")
|
| 264 |
-
|
| 265 |
-
# Try M2
|
| 266 |
-
client = get_m2_client()
|
| 267 |
-
if client is not None:
|
| 268 |
-
try:
|
| 269 |
-
result = client.predict(
|
| 270 |
-
prompt=f"User: {query}\nAssistant:",
|
| 271 |
-
max_tokens=64,
|
| 272 |
-
temperature=0.7,
|
| 273 |
-
api_name="/generate"
|
| 274 |
-
)
|
| 275 |
-
if result and isinstance(result, str) and result.strip():
|
| 276 |
-
return result.strip()
|
| 277 |
-
except Exception as e:
|
| 278 |
-
print(f"M2 casual call failed: {e}")
|
| 279 |
-
|
| 280 |
-
# Ultimate fallback
|
| 281 |
-
return (
|
| 282 |
-
f"👋 Hi there! I'm X‑RUDRA, your research assistant. "
|
| 283 |
-
f"How can I help you today? (Your message `{query}` was casual, so I kept it light.)"
|
| 284 |
-
)
|
| 285 |
-
|
| 286 |
-
|
| 287 |
# ============================================================
|
| 288 |
# MAIN RESEARCH FUNCTION
|
| 289 |
# ============================================================
|
|
@@ -294,9 +363,7 @@ async def do_research(question, max_results, max_rounds, use_models, freshness):
|
|
| 294 |
|
| 295 |
question = str(question).strip()
|
| 296 |
|
| 297 |
-
#
|
| 298 |
-
# CASUAL QUERY – get a model‑generated reply (no web search)
|
| 299 |
-
# ------------------------------------------------------------
|
| 300 |
if is_casual_query(question):
|
| 301 |
answer = get_casual_model_response(question)
|
| 302 |
history = [
|
|
@@ -305,11 +372,8 @@ async def do_research(question, max_results, max_rounds, use_models, freshness):
|
|
| 305 |
]
|
| 306 |
return history, "⚡ Casual chat (model reply, no search).", "", "", ""
|
| 307 |
|
| 308 |
-
#
|
| 309 |
-
# SERIOUS QUERY – run the engine (web + models)
|
| 310 |
-
# ------------------------------------------------------------
|
| 311 |
started = time.perf_counter()
|
| 312 |
-
|
| 313 |
try:
|
| 314 |
engine = get_engine()
|
| 315 |
report = await engine.search(
|
|
@@ -322,12 +386,6 @@ async def do_research(question, max_results, max_rounds, use_models, freshness):
|
|
| 322 |
data = safe_dict(report)
|
| 323 |
elapsed_ms = int((time.perf_counter() - started) * 1000)
|
| 324 |
|
| 325 |
-
# Debug (optional)
|
| 326 |
-
print("\n" + "="*60)
|
| 327 |
-
print("RAW ENGINE DATA (first 3000 chars):")
|
| 328 |
-
print(json.dumps(data, indent=2, default=str)[:3000])
|
| 329 |
-
print("="*60 + "\n")
|
| 330 |
-
|
| 331 |
# Convert 'results' to 'sources' if needed
|
| 332 |
sources = data.get("sources", [])
|
| 333 |
if not sources:
|
|
@@ -344,29 +402,18 @@ async def do_research(question, max_results, max_rounds, use_models, freshness):
|
|
| 344 |
})
|
| 345 |
data["sources"] = sources
|
| 346 |
|
| 347 |
-
#
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
top = sources[:5]
|
| 352 |
-
parts = [f"Based on the top results:"]
|
| 353 |
-
for i, src in enumerate(top, 1):
|
| 354 |
-
title = src.get("title", "Untitled")
|
| 355 |
-
snippet = src.get("snippet", src.get("description", ""))
|
| 356 |
-
parts.append(f"{i}. **{title}** – {snippet[:200]}..." if snippet else f"{i}. **{title}**")
|
| 357 |
-
answer = "\n\n".join(parts)
|
| 358 |
-
else:
|
| 359 |
-
answer = "No information found. Try rephrasing your question."
|
| 360 |
-
|
| 361 |
-
claims = data.get("claims", [])
|
| 362 |
sources_md = format_sources(sources)
|
| 363 |
-
evidence_md = format_evidence(claims)
|
| 364 |
verification_md = format_verification(data.get("contradictions", []))
|
| 365 |
activity_md = build_activity(data, elapsed_ms)
|
| 366 |
|
| 367 |
history = [
|
| 368 |
{"role": "user", "content": question},
|
| 369 |
-
{"role": "assistant", "content":
|
| 370 |
]
|
| 371 |
|
| 372 |
return history, activity_md, sources_md, evidence_md, verification_md
|
|
@@ -408,7 +455,7 @@ def health_check():
|
|
| 408 |
|
| 409 |
|
| 410 |
# ============================================================
|
| 411 |
-
# CSS
|
| 412 |
# ============================================================
|
| 413 |
|
| 414 |
CSS = """
|
|
@@ -423,15 +470,11 @@ footer { display: none !important; }
|
|
| 423 |
"""
|
| 424 |
|
| 425 |
|
| 426 |
-
# ============================================================
|
| 427 |
-
# GRADIO UI
|
| 428 |
-
# ============================================================
|
| 429 |
-
|
| 430 |
with gr.Blocks(title=APP_NAME) as demo:
|
| 431 |
gr.HTML("""
|
| 432 |
<div id="header">
|
| 433 |
<div id="logo">⚡ X-RUDRA</div>
|
| 434 |
-
<div id="tagline">Dual
|
| 435 |
</div>
|
| 436 |
""")
|
| 437 |
|
|
|
|
| 1 |
# ============================================================
|
| 2 |
# X-RUDRA CHAT
|
| 3 |
+
# Dual‑Model + Web Research · Gradio Space
|
| 4 |
# ============================================================
|
| 5 |
|
| 6 |
from __future__ import annotations
|
|
|
|
| 21 |
# ============================================================
|
| 22 |
|
| 23 |
APP_NAME = "X-RUDRA"
|
| 24 |
+
VERSION = "3.5.0" # bumped version
|
| 25 |
|
|
|
|
| 26 |
M1_REPO = os.getenv("M1_REPO", "Shrijanagain/M1")
|
| 27 |
M2_REPO = os.getenv("M2_REPO", "Shrijanagain/M2")
|
| 28 |
PORT = int(os.getenv("PORT", "7860"))
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 31 |
if HF_TOKEN:
|
| 32 |
+
os.environ["HF_TOKEN"] = HF_TOKEN
|
| 33 |
|
| 34 |
|
| 35 |
# ============================================================
|
| 36 |
+
# GRADIO CLIENTS FOR M1 / M2
|
| 37 |
# ============================================================
|
| 38 |
|
| 39 |
_M1_CLIENT = None
|
|
|
|
| 43 |
global _M1_CLIENT
|
| 44 |
if _M1_CLIENT is None:
|
| 45 |
try:
|
|
|
|
| 46 |
url = f"https://{M1_REPO.replace('/', '-')}.hf.space"
|
| 47 |
_M1_CLIENT = Client(url)
|
| 48 |
except Exception as e:
|
|
|
|
| 67 |
# ============================================================
|
| 68 |
|
| 69 |
def is_casual_query(text: str) -> bool:
|
|
|
|
| 70 |
text = text.lower().strip()
|
| 71 |
+
words = text.split()
|
| 72 |
+
if len(words) <= 2:
|
| 73 |
return True
|
| 74 |
+
question_words = {"what", "how", "why", "when", "where", "who", "which",
|
| 75 |
+
"can", "could", "would", "will", "is", "are", "do", "does"}
|
| 76 |
+
if words[0] in question_words and len(words) >= 3:
|
| 77 |
+
return False
|
| 78 |
casual_patterns = [
|
| 79 |
"hey", "hi", "hello", "yo", "what's up", "how are you",
|
| 80 |
"good morning", "good evening", "good night",
|
|
|
|
| 83 |
"who are you", "what can you do", "help", "thanks"
|
| 84 |
]
|
| 85 |
for pattern in casual_patterns:
|
| 86 |
+
if text == pattern or text.startswith(pattern + " "):
|
| 87 |
return True
|
| 88 |
return False
|
| 89 |
|
| 90 |
|
| 91 |
# ============================================================
|
| 92 |
+
# LAZY ENGINE (web search)
|
| 93 |
# ============================================================
|
| 94 |
|
| 95 |
_ENGINE = None
|
|
|
|
| 102 |
return _ENGINE
|
| 103 |
|
| 104 |
|
| 105 |
+
# ============================================================
|
| 106 |
+
# HELPERS – CALL MODELS
|
| 107 |
+
# ============================================================
|
| 108 |
+
|
| 109 |
+
def call_model(client, prompt, max_tokens=512, temperature=0.7):
|
| 110 |
+
if client is None:
|
| 111 |
+
return None
|
| 112 |
+
try:
|
| 113 |
+
result = client.predict(
|
| 114 |
+
prompt=prompt,
|
| 115 |
+
max_tokens=max_tokens,
|
| 116 |
+
temperature=temperature,
|
| 117 |
+
api_name="/generate"
|
| 118 |
+
)
|
| 119 |
+
if result and isinstance(result, str) and result.strip():
|
| 120 |
+
return result.strip()
|
| 121 |
+
except Exception as e:
|
| 122 |
+
print(f"Model call failed: {e}")
|
| 123 |
+
return None
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
# ============================================================
|
| 127 |
+
# SYNTHESIS: M1 (draft) → M2 (refine)
|
| 128 |
+
# ============================================================
|
| 129 |
+
|
| 130 |
+
def get_combined_model_answer(question, sources, max_tokens=512, temperature=0.7):
|
| 131 |
+
# Build a concise summary of top sources (max 5)
|
| 132 |
+
top_sources = sources[:5] if sources else []
|
| 133 |
+
sources_text = ""
|
| 134 |
+
if top_sources:
|
| 135 |
+
for i, src in enumerate(top_sources, 1):
|
| 136 |
+
title = src.get("title", "Untitled")
|
| 137 |
+
snippet = src.get("snippet", src.get("description", ""))
|
| 138 |
+
sources_text += f"{i}. {title}: {snippet[:300]}\n"
|
| 139 |
+
else:
|
| 140 |
+
sources_text = "No specific information available."
|
| 141 |
+
|
| 142 |
+
# ----- 1. M1 generates draft answer -----
|
| 143 |
+
prompt_m1 = f"""Question: {question}
|
| 144 |
+
|
| 145 |
+
Information:
|
| 146 |
+
{sources_text}
|
| 147 |
+
|
| 148 |
+
Based on the information above and your knowledge, provide a comprehensive, accurate, and well‑structured answer to the question. Be direct and natural – write as if you are an expert answering a user.
|
| 149 |
+
|
| 150 |
+
Answer:"""
|
| 151 |
+
|
| 152 |
+
m1_client = get_m1_client()
|
| 153 |
+
m2_client = get_m2_client()
|
| 154 |
+
|
| 155 |
+
draft = call_model(m1_client, prompt_m1, max_tokens, temperature)
|
| 156 |
+
if not draft:
|
| 157 |
+
# Fallback: try M2 directly for draft
|
| 158 |
+
draft = call_model(m2_client, prompt_m1, max_tokens, temperature)
|
| 159 |
+
|
| 160 |
+
if not draft:
|
| 161 |
+
# Ultimate fallback – simple summarization
|
| 162 |
+
if sources:
|
| 163 |
+
parts = ["Based on available information:"]
|
| 164 |
+
for i, src in enumerate(sources[:5], 1):
|
| 165 |
+
title = src.get("title", "Untitled")
|
| 166 |
+
snippet = src.get("snippet", src.get("description", ""))
|
| 167 |
+
parts.append(f"{i}. {title}: {snippet[:200]}..." if snippet else f"{i}. {title}")
|
| 168 |
+
return "\n\n".join(parts)
|
| 169 |
+
else:
|
| 170 |
+
return "I couldn't find specific information on that topic. Could you rephrase?"
|
| 171 |
+
|
| 172 |
+
# ----- 2. M2 refines the draft -----
|
| 173 |
+
prompt_m2 = f"""Question: {question}
|
| 174 |
+
|
| 175 |
+
Draft answer:
|
| 176 |
+
{draft}
|
| 177 |
+
|
| 178 |
+
Please refine and improve this answer to make it more comprehensive, accurate, and natural. Ensure it directly addresses the question. Provide only the final improved answer, without any extra commentary or meta‑references.
|
| 179 |
+
|
| 180 |
+
Improved answer:"""
|
| 181 |
+
|
| 182 |
+
refined = call_model(m2_client, prompt_m2, max_tokens, temperature)
|
| 183 |
+
if refined:
|
| 184 |
+
return refined
|
| 185 |
+
else:
|
| 186 |
+
return draft
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
# ============================================================
|
| 190 |
+
# CASUAL REPLY (calls M1 or M2)
|
| 191 |
+
# ============================================================
|
| 192 |
+
|
| 193 |
+
def get_casual_model_response(query: str) -> str:
|
| 194 |
+
client = get_m1_client()
|
| 195 |
+
if client is not None:
|
| 196 |
+
try:
|
| 197 |
+
result = client.predict(
|
| 198 |
+
prompt=f"User: {query}\nAssistant:",
|
| 199 |
+
max_tokens=64,
|
| 200 |
+
temperature=0.7,
|
| 201 |
+
api_name="/generate"
|
| 202 |
+
)
|
| 203 |
+
if result and isinstance(result, str) and result.strip():
|
| 204 |
+
return result.strip()
|
| 205 |
+
except Exception as e:
|
| 206 |
+
print(f"M1 casual failed: {e}")
|
| 207 |
+
|
| 208 |
+
client = get_m2_client()
|
| 209 |
+
if client is not None:
|
| 210 |
+
try:
|
| 211 |
+
result = client.predict(
|
| 212 |
+
prompt=f"User: {query}\nAssistant:",
|
| 213 |
+
max_tokens=64,
|
| 214 |
+
temperature=0.7,
|
| 215 |
+
api_name="/generate"
|
| 216 |
+
)
|
| 217 |
+
if result and isinstance(result, str) and result.strip():
|
| 218 |
+
return result.strip()
|
| 219 |
+
except Exception as e:
|
| 220 |
+
print(f"M2 casual failed: {e}")
|
| 221 |
+
|
| 222 |
+
return (
|
| 223 |
+
f"👋 Hi there! I'm X‑RUDRA, your research assistant. "
|
| 224 |
+
f"How can I help you today? (Your message `{query}` was casual, so I kept it light.)"
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
|
| 228 |
# ============================================================
|
| 229 |
# FORMATTERS (Sources, Evidence, Verification)
|
| 230 |
# ============================================================
|
|
|
|
| 295 |
return "\n\n".join(output)
|
| 296 |
|
| 297 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
def build_activity(data, elapsed_ms):
|
| 299 |
sources = data.get("sources", []) or data.get("results", [])
|
| 300 |
claims = data.get("claims", [])
|
|
|
|
| 306 |
| Stage | Status |
|
| 307 |
|---|---|
|
| 308 |
| Task analysis | ✅ Complete |
|
| 309 |
+
| M1 research | ✅ Generated draft |
|
| 310 |
+
| M2 research | ✅ Refined answer |
|
| 311 |
| Web discovery | ✅ Complete |
|
| 312 |
+
| Evidence extraction | {"✅" if claims else "⚙️"} |
|
| 313 |
| Source verification | ✅ Complete |
|
| 314 |
| Contradiction check | {"⚠️ Found" if contradictions else "✅ Clear"} |
|
| 315 |
+
| Final synthesis | ✅ Complete |
|
| 316 |
|
| 317 |
**Sources:** `{len(sources)}`
|
| 318 |
**Claims:** `{len(claims)}`
|
|
|
|
| 353 |
return {"result": str(value)}
|
| 354 |
|
| 355 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
# ============================================================
|
| 357 |
# MAIN RESEARCH FUNCTION
|
| 358 |
# ============================================================
|
|
|
|
| 363 |
|
| 364 |
question = str(question).strip()
|
| 365 |
|
| 366 |
+
# Casual query – skip heavy research
|
|
|
|
|
|
|
| 367 |
if is_casual_query(question):
|
| 368 |
answer = get_casual_model_response(question)
|
| 369 |
history = [
|
|
|
|
| 372 |
]
|
| 373 |
return history, "⚡ Casual chat (model reply, no search).", "", "", ""
|
| 374 |
|
| 375 |
+
# Serious query – web search + two‑stage synthesis
|
|
|
|
|
|
|
| 376 |
started = time.perf_counter()
|
|
|
|
| 377 |
try:
|
| 378 |
engine = get_engine()
|
| 379 |
report = await engine.search(
|
|
|
|
| 386 |
data = safe_dict(report)
|
| 387 |
elapsed_ms = int((time.perf_counter() - started) * 1000)
|
| 388 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
# Convert 'results' to 'sources' if needed
|
| 390 |
sources = data.get("sources", [])
|
| 391 |
if not sources:
|
|
|
|
| 402 |
})
|
| 403 |
data["sources"] = sources
|
| 404 |
|
| 405 |
+
# Generate final answer using M1 + M2
|
| 406 |
+
final_answer = get_combined_model_answer(question, sources)
|
| 407 |
+
|
| 408 |
+
# Build outputs for tabs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
sources_md = format_sources(sources)
|
| 410 |
+
evidence_md = format_evidence(data.get("claims", []))
|
| 411 |
verification_md = format_verification(data.get("contradictions", []))
|
| 412 |
activity_md = build_activity(data, elapsed_ms)
|
| 413 |
|
| 414 |
history = [
|
| 415 |
{"role": "user", "content": question},
|
| 416 |
+
{"role": "assistant", "content": final_answer}
|
| 417 |
]
|
| 418 |
|
| 419 |
return history, activity_md, sources_md, evidence_md, verification_md
|
|
|
|
| 455 |
|
| 456 |
|
| 457 |
# ============================================================
|
| 458 |
+
# CSS AND UI
|
| 459 |
# ============================================================
|
| 460 |
|
| 461 |
CSS = """
|
|
|
|
| 470 |
"""
|
| 471 |
|
| 472 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 473 |
with gr.Blocks(title=APP_NAME) as demo:
|
| 474 |
gr.HTML("""
|
| 475 |
<div id="header">
|
| 476 |
<div id="logo">⚡ X-RUDRA</div>
|
| 477 |
+
<div id="tagline">Dual‑Model AI · Live Web Research · Evidence</div>
|
| 478 |
</div>
|
| 479 |
""")
|
| 480 |
|