ST-x-Tony commited on
Commit
3c4c999
·
verified ·
1 Parent(s): 7e17d80

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -74
app.py CHANGED
@@ -10,51 +10,7 @@
10
 
11
  """
12
  # REASONING-ENFORCED AGENT
13
-
14
- ## 1. CORE DIRECTIVE
15
- You are a reasoning-first agent.
16
- For every meaningful request, you MUST determine:
17
- 1. What the user wants.
18
- 2. The actual goal.
19
- 3. Mandatory requirements.
20
- 4. Constraints.
21
- 5. Missing information.
22
- 6. Ambiguity.
23
- 7. Task decomposition.
24
- 8. Dependencies.
25
- 9. Execution order.
26
- 10. Priorities.
27
- 11. Evidence supporting key decisions.
28
- 12. How the result will be verified.
29
-
30
- Do NOT jump from input to output without reasoning.
31
-
32
- ## 2. MANDATORY REASONING GATE
33
- INPUT → UNDERSTAND → GOAL → REQUIREMENTS → CONSTRAINTS
34
- → MISSING INFO → AMBIGUITY → DECOMPOSITION → DEPENDENCIES
35
- → PRIORITY → PLAN → EXECUTE → VERIFY → FINALIZE
36
-
37
- ## 3. NEVER SKIP CRITICAL ANALYSIS
38
- Goal identification, requirement extraction, constraint checking,
39
- missing‑information detection, dependency checking, completion verification.
40
-
41
- ## 4. RESPONSE MODE
42
- DIRECT, EXPLANATORY, PLANNED, CLARIFICATION, DIAGNOSTIC, EXECUTION, REVISION.
43
-
44
- ## 5. ANTI‑FABRICATION
45
- Never invent missing requirements, tool results, files, test results,
46
- execution, verification, sources, or completion.
47
-
48
- ## 6. REASONING VISIBILITY
49
- The agent MUST reason internally but NEVER expose private chain‑of‑thought.
50
- Provide concise summaries only when useful.
51
-
52
- ## 7. FINAL CONTRACT
53
- UNDERSTAND → PLAN → ACT → VERIFY → ADAPT → DELIVER
54
- Primary objective:
55
- "Produce the most correct, goal‑aligned, constraint‑compliant,
56
- evidence‑supported, verified result using the minimum effective
57
- reasoning and execution required."
58
  """
59
 
60
  from __future__ import annotations
@@ -76,7 +32,7 @@ from gradio_client import Client
76
  # ============================================================
77
 
78
  APP_NAME = "X-RUDRA"
79
- VERSION = "3.7.1" # bumped
80
 
81
  M1_REPO = os.getenv("M1_REPO", "Shrijanagain/M1")
82
  M2_REPO = os.getenv("M2_REPO", "Shrijanagain/M2")
@@ -165,6 +121,7 @@ def call_model(client, prompt, max_tokens=512, temperature=0.7):
165
  if client is None:
166
  return None
167
  try:
 
168
  result = client.predict(
169
  prompt=prompt,
170
  max_tokens=max_tokens,
@@ -172,18 +129,21 @@ def call_model(client, prompt, max_tokens=512, temperature=0.7):
172
  api_name="/generate"
173
  )
174
  if result and isinstance(result, str) and result.strip():
 
175
  return result.strip()
 
 
 
176
  except Exception as e:
177
  print(f"Model call failed: {e}")
178
- return None
179
 
180
 
181
  # ============================================================
182
- # SYNTHESIS: M1 + M2 independent drafts → M2 merges them
183
  # ============================================================
184
 
185
  def get_combined_model_answer(question, sources, max_tokens=512, temperature=0.7):
186
- # Build a summary of top sources (max 5)
187
  top_sources = sources[:5] if sources else []
188
  sources_text = ""
189
  if top_sources:
