import plotly.graph_objects as go import plotly.express as px from plotly.subplots import make_subplots import pandas as pd from typing import List, Dict, Any # Color scale for heatmap: dark (low prob) → teal (high prob) HEATMAP_COLORSCALE = [ [0.0, "#1a1a2e"], [0.15, "#16213e"], [0.35, "#0f3460"], [0.60, "#1a6b6f"], [0.80, "#01696f"], [1.0, "#4fc3a1"], ] ENTROPY_LINE_COLOR = "#4fc3a1" CONFIDENCE_COLORS = { "CONFIDENT": "#6daa45", "NEUTRAL": "#e8af34", "UNCERTAIN": "#a13544", } def build_heatmap(results: List[Dict[str, Any]], top_k: int = 10) -> go.Figure: """ Build a 2D token probability heatmap. X-axis: Decoding step Y-axis: Top-K token candidates (ranked by probability at each step) Cell color: Probability value Args: results: List of step result dicts from decoder top_k: Number of top tokens to show (Y-axis height) Returns: Plotly Figure """ steps = [r["step"] for r in results] n_steps = len(steps) # Build matrices: z[token_rank][step] = prob z_matrix = [] text_matrix = [] y_labels = [f"Top {i+1}" for i in range(top_k)] for rank in range(top_k): row_z = [] row_text = [] for r in results: candidates = r["top_candidates"] if rank < len(candidates): prob = candidates[rank]["prob"] token = candidates[rank]["token"].replace("\n", "\\n").replace(" ", "·") row_z.append(prob) row_text.append(f"{token}
{prob:.4f}") else: row_z.append(0.0) row_text.append("—") z_matrix.append(row_z) text_matrix.append(row_text) step_labels = [f"Step {s}" for s in steps] fig = go.Figure(data=go.Heatmap( z=z_matrix, x=step_labels, y=y_labels, text=text_matrix, hovertemplate="%{y} @ %{x}
Token: %{text}", colorscale=HEATMAP_COLORSCALE, showscale=True, colorbar=dict( title=dict(text="Probability", side="right"), tickformat=".3f", thickness=14, ), zmin=0, zmax=max( max(row) for row in z_matrix if row ), )) fig.update_layout( title=dict( text="Token Probability Heatmap — Top-K Candidates per Decoding Step", font=dict(size=14), ), xaxis=dict( title="Decoding Step", tickangle=-45, tickfont=dict(size=10), showgrid=False, ), yaxis=dict( title="Token Rank", tickfont=dict(size=10), autorange="reversed", ), height=420, margin=dict(l=80, r=80, t=60, b=80), paper_bgcolor="#0e1117", plot_bgcolor="#0e1117", font=dict(color="#cdccca"), ) return fig def build_entropy_chart(results: List[Dict[str, Any]]) -> go.Figure: """ Build an entropy line chart with confidence-level color markers. X-axis: Decoding step Y-axis: Shannon entropy (nats) Markers: Colored by confidence level (green/yellow/red) Args: results: List of step result dicts from decoder Returns: Plotly Figure """ steps = [r["step"] for r in results] entropies = [r["entropy"] for r in results] confidence = [r["confidence_level"] for r in results] tokens = [r["chosen_token"].replace("\n", "\\n") for r in results] marker_colors = [CONFIDENCE_COLORS[c] for c in confidence] fig = go.Figure() # Line trace fig.add_trace(go.Scatter( x=steps, y=entropies, mode="lines", line=dict(color=ENTROPY_LINE_COLOR, width=2), name="Entropy", showlegend=False, )) # Marker trace (colored by confidence) fig.add_trace(go.Scatter( x=steps, y=entropies, mode="markers", marker=dict( color=marker_colors, size=9, line=dict(width=1, color="#1a1a2e"), ), customdata=list(zip(tokens, confidence)), hovertemplate=( "Step %{x}
" "Entropy: %{y:.4f} nats
" "Token: %{customdata[0]}
" "Confidence: %{customdata[1]}" ), name="Step", showlegend=False, )) # Threshold reference lines fig.add_hline(y=1.0, line_dash="dot", line_color="#6daa45", annotation_text="Confident threshold", annotation_font_size=10, annotation_position="bottom right") fig.add_hline(y=2.5, line_dash="dot", line_color="#a13544", annotation_text="Uncertain threshold", annotation_font_size=10, annotation_position="bottom right") # Legend annotations for label, color in CONFIDENCE_COLORS.items(): fig.add_trace(go.Scatter( x=[None], y=[None], mode="markers", marker=dict(color=color, size=9), name=label, showlegend=True, )) fig.update_layout( title=dict( text="Entropy per Decoding Step — Model Confidence Over Time", font=dict(size=14), ), xaxis=dict( title="Decoding Step", showgrid=True, gridcolor="#262523", tickmode="linear", dtick=max(1, len(steps) // 10), ), yaxis=dict( title="Entropy (nats)", showgrid=True, gridcolor="#262523", ), legend=dict( orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1, font=dict(size=11), ), height=320, margin=dict(l=60, r=60, t=60, b=50), paper_bgcolor="#0e1117", plot_bgcolor="#0e1117", font=dict(color="#cdccca"), ) return fig def build_step_bar(step_result: Dict[str, Any]) -> go.Figure: """ Build a horizontal bar chart for top-K candidates at a single step. Used in the step detail panel. Args: step_result: Single step dict from decoder results Returns: Plotly Figure """ candidates = step_result["top_candidates"] tokens = [c["token"].replace("\n", "\\n").replace(" ", "·") for c in candidates] probs = [c["prob"] for c in candidates] # Color the chosen token (rank 0) differently colors = [ENTROPY_LINE_COLOR if i == 0 else "#393836" for i in range(len(tokens))] fig = go.Figure(go.Bar( x=probs, y=tokens, orientation="h", marker_color=colors, hovertemplate="%{y}
Prob: %{x:.6f}", )) fig.update_layout( title=dict( text=f"Step {step_result['step']} — Top-{len(candidates)} Candidates", font=dict(size=13), ), xaxis=dict( title="Probability", showgrid=True, gridcolor="#262523", ), yaxis=dict( autorange="reversed", tickfont=dict(size=11), ), height=350, margin=dict(l=100, r=40, t=50, b=50), paper_bgcolor="#0e1117", plot_bgcolor="#0e1117", font=dict(color="#cdccca"), ) return fig