| |
| from __future__ import annotations |
|
|
| import re |
| from pathlib import Path |
|
|
| import matplotlib.pyplot as plt |
| import numpy as np |
| import pandas as pd |
|
|
|
|
| |
| |
| |
| XLSX_PATH = Path("Context_Consuming.xlsx") |
| OUT_PREFIX = Path("bioagentbench_context_radar_final") |
|
|
| METRIC = "prompt_tokens" |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| METHOD_ORDER = [ |
| "No MCP", |
| "Biomni-100", |
| "Biomni-500", |
| "Biomni-1k", |
| "Biomni-2k", |
| "BioManus", |
| ] |
|
|
| TASK_ORDER = [ |
| "alzheimer-mouse", |
| "cystic-fibrosis", |
| "deseq", |
| "transcript-quant", |
| "single-cell", |
| "metagenomics", |
| "viral-metagenomics", |
| "comparative-genomics", |
| "evolution", |
| "giab", |
| ] |
|
|
| METHOD_STYLE = { |
| "No MCP": { |
| "color": "#6B7280", |
| "linewidth": 1.8, |
| "linestyle": "-", |
| "fill_alpha": 0.00, |
| }, |
| "Biomni-100": { |
| "color": "#2563EB", |
| "linewidth": 1.8, |
| "linestyle": "-", |
| "fill_alpha": 0.00, |
| }, |
| "Biomni-500": { |
| "color": "#059669", |
| "linewidth": 1.6, |
| "linestyle": "-", |
| "fill_alpha": 0.00, |
| }, |
| "Biomni-1k": { |
| "color": "#D97706", |
| "linewidth": 1.6, |
| "linestyle": "-", |
| "fill_alpha": 0.00, |
| }, |
| "Biomni-2k": { |
| "color": "#7C3AED", |
| "linewidth": 2.0, |
| "linestyle": "-", |
| "fill_alpha": 0.00, |
| }, |
| "BioManus": { |
| "color": "#DC2626", |
| "linewidth": 2.6, |
| "linestyle": "-", |
| "fill_alpha": 0.04, |
| }, |
| } |
|
|
|
|
| |
| |
| |
| def clean_text(x) -> str: |
| if pd.isna(x): |
| return "" |
| s = str(x) |
| s = s.replace("\ufeff", "").replace("\u200b", "").replace("\u200c", "") |
| s = re.sub(r"\s+", " ", s) |
| return s.strip() |
|
|
|
|
| def method_name(x) -> str | None: |
| s = clean_text(x) |
| if not s: |
| return None |
|
|
| lower = s.lower() |
| if "biomanus" in lower: |
| return "BioManus" |
|
|
| try: |
| scale = int(float(s)) |
| except ValueError: |
| return None |
|
|
| if scale == 0: |
| return "No MCP" |
| if scale == 100: |
| return "Biomni-100" |
| if scale == 500: |
| return "Biomni-500" |
| if scale == 1000: |
| return "Biomni-1k" |
| if scale == 2000: |
| return "Biomni-2k" |
|
|
| |
| return None |
|
|
|
|
| def wrap_task_label(task: str) -> str: |
| labels = { |
| "alzheimer-mouse": "alzheimer\nmouse", |
| "cystic-fibrosis": "cystic\nfibrosis", |
| "deseq": "deseq", |
| "transcript-quant": "transcript\nquant", |
| "single-cell": "single-cell", |
| "metagenomics": "metagenomics", |
| "viral-metagenomics": "viral\nmetagenomics", |
| "comparative-genomics": "comparative\ngenomics", |
| "evolution": "evolution", |
| "giab": "giab", |
| } |
| return labels.get(task, task.replace("-", "\n")) |
|
|
|
|
| def geometric_mean(values: np.ndarray) -> float: |
| values = np.asarray(values, dtype=float) |
| values = values[values > 0] |
| return float(np.exp(np.mean(np.log(values)))) if len(values) else np.nan |
|
|
|
|
| def format_token(x: float) -> str: |
| if x >= 1_000_000: |
| return f"{x / 1_000_000:.1f}M" |
| if x >= 1_000: |
| return f"{x / 1_000:.0f}K" |
| return f"{x:.0f}" |
|
|
|
|
| |
| |
| |
| raw = pd.read_excel(XLSX_PATH, sheet_name=0, header=None) |
|
|
| header_candidates = raw.index[ |
| raw.iloc[:, 1].astype(str).map(clean_text).eq("Tasks") |
| ].tolist() |
|
|
| if not header_candidates: |
| raise ValueError("Cannot find header row with 'Tasks' in column B.") |
|
|
| header_idx = header_candidates[0] |
|
|
| df = raw.iloc[header_idx + 1 :, :6].copy() |
| df.columns = [ |
| "group", |
| "task", |
| "results_match", |
| "prompt_tokens", |
| "completion_tokens", |
| "total_tokens", |
| ] |
|
|
| df["group"] = df["group"].ffill() |
| df["method"] = df["group"].map(method_name) |
| df["task"] = df["task"].map(clean_text) |
|
|
| df = df[df["task"].ne("")] |
| df = df[df["method"].notna()] |
|
|
| for col in ["prompt_tokens", "completion_tokens", "total_tokens"]: |
| df[col] = pd.to_numeric(df[col], errors="coerce") |
|
|
| df = df.dropna(subset=[METRIC]) |
| df = df[df["method"].isin(METHOD_ORDER)] |
| df["method"] = pd.Categorical(df["method"], categories=METHOD_ORDER, ordered=True) |
|
|
| pivot = df.pivot_table( |
| index="method", |
| columns="task", |
| values=METRIC, |
| aggfunc="mean", |
| ) |
|
|
| available_methods = [m for m in METHOD_ORDER if m in pivot.index] |
| task_order = [t for t in TASK_ORDER if t in pivot.columns] |
|
|
| common_tasks = [ |
| t for t in task_order |
| if pivot.loc[available_methods, t].notna().all() |
| ] |
|
|
| pivot = pivot.loc[available_methods, common_tasks] |
|
|
| if pivot.empty: |
| raise ValueError("No valid method-task matrix found for radar plotting.") |
|
|
|
|
| |
| |
| |
| plt.rcParams.update( |
| { |
| "font.family": "DejaVu Sans", |
| "font.size": 10, |
| "axes.titlesize": 14, |
| "legend.fontsize": 9, |
| "pdf.fonttype": 42, |
| "ps.fonttype": 42, |
| } |
| ) |
|
|
| tasks = list(pivot.columns) |
| task_labels = [wrap_task_label(t) for t in tasks] |
| n_tasks = len(tasks) |
|
|
| angles = np.linspace(0, 2 * np.pi, n_tasks, endpoint=False) |
| angles_closed = np.concatenate([angles, [angles[0]]]) |
|
|
| fig, ax = plt.subplots( |
| figsize=(7.6, 6.6), |
| subplot_kw={"projection": "polar"}, |
| ) |
|
|
| ax.set_theta_offset(np.pi / 2) |
| ax.set_theta_direction(-1) |
|
|
| values_log = np.log10(pivot.values.astype(float)) |
|
|
| |
| r_min = 5.0 |
| r_max = max(7.2, float(np.nanmax(values_log)) + 0.15) |
| ax.set_ylim(r_min, r_max) |
|
|
| |
| ax.set_xticks(angles) |
| ax.set_xticklabels(task_labels, fontsize=9.5) |
|
|
| |
| radial_ticks = [5, 6, 7] |
| radial_ticks = [t for t in radial_ticks if r_min <= t <= r_max] |
| ax.set_yticks(radial_ticks) |
| ax.set_yticklabels([rf"$10^{t}$" for t in radial_ticks], fontsize=9, color="#4B5563") |
| ax.set_rlabel_position(90) |
|
|
| |
| ax.grid(True, color="#CBD5E1", linewidth=0.75, alpha=0.75) |
| ax.spines["polar"].set_color("#94A3B8") |
| ax.spines["polar"].set_linewidth(0.8) |
|
|
| |
| for method in available_methods: |
| values = np.log10(pivot.loc[method, tasks].values.astype(float)) |
| values_closed = np.concatenate([values, [values[0]]]) |
| style = METHOD_STYLE[method] |
|
|
| ax.plot( |
| angles_closed, |
| values_closed, |
| color=style["color"], |
| linewidth=style["linewidth"], |
| linestyle=style["linestyle"], |
| label=method, |
| zorder=4 if method == "BioManus" else 3, |
| ) |
|
|
| if style["fill_alpha"] > 0: |
| ax.fill( |
| angles_closed, |
| values_closed, |
| color=style["color"], |
| alpha=style["fill_alpha"], |
| zorder=2, |
| ) |
|
|
| |
| ax.set_title("BioAgentBench Context Consumption", pad=22, fontweight="bold") |
|
|
| |
| ax.legend( |
| loc="upper left", |
| bbox_to_anchor=(1.05, 1.03), |
| frameon=False, |
| handlelength=2.4, |
| borderaxespad=0.0, |
| ) |
|
|
| fig.subplots_adjust(left=0.05, right=0.78, top=0.90, bottom=0.06) |
|
|
| |
| fig.savefig(f"{OUT_PREFIX}.pdf", bbox_inches="tight") |
| fig.savefig(f"{OUT_PREFIX}.png", dpi=400, bbox_inches="tight") |
| fig.savefig(f"{OUT_PREFIX}.svg", bbox_inches="tight") |
|
|
| print("Saved:") |
| print(f" {OUT_PREFIX}.pdf") |
| print(f" {OUT_PREFIX}.png") |
| print(f" {OUT_PREFIX}.svg") |
|
|
| print("\nGeometric mean prompt tokens:") |
| for method in available_methods: |
| gm = geometric_mean(pivot.loc[method, tasks].values.astype(float)) |
| print(f"{method:12s}: {format_token(gm)}") |