ST-x-Tony commited on
Commit
60c933d
·
verified ·
1 Parent(s): 1dec737

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +722 -207
app.py CHANGED
@@ -1,205 +1,660 @@
 
 
 
 
 
 
 
1
  import os
2
- import asyncio
3
  import json
4
  import time
 
 
 
5
  import gradio as gr
6
 
7
- from web_search import XrudraWebSearch
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- ENGINE = XrudraWebSearch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
 
13
  # ============================================================
14
- # RESEARCH ENGINE
15
  # ============================================================
16
 
17
- async def research(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  question,
19
  max_results,
20
  max_rounds,
21
  use_models,
22
  freshness,
23
  ):
24
- if not question or not question.strip():
 
 
 
 
25
  return (
26
  [],
27
- "⚪ Waiting for a question...",
28
- "No research started.",
29
  "",
30
  )
31
 
32
- start = time.perf_counter()
 
 
 
 
33
 
34
  try:
35
- report = await ENGINE.search(
36
- question=question.strip(),
37
- max_results=int(max_results),
38
- max_rounds=int(max_rounds),
39
- use_models=use_models,
40
- freshness_mode=freshness,
41
- )
42
 
43
- data = (
44
- report.model_dump()
45
- if hasattr(report, "model_dump")
46
- else report
47
- )
48
 
49
- sources = data.get("sources", [])
50
- claims = data.get("claims", [])
51
- contradictions = data.get("contradictions", [])
52
 
53
  # ----------------------------------------------------
54
- # FINAL ANSWER
55
  # ----------------------------------------------------
56
 
57
- answer_parts = []
58
 
59
- for claim in claims[:8]:
60
- text = claim.get("claim", "").strip()
61
 
62
- if text:
63
- answer_parts.append(text)
 
64
 
65
- if answer_parts:
66
- answer = "\n\n".join(answer_parts)
67
- else:
68
- answer = (
69
- "Research completed, but no high-confidence "
70
- "evidence passages were extracted."
71
- )
72
 
73
- # ----------------------------------------------------
74
- # ACTIVITY
75
- # ----------------------------------------------------
76
 
77
- activity = f"""
78
- ### ⚡ X-RUDRA Research Pipeline
 
 
79
 
80
- Task analyzed
81
- ✓ M1 + M2 query planning completed
82
- ✓ DuckDuckGo web discovery completed
83
- ✓ {len(sources)} sources fetched
84
- ✓ Evidence extraction completed
85
- ✓ Source quality evaluated
86
- ✓ Cross-source verification completed
87
- {"⚠️ Contradictions detected" if contradictions else "✓ No major contradictions detected"}
88
- ✓ Final synthesis completed
89
 
90
- **Research time:** {int((time.perf_counter() - start) * 1000)} ms
91
- """
 
 
 
 
92
 
93
  # ----------------------------------------------------
94
- # SOURCES
95
  # ----------------------------------------------------
96
 
97
- source_md = "### 📚 Sources\n\n"
 
 
98
 
99
- for i, source in enumerate(sources, 1):
100
- title = source.get("title") or "Untitled"
101
- url = source.get("url", "")
102
- method = source.get("fetch_method", "unknown")
103
- score = source.get("source_score", 0)
104
 
105
- source_md += (
106
- f"**{i}. [{title}]({url})**\n\n"
107
- f"`{method}` · source score `{score}`\n\n"
108
- )
109
 
110
- if not sources:
111
- source_md += "No sources returned."
 
 
112
 
113
- # ----------------------------------------------------
114
- # EVIDENCE
115
- # ----------------------------------------------------
 
116
 
117
- evidence_md = "### 🧠 Evidence\n\n"
 
 
118
 
119
- for i, claim in enumerate(claims[:20], 1):
120
- evidence_md += (
121
- f"**Evidence {i}**\n\n"
122
- f"{claim.get('claim', '')}\n\n"
123
- f"Support score: "
124
- f"`{claim.get('support_score', 0)}`\n\n"
125
- f"Source: `{claim.get('source_url', '')}`\n\n"
126
- "---\n\n"
127
- )
128
 
129
- if not claims:
130
- evidence_md += "No evidence extracted."
 
131
 
132
  # ----------------------------------------------------
133
- # CONTRADICTIONS
 
 
 
 
 
134
  # ----------------------------------------------------
135
 