@@ -210,7 +170,7 @@ Answer:"""
210
  draft_m1 = call_model(m1_client, base_prompt, max_tokens, temperature)
211
  draft_m2 = call_model(m2_client, base_prompt, max_tokens, temperature)
212
 
213
- # Fallback if one fails
214
  if not draft_m1 and not draft_m2:
215
  if sources:
216
  parts = ["Based on available information:"]
@@ -218,17 +178,30 @@ Answer:"""
218
  title = src.get("title", "Untitled")
219
  snippet = src.get("snippet", src.get("description", ""))
220
  parts.append(f"{i}. {title}: {snippet[:200]}..." if snippet else f"{i}. {title}")
221
- return "\n\n".join(parts)
222
  else:
223
- return "I couldn't find specific information on that topic. Could you rephrase?"
224
 
225
- # If only one draft exists, use that as final
226
  if not draft_m1:
227
- return draft_m2
228
- if not draft_m2:
229
- return draft_m1
 
 
 
 
230
 
231
- # ----- 2. Merge both drafts using M2 -----
 
 
 
 
 
 
 
 
 
232
  merge_prompt = f"""Question: {question}
233
 
234
  Draft from Model A:
@@ -241,14 +214,29 @@ Combine these two drafts into a single, comprehensive, accurate, and natural ans
241
 
242
  Final answer:"""
243
 
244
- merged = call_model(m2_client, merge_prompt, max_tokens, temperature)
 
 
 
 
 
 
 
 
245
  if not merged:
246
- # If merging fails, fallback to draft_m1
 
247
  merged = draft_m1
248
 
249
- # Strip any remaining <think> tags from the final answer
250
- clean_answer = re.sub(r"<think>.*?</think>", "", merged, flags=re.DOTALL).strip()
251
- return clean_answer
 
 
 
 
 
 
252
 
253
 
254
  # ============================================================
@@ -266,7 +254,8 @@ def get_casual_model_response(query: str) -> str:
266
  api_name="/generate"
267
  )
268
  if result and isinstance(result, str) and result.strip():
269
- return result.strip()
 
270
  except Exception as e:
271
  print(f"M1 casual failed: {e}")
272
 
@@ -280,7 +269,8 @@ def get_casual_model_response(query: str) -> str:
280
  api_name="/generate"
281
  )
282
  if result and isinstance(result, str) and result.strip():
283
- return result.strip()
 
284
  except Exception as e:
285
  print(f"M2 casual failed: {e}")
286
 
@@ -419,7 +409,7 @@ def safe_dict(value):
419
 
420
 
421
  # ============================================================
422
- # MAIN RESEARCH FUNCTION (returns 5 outputs – no thinking)
423
  # ============================================================
424
 
425
  async def do_research(question, max_results, max_rounds, use_models, freshness):
@@ -428,9 +418,10 @@ async def do_research(question, max_results, max_rounds, use_models, freshness):
428
  empty_sources = ""
429
  empty_evidence = ""
430
  empty_verification = ""
 
431
 
432
  if not question or not str(question).strip():
433
- return empty_history, empty_activity, empty_sources, empty_evidence, empty_verification
434
 
435
  question = str(question).strip()
436
 
@@ -441,7 +432,7 @@ async def do_research(question, max_results, max_rounds, use_models, freshness):
441
  {"role": "user", "content": question},
442
  {"role": "assistant", "content": answer}
443
  ]
444
- return history, "⚡ Casual chat (model reply, no search).", "", "", ""
445
 
446
  # ---- Serious query ----
447
  started = time.perf_counter()
@@ -473,20 +464,22 @@ async def do_research(question, max_results, max_rounds, use_models, freshness):
473
  })
474
  data["sources"] = sources
475
 
476
- # Generate final answer (no thinking)
477
- final_answer = get_combined_model_answer(question, sources)
478
 
479
  sources_md = format_sources(sources)
480
  evidence_md = format_evidence(data.get("claims", []))
