nothingworry commited on
Commit
182af62
Β·
1 Parent(s): 1d2a779

feat: Add reasoning visualizer, tool timeline, tenant heatmap, and expand analytics access to all roles

Browse files
Files changed (1) hide show
  1. app.py +158 -28
app.py CHANGED
@@ -32,8 +32,8 @@ def can_delete_documents(role: str) -> bool:
32
  return role in ["admin", "owner"]
33
 
34
  def can_view_analytics(role: str) -> bool:
35
- """Check if role can view analytics (admin/owner only)."""
36
- return role in ["admin", "owner"]
37
 
38
 
39
  def chat_with_agent(message, tenant_id, role, history):
@@ -169,6 +169,88 @@ def chat_with_agent(message, tenant_id, role, history):
169
  yield history
170
 
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  def ingest_document(
173
  tenant_id: str,
174
  role: str,
@@ -565,9 +647,8 @@ def fetch_admin_analytics(tenant_id: str, role: str):
565
  error_msg = "❗ Tenant ID is required to view analytics."
566
  return error_msg, {}, None, None, None, None
567
 
568
- if not can_view_analytics(role):
569
- error_msg = "❌ Access Denied: You need Admin or Owner role to view analytics."
570
- return error_msg, {}, None, None, None, None
571
 
572
  tenant_id = tenant_id.strip()
573
  headers = {
@@ -1403,23 +1484,30 @@ with gr.Blocks(
1403
  )
1404
  send_button = gr.Button("Send", variant="primary", scale=1)
1405
 
1406
- with gr.Column(scale=1):
1407
- gr.Markdown(
1408
- """
1409
- <div style="background: linear-gradient(135deg, rgba(6, 182, 212, 0.1) 0%, rgba(59, 130, 246, 0.1) 100%); padding: 20px; border-radius: 12px; border: 1px solid rgba(6, 182, 212, 0.2);">
1410
- ### πŸ“ Chat Instructions
1411
- 1. Enter your **Tenant ID** and **Role** above
1412
- 2. Ask a question or give a task to the agent
1413
- 3. The MCP agent will automatically select tools (RAG, Web, etc.)
 
 
 
 
 
 
 
 
 
 
1414
 
1415
- ### ⚑ Features
1416
- - ✨ Real-time streaming responses
1417
- - 🧠 Multi-step planning & reasoning
1418
- - πŸ” Automatic tool selection
1419
- - πŸ’Ύ Conversation memory
1420
- </div>
1421
- """
1422
- )
1423
 
1424
  # Event handlers for chat tab with streaming
1425
  def send_message(message, tenant_id, role, history):
@@ -1462,6 +1550,49 @@ with gr.Blocks(
1462
  outputs=[chat_access_denied, chat_content]
1463
  )
1464
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1465
  with gr.Tab("πŸ“š Document Ingestion"):
1466
  gr.Markdown(
1467
  """
@@ -1830,10 +1961,10 @@ with gr.Blocks(
1830
  <div style="background: linear-gradient(135deg, rgba(239, 68, 68, 0.2) 0%, rgba(220, 38, 38, 0.2) 100%); padding: 40px; border-radius: 16px; border: 2px solid rgba(239, 68, 68, 0.4); text-align: center; margin: 20px 0;">
1831
  <h2 style="color: #fca5a5; margin-bottom: 16px;">πŸ”’ Access Denied</h2>
1832
  <p style="color: #f1f5f9; font-size: 16px; margin-bottom: 12px;">
1833
- <strong>You need Admin or Owner role to access Analytics Dashboard.</strong>
1834
  </p>
1835
  <p style="color: #cbd5e1; font-size: 14px;">
1836
- Analytics features are restricted to administrative roles for data security and privacy.
1837
  </p>
1838
  </div>
1839
  """,
@@ -1987,13 +2118,12 @@ with gr.Blocks(
1987
  ]
1988
  )
1989
 
1990
- # Function to update Analytics tab visibility based on role (Editor sees access denied)
1991
  def update_analytics_visibility(role):
1992
- is_editor = role == "editor"
1993
- has_access = can_view_analytics(role)
1994
  return (
1995
- gr.update(visible=is_editor or not has_access), # Access denied for Editor or non-admin
1996
- gr.update(visible=has_access and not is_editor), # Analytics content for Admin/Owner only
1997
  )
1998
 
1999
  # Update visibility when role changes
 
32
  return role in ["admin", "owner"]
33
 
34
  def can_view_analytics(role: str) -> bool:
35
+ """Check if role can view analytics (all roles can view)."""
36
+ return role in VALID_ROLES # All roles can view analytics
37
 
38
 
39
  def chat_with_agent(message, tenant_id, role, history):
 
169
  yield history
170
 
171
 
172
+ def get_reasoning_trace(tenant_id: str, role: str, message: str):
173
+ """
174
+ Fetch reasoning trace and tool traces for a message using the debug endpoint.
175
+ Returns formatted markdown showing the reasoning path.
176
+ """
177
+ if not tenant_id or not tenant_id.strip():
178
+ return "❗ Tenant ID is required."
179
+
180
+ try:
181
+ headers = {
182
+ "Content-Type": "application/json",
183
+ "x-tenant-id": tenant_id.strip(),
184
+ "x-user-role": role if role else DEFAULT_ROLE
185
+ }
186
+ response = requests.post(
187
+ f"{BACKEND_BASE_URL}/agent/debug",
188
+ json={
189
+ "tenant_id": tenant_id.strip(),
190
+ "message": message,
191
+ "conversation_history": [],
192
+ "temperature": 0.0
193
+ },
194
+ headers=headers,
195
+ timeout=60
196
+ )
197
+
198
+ if response.status_code == 200:
199
+ data = response.json()
200
+ response_data = data.get("response", {})
201
+ reasoning_trace = response_data.get("reasoning_trace", [])
202
+ tool_traces = response_data.get("tool_traces", [])
203
+ decision = response_data.get("decision", {})
204
+
205
+ # Format reasoning trace
206
+ trace_md = "## 🧠 Reasoning Path\n\n"
207
+ for idx, step in enumerate(reasoning_trace, 1):
208
+ step_name = step.get("step", "unknown")
209
+ trace_md += f"### {idx}. {step_name.replace('_', ' ').title()}\n"
210
+
211
+ if step.get("intent"):
212
+ trace_md += f"- **Intent:** {step['intent']}\n"
213
+ if step.get("match_count"):
214
+ trace_md += f"- **Rule Matches:** {step['match_count']}\n"
215
+ if step.get("hit_count"):
216
+ trace_md += f"- **RAG Hits:** {step['hit_count']}\n"
217
+ if step.get("latency_ms"):
218
+ trace_md += f"- **Latency:** {step['latency_ms']}ms\n"
219
+ if step.get("decision"):
220
+ dec = step['decision']
221
+ trace_md += f"- **Tool:** {dec.get('tool', 'N/A')}\n"
222
+ trace_md += f"- **Action:** {dec.get('action', 'N/A')}\n"
223
+ trace_md += "\n"
224
+
225
+ # Format tool traces
226
+ if tool_traces:
227
+ trace_md += "## βš™οΈ Tool Invocations\n\n"
228
+ for idx, tool in enumerate(tool_traces, 1):
229
+ tool_name = tool.get("tool", tool.get("tool_name", "unknown"))
230
+ latency = tool.get("latency_ms", tool.get("latency", 0))
231
+ status = tool.get("status", "success")
232
+ trace_md += f"### {idx}. {tool_name}\n"
233
+ trace_md += f"- **Status:** {status}\n"
234
+ trace_md += f"- **Latency:** {latency}ms\n"
235
+ if tool.get("result_count"):
236
+ trace_md += f"- **Results:** {tool['result_count']}\n"
237
+ trace_md += "\n"
238
+
239
+ # Format decision
240
+ if decision:
241
+ trace_md += "## 🎯 Final Decision\n\n"
242
+ trace_md += f"- **Tool:** {decision.get('tool', 'N/A')}\n"
243
+ trace_md += f"- **Action:** {decision.get('action', 'N/A')}\n"
244
+ if decision.get('reason'):
245
+ trace_md += f"- **Reason:** {decision['reason']}\n"
246
+
247
+ return trace_md
248
+ else:
249
+ return f"❌ Error {response.status_code}: {response.text}"
250
+ except Exception as e:
251
+ return f"❌ Error fetching reasoning trace: {str(e)}"
252
+
253
+
254
  def ingest_document(
255
  tenant_id: str,
256
  role: str,
 
647
  error_msg = "❗ Tenant ID is required to view analytics."
648
  return error_msg, {}, None, None, None, None
649
 
650
+ # All roles can view analytics (matching backend permissions)
651
+ # No access check needed here
 
652
 
653
  tenant_id = tenant_id.strip()
654
  headers = {
 
1484
  )
1485
  send_button = gr.Button("Send", variant="primary", scale=1)
1486
 
1487
+ with gr.Column(scale=1):
1488
+ gr.Markdown(
1489
+ """
1490
+ <div style="background: linear-gradient(135deg, rgba(6, 182, 212, 0.1) 0%, rgba(59, 130, 246, 0.1) 100%); padding: 20px; border-radius: 12px; border: 1px solid rgba(6, 182, 212, 0.2);">
1491
+ ### πŸ“ Chat Instructions
1492
+ 1. Enter your **Tenant ID** and **Role** above
1493
+ 2. Ask a question or give a task to the agent
1494
+ 3. The MCP agent will automatically select tools (RAG, Web, etc.)
1495
+
1496
+ ### ⚑ Features
1497
+ - ✨ Real-time streaming responses
1498
+ - 🧠 Multi-step planning & reasoning
1499
+ - πŸ” Automatic tool selection
1500
+ - πŸ’Ύ Conversation memory
1501
+ - πŸ“Š Reasoning visualization (see Debug tab)
1502
+ </div>
1503
+ """
1504
+ )
1505
 
1506
+ # Reasoning trace viewer
1507
+ reasoning_trace_viewer = gr.Markdown(
1508
+ "πŸ’‘ **Tip:** Use the Debug tab to view detailed reasoning traces for your messages.",
1509
+ visible=True
1510
+ )
 
 
 
1511
 
1512
  # Event handlers for chat tab with streaming
1513
  def send_message(message, tenant_id, role, history):
 
1550
  outputs=[chat_access_denied, chat_content]
1551
  )
1552
 
1553
+ with gr.Tab("πŸ” Debug & Reasoning"):
1554
+ gr.Markdown(
1555
+ """
1556
+ <div style="background: linear-gradient(135deg, rgba(139, 92, 246, 0.1) 0%, rgba(124, 58, 237, 0.1) 100%); padding: 20px; border-radius: 12px; border: 1px solid rgba(139, 92, 246, 0.2); margin-bottom: 20px;">
1557
+ ### πŸ” Agent Reasoning Debugger
1558
+ View the complete reasoning path, tool invocations, and decision-making process for any message.
1559
+
1560
+ **Features:**
1561
+ - 🧠 Step-by-step reasoning trace
1562
+ - βš™οΈ Tool invocation timeline
1563
+ - 🎯 Final decision breakdown
1564
+ - πŸ“Š Performance metrics
1565
+ </div>
1566
+ """
1567
+ )
1568
+
1569
+ debug_message = gr.Textbox(
1570
+ label="Message to Debug",
1571
+ placeholder="Enter the same message you sent in Chat to see its reasoning path...",
1572
+ lines=2
1573
+ )
1574
+ debug_button = gr.Button("πŸ” Analyze Reasoning", variant="primary")
1575
+ debug_output = gr.Markdown("πŸ‘‰ Enter a message and click 'Analyze Reasoning' to see the agent's reasoning path.")
1576
+
1577
+ def analyze_reasoning(message, tenant_id, role):
1578
+ if not message or not message.strip():
1579
+ return "❗ Please enter a message to analyze."
1580
+ if not tenant_id or not tenant_id.strip():
1581
+ return "❗ Please enter a Tenant ID."
1582
+ return get_reasoning_trace(tenant_id, role, message)
1583
+
1584
+ debug_button.click(
1585
+ fn=analyze_reasoning,
1586
+ inputs=[debug_message, tenant_id_input, role_input],
1587
+ outputs=[debug_output]
1588
+ )
1589
+
1590
+ debug_message.submit(
1591
+ fn=analyze_reasoning,
1592
+ inputs=[debug_message, tenant_id_input, role_input],
1593
+ outputs=[debug_output]
1594
+ )
1595
+
1596
  with gr.Tab("πŸ“š Document Ingestion"):
1597
  gr.Markdown(
1598
  """
 
1961
  <div style="background: linear-gradient(135deg, rgba(239, 68, 68, 0.2) 0%, rgba(220, 38, 38, 0.2) 100%); padding: 40px; border-radius: 16px; border: 2px solid rgba(239, 68, 68, 0.4); text-align: center; margin: 20px 0;">
1962
  <h2 style="color: #fca5a5; margin-bottom: 16px;">πŸ”’ Access Denied</h2>
1963
  <p style="color: #f1f5f9; font-size: 16px; margin-bottom: 12px;">
1964
+ <strong>Analytics is available to all roles.</strong>
1965
  </p>
1966
  <p style="color: #cbd5e1; font-size: 14px;">
1967
+ If you're seeing this message, there may be a configuration issue.
1968
  </p>
1969
  </div>
1970
  """,
 
2118
  ]
2119
  )
2120
 
2121
+ # Function to update Analytics tab visibility based on role (all roles can view)
2122
  def update_analytics_visibility(role):
2123
+ has_access = can_view_analytics(role) # All roles can view now
 
2124
  return (
2125
+ gr.update(visible=False), # No access denied message
2126
+ gr.update(visible=True), # Analytics content visible for all
2127
  )
2128
 
2129
  # Update visibility when role changes