import gradio as gr import json import matplotlib.pyplot as plt import pandas as pd import io import base64 import math import ast import logging import numpy as np import plotly.graph_objects as go # Set up logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) # Function to safely parse JSON or Python dictionary input def parse_input(json_input): logger.debug("Attempting to parse input: %s", json_input) try: # Try to parse as JSON first data = json.loads(json_input) logger.debug("Successfully parsed as JSON") return data except json.JSONDecodeError as e: logger.error("JSON parsing failed: %s", str(e)) try: # If JSON fails, try to parse as Python literal (e.g., with single quotes) data = ast.literal_eval(json_input) logger.debug("Successfully parsed as Python literal") # Convert Python dictionary to JSON-compatible format (replace single quotes with double quotes) def dict_to_json(obj): if isinstance(obj, dict): return {str(k): dict_to_json(v) for k, v in obj.items()} elif isinstance(obj, list): return [dict_to_json(item) for item in obj] else: return obj converted_data = dict_to_json(data) logger.debug("Converted to JSON-compatible format") return converted_data except (SyntaxError, ValueError) as e: logger.error("Python literal parsing failed: %s", str(e)) raise ValueError(f"Malformed input: {str(e)}. Ensure property names are in double quotes (e.g., \"content\") or correct Python dictionary format.") # Function to ensure a value is a float, converting from string if necessary def ensure_float(value): if value is None: logger.debug("Replacing None logprob with 0.0") return 0.0 # Default to 0.0 for None to ensure visualization if isinstance(value, str): try: return float(value) except ValueError: logger.error("Failed to convert string '%s' to float", value) return 0.0 # Default to 0.0 for invalid strings if isinstance(value, (int, float)): return float(value) return 0.0 # Default for any other type # Function to get or generate a token value (default to "Unknown" if missing) def get_token(entry): token = entry.get("token", "Unknown") if token == "Unknown": logger.warning("Missing 'token' key for entry: %s, using 'Unknown'", entry) return token # Function to create an empty Plotly figure def create_empty_figure(title): return go.Figure().update_layout(title=title, xaxis_title="", yaxis_title="", showlegend=False) # Function to process and visualize the full log probs with dynamic top_logprobs, handling missing tokens def visualize_logprobs(json_input): try: # Parse the input (handles both JSON and Python dictionaries) data = parse_input(json_input) # Ensure data is a list or dictionary with 'content' if isinstance(data, dict) and "content" in data: content = data["content"] elif isinstance(data, list): content = data else: raise ValueError("Input must be a list or dictionary with 'content' key") # Extract tokens, log probs, and top alternatives, skipping non-finite values with fixed filter of -100000 tokens = [] logprobs = [] top_alternatives = [] # List to store all top_logprobs (dynamic length) for entry in content: logprob = ensure_float(entry.get("logprob", None)) if math.isfinite(logprob) and logprob >= -100000: token = get_token(entry) # Safely get token, defaulting to "Unknown" if missing tokens.append(token) logprobs.append(logprob) # Get top_logprobs, default to empty dict if None top_probs = entry.get("top_logprobs", {}) if top_probs is None: logger.debug("top_logprobs is None for token: %s, using empty dict", token) top_probs = {} # Default to empty dict for None # Ensure all values in top_logprobs are floats and create a list of tuples finite_top_probs = [] for key, value in top_probs.items(): float_value = ensure_float(value) if float_value is not None and math.isfinite(float_value): finite_top_probs.append((key, float_value)) # Sort by log probability (descending) to get all alternatives sorted_probs = sorted(finite_top_probs, key=lambda x: x[1], reverse=True) top_alternatives.append(sorted_probs) # Store all alternatives, dynamic length else: logger.debug("Skipping entry with logprob: %s (type: %s)", entry.get("logprob"), type(entry.get("logprob", None))) # Check if there's valid data after filtering if not logprobs or not tokens: return (create_empty_figure("Log Probabilities of Generated Tokens"), None, "No finite log probabilities to display.", create_empty_figure("Top Token Log Probabilities"), create_empty_figure("Significant Probability Drops")) # 1. Main Log Probability Plot (Interactive Plotly) main_fig = go.Figure() main_fig.add_trace(go.Scatter(x=list(range(len(logprobs))), y=logprobs, mode='markers+lines', name='Log Prob', marker=dict(color='blue'))) main_fig.update_layout( title="Log Probabilities of Generated Tokens", xaxis_title="Token Position", yaxis_title="Log Probability", hovermode="closest", clickmode='event+select' ) main_fig.update_traces( customdata=[f"Token: {tok}, Log Prob: {prob:.4f}, Position: {i}" for i, (tok, prob) in enumerate(zip(tokens, logprobs))], hovertemplate='%{customdata}' ) # 2. Probability Drop Analysis (Interactive Plotly) if len(logprobs) < 2: drops_fig = create_empty_figure("Significant Probability Drops") else: drops = [logprobs[i+1] - logprobs[i] for i in range(len(logprobs)-1)] drops_fig = go.Figure() drops_fig.add_trace(go.Bar(x=list(range(len(drops))), y=drops, name='Drop', marker_color='red')) drops_fig.update_layout( title="Significant Probability Drops", xaxis_title="Token Position", yaxis_title="Log Probability Drop", hovermode="closest", clickmode='event+select' ) drops_fig.update_traces( customdata=[f"Drop: {drop:.4f}, From: {tokens[i]} to {tokens[i+1]}, Position: {i}" for i, drop in enumerate(drops)], hovertemplate='%{customdata}' ) # Create DataFrame for the table with dynamic top_logprobs table_data = [] max_alternatives = max(len(alts) for alts in top_alternatives) if top_alternatives else 0 for i, entry in enumerate(content): logprob = ensure_float(entry.get("logprob", None)) if math.isfinite(logprob) and logprob >= -100000 and "top_logprobs" in entry: token = get_token(entry) # Safely get token, defaulting to "Unknown" if missing top_logprobs = entry.get("top_logprobs", {}) if top_logprobs is None: logger.debug("top_logprobs is None for token: %s, using empty dict", token) top_logprobs = {} # Default to empty dict for None # Ensure all values in top_logprobs are floats finite_top_logprobs = [] for key, value in top_logprobs.items(): float_value = ensure_float(value) if float_value is not None and math.isfinite(float_value): finite_top_logprobs.append((key, float_value)) # Sort by log probability (descending) sorted_probs = sorted(finite_top_logprobs, key=lambda x: x[1], reverse=True) row = [token, f"{logprob:.4f}"] for alt_token, alt_logprob in sorted_probs[:max_alternatives]: # Use max number of alternatives row.append(f"{alt_token}: {alt_logprob:.4f}") # Pad with empty strings if fewer alternatives than max while len(row) < 2 + max_alternatives: row.append("") table_data.append(row) df = ( pd.DataFrame( table_data, columns=["Token", "Log Prob"] + [f"Alt {i+1}" for i in range(max_alternatives)], ) if table_data else None ) # Generate colored text if logprobs: min_logprob = min(logprobs) max_logprob = max(logprobs) if max_logprob == min_logprob: normalized_probs = [0.5] * len(logprobs) else: normalized_probs = [ (lp - min_logprob) / (max_logprob - min_logprob) for lp in logprobs ] colored_text = "" for i, (token, norm_prob) in enumerate(zip(tokens, normalized_probs)): r = int(255 * (1 - norm_prob)) # Red for low confidence g = int(255 * norm_prob) # Green for high confidence b = 0 color = f"rgb({r}, {g}, {b})" colored_text += f'{token}' if i < len(tokens) - 1: colored_text += " " colored_text_html = f"