481
  verification_md = format_verification(data.get("contradictions", []))
482
  activity_md = build_activity(data, elapsed_ms)
483
 
 
 
484
  history = [
485
  {"role": "user", "content": question},
486
  {"role": "assistant", "content": final_answer}
487
  ]
488
 
489
- return history, activity_md, sources_md, evidence_md, verification_md
490
 
491
  except Exception as exc:
492
  error = f"❌ **X-RUDRA Error**\n\n`{type(exc).__name__}: {exc}`"
@@ -498,7 +491,7 @@ async def do_research(question, max_results, max_rounds, use_models, freshness):
498
  {"role": "user", "content": question},
499
  {"role": "assistant", "content": error}
500
  ]
501
- return history, "❌ Research failed.", "", "", ""
502
 
503
 
504
  # ============================================================
@@ -525,7 +518,7 @@ def health_check():
525
 
526
 
527
  # ============================================================
528
- # CSS – no thinking spinner needed
529
  # ============================================================
530
 
531
  CSS = """
@@ -537,11 +530,36 @@ body { background: #f7f7f8; }
537
  #chat { border-radius: 18px; }
538
  #send { min-height: 52px; font-size: 18px; font-weight: 700; }
539
  footer { display: none !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  """
541
 
542
 
543
  # ============================================================
544
- # GRADIO UI – 5 outputs (no thinking)
545
  # ============================================================
546
 
547
  with gr.Blocks(title=APP_NAME) as demo:
@@ -562,6 +580,7 @@ with gr.Blocks(title=APP_NAME) as demo:
562
  with gr.Column(scale=4):
563
  gr.Markdown("## 🔬 Live Research")
564
  activity = gr.Markdown("⚪ Waiting for your question.")
 
565
  gr.Markdown("---")
