import plotly.graph_objects as go
from typing import List, Dict, Any
from collections import Counter
from datetime import datetime
def create_tool_usage_chart(tool_calls: List[Dict[str, Any]]) -> go.Figure:
"""
Create a bar chart showing tool call frequency.
"""
if not tool_calls:
# Empty state
fig = go.Figure()
fig.add_annotation(
text="No tool calls yet",
xref="paper",
yref="paper",
x=0.5,
y=0.5,
showarrow=False,
font=dict(size=14, color="gray"),
)
fig.update_layout(
height=250,
margin=dict(l=20, r=20, t=30, b=20),
xaxis=dict(visible=False),
yaxis=dict(visible=False),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
)
return fig
# Count tool calls
tool_names = [tc.get("tool", "unknown") for tc in tool_calls if "tool" in tc]
counts = Counter(tool_names)
# Sort by call sequence (order of first appearance) instead of frequency
# This is more meaningful when all tools are called once
seen = {}
for i, tc in enumerate(tool_calls):
tool = tc.get("tool", "unknown")
if tool not in seen:
seen[tool] = i
sorted_tools = sorted(counts.items(), key=lambda x: seen.get(x[0], 999))
tools = [t[0] for t in sorted_tools]
frequencies = [t[1] for t in sorted_tools]
# Color scheme matching Imaging Plaza green theme
colors = ["#00A991"] * len(tools)
fig = go.Figure(
data=[
go.Bar(
x=frequencies,
y=tools,
orientation="h",
marker=dict(color=colors),
text=[f"{count}×" for count in frequencies],
textposition="outside",
textfont=dict(size=12),
hovertemplate="%{y}
Called %{x} time(s)",
)
]
)
fig.update_layout(
title=dict(
text=f"Tool Calls ({len(tool_calls)} total, {len(tools)} unique)",
font=dict(size=13, color="#333"),
x=0,
xanchor="left",
),
height=max(150, 40 + len(tools) * 30), # Dynamic height based on tool count
margin=dict(l=10, r=50, t=35, b=30),
xaxis=dict(
title="Number of Calls",
showgrid=True,
gridcolor="rgba(0,0,0,0.1)",
range=[0, max(frequencies) * 1.2] if frequencies else [0, 1],
),
yaxis=dict(
title="",
showgrid=False,
tickfont=dict(size=11),
),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(255,255,255,0.95)",
showlegend=False,
)
return fig
def create_tool_timeline(tool_calls: List[Dict[str, Any]]) -> go.Figure:
"""
Create a timeline visualization of tool calls in sequence.
"""
if not tool_calls:
# Empty state
fig = go.Figure()
fig.add_annotation(
text="No tool calls yet",
xref="paper",
yref="paper",
x=0.5,
y=0.5,
showarrow=False,
font=dict(size=14, color="gray"),
)
fig.update_layout(
height=200,
margin=dict(l=20, r=20, t=30, b=20),
xaxis=dict(visible=False),
yaxis=dict(visible=False),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
)
return fig
# Extract tool names and create sequence
tool_names = []
statuses = []
timestamps = []
for tc in tool_calls:
tool = tc.get("tool", "unknown")
tool_names.append(tool)
# Capture timestamp if available
ts = tc.get("timestamp", "")
timestamps.append(ts)
# Determine status
if tc.get("blocked"):
statuses.append("blocked")
elif tc.get("error"):
statuses.append("error")
else:
statuses.append("success")
# Color mapping
color_map = {
"success": "#00A991", # Imaging Plaza green
"error": "#FF6B6B",
"blocked": "#FFA500",
}
# Create scatter plot as timeline
x_positions = list(range(1, len(tool_names) + 1))
fig = go.Figure()
# Add trace for each status type
for status, color in color_map.items():
indices = [i for i, s in enumerate(statuses) if s == status]
if indices:
# Format timestamps for display
display_timestamps = []
for i in indices:
ts = timestamps[i]
if ts:
try:
# Parse ISO format and format as HH:MM:SS
dt = datetime.fromisoformat(ts)
display_timestamps.append(dt.strftime("%H:%M:%S"))
except:
display_timestamps.append(ts[:19]) # Fallback to raw string
else:
display_timestamps.append("N/A")
fig.add_trace(
go.Scatter(
x=[x_positions[i] for i in indices],
y=[tool_names[i] for i in indices],
mode="markers",
name=status.capitalize(),
marker=dict(
size=12,
color=color,
line=dict(width=1, color="white"),
),
customdata=display_timestamps,
hovertemplate="%{y}
Call #%{x}
Time: %{customdata}",
)
)
fig.update_layout(
title=dict(
text=f"Call Sequence ({len(tool_names)} calls)",
font=dict(size=13, color="#333"),
x=0,
xanchor="left",
),
height=max(150, 40 + len(set(tool_names)) * 30), # Dynamic height
margin=dict(l=10, r=20, t=60, b=30),
xaxis=dict(
title="Order",
showgrid=True,
gridcolor="rgba(0,0,0,0.1)",
dtick=1,
range=[0.5, len(tool_names) + 0.5],
),
yaxis=dict(
title="",
showgrid=False,
tickfont=dict(size=11),
),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(255,255,255,0.95)",
showlegend=True,
legend=dict(
orientation="h",
yanchor="bottom",
y=1.02,
xanchor="left",
x=0,
font=dict(size=10),
),
)
return fig
def create_disabled_tools_display(tool_calls: List[Dict[str, Any]]) -> str:
"""
Create a text summary of disabled/blocked tools.
"""
blocked = [
tc for tc in tool_calls if tc.get("blocked") or tc.get("reason") == "quota"
]
if not blocked:
return "✅ No tools disabled"
lines = ["⚠️ **Disabled Tools:**\n"]
for tc in blocked:
tool_name = tc.get("tool", "unknown")
reason = tc.get("reason", "unknown")
cap = tc.get("cap", "?")
lines.append(f"- `{tool_name}`: {reason} (limit: {cap})")
return "\n".join(lines)