{colored_text}

" else: colored_text_html = "No finite log probabilities to display." # Top Token Log Probabilities (Interactive Plotly, dynamic length) alt_viz_fig = create_empty_figure("Top Token Log Probabilities") if not logprobs or not top_alternatives else go.Figure() if logprobs and top_alternatives: for i, (token, probs) in enumerate(zip(tokens, top_alternatives)): for j, (alt_tok, prob) in enumerate(probs): alt_viz_fig.add_trace(go.Bar(x=[f"{token} (Pos {i})"], y=[prob], name=f"{alt_tok}", marker_color=['blue', 'green', 'red', 'purple', 'orange'][:len(probs)])) alt_viz_fig.update_layout( title="Top Token Log Probabilities", xaxis_title="Token (Position)", yaxis_title="Log Probability", barmode='stack', hovermode="closest", clickmode='event+select' ) alt_viz_fig.update_traces( customdata=[f"Token: {tok}, Alt: {alt}, Log Prob: {prob:.4f}, Position: {i}" for i, (tok, alts) in enumerate(zip(tokens, top_alternatives)) for alt, prob in alts], hovertemplate='%{customdata}' ) return (main_fig, df, colored_text_html, alt_viz_fig, drops_fig) except Exception as e: logger.error("Visualization failed: %s", str(e)) return (create_empty_figure("Log Probabilities of Generated Tokens"), None, "No finite log probabilities to display.", create_empty_figure("Top Token Log Probabilities"), create_empty_figure("Significant Probability Drops")) # Gradio interface with full dataset visualization, dynamic top_logprobs, and handling missing tokens with gr.Blocks(title="Log Probability Visualizer") as app: gr.Markdown("# Log Probability Visualizer") gr.Markdown( "Paste your JSON or Python dictionary log prob data below to visualize all tokens at once. Fixed filter ≥ -100000, dynamic number of top_logprobs, handles missing 'token'." ) with gr.Row(): json_input = gr.Textbox( label="JSON Input", lines=10, placeholder="Paste your JSON (e.g., {\"content\": [...]}) or Python dict (e.g., {'content': [...]}) here...", ) with gr.Row(): plot_output = gr.Plot(label="Log Probability Plot (Click for Tokens)") drops_output = gr.Plot(label="Probability Drops (Click for Details)") with gr.Row(): table_output = gr.Dataframe(label="Token Log Probabilities and Top Alternatives") alt_viz_output = gr.Plot(label="Top Token Log Probabilities (Click for Details)") with gr.Row(): text_output = gr.HTML(label="Colored Text (Confidence Visualization)") btn = gr.Button("Visualize") btn.click( fn=visualize_logprobs, inputs=[json_input], outputs=[plot_output, table_output, text_output, alt_viz_output, drops_output], ) app.launch()