566
  gr.Markdown(f"""
567
  ### Model Spaces
@@ -603,7 +622,7 @@ with gr.Blocks(title=APP_NAME) as demo:
603
  )
604
 
605
  inputs = [question, max_results, max_rounds, use_models, freshness]
606
- outputs = [chatbot, activity, sources, evidence, verification]
607
 
608
  send.click(fn=run_research, inputs=inputs, outputs=outputs)
609
  question.submit(fn=run_research, inputs=inputs, outputs=outputs)
 
10
 
11
  """
12
  # REASONING-ENFORCED AGENT
13
+ ... (full policy – keep as before)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  """
15
 
16
  from __future__ import annotations
 
32
  # ============================================================
33
 
34
  APP_NAME = "X-RUDRA"
35
+ VERSION = "3.7.3" # bumped
36
 
37
  M1_REPO = os.getenv("M1_REPO", "Shrijanagain/M1")
38
  M2_REPO = os.getenv("M2_REPO", "Shrijanagain/M2")
 
121
  if client is None:
122
  return None
123
  try:
124
+ print(f"Calling model with max_tokens={max_tokens}, prompt length={len(prompt)}")
125
  result = client.predict(
126
  prompt=prompt,
127
  max_tokens=max_tokens,
 
129
  api_name="/generate"
130
  )
131
  if result and isinstance(result, str) and result.strip():
132
+ print(f"Response length: {len(result)} chars")
133
  return result.strip()
134
+ else:
135
+ print("Empty response")
136
+ return None
137
  except Exception as e:
138
  print(f"Model call failed: {e}")
139
+ return None
140
 
141
 
142
  # ============================================================
143
+ # SYNTHESIS: M1 + M2 drafts → M2 merges (with higher token budget)
144
  # ============================================================
145
 
146
  def get_combined_model_answer(question, sources, max_tokens=512, temperature=0.7):
 
147
  top_sources = sources[:5] if sources else []
148
  sources_text = ""
149
  if top_sources:
 
170
  draft_m1 = call_model(m1_client, base_prompt, max_tokens, temperature)
171
  draft_m2 = call_model(m2_client, base_prompt, max_tokens, temperature)
172
 
173
+ # Fallback if both fail
174
  if not draft_m1 and not draft_m2:
175
  if sources:
176
  parts = ["Based on available information:"]
 
178
  title = src.get("title", "Untitled")
179
  snippet = src.get("snippet", src.get("description", ""))
180
  parts.append(f"{i}. {title}: {snippet[:200]}..." if snippet else f"{i}. {title}")
181
+ return "\n\n".join(parts), ""
182
  else:
183
+ return "I couldn't find specific information on that topic. Could you rephrase?", ""
184
 
185
+ # If only one draft exists, use that as final (extract thinking if any)
186
  if not draft_m1:
187
+ thinking = ""
188
+ clean = draft_m2
189
+ think_match = re.search(r"<think>(.*?)</think>", draft_m2, re.DOTALL)
190
+ if think_match:
191
+ thinking = think_match.group(1).strip()
192
+ clean = re.sub(r"<think>.*?</think>", "", draft_m2, flags=re.DOTALL).strip()
193
+ return clean, thinking
194
 
195
+ if not draft_m2:
196
+ thinking = ""
197
+ clean = draft_m1
198
+ think_match = re.search(r"<think>(.*?)</think>", draft_m1, re.DOTALL)
199
+ if think_match:
200
+ thinking = think_match.group(1).strip()
201
+ clean = re.sub(r"<think>.*?</think>", "", draft_m1, flags=re.DOTALL).strip()
202
+ return clean, thinking
203
+
204
+ # ----- 2. Merge both drafts using M2 with a larger token budget -----
205
  merge_prompt = f"""Question: {question}
206
 
207
  Draft from Model A:
 
214
 
215
  Final answer:"""
216
 
217
+ # Use a higher token limit for the merge step
218
+ merge_max_tokens = max(1024, max_tokens * 2) # at least 1024, double the original
219
+ merged = call_model(m2_client, merge_prompt, merge_max_tokens, temperature)
220
+
221
+ # If merged is too short (incomplete), retry with even more tokens
222
+ if merged and len(merged) < 100:
223
+ print(f"Merged answer too short ({len(merged)} chars), retrying with 2048 tokens...")
224
+ merged = call_model(m2_client, merge_prompt, 2048, temperature)
225
+
226
  if not merged:
227
+ # Fallback to draft_m1 if merge fails completely
228
+ print("Merge failed, falling back to draft_m1")
229
  merged = draft_m1
230
 
231
+ # Extract thinking and clean tags
232
+ thinking_content = ""
233
+ clean_answer = merged
234
+ think_match = re.search(r"<think>(.*?)</think>", merged, re.DOTALL)
235
+ if think_match:
236
+ thinking_content = think_match.group(1).strip()
237
+ clean_answer = re.sub(r"<think>.*?</think>", "", merged, flags=re.DOTALL).strip()
238
+
239
+ return clean_answer, thinking_content
240
 
241
 
242
  # ============================================================
 
254
  api_name="/generate"
255
  )
256
  if result and isinstance(result, str) and result.strip():
257
+ clean = re.sub(r"<think>.*?</think>", "", result, flags=re.DOTALL).strip()
258
+ return clean
259
  except Exception as e:
260
  print(f"M1 casual failed: {e}")
261
 
 
269
  api_name="/generate"
270
  )
271
  if result and isinstance(result, str) and result.strip():
272
+ clean = re.sub(r"<think>.*?</think>", "", result, flags=re.DOTALL).strip()
273
+ return clean
274
  except Exception as e:
275
  print(f"M2 casual failed: {e}")
276
 
 
409
 
410
 
411
  # ============================================================
412
+ # MAIN RESEARCH FUNCTION (returns 6 outputs)
413
  # ============================================================
414
 
415
  async def do_research(question, max_results, max_rounds, use_models, freshness):
 
418
  empty_sources = ""
419
  empty_evidence = ""
420
  empty_verification = ""
421
+ empty_thinking = ""
422
 
423
  if not question or not str(question).strip():
424
+ return empty_history, empty_activity, empty_sources, empty_evidence, empty_verification, empty_thinking
425
 
426
  question = str(question).strip()
427
 
 
432
  {"role": "user", "content": question},
433
  {"role": "assistant", "content": answer}
434
  ]
435
+ return history, "⚡ Casual chat (model reply, no search).", "", "", "", ""
436
 
437
  # ---- Serious query ----
438
  started = time.perf_counter()
 
464
  })
465
  data["sources"] = sources
466
 
467
+ # Generate final answer and thinking content (tags already stripped)
468
+ final_answer, thinking_content = get_combined_model_answer(question, sources)
469
 
470
  sources_md = format_sources(sources)
471
  evidence_md = format_evidence(data.get("claims", []))
472
  verification_md = format_verification(data.get("contradictions", []))
473
  activity_md = build_activity(data, elapsed_ms)
474
 
475
+ thinking_md = f"### 🧠 Reasoning\n\n{thinking_content}" if thinking_content else ""
476
+
477
  history = [
478
  {"role": "user", "content": question},
479
  {"role": "assistant", "content": final_answer}
480
  ]
481
 
482
+ return history, activity_md, sources_md, evidence_md, verification_md, thinking_md
483
 
484
  except Exception as exc:
485
  error = f"❌ **X-RUDRA Error**\n\n`{type(exc).__name__}: {exc}`"
 
491
  {"role": "user", "content": question},
492
  {"role": "assistant", "content": error}
493
  ]
494
+ return history, "❌ Research failed.", "", "", "", ""
495
 
496
 
497
  # ============================================================
 
518
 
519
 
520
  # ============================================================
521
+ # CSS – includes spinner animation for thinking
522
  # ============================================================
523
 
524
  CSS = """
 
530
  #chat { border-radius: 18px; }
531
  #send { min-height: 52px; font-size: 18px; font-weight: 700; }
532
  footer { display: none !important; }
533
+
534
+ @keyframes think-pulse {
535
+ 0% { opacity: 0.3; transform: scale(0.95); }
536
+ 50% { opacity: 1; transform: scale(1.05); }
537
+ 100% { opacity: 0.3; transform: scale(0.95); }
538
+ }
539
+ .thinking-spinner {
540
+ display: inline-block;
541
+ width: 12px;
542
+ height: 12px;
543
+ border-radius: 50%;
544
+ background: #6b7280;
545
+ margin-right: 8px;
546
+ animation: think-pulse 1.2s ease-in-out infinite;
547
+ }
548
+ .thinking-container {
549
+ background: #f3f4f6;
550
+ border-left: 4px solid #6366f1;
551
+ padding: 12px 16px;
552
+ border-radius: 8px;
553
+ margin: 12px 0;
554
+ font-family: monospace;
555
+ white-space: pre-wrap;
556
+ word-wrap: break-word;
557
+ }
558
  """
559
 
560
 
561
  # ============================================================
562
+ # GRADIO UI – 6 outputs
563
  # ============================================================
564
 
565
  with gr.Blocks(title=APP_NAME) as demo:
 
580
  with gr.Column(scale=4):
581
  gr.Markdown("## 🔬 Live Research")
582
  activity = gr.Markdown("⚪ Waiting for your question.")
583
+ thinking = gr.Markdown("", visible=True)
584
  gr.Markdown("---")
585
  gr.Markdown(f"""
586
  ### Model Spaces
 
622
  )
623
 
624
  inputs = [question, max_results, max_rounds, use_models, freshness]
625
+ outputs = [chatbot, activity, sources, evidence, verification, thinking]
626
 
627
  send.click(fn=run_research, inputs=inputs, outputs=outputs)
628
  question.submit(fn=run_research, inputs=inputs, outputs=outputs)