136
- if contradictions:
137
- verification_md = "### ⚠️ Contradictions\n\n"
138
-
139
- for i, item in enumerate(
140
- contradictions,
141
- 1,
142
- ):
143
- verification_md += (
144
- f"**Potential contradiction {i}**\n\n"
145
- f"**Source A:**\n"
146
- f"{item.get('claim_a', '')}\n\n"
147
- f"**Source B:**\n"
148
- f"{item.get('claim_b', '')}\n\n"
149
- "---\n\n"
150
- )
151
- else:
152
- verification_md = (
153
- "### ✅ Verification\n\n"
154
- "No major automatic contradictions detected."
155
  )
156
-
157
- # ----------------------------------------------------
158
- # RETURN CHAT + PANELS
159
- # ----------------------------------------------------
160
-
161
- chat = [
162
- {
163
- "role": "user",
164
- "content": question,
165
- },
166
- {
167
- "role": "assistant",
168
- "content": answer,
169
- },
170
  ]
171
 
172
  return (
173
- chat,
174
  activity,
175
- source_md,
176
- evidence_md + "\n" + verification_md,
 
177
  )
178
 
179
  except Exception as exc:
180
 
181
- chat = [
182
- {
183
- "role": "user",
184
- "content": question,
185
- },
186
- {
187
- "role": "assistant",
188
- "content": (
189
- "❌ X-RUDRA encountered an error:\n\n"
190
- f"`{type(exc).__name__}: {exc}`"
191
- ),
192
- },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  ]
194
 
195
  return (
196
- chat,
197
  "❌ Research failed.",
198
  "",
199
  "",
 
200
  )
201
 
202
 
 
 
 
 
