fleet-memory/hindsight-dev/benchmarks/visualizer/main.py
Nicolò Boschi 5fddd9a79c
feat: add reflect mode to LoComo benchmark and improve reflect agent (#428)
* feat: add reflect mode to LoComo benchmark and improve reflect agent

- Replace think mode with reflect mode in LoComo benchmark using reflect_async with Budget.HIGH
- Add --question-index CLI flag to run a single question by its index
- Track and display original question index in logs and visualizer
- Update visualizer to show reflect mode results

Reflect agent improvements:
- tool_recall: always fetch chunks (max_chunk_tokens=1000 min, non-optional)
- tool_search_observations: use include_source_facts=True instead of separate DB query
- Use model_dump() throughout to avoid manual error-prone dict conversion
- Enforce minimum 1000 tokens for max_tokens and max_chunk_tokens in _execute_tool
- Fix NoneType error when LLM passes null for mental_model_ids/observation_ids arrays
- Add non-conversational constraint to system prompt to prevent follow-up questions
- Fix recall_fn Callable type hint to include max_chunk_tokens parameter
- Fix main.py missing reranker_zeroentropy fields in HindsightConfig constructor

* fix: update tests for reflect tool API changes

- source_memory_ids -> source_fact_ids in test_search_observations (MemoryFact.model_dump() field name)
- Remove proof_count check (not in MemoryFact, was ObservationResult-specific)
- Remove max_results param from tool_recall call (no longer supported)
- Fix recall_result["count"] -> len(recall_result["memories"])
2026-02-24 09:48:23 +01:00

1465 lines
64 KiB
Python

"""Benchmark Visualizer - FastHTML App
A fast web interface for visualizing benchmark results.
Supports LoComo and LongMemEval benchmark visualization.
Usage:
python main.py
"""
import json
from pathlib import Path
from typing import Any
from fasthtml.common import *
# Get the benchmarks directory
BENCHMARKS_DIR = Path(__file__).resolve().parent.parent
# Tailwind + shadcn/ui theme
def get_head():
return (
Script(src="https://cdn.tailwindcss.com"),
Script("""
tailwind.config = {
theme: {
extend: {
colors: {
border: "hsl(214.3 31.8% 91.4%)",
input: "hsl(214.3 31.8% 91.4%)",
ring: "hsl(222.2 84% 4.9%)",
background: "hsl(0 0% 100%)",
foreground: "hsl(222.2 84% 4.9%)",
primary: {
DEFAULT: "hsl(222.2 47.4% 11.2%)",
foreground: "hsl(210 40% 98%)",
},
secondary: {
DEFAULT: "hsl(210 40% 96.1%)",
foreground: "hsl(222.2 47.4% 11.2%)",
},
destructive: {
DEFAULT: "hsl(0 84.2% 60.2%)",
foreground: "hsl(210 40% 98%)",
},
muted: {
DEFAULT: "hsl(210 40% 96.1%)",
foreground: "hsl(215.4 16.3% 46.9%)",
},
accent: {
DEFAULT: "hsl(210 40% 96.1%)",
foreground: "hsl(222.2 47.4% 11.2%)",
},
success: {
DEFAULT: "hsl(142.1 76.2% 36.3%)",
foreground: "hsl(355.7 100% 97.3%)",
},
},
borderRadius: {
lg: "0.5rem",
md: "calc(0.5rem - 2px)",
sm: "calc(0.5rem - 4px)",
}
}
}
}
"""),
Style("""
@layer base {
* { border-color: hsl(var(--border)); }
body {
background-color: hsl(210 40% 96.1%);
color: hsl(222.2 84% 4.9%);
}
}
"""),
)
# Create FastHTML app
app, rt = fast_app()
def load_locomo_results(mode: str = "search") -> dict[str, Any] | None:
"""Load LoComo benchmark results."""
filename = "benchmark_results_reflect.json" if mode == "reflect" else "benchmark_results.json"
results_path = BENCHMARKS_DIR / "locomo" / "results" / filename
if not results_path.exists():
return None
try:
with open(results_path) as f:
return json.load(f)
except json.JSONDecodeError:
return None
def load_longmemeval_results() -> dict[str, Any] | None:
"""Load LongMemEval benchmark results."""
results_path = BENCHMARKS_DIR / "longmemeval" / "results" / "benchmark_results.json"
if not results_path.exists():
return None
try:
with open(results_path) as f:
return json.load(f)
except json.JSONDecodeError:
return None
def get_category_name(category: int | str) -> str:
"""Map category ID to name for LoComo."""
if isinstance(category, str):
return category
categories = {1: "Multi-hop", 2: "Single-hop", 3: "Temporal", 4: "Open-domain"}
return categories.get(category, "Unknown")
@rt("/")
def get():
"""Main page."""
return (
Title("Benchmark Visualizer"),
get_head(),
Main(
Div(
H1("📊 Benchmark Visualizer", cls="text-4xl font-bold text-foreground"),
P("Analyze and visualize benchmark results", cls="text-muted-foreground mt-2"),
cls="text-center py-12",
),
Div(
Label("Select a benchmark to view:", cls="block text-sm font-medium text-foreground mb-2"),
Select(
Option("-- Choose a benchmark --", value="", selected=True),
Option("LoComo (search mode)", value="/locomo/search"),
Option("LoComo (reflect mode)", value="/locomo/reflect"),
Option("LongMemEval", value="/longmemeval"),
onchange="if(this.value) window.location.href = this.value;",
cls="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring",
),
cls="max-w-md mx-auto bg-white border border-border rounded-lg p-6 shadow-sm",
),
cls="container mx-auto max-w-7xl px-4 py-8",
),
)
@rt("/locomo/{mode}")
def get_locomo(mode: str, filter_type: str = "all", category_filter: str = "all"):
"""Render LoComo results."""
data = load_locomo_results(mode)
if not data:
mode_label = "think" if mode == "think" else "search"
extra_args = " --use-think" if mode == "think" else ""
return (
Title(f"LoComo ({mode_label}) - Not Found"),
get_head(),
Main(
H1("⚠️ Benchmark Results Not Found", cls="text-3xl font-bold text-foreground mb-4"),
P(f"The {mode_label} mode results are not available.", cls="text-muted-foreground mb-6"),
H4("To generate results:", cls="text-lg font-semibold text-foreground mb-2"),
Pre(
f"./scripts/benchmarks/run-locomo.sh --env local{extra_args}",
cls="bg-slate-900 text-slate-100 p-4 rounded-md overflow-x-auto text-sm",
),
A(
"← Back",
href="/",
cls="inline-flex items-center mt-6 px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 text-sm font-medium",
),
cls="container mx-auto max-w-7xl px-4 py-8",
),
)
all_results = data.get("item_results", data.get("conversation_results", []))
# Filter items based on their questions
results = []
for item in all_results:
detailed_results = item.get("metrics", {}).get("detailed_results", [])
# Apply correctness filter
passes_correctness_filter = False
if filter_type == "all":
passes_correctness_filter = True
elif filter_type == "correct":
# Show items where all questions are correct (and not invalid)
passes_correctness_filter = detailed_results and all(
r.get("is_correct") and not r.get("is_invalid") for r in detailed_results
)
elif filter_type == "incorrect":
# Show items that have at least one incorrect question
passes_correctness_filter = any(
not r.get("is_correct") and not r.get("is_invalid") for r in detailed_results
)
elif filter_type == "invalid":
# Show items that have at least one invalid question
passes_correctness_filter = any(r.get("is_invalid") for r in detailed_results)
# Apply category filter
passes_category_filter = False
if category_filter == "all":
passes_category_filter = True
else:
# Show items that have at least one question of the specified category
category_id = int(category_filter)
passes_category_filter = any(r.get("category") == category_id for r in detailed_results)
if passes_correctness_filter and passes_category_filter:
results.append(item)
# Calculate stats (use all_results for overall stats, not filtered results)
category_stats = {
1: {"name": "Multi-hop", "correct": 0, "total": 0, "invalid": 0},
2: {"name": "Single-hop", "correct": 0, "total": 0, "invalid": 0},
3: {"name": "Temporal", "correct": 0, "total": 0, "invalid": 0},
4: {"name": "Open-domain", "correct": 0, "total": 0, "invalid": 0},
}
total_invalid = 0
for item in all_results:
if item.get("metrics", {}).get("detailed_results"):
for result in item["metrics"]["detailed_results"]:
category = result.get("category")
if category in category_stats:
category_stats[category]["total"] += 1
if result.get("is_invalid"):
category_stats[category]["invalid"] += 1
total_invalid += 1
elif result.get("is_correct"):
category_stats[category]["correct"] += 1
mode_label = " (Reflect Mode)" if mode == "reflect" else " (Search Mode)"
# Overall stats
stats_html = Div(
H3(f"LoComo Benchmark{mode_label} - Overall Performance", cls="text-2xl font-bold text-foreground mb-6"),
Div(
Div(
P("Overall Accuracy", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(f"{data['overall_accuracy']:.2f}%", cls="text-3xl font-bold text-foreground"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
),
Div(
P("Correct Answers", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(f"{data['total_correct']} / {data['total_questions']}", cls="text-3xl font-bold text-foreground"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
),
Div(
P("Invalid Questions", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(str(total_invalid), cls="text-3xl font-bold text-foreground"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
)
if total_invalid > 0
else None,
Div(
P("Items", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(str(len(all_results)), cls="text-3xl font-bold text-foreground"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
),
cls="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8",
),
H4("Accuracy by Category", cls="text-xl font-semibold text-foreground mb-4"),
Div(
*[
Div(
P(cat["name"], cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(
f"{(cat['correct'] / (cat['total'] - cat['invalid']) * 100) if (cat['total'] - cat['invalid']) > 0 else 0:.1f}%",
cls="text-2xl font-bold text-foreground",
),
P(f"{cat['correct']} / {cat['total']}", cls="text-sm text-muted-foreground mt-1"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
)
for cat in category_stats.values()
],
cls="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4",
),
)
# Filter controls
filters = Div(
# Correctness filter
Div(
P("Filter by correctness:", cls="text-sm font-medium text-foreground mb-2"),
Div(
A(
"All",
href=f"/locomo/{mode}?filter_type=all&category_filter={category_filter}",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "all"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"✅ All Correct",
href=f"/locomo/{mode}?filter_type=correct&category_filter={category_filter}",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "correct"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"❌ Has Incorrect",
href=f"/locomo/{mode}?filter_type=incorrect&category_filter={category_filter}",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "incorrect"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"⚠️ Has Invalid",
href=f"/locomo/{mode}?filter_type=invalid&category_filter={category_filter}",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "invalid"
else "bg-white text-foreground border border-border hover:bg-accent"
),
)
if total_invalid > 0
else None,
cls="flex flex-wrap gap-2",
),
cls="mb-4",
),
# Category filter
Div(
P("Filter by question category:", cls="text-sm font-medium text-foreground mb-2"),
Div(
A(
"All Categories",
href=f"/locomo/{mode}?filter_type={filter_type}&category_filter=all",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "all"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"Multi-hop",
href=f"/locomo/{mode}?filter_type={filter_type}&category_filter=1",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "1"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"Single-hop",
href=f"/locomo/{mode}?filter_type={filter_type}&category_filter=2",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "2"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"Temporal",
href=f"/locomo/{mode}?filter_type={filter_type}&category_filter=3",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "3"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"Open-domain",
href=f"/locomo/{mode}?filter_type={filter_type}&category_filter=4",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "4"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
cls="flex flex-wrap gap-2",
),
cls="mb-4",
),
cls="mb-6",
)
# Render items
items_html = []
for item in results:
# Find the original index in all_results
original_idx = all_results.index(item)
item_id = item.get("item_id", item.get("sample_id", f"item-{original_idx}"))
metrics = item.get("metrics", {})
# Calculate accuracy for filtered category
if category_filter != "all":
detailed_results = metrics.get("detailed_results", [])
category_id = int(category_filter)
filtered_correct = sum(
1
for r in detailed_results
if r.get("category") == category_id and r.get("is_correct") and not r.get("is_invalid")
)
filtered_total = sum(
1 for r in detailed_results if r.get("category") == category_id and not r.get("is_invalid")
)
accuracy = (filtered_correct / filtered_total * 100) if filtered_total > 0 else 0
correct = filtered_correct
total = filtered_total
else:
accuracy = metrics.get("accuracy", 0)
correct = metrics.get("correct", 0)
total = metrics.get("total", 0)
color = "🟢" if accuracy >= 70 else ("🟡" if accuracy >= 50 else "🔴")
# Determine border color based on accuracy
if accuracy >= 70:
border_class = "border-l-4 border-green-600"
bg_class = "hover:bg-green-50"
elif accuracy >= 50:
border_class = "border-l-4 border-yellow-500"
bg_class = "hover:bg-yellow-50"
else:
border_class = "border-l-4 border-red-600"
bg_class = "hover:bg-red-50"
# Show preview with link to detail page
items_html.append(
A(
Div(
Div(
P(f"{color} {item_id}", cls="text-lg font-semibold text-foreground mb-2"),
Div(
Div(
P("Accuracy", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide"),
P(f"{accuracy:.1f}%", cls="text-2xl font-bold text-foreground"),
cls="text-center",
),
Div(
P("Correct", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide"),
P(f"{correct}/{total}", cls="text-xl font-semibold text-foreground"),
cls="text-center",
),
cls="flex gap-6 items-center",
),
cls="p-6",
),
cls=f"bg-white border border-border rounded-lg shadow-sm transition-all {border_class} {bg_class}",
),
href=f"/locomo/{mode}/item/{original_idx}?filter_type={filter_type}&category_filter={category_filter}",
cls="block no-underline",
)
)
return (
Title(f"LoComo ({mode_label})"),
get_head(),
Main(
Div(
A(
"← Back to benchmarks",
href="/",
cls="inline-flex items-center px-4 py-2 bg-white border border-border rounded-md text-sm font-medium text-foreground hover:bg-accent mb-6",
),
stats_html,
Hr(cls="my-6 border-border"),
filters,
P(f"Showing {len(results)} of {len(all_results)} items", cls="text-sm text-muted-foreground mb-6"),
Div(*items_html, cls="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"),
cls="container mx-auto max-w-7xl px-4 py-8",
)
),
)
@rt("/locomo/{mode}/item/{item_idx}")
def get_locomo_item(mode: str, item_idx: int, filter_type: str = "all", category_filter: str = "all"):
"""Render a single LoComo item with questions."""
data = load_locomo_results(mode)
if not data:
return Redirect("/")
results = data.get("item_results", data.get("conversation_results", []))
if item_idx >= len(results):
return Redirect(f"/locomo/{mode}")
item = results[item_idx]
item_id = item.get("item_id", item.get("sample_id", f"item-{item_idx}"))
metrics = item.get("metrics", {})
accuracy = metrics.get("accuracy", 0)
correct = metrics.get("correct", 0)
total = metrics.get("total", 0)
invalid = metrics.get("invalid", 0)
detailed_results = metrics.get("detailed_results", [])
category_stats_raw = metrics.get("category_stats", {})
# Filter questions
filtered_questions = []
for q_idx, result in enumerate(detailed_results):
is_invalid = result.get("is_invalid", False)
is_correct = result.get("is_correct", False)
question_category = result.get("category")
# Apply correctness filter
passes_correctness = False
if filter_type == "all":
passes_correctness = True
elif filter_type == "correct" and is_correct and not is_invalid:
passes_correctness = True
elif filter_type == "incorrect" and not is_correct and not is_invalid:
passes_correctness = True
elif filter_type == "invalid" and is_invalid:
passes_correctness = True
# Apply category filter
passes_category = False
if category_filter == "all":
passes_category = True
elif category_filter.isdigit() and question_category == int(category_filter):
passes_category = True
if passes_correctness and passes_category:
filtered_questions.append((q_idx, result))
# Build category stats for this item
category_stats = {
1: {"name": "Multi-hop", "correct": 0, "total": 0, "invalid": 0},
2: {"name": "Single-hop", "correct": 0, "total": 0, "invalid": 0},
3: {"name": "Temporal", "correct": 0, "total": 0, "invalid": 0},
4: {"name": "Open-domain", "correct": 0, "total": 0, "invalid": 0},
}
for cat_id_str, stats in category_stats_raw.items():
cat_id = int(cat_id_str)
if cat_id in category_stats:
category_stats[cat_id]["correct"] = stats.get("correct", 0)
category_stats[cat_id]["total"] = stats.get("total", 0)
category_stats[cat_id]["invalid"] = stats.get("invalid", 0)
# Overall stats for this item
mode_label = " (Reflect Mode)" if mode == "reflect" else " (Search Mode)"
stats_html = Div(
H3(f"{item_id}{mode_label} - Performance", cls="text-2xl font-bold text-foreground mb-6"),
Div(
Div(
P("Overall Accuracy", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(f"{accuracy:.2f}%", cls="text-3xl font-bold text-foreground"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
),
Div(
P("Correct Answers", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(f"{correct} / {total}", cls="text-3xl font-bold text-foreground"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
),
Div(
P("Invalid Questions", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(str(invalid), cls="text-3xl font-bold text-foreground"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
)
if invalid > 0
else None,
Div(
P("Total Questions", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(str(total), cls="text-3xl font-bold text-foreground"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
),
cls="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8",
),
H4("Accuracy by Category", cls="text-xl font-semibold text-foreground mb-4"),
Div(
*[
Div(
P(cat["name"], cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(
f"{(cat['correct'] / (cat['total'] - cat['invalid']) * 100) if (cat['total'] - cat['invalid']) > 0 else 0:.1f}%",
cls="text-2xl font-bold text-foreground",
),
P(f"{cat['correct']} / {cat['total']}", cls="text-sm text-muted-foreground mt-1"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
)
for cat in category_stats.values()
if cat["total"] > 0
],
cls="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4",
)
if any(cat["total"] > 0 for cat in category_stats.values())
else None,
cls="mb-6",
)
# Generate markdown table for copying
markdown_rows = [f"| {item_id} | {accuracy:.1f}% |"]
for cat in category_stats.values():
if cat["total"] > 0:
cat_accuracy = (
(cat["correct"] / (cat["total"] - cat["invalid"]) * 100) if (cat["total"] - cat["invalid"]) > 0 else 0
)
markdown_rows.append(f" {cat_accuracy:.1f}% |")
markdown_table = f"""| Conversation | Overall |{" | ".join([cat["name"] for cat in category_stats.values() if cat["total"] > 0])} |
|---|---|{" | ".join(["---" for cat in category_stats.values() if cat["total"] > 0])} |
{"".join(markdown_rows)}"""
# Copy button
copy_button = Div(
Button(
"📋 Copy Stats Table",
onclick=f"""
navigator.clipboard.writeText(`{markdown_table}`).then(() => {{
this.textContent = '✓ Copied!';
setTimeout(() => {{ this.textContent = '📋 Copy Stats Table'; }}, 2000);
}});
""",
cls="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 text-sm font-medium cursor-pointer",
),
cls="mb-6",
)
# Filters for questions
has_invalid = any(r.get("is_invalid", False) for r in detailed_results)
q_filters = Div(
# Correctness filter
Div(
P("Filter by correctness:", cls="text-sm font-medium text-foreground mb-2"),
Div(
A(
"All",
href=f"/locomo/{mode}/item/{item_idx}?filter_type=all&category_filter={category_filter}",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "all"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"✅ Correct",
href=f"/locomo/{mode}/item/{item_idx}?filter_type=correct&category_filter={category_filter}",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "correct"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"❌ Incorrect",
href=f"/locomo/{mode}/item/{item_idx}?filter_type=incorrect&category_filter={category_filter}",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "incorrect"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"⚠️ Invalid",
href=f"/locomo/{mode}/item/{item_idx}?filter_type=invalid&category_filter={category_filter}",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "invalid"
else "bg-white text-foreground border border-border hover:bg-accent"
),
)
if has_invalid
else None,
cls="flex flex-wrap gap-2",
),
cls="mb-4",
),
# Category filter
Div(
P("Filter by category:", cls="text-sm font-medium text-foreground mb-2"),
Div(
A(
"All Categories",
href=f"/locomo/{mode}/item/{item_idx}?filter_type={filter_type}&category_filter=all",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "all"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"Multi-hop",
href=f"/locomo/{mode}/item/{item_idx}?filter_type={filter_type}&category_filter=1",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "1"
else "bg-white text-foreground border border-border hover:bg-accent"
),
)
if any(cat_id == 1 for cat_id in category_stats.keys() if category_stats[cat_id]["total"] > 0)
else None,
A(
"Single-hop",
href=f"/locomo/{mode}/item/{item_idx}?filter_type={filter_type}&category_filter=2",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "2"
else "bg-white text-foreground border border-border hover:bg-accent"
),
)
if any(cat_id == 2 for cat_id in category_stats.keys() if category_stats[cat_id]["total"] > 0)
else None,
A(
"Temporal",
href=f"/locomo/{mode}/item/{item_idx}?filter_type={filter_type}&category_filter=3",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "3"
else "bg-white text-foreground border border-border hover:bg-accent"
),
)
if any(cat_id == 3 for cat_id in category_stats.keys() if category_stats[cat_id]["total"] > 0)
else None,
A(
"Open-domain",
href=f"/locomo/{mode}/item/{item_idx}?filter_type={filter_type}&category_filter=4",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "4"
else "bg-white text-foreground border border-border hover:bg-accent"
),
)
if any(cat_id == 4 for cat_id in category_stats.keys() if category_stats[cat_id]["total"] > 0)
else None,
cls="flex flex-wrap gap-2",
),
cls="mb-4",
),
cls="mb-6",
)
# Render questions
questions_html = []
for q_idx, result in filtered_questions:
is_invalid = result.get("is_invalid", False)
is_correct = result.get("is_correct", False)
question = result.get("question", "")
correct_answer = result.get("correct_answer", "")
predicted_answer = result.get("predicted_answer", "")
category = get_category_name(result.get("category", "Unknown"))
question_index = result.get("question_index", q_idx)
icon = "⚠️" if is_invalid else ("" if is_correct else "")
border_class = (
"border-l-4 border-yellow-500"
if is_invalid
else ("border-l-4 border-green-600" if is_correct else "border-l-4 border-red-600")
)
questions_html.append(
Div(
# Header
Div(
P(f"{icon} Question #{question_index}", cls="text-lg font-semibold text-foreground"),
P(f"Category: {category}", cls="text-sm text-muted-foreground"),
cls="mb-4",
),
# Question
Div(
P("Question:", cls="text-sm font-medium text-foreground mb-1"),
P(question, cls="text-foreground"),
cls="mb-4",
),
# Answers side by side
Div(
Div(
P("✓ Correct Answer", cls="text-sm font-medium text-foreground mb-2"),
Div(correct_answer, cls="bg-green-50 border border-green-200 rounded-md p-3 text-foreground"),
cls="flex-1",
),
Div(
P(
f"{'' if is_correct else ''} Predicted Answer",
cls="text-sm font-medium text-foreground mb-2",
),
Div(
predicted_answer,
cls="border rounded-md p-3 text-foreground "
+ ("bg-green-50 border-green-200" if is_correct else "bg-red-50 border-red-200"),
),
cls="flex-1",
),
cls="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4",
),
# Details
Details(
Summary(
"📝 Show Reasoning & Retrieved Memories",
cls="cursor-pointer font-medium text-foreground hover:text-primary py-2",
),
Div(
# System Reasoning
Div(
P("System Reasoning:", cls="text-sm font-medium text-foreground mb-2"),
Pre(
result.get("reasoning", "N/A"),
cls="bg-slate-900 text-slate-100 p-3 rounded-md overflow-x-auto text-sm",
),
cls="mb-4",
),
# Judge Reasoning
Div(
P("Judge Reasoning:", cls="text-sm font-medium text-foreground mb-2"),
Pre(
result.get("correctness_reasoning", "N/A"),
cls="bg-slate-900 text-slate-100 p-3 rounded-md overflow-x-auto text-sm",
),
cls="mb-4",
),
# Retrieved Memories
Div(
P(
f"Retrieved Memories ({len(result.get('retrieved_memories', []))}):",
cls="text-sm font-medium text-foreground mb-2",
),
*[
Div(
P(
f"#{i + 1}"
+ "".join(
filter(
None,
[
f"Occurred: {mem.get('occurred_start', '')[:10]}"
+ (
f" to {mem.get('occurred_end', '')[:10]}"
if mem.get("occurred_end")
and mem.get("occurred_start", "")[:10]
!= mem.get("occurred_end", "")[:10]
else ""
)
if mem.get("occurred_start")
else None,
f"Mentioned: {mem.get('mentioned_at', '')[:10]}"
if mem.get("mentioned_at")
else None,
f"Type: {mem.get('fact_type', 'unknown').upper()}",
],
)
),
cls="text-xs text-muted-foreground mb-1",
),
P(mem.get("text", ""), cls="text-sm text-foreground"),
cls="bg-muted/50 border border-border rounded-md p-3 mb-2",
)
for i, mem in enumerate(result.get("retrieved_memories", []))
]
if result.get("retrieved_memories")
else [P("No memories retrieved", cls="text-sm text-muted-foreground")],
),
cls="mt-3 space-y-2",
),
cls="border border-border rounded-md p-4 bg-muted/30",
),
cls=f"bg-white border border-border rounded-lg p-6 mb-4 shadow-sm {border_class}",
)
)
return (
Title(f"{item_id}"),
get_head(),
Main(
Div(
A(
f"← Back to LoComo ({mode})",
href=f"/locomo/{mode}",
cls="inline-flex items-center px-4 py-2 bg-white border border-border rounded-md text-sm font-medium text-foreground hover:bg-accent mb-6",
),
stats_html,
copy_button,
Hr(cls="my-6 border-border"),
q_filters,
P(f"Showing {len(filtered_questions)} questions", cls="text-sm text-muted-foreground mb-6"),
Div(*questions_html, cls="space-y-4"),
cls="container mx-auto max-w-7xl px-4 py-8",
)
),
)
@rt("/longmemeval")
def get_longmemeval(filter_type: str = "all", category_filter: str = "all"):
"""Render LongMemEval results."""
data = load_longmemeval_results()
if not data:
return (
Title("LongMemEval - Not Found"),
get_head(),
Main(
H1("⚠️ Benchmark Results Not Found"),
P("The benchmark results are not available."),
H4("To generate results:"),
Pre("./scripts/benchmarks/run-longmemeval.sh --env local"),
A("← Back", href="/", cls="btn mt-3"),
cls="container",
),
)
all_results = data.get("item_results", [])
# Filter items based on their questions
results = []
for item in all_results:
detailed_results = item.get("metrics", {}).get("detailed_results", [])
# Apply correctness filter
passes_correctness_filter = False
if filter_type == "all":
passes_correctness_filter = True
elif filter_type == "correct":
# Show items where all questions are correct (and not invalid)
passes_correctness_filter = detailed_results and all(
r.get("is_correct") and not r.get("is_invalid") for r in detailed_results
)
elif filter_type == "incorrect":
# Show items that have at least one incorrect question
passes_correctness_filter = any(
not r.get("is_correct") and not r.get("is_invalid") for r in detailed_results
)
elif filter_type == "invalid":
# Show items that have at least one invalid question
passes_correctness_filter = any(r.get("is_invalid") for r in detailed_results)
# Apply category filter
passes_category_filter = False
if category_filter == "all":
passes_category_filter = True
else:
# Show items that have at least one question of the specified category
passes_category_filter = any(r.get("category") == category_filter for r in detailed_results)
if passes_correctness_filter and passes_category_filter:
results.append(item)
# Calculate stats (use all_results for overall stats, not filtered results)
category_stats = {}
total_invalid = 0
for item in all_results:
if item.get("metrics", {}).get("category_stats"):
for category, stats in item["metrics"]["category_stats"].items():
if category not in category_stats:
category_stats[category] = {"name": category, "correct": 0, "total": 0, "invalid": 0}
category_stats[category]["correct"] += stats.get("correct", 0)
category_stats[category]["total"] += stats.get("total", 0)
category_stats[category]["invalid"] += stats.get("invalid", 0)
if item.get("metrics", {}).get("detailed_results"):
for result in item["metrics"]["detailed_results"]:
if result.get("is_invalid"):
total_invalid += 1
# Overall stats
stats_html = Div(
H3("LongMemEval Benchmark - Overall Performance", cls="text-2xl font-bold text-foreground mb-6"),
Div(
Div(
P("Overall Accuracy", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(f"{data['overall_accuracy']:.2f}%", cls="text-3xl font-bold text-foreground"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
),
Div(
P("Correct Answers", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(f"{data['total_correct']} / {data['total_questions']}", cls="text-3xl font-bold text-foreground"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
),
Div(
P("Invalid Questions", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(str(total_invalid), cls="text-3xl font-bold text-foreground"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
)
if total_invalid > 0
else None,
Div(
P("Items", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(str(len(all_results)), cls="text-3xl font-bold text-foreground"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
),
cls="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8",
),
H4("Accuracy by Category", cls="text-xl font-semibold text-foreground mb-4"),
Div(
*[
Div(
P(cat["name"], cls="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2"),
P(
f"{(cat['correct'] / (cat['total'] - cat['invalid']) * 100) if (cat['total'] - cat['invalid']) > 0 else 0:.1f}%",
cls="text-2xl font-bold text-foreground",
),
P(f"{cat['correct']} / {cat['total']}", cls="text-sm text-muted-foreground mt-1"),
cls="bg-white border border-border rounded-lg p-6 text-center shadow-sm",
)
for cat in category_stats.values()
],
cls="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4",
)
if category_stats
else None,
)
# Filter controls
filters = Div(
# Correctness filter
Div(
P("Filter by correctness:", cls="text-sm font-medium text-foreground mb-2"),
Div(
A(
"All",
href=f"/longmemeval?filter_type=all&category_filter={category_filter}",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "all"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"✅ All Correct",
href=f"/longmemeval?filter_type=correct&category_filter={category_filter}",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "correct"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"❌ Has Incorrect",
href=f"/longmemeval?filter_type=incorrect&category_filter={category_filter}",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "incorrect"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"⚠️ Has Invalid",
href=f"/longmemeval?filter_type=invalid&category_filter={category_filter}",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "invalid"
else "bg-white text-foreground border border-border hover:bg-accent"
),
)
if total_invalid > 0
else None,
cls="flex flex-wrap gap-2",
),
cls="mb-4",
),
# Category filter
Div(
P("Filter by question category:", cls="text-sm font-medium text-foreground mb-2"),
Div(
A(
"All Categories",
href=f"/longmemeval?filter_type={filter_type}&category_filter=all",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "all"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"Multi-session",
href=f"/longmemeval?filter_type={filter_type}&category_filter=multi-session",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "multi-session"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"Single-session User",
href=f"/longmemeval?filter_type={filter_type}&category_filter=single-session-user",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "single-session-user"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"Single-session Assistant",
href=f"/longmemeval?filter_type={filter_type}&category_filter=single-session-assistant",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "single-session-assistant"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"Single-session Preference",
href=f"/longmemeval?filter_type={filter_type}&category_filter=single-session-preference",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "single-session-preference"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"Temporal Reasoning",
href=f"/longmemeval?filter_type={filter_type}&category_filter=temporal-reasoning",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "temporal-reasoning"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"Knowledge Update",
href=f"/longmemeval?filter_type={filter_type}&category_filter=knowledge-update",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if category_filter == "knowledge-update"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
cls="flex flex-wrap gap-2",
),
cls="mb-4",
),
cls="mb-6",
)
# Render items
items_html = []
for item in results:
# Find the original index in all_results
original_idx = all_results.index(item)
item_id = item.get("item_id", f"item-{original_idx}")
metrics = item.get("metrics", {})
accuracy = metrics.get("accuracy", 0)
correct = metrics.get("correct", 0)
total = metrics.get("total", 0)
color = "🟢" if accuracy >= 70 else ("🟡" if accuracy >= 50 else "🔴")
# Determine border color based on accuracy
if accuracy >= 70:
border_class = "border-l-4 border-green-600"
bg_class = "hover:bg-green-50"
elif accuracy >= 50:
border_class = "border-l-4 border-yellow-500"
bg_class = "hover:bg-yellow-50"
else:
border_class = "border-l-4 border-red-600"
bg_class = "hover:bg-red-50"
items_html.append(
A(
Div(
Div(
P(f"{color} {item_id}", cls="text-lg font-semibold text-foreground mb-2"),
Div(
Div(
P("Accuracy", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide"),
P(f"{accuracy:.1f}%", cls="text-2xl font-bold text-foreground"),
cls="text-center",
),
Div(
P("Correct", cls="text-xs font-medium text-muted-foreground uppercase tracking-wide"),
P(f"{correct}/{total}", cls="text-xl font-semibold text-foreground"),
cls="text-center",
),
cls="flex gap-6 items-center",
),
cls="p-6",
),
cls=f"bg-white border border-border rounded-lg shadow-sm transition-all {border_class} {bg_class}",
),
href=f"/longmemeval/item/{original_idx}?filter_type={filter_type}&category_filter={category_filter}",
cls="block no-underline",
)
)
return (
Title("LongMemEval"),
get_head(),
Main(
Div(
A(
"← Back to benchmarks",
href="/",
cls="inline-flex items-center px-4 py-2 bg-white border border-border rounded-md text-sm font-medium text-foreground hover:bg-accent mb-6",
),
stats_html,
Hr(cls="my-6 border-border"),
filters,
P(f"Showing {len(results)} of {len(all_results)} items", cls="text-sm text-muted-foreground mb-6"),
Div(*items_html, cls="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"),
cls="container mx-auto max-w-7xl px-4 py-8",
)
),
)
@rt("/longmemeval/item/{item_idx}")
def get_longmemeval_item(item_idx: int, filter_type: str = "all"):
"""Render a single LongMemEval item with questions."""
data = load_longmemeval_results()
if not data:
return Redirect("/")
results = data.get("item_results", [])
if item_idx >= len(results):
return Redirect("/longmemeval")
item = results[item_idx]
item_id = item.get("item_id", f"item-{item_idx}")
metrics = item.get("metrics", {})
accuracy = metrics.get("accuracy", 0)
detailed_results = metrics.get("detailed_results", [])
# Filter questions
filtered_questions = []
for q_idx, result in enumerate(detailed_results):
is_invalid = result.get("is_invalid", False)
is_correct = result.get("is_correct", False)
if filter_type == "all":
filtered_questions.append((q_idx, result))
elif filter_type == "correct" and is_correct and not is_invalid:
filtered_questions.append((q_idx, result))
elif filter_type == "incorrect" and not is_correct and not is_invalid:
filtered_questions.append((q_idx, result))
elif filter_type == "invalid" and is_invalid:
filtered_questions.append((q_idx, result))
# Filters for questions
has_invalid = any(r.get("is_invalid", False) for r in detailed_results)
q_filters = Div(
P("Filter:", cls="text-sm font-medium text-foreground mb-2"),
Div(
A(
"All",
href=f"/longmemeval/item/{item_idx}?filter_type=all",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "all"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"✅ Correct",
href=f"/longmemeval/item/{item_idx}?filter_type=correct",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "correct"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"❌ Incorrect",
href=f"/longmemeval/item/{item_idx}?filter_type=incorrect",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "incorrect"
else "bg-white text-foreground border border-border hover:bg-accent"
),
),
A(
"⚠️ Invalid",
href=f"/longmemeval/item/{item_idx}?filter_type=invalid",
cls="px-3 py-1.5 rounded-md text-sm font-medium "
+ (
"bg-primary text-primary-foreground"
if filter_type == "invalid"
else "bg-white text-foreground border border-border hover:bg-accent"
),
)
if has_invalid
else None,
cls="flex flex-wrap gap-2",
),
cls="mb-6",
)
# Render questions
questions_html = []
for q_idx, result in filtered_questions:
is_invalid = result.get("is_invalid", False)
is_correct = result.get("is_correct", False)
question = result.get("question", "")
correct_answer = result.get("correct_answer", "")
predicted_answer = result.get("predicted_answer", "")
category = result.get("category", "Unknown")
question_index = result.get("question_index", q_idx)
icon = "⚠️" if is_invalid else ("" if is_correct else "")
border_class = (
"border-l-4 border-yellow-500"
if is_invalid
else ("border-l-4 border-green-600" if is_correct else "border-l-4 border-red-600")
)
questions_html.append(
Div(
# Header
Div(
P(f"{icon} Question #{question_index}", cls="text-lg font-semibold text-foreground"),
P(f"Category: {category}", cls="text-sm text-muted-foreground"),
cls="mb-4",
),
# Question
Div(
P("Question:", cls="text-sm font-medium text-foreground mb-1"),
P(question, cls="text-foreground"),
cls="mb-4",
),
# Answers side by side
Div(
Div(
P("✓ Correct Answer", cls="text-sm font-medium text-foreground mb-2"),
Div(correct_answer, cls="bg-green-50 border border-green-200 rounded-md p-3 text-foreground"),
cls="flex-1",
),
Div(
P(
f"{'' if is_correct else ''} Predicted Answer",
cls="text-sm font-medium text-foreground mb-2",
),
Div(
predicted_answer,
cls="border rounded-md p-3 text-foreground "
+ ("bg-green-50 border-green-200" if is_correct else "bg-red-50 border-red-200"),
),
cls="flex-1",
),
cls="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4",
),
# Details
Details(
Summary(
"📝 Show Reasoning & Retrieved Memories",
cls="cursor-pointer font-medium text-foreground hover:text-primary py-2",
),
Div(
# System Reasoning
Div(
P("System Reasoning:", cls="text-sm font-medium text-foreground mb-2"),
Pre(
result.get("reasoning", "N/A"),
cls="bg-slate-900 text-slate-100 p-3 rounded-md overflow-x-auto text-sm",
),
cls="mb-4",
),
# Judge Reasoning
Div(
P("Judge Reasoning:", cls="text-sm font-medium text-foreground mb-2"),
Pre(
result.get("correctness_reasoning", "N/A"),
cls="bg-slate-900 text-slate-100 p-3 rounded-md overflow-x-auto text-sm",
),
cls="mb-4",
),
# Retrieved Memories
Div(
P(
f"Retrieved Memories ({len(result.get('retrieved_memories', []))}):",
cls="text-sm font-medium text-foreground mb-2",
),
*[
Div(
P(
f"#{i + 1}"
+ "".join(
filter(
None,
[
f"Occurred: {mem.get('occurred_start', '')[:10]}"
+ (
f" to {mem.get('occurred_end', '')[:10]}"
if mem.get("occurred_end")
and mem.get("occurred_start", "")[:10]
!= mem.get("occurred_end", "")[:10]
else ""
)
if mem.get("occurred_start")
else None,
f"Mentioned: {mem.get('mentioned_at', '')[:10]}"
if mem.get("mentioned_at")
else None,
f"Type: {mem.get('fact_type', 'unknown').upper()}",
],
)
),
cls="text-xs text-muted-foreground mb-1",
),
P(mem.get("text", ""), cls="text-sm text-foreground"),
cls="bg-muted/50 border border-border rounded-md p-3 mb-2",
)
for i, mem in enumerate(result.get("retrieved_memories", []))
]
if result.get("retrieved_memories")
else [P("No memories retrieved", cls="text-sm text-muted-foreground")],
),
cls="mt-3 space-y-2",
),
cls="border border-border rounded-md p-4 bg-muted/30",
),
cls=f"bg-white border border-border rounded-lg p-6 mb-4 shadow-sm {border_class}",
)
)
return (
Title(f"{item_id}"),
get_head(),
Main(
Div(
A(
"← Back to LongMemEval",
href="/longmemeval",
cls="inline-flex items-center px-4 py-2 bg-white border border-border rounded-md text-sm font-medium text-foreground hover:bg-accent mb-6",
),
H3(f"📊 {item_id} - {accuracy:.2f}%", cls="text-2xl font-bold text-foreground mb-4"),
Hr(cls="my-6 border-border"),
q_filters,
P(f"Showing {len(filtered_questions)} questions", cls="text-sm text-muted-foreground mb-6"),
Div(*questions_html, cls="space-y-4"),
cls="container mx-auto max-w-7xl px-4 py-8",
)
),
)
if __name__ == "__main__":
import uvicorn
print("🚀 Starting Benchmark Visualizer...")
print("📊 Server running at: http://localhost:8001")
uvicorn.run("main:app", host="127.0.0.1", port=8001, reload=True)