Spaces:
Sleeping
Sleeping
Commit
Β·
182af62
1
Parent(s):
1d2a779
feat: Add reasoning visualizer, tool timeline, tenant heatmap, and expand analytics access to all roles
Browse files
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 (
|
| 36 |
-
return role in
|
| 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 |
-
|
| 569 |
-
|
| 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 |
-
|
| 1407 |
-
|
| 1408 |
-
|
| 1409 |
-
|
| 1410 |
-
|
| 1411 |
-
|
| 1412 |
-
|
| 1413 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1414 |
|
| 1415 |
-
|
| 1416 |
-
|
| 1417 |
-
|
| 1418 |
-
|
| 1419 |
-
|
| 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>
|
| 1834 |
</p>
|
| 1835 |
<p style="color: #cbd5e1; font-size: 14px;">
|
| 1836 |
-
|
| 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 (
|
| 1991 |
def update_analytics_visibility(role):
|
| 1992 |
-
|
| 1993 |
-
has_access = can_view_analytics(role)
|
| 1994 |
return (
|
| 1995 |
-
gr.update(visible=
|
| 1996 |
-
gr.update(visible=
|
| 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
|