203
  def run_research(
204
  question,
205
  max_results,
@@ -207,8 +662,9 @@ def run_research(
207
  use_models,
208
  freshness,
209
  ):
 
210
  return asyncio.run(
211
- research(
212
  question,
213
  max_results,
214
  max_rounds,
@@ -218,75 +674,89 @@ def run_research(
218
  )
219
 
220
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  # ============================================================
222
  # CSS
223
  # ============================================================
224
 
225
- CSS = r"""
226
- :root {
227
- --radius: 18px;
228
- }
229
 
230
  body {
231
- background: #f7f7f8 !important;
232
  }
233
 
234
  .gradio-container {
235
  max-width: 1500px !important;
236
- margin: auto !important;
237
  }
238
 
239
  #header {
240
  text-align: center;
241
- padding: 18px 0 8px 0;
242
  }
243
 
244
  #logo {
245
- font-size: 34px;
246
  font-weight: 800;
247
- letter-spacing: -1px;
248
  }
249
 
250
  #tagline {
251
- opacity: .65;
252
- font-size: 14px;
253
  }
254
 
255
  #chat {
256
- border-radius: 18px !important;
257
  }
258
 
259
- #research-panel {
260
- border-radius: 18px !important;
261
  }
262
 
263
- #search-button {
264
- border-radius: 14px !important;
265
- min-height: 50px !important;
266
- font-weight: 700 !important;
267
- }
268
-
269
- .status-box {
270
- border-radius: 14px !important;
271
  }
272
 
273
  footer {
274
  display: none !important;
275
  }
 
276
  """
277
 
278
 
279
  # ============================================================
280
- # UI
281
  # ============================================================
282
 
283
  with gr.Blocks(
284
- title="X-RUDRA",
285
- css=CSS,
286
- theme=gr.themes.Soft(
287
- primary_hue="indigo",
288
- neutral_hue="slate",
289
- ),
290
  ) as demo:
291
 
292
  # --------------------------------------------------------
@@ -296,22 +766,25 @@ with gr.Blocks(
296
  gr.HTML(
297
  """
298
  <div id="header">
299
- <div id="logo">⚡ X-RUDRA</div>
 
 
 
300
  <div id="tagline">
301
- Dual-Model AI Research · Live Web Intelligence · Evidence
302
  </div>
303
  </div>
304
  """
305
  )
306
 
307
  # --------------------------------------------------------
308
- # MAIN
309
  # --------------------------------------------------------
310
 
311
  with gr.Row():
312
 
313
  # ====================================================
314
- # CHAT
315
  # ====================================================
316
 
317
  with gr.Column(
@@ -320,35 +793,30 @@ with gr.Blocks(
320
 
321
  chatbot = gr.Chatbot(
322
  label="X-RUDRA",
323
- type="messages",
324
- height=620,
325
  elem_id="chat",
326
- placeholder=(
327
- "Ask X-RUDRA anything that needs "
328
- "real-time research..."
329
- ),
330
  )
331
 
332
  with gr.Row():
333
 
334
  question = gr.Textbox(
335
  placeholder=(
336
- "Ask a research question..."
337
  ),
338
  lines=2,
339
- scale=8,
340
  show_label=False,
 
341
  )
342
 
343
  send = gr.Button(
344
  "➤",
345
  variant="primary",
 
346
  scale=1,
347
- elem_id="search-button",
348
  )
349
 
350
  # ====================================================
351
- # LIVE RESEARCH PANEL
352
  # ====================================================
353
 
354
  with gr.Column(
@@ -360,7 +828,9 @@ with gr.Blocks(
360
  )
361
 
362
  activity = gr.Markdown(
363
- "⚪ Waiting for research..."
 
 
364
  )
365
 
366
  gr.Markdown(
@@ -368,32 +838,30 @@ with gr.Blocks(
368
  )
369
 
370
  gr.Markdown(
371
- "### ⚙️ Engine"
372
- )
373
 
374
- gr.Markdown(
375
- """
376
- **Models**
377
 
378
- `M1` · `Shrijanagain/M1`
379
 
380
- `M2` · `Shrijanagain/M2`
381
 
382
- **Search**
383
 
384
- `DuckDuckGo`
385
 
386
- **Fetching**
387
 
388
  `Scrapling`
389
 
390
- **Dynamic pages**
391
-
392
  `Playwright`
393
 
394
- **Verification**
395
 
396
  `Evidence Engine`
 
 
397
  """
398
  )
399
 
@@ -443,32 +911,50 @@ with gr.Blocks(
443
  )
444
 
445
  # --------------------------------------------------------
446
- # RESEARCH DETAILS
447
  # --------------------------------------------------------
448
 
449
  with gr.Tabs():
450
 
451
  with gr.Tab(
452
- "📚 Sources",
453
  ):
 
454
  sources = gr.Markdown(
455
- "Sources will appear after research."
456
  )
457
 
458
  with gr.Tab(
459
- "🧠 Evidence",
460
  ):
 
461
  evidence = gr.Markdown(
462
- "Evidence will appear after research."
463
  )
464
 
465
  with gr.Tab(
466
- "⚖️ Verification",
467
  ):
 
468
  verification = gr.Markdown(
469
- "Verification will appear after research."
470
  )
471
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
  # --------------------------------------------------------
473
  # EXAMPLES
474
  # --------------------------------------------------------
@@ -479,10 +965,18 @@ with gr.Blocks(
479
 
480
  gr.Examples(
481
  examples=[
482
- ["What are the latest AI education initiatives by UNESCO?"],
483
- ["What happened in AI research this week?"],
484
- ["Compare the latest open-source AI models."],
485
- ["Find reliable evidence about India's AI ecosystem."],
 
 
 
 
 
 
 
 
486
  ],
487
  inputs=question,
488
  )
@@ -504,6 +998,7 @@ with gr.Blocks(
504
  activity,
505
  sources,
506
  evidence,
 
507
  ]
508
 
509
  send.click(
@@ -518,22 +1013,42 @@ with gr.Blocks(
518
  outputs=outputs,
519
  )
520
 
 
 
 
 
 
 
 
 
521
 
522
  # ============================================================
523
- # HF SPACES LAUNCH
524
  # ============================================================
525
 
526
  if __name__ == "__main__":
527
 
528
- port = int(
529
- os.environ.get(
530
- "PORT",
531
- "7860",
532
- )
 
 
 
 
 
 
 
 
 
 
 
533
  )
534
 
535
  demo.launch(
536
  server_name="0.0.0.0",
537
- server_port=port,
 
538
  show_error=True,
539
  )
 
1
+ # ============================================================
2
+ # X-RUDRA CHAT
3
+ # Hugging Face Gradio Space Edition
4
+ # ============================================================
5
+
6
+ from __future__ import annotations
7
+
8
  import os
 
9
  import json
10
  import time
11
+ import asyncio
12
+ import traceback
13
+
14
  import gradio as gr
15
 
 
16
 
17
+ # ============================================================
18
+ # CONFIG
19
+ # ============================================================
20
+
21
+ APP_NAME = "X-RUDRA"
22
+ VERSION = "3.1.0"
23
+
24
+ M1_REPO = os.getenv(
25
+ "M1_REPO",
26
+ "Shrijanagain/M1",
27
+ )
28
+
29
+ M2_REPO = os.getenv(
30
+ "M2_REPO",
31
+ "Shrijanagain/M2",
32
+ )
33
+
34
+ PORT = int(
35
+ os.getenv(
36
+ "PORT",
37
+ "7860",
38
+ )
39
+ )
40
+
41
+
42
+ # ============================================================
43
+ # LAZY ENGINE
44
+ # ============================================================
45
+
46
+ _ENGINE = None
47
+
48
+
49
+ def get_engine():
50
+ """
51
+ IMPORTANT:
52
+ Do not initialize the web-search engine during
53
+ Space startup.
54
+
55
+ It is created only when the first user request arrives.
56
+ """
57
+
58
+ global _ENGINE
59
+
60
+ if _ENGINE is None:
61
+
62
+ from web_search import XrudraWebSearch
63
+
64
+ _ENGINE = XrudraWebSearch()
65
+
66
+ return _ENGINE
67
+
68
+
69
+ # ============================================================
70
+ # SAFE VALUE HELPERS
71
+ # ============================================================
72
+
73
+ def safe_dict(value):
74
+
75
+ if isinstance(value, dict):
76
+ return value
77
+
78
+ if hasattr(value, "model_dump"):
79
+
80
+ try:
81
+ return value.model_dump()
82
+
83
+ except Exception:
84
+ pass
85
+
86
+ if hasattr(value, "dict"):
87
+
88
+ try:
89
+ return value.dict()
90
+
91
+ except Exception:
92
+ pass
93
+
94
+ return {
95
+ "result": str(value)
96
+ }
97
+
98
+
99
+ def get_value(data, key, default=None):
100
+
101
+ if not isinstance(data, dict):
102
+ return default
103
+
104
+ return data.get(
105
+ key,
106
+ default,
107
+ )
108
+
109
+
110
+ # ============================================================
111
+ # SOURCE FORMATTER
112
+ # ============================================================
113
+
114
+ def format_sources(sources):
115
+
116
+ if not sources:
117
+
118
+ return (
119
+ "## 📚 Sources\n\n"
120
+ "No sources were returned."
121
+ )
122
+
123
+ output = [
124
+ "## 📚 Sources",
125
+ "",
126
+ ]
127
+
128
+ for index, source in enumerate(
129
+ sources,
130
+ start=1,
131
+ ):
132
+
133
+ if not isinstance(source, dict):
134
+ continue
135
+
136
+ title = source.get(
137
+ "title",
138
+ "Untitled",
139
+ )
140
+
141
+ url = source.get(
142
+ "url",
143
+ "",
144
+ )
145
+
146
+ method = source.get(
147
+ "fetch_method",
148
+ "unknown",
149
+ )
150
+
151
+ score = source.get(
152
+ "source_score",
153
+ source.get(
154
+ "score",
155
+ "N/A",
156
+ ),
157
+ )
158
+
159
+ if url:
160
+
161
+ output.append(
162
+ f"### {index}. [{title}]({url})"
163
+ )
164
+
165
+ else:
166
+
167
+ output.append(
168
+ f"### {index}. {title}"
169
+ )
170
+
171
+ output.append(
172
+ f"**Fetcher:** `{method}`"
173
+ )
174
+
175
+ output.append(
176
+ f"**Source score:** `{score}`"
177
+ )
178
+
179
+ output.append("")
180
+
181
+ return "\n".join(output)
182
+
183
+
184
+ # ============================================================
185
+ # EVIDENCE FORMATTER
186
+ # ============================================================
187
+
188
+ def format_evidence(claims):
189
+
190
+ if not claims:
191
+
192
+ return (
193
+ "## 🧠 Evidence\n\n"
194
+ "No structured evidence was returned."
195
+ )
196
+
197
+ output = [
198
+ "## 🧠 Evidence",
199
+ "",
200
+ ]
201
+
202
+ for index, claim in enumerate(
203
+ claims,
204
+ start=1,
205
+ ):
206
+
207
+ if not isinstance(claim, dict):
208
+ continue
209
+
210
+ text = claim.get(
211
+ "claim",
212
+ claim.get(
213
+ "text",
214
+ "",
215
+ ),
216
+ )
217
+
218
+ score = claim.get(
219
+ "support_score",
220
+ claim.get(
221
+ "score",
222
+ "N/A",
223
+ ),
224
+ )
225
+
226
+ source = claim.get(
227
+ "source_url",
228
+ claim.get(
229
+ "url",
230
+ "",
231
+ ),
232
+ )
233
+
234
+ output.append(
235
+ f"### Evidence {index}"
236
+ )
237
+
238
+ output.append(
239
+ str(text)
240
+ )
241
+
242
+ output.append(
243
+ f"**Support:** `{score}`"
244
+ )
245
+
246
+ if source:
247
+
248
+ output.append(
249
+ f"**Source:** {source}"
250
+ )
251
+
252
+ output.append("---")
253
+
254
+ return "\n\n".join(output)
255
+
256
+
257
+ # ============================================================
258
+ # VERIFICATION FORMATTER
259
+ # ============================================================
260
+
261
+ def format_verification(contradictions):
262
+
263
+ if not contradictions:
264
+
265
+ return (
266
+ "## ⚖️ Verification\n\n"
267
+ "✅ No major automatic contradictions "
268
+ "were detected."
269
+ )
270
+
271
+ output = [
272
+ "## ⚖️ Verification",
273
+ "",
274
+ "⚠️ Potential contradictions detected:",
275
+ "",
276
+ ]
277
+
278
+ for index, item in enumerate(
279
+ contradictions,
280
+ start=1,
281
+ ):
282
+
283
+ if not isinstance(item, dict):
284
+ continue
285
+
286
+ claim_a = item.get(
287
+ "claim_a",
288
+ "",
289
+ )
290
+
291
+ claim_b = item.get(
292
+ "claim_b",
293
+ "",
294
+ )
295
+
296
+ source_a = item.get(
297
+ "source_a",
298
+ "",
299
+ )
300
+
301
+ source_b = item.get(
302
+ "source_b",
303
+ "",
304
+ )
305
+
306
+ output.append(
307
+ f"### Contradiction {index}"
308
+ )
309
+
310
+ output.append(
311
+ f"**A:** {claim_a}"
312
+ )
313
+
314
+ if source_a:
315
+
316
+ output.append(
317
+ f"Source A: `{source_a}`"
318
+ )
319
+
320
+ output.append("")
321
+
322
+ output.append(
323
+ f"**B:** {claim_b}"
324
+ )
325
+
326
+ if source_b:
327
+
328
+ output.append(
329
+ f"Source B: `{source_b}`"
330
+ )
331
+
332
+ output.append("---")
333
+
334
+ return "\n\n".join(output)
335
+
336
+
337
+ # ============================================================
338
+ # ANSWER EXTRACTOR
339
+ # ============================================================
340
+
341
+ def extract_answer(data):
342
+
343
+ # Try common final-answer fields first.
344
+
345
+ for key in (
346
+ "final_answer",
347
+ "answer",
348
+ "response",
349
+ "final",
350
+ "synthesis",
351
+ "summary",
352
+ ):
353
+
354
+ value = data.get(
355
+ key,
356
+ None,
357
+ )
358
 
359
+ if isinstance(
360
+ value,
361
+ str,
362
+ ) and value.strip():
363
+
364
+ return value.strip()
365
+
366
+ # Otherwise construct an answer from claims.
367
+
368
+ claims = data.get(
369
+ "claims",
370
+ [],
371
+ )
372
+
373
+ if claims:
374
+
375
+ parts = []
376
+
377
+ for claim in claims:
378
+
379
+ if not isinstance(
380
+ claim,
381
+ dict,
382
+ ):
383
+ continue
384
+
385
+ text = claim.get(
386
+ "claim",
387
+ claim.get(
388
+ "text",
389
+ "",
390
+ ),
391
+ )
392
+
393
+ if text:
394
+
395
+ parts.append(
396
+ str(text).strip()
397
+ )
398
+
399
+ if parts:
400
+
401
+ return "\n\n".join(
402
+ parts[:10]
403
+ )
404
+
405
+ return (
406
+ "Research completed, but the engine "
407
+ "did not return a final synthesized answer."
408
+ )
409
 
410
 
411
  # ============================================================
412
+ # ACTIVITY PANEL
413
  # ============================================================
414
 
415
+ def build_activity(
416
+ data,
417
+ elapsed_ms,
418
+ ):
419
+
420
+ sources = data.get(
421
+ "sources",
422
+ [],
423
+ )
424
+
425
+ claims = data.get(
426
+ "claims",
427
+ [],
428
+ )
429
+
430
+ contradictions = data.get(
431
+ "contradictions",
432
+ [],
433
+ )
434
+
435
+ rounds = data.get(
436
+ "rounds",
437
+ data.get(
438
+ "research_rounds",
439
+ "N/A",
440
+ ),
441
+ )
442
+
443
+ return f"""
444
+ ## ⚡ X-RUDRA Research
445
+
446
+ | Stage | Status |
447
+ |---|---|
448
+ | Task analysis | ✅ Complete |
449
+ | M1 research | {"✅ Enabled" if data.get("m1") is not None else "⚙️ Pipeline"} |
450
+ | M2 research | {"✅ Enabled" if data.get("m2") is not None else "⚙️ Pipeline"} |
451
+ | Web discovery | ✅ Complete |
452
+ | Evidence extraction | ✅ Complete |
453
+ | Source verification | ✅ Complete |
454
+ | Contradiction check | {"⚠️ Found" if contradictions else "✅ Clear"} |
455
+ | Final synthesis | ✅ Complete |
456
+
457
+ **Sources:** `{len(sources)}`
458
+ **Claims:** `{len(claims)}`
459
+ **Rounds:** `{rounds}`
460
+ **Time:** `{elapsed_ms} ms`
461
+
462
+ ### Engine
463
+
464
+ `M1` → `{M1_REPO}`
465
+
466
+ `M2` → `{M2_REPO}`
467
+
468
+ `Web` → `DuckDuckGo`
469
+
470
+ `Fetcher` → `Scrapling`
471
+
472
+ `Browser` → `Playwright`
473
+ """
474
+
475
+
476
+ # ============================================================
477
+ # RESEARCH
478
+ # ============================================================
479
+
480
+ async def do_research(
481
  question,
482
  max_results,
483
  max_rounds,
484
  use_models,
485
  freshness,
486
  ):
487
+
488
+ if not question or not str(
489
+ question
490
+ ).strip():
491
+
492
  return (
493
  [],
494
+ "⚪ Enter a question to start.",
495
+ "",
496
  "",
497
  )
498
 
499
+ question = str(
500
+ question
501
+ ).strip()
502
+
503
+ started = time.perf_counter()
504
 
505
  try:
 
 
 
 
 
 
 
506
 
507
+ # ----------------------------------------------------
508
+ # LAZY INITIALIZATION
509
+ # ----------------------------------------------------
 
 
510
 
511
+ engine = get_engine()
 
 
512
 
513
  # ----------------------------------------------------
514
+ # RUN WEB / MODEL PIPELINE
515
  # ----------------------------------------------------
516
 
517
+ report = await engine.search(
518
 
519
+ question=question,
 
520
 
521
+ max_results=int(
522
+ max_results
523
+ ),
524
 
525
+ max_rounds=int(
526
+ max_rounds
527
+ ),
 
 
 
 
528
 
529
+ use_models=bool(
530
+ use_models
531
+ ),
532
 
533
+ freshness_mode=str(
534
+ freshness
535
+ ),
536
+ )
537
 
538
+ data = safe_dict(
539
+ report
540
+ )
 
 
 
 
 
 
541
 
542
+ elapsed_ms = int(
543
+ (
544
+ time.perf_counter()
545
+ - started
546
+ ) * 1000
547
+ )
548
 
549
  # ----------------------------------------------------
550
+ # OUTPUT
551
  # ----------------------------------------------------
552
 
553
+ answer = extract_answer(
554
+ data
555
+ )
556
 
557
+ sources = data.get(
558
+ "sources",
559
+ [],
560
+ )
 
561
 
562
+ claims = data.get(
563
+ "claims",
564
+ [],
565
+ )
566
 
567
+ contradictions = data.get(
568
+ "contradictions",
569
+ [],
570
+ )
571
 
572
+ activity = build_activity(
573
+ data,
574
+ elapsed_ms,
575
+ )
576
 
577
+ sources_md = format_sources(
578
+ sources
579
+ )
580
 
581
+ evidence_md = format_evidence(
582
+ claims
583
+ )
 
 
 
 
 
 
584
 
585
+ verification_md = format_verification(
586
+ contradictions
587
+ )
588
 
589
  # ----------------------------------------------------
590
+ # OLD-COMPATIBLE GRADIO CHAT FORMAT
591
+ #
592
+ # IMPORTANT:
593
+ # No type="messages".
594
+ # This works with the Gradio version shown
595
+ # in the uploaded Space logs.
596
  # ----------------------------------------------------
597
 
598
+ history = [
599
+ (
600
+ question,
601
+ answer,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
603
  ]
604
 
605
  return (
606
+ history,
607
  activity,
608
+ sources_md,
609
+ evidence_md,
610
+ verification_md,
611
  )
612
 
613
  except Exception as exc:
614
 
615
+ error = (
616
+ f"❌ **X-RUDRA Error**\n\n"
617
+ f"`{type(exc).__name__}: {exc}`"
618
+ )
619
+
620
+ print(
621
+ "\n"
622
+ + "=" * 70
623
+ )
624
+
625
+ print(
626
+ "X-RUDRA ERROR"
627
+ )
628
+
629
+ print(
630
+ traceback.format_exc()
631
+ )
632
+
633
+ print(
634
+ "=" * 70
635
+ + "\n"
636
+ )
637
+
638
+ history = [
639
+ (
640
+ question,
641
+ error,
642
+ )
643
  ]
644
 
645
  return (
646
+ history,
647
  "❌ Research failed.",
648
  "",
649
  "",
650
+ "",
651
  )
652
 
653
 
654
+ # ============================================================
655
+ # GRADIO SYNC WRAPPER
656
+ # ============================================================
657
+
658
  def run_research(
659
  question,
660
  max_results,
 
662
  use_models,
663
  freshness,
664
  ):
665
+
666
  return asyncio.run(
667
+ do_research(
668
  question,
669
  max_results,
670
  max_rounds,
 
674
  )
675
 
676
 
677
+ # ============================================================
678
+ # HEALTH
679
+ # ============================================================
680
+
681
+ def health_check():
682
+
683
+ return f"""
684
+ ## 🟢 X-RUDRA Online
685
+
686
+ **Version:** `{VERSION}`
687
+
688
+ **M1:** `{M1_REPO}`
689
+
690
+ **M2:** `{M2_REPO}`
691
+
692
+ **Web Search:** `DuckDuckGo`
693
+
694
+ **HTTP Fetch:** `Scrapling`
695
+
696
+ **Dynamic Fetch:** `Playwright`
697
+
698
+ **Startup model loading:** `Disabled`
699
+
700
+ **Engine:** `Lazy initialized`
701
+ """
702
+
703
+
704
  # ============================================================
705
  # CSS
706
  # ============================================================
707
 
708
+ CSS = """
 
 
 
709
 
710
  body {
711
+ background: #f7f7f8;
712
  }
713
 
714
  .gradio-container {
715
  max-width: 1500px !important;
 
716
  }
717
 
718
  #header {
719
  text-align: center;
720
+ padding: 20px 0 10px 0;
721
  }
722
 
723
  #logo {
724
+ font-size: 38px;
725
  font-weight: 800;
 
726
  }
727
 
728
  #tagline {
729
+ opacity: 0.65;
730
+ font-size: 15px;
731
  }
732
 
733
  #chat {
734
+ border-radius: 18px;
735
  }
736
 
737
+ #research {
738
+ border-radius: 18px;
739
  }
740
 
741
+ #send {
742
+ min-height: 52px;
743
+ font-size: 18px;
744
+ font-weight: 700;
 
 
 
 
745
  }
746
 
747
  footer {
748
  display: none !important;
749
  }
750
+
751
  """
752
 
753
 
754
  # ============================================================
755
+ # GRADIO APPLICATION
756
  # ============================================================
757
 
758
  with gr.Blocks(
759
+ title=APP_NAME,
 
 
 
 
 
760
  ) as demo:
761
 
762
  # --------------------------------------------------------
 
766
  gr.HTML(
767
  """
768
  <div id="header">
769
+ <div id="logo">
770
+ ⚡ X-RUDRA
771
+ </div>
772
+
773
  <div id="tagline">
774
+ Dual-Model AI · Live Web Research · Evidence
775
  </div>
776
  </div>
777
  """
778
  )
779
 
780
  # --------------------------------------------------------
781
+ # MAIN CHAT
782
  # --------------------------------------------------------
783
 
784
  with gr.Row():
785
 
786
  # ====================================================
787
+ # CHAT COLUMN
788
  # ====================================================
789
 
790
  with gr.Column(
 
793
 
794
  chatbot = gr.Chatbot(
795
  label="X-RUDRA",
796
+ height=600,
 
797
  elem_id="chat",
 
 
 
 
798
  )
799
 
800
  with gr.Row():
801
 
802
  question = gr.Textbox(
803
  placeholder=(
804
+ "Ask X-RUDRA anything..."
805
  ),
806
  lines=2,
 
807
  show_label=False,
808
+ scale=8,
809
  )
810
 
811
  send = gr.Button(
812
  "➤",
813
  variant="primary",
814
+ elem_id="send",
815
  scale=1,
 
816
  )
817
 
818
  # ====================================================
819
+ # RESEARCH STATUS
820
  # ====================================================
821
 
822
  with gr.Column(
 
828
  )
829
 
830
  activity = gr.Markdown(
831
+ """
832
+ ⚪ Waiting for your question.
833
+ """
834
  )
835
 
836
  gr.Markdown(
 
838
  )
839
 
840
  gr.Markdown(
841
+ f"""
842
+ ### Model Spaces
843
 
844
+ **M1**
 
 
845
 
846
+ `{M1_REPO}`
847
 
848
+ **M2**
849
 
850
+ `{M2_REPO}`
851
 
852
+ ### Web Stack
853
 
854
+ `DuckDuckGo`
855
 
856
  `Scrapling`
857
 
 
 
858
  `Playwright`
859
 
860
+ ### Verification
861
 
862
  `Evidence Engine`
863
+
864
+ `Cross-source checking`
865
  """
866
  )
867
 
 
911
  )
912
 
913
  # --------------------------------------------------------
914
+ # RESEARCH DATA
915
  # --------------------------------------------------------
916
 
917
  with gr.Tabs():
918
 
919
  with gr.Tab(
920
+ "📚 Sources"
921
  ):
922
+
923
  sources = gr.Markdown(
924
+ "Sources will appear here."
925
  )
926
 
927
  with gr.Tab(
928
+ "🧠 Evidence"
929
  ):
930
+
931
  evidence = gr.Markdown(
932
+ "Evidence will appear here."
933
  )
934
 
935
  with gr.Tab(
936
+ "⚖️ Verification"
937
  ):
938
+
939
  verification = gr.Markdown(
940
+ "Verification will appear here."
941
  )
942
 
943
+ # --------------------------------------------------------
944
+ # HEALTH
945
+ # --------------------------------------------------------
946
+
947
+ with gr.Accordion(
948
+ "🩺 System Health",
949
+ open=False,
950
+ ):
951
+
952
+ health_button = gr.Button(
953
+ "Check X-RUDRA",
954
+ )
955
+
956
+ health_output = gr.Markdown()
957
+
958
  # --------------------------------------------------------
959
  # EXAMPLES
960
  # --------------------------------------------------------
 
965
 
966
  gr.Examples(
967
  examples=[
968
+ [
969
+ "What are the latest UNESCO AI education initiatives?"
970
+ ],
971
+ [
972
+ "What are the latest developments in open source AI?"
973
+ ],
974
+ [
975
+ "Compare the latest major AI models."
976
+ ],
977
+ [
978
+ "Research India's current AI ecosystem."
979
+ ],
980
  ],
981
  inputs=question,
982
  )
 
998
  activity,
999
  sources,
1000
  evidence,
1001
+ verification,
1002
  ]
1003
 
1004
  send.click(
 
1013
  outputs=outputs,
1014
  )
1015
 
1016
+ health_button.click(
1017
+ fn=health_check,
1018
+ inputs=[],
1019
+ outputs=[
1020
+ health_output
1021
+ ],
1022
+ )
1023
+
1024
 
1025
  # ============================================================
1026
+ # START
1027
  # ============================================================
1028
 
1029
  if __name__ == "__main__":
1030
 
1031
+ print(
1032
+ f"Starting {APP_NAME} {VERSION}"
1033
+ )
1034
+
1035
+ print(
1036
+ "M1:",
1037
+ M1_REPO,
1038
+ )
1039
+
1040
+ print(
1041
+ "M2:",
1042
+ M2_REPO,
1043
+ )
1044
+
1045
+ print(
1046
+ "Lazy engine initialization: ON"
1047
  )
1048
 
1049
  demo.launch(
1050
  server_name="0.0.0.0",
1051
+ server_port=PORT,
1052
+ css=CSS,
1053
  show_error=True,
1054
  )