Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions openagent_eval/cli/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""CLI commands for OpenAgent Eval."""

from openagent_eval.cli.commands.init import init_command
from openagent_eval.cli.commands.run import run_command
from openagent_eval.cli.commands.report import report_command
from openagent_eval.cli.commands.compare import compare_command
from openagent_eval.cli.commands.list_evaluations import list_command
from openagent_eval.cli.commands.doctor import doctor_command
from openagent_eval.cli.commands.init import init_command
from openagent_eval.cli.commands.list_evaluations import list_command
from openagent_eval.cli.commands.report import report_command
from openagent_eval.cli.commands.run import run_command

__all__ = [
"init_command",
Expand Down
64 changes: 58 additions & 6 deletions openagent_eval/cli/commands/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
from pathlib import Path

import typer
from rich.console import Console
from rich.console import Console # noqa: B008

from openagent_eval.cli.context import get_context
from openagent_eval.cli.utils.constants import DEFAULT_OUTPUT_DIR
from openagent_eval.cli.utils.helpers import resolve_report_id
from openagent_eval.exceptions.cli import CommandError
from openagent_eval.reports.comparison import ComparisonReport
from openagent_eval.reports.base import ExperimentComparison
from openagent_eval.reports.comparison import ComparisonReport
from openagent_eval.reports.manager import ReportManager
from openagent_eval.cli.utils.constants import DEFAULT_OUTPUT_DIR
from openagent_eval.cli.utils.helpers import resolve_report_id

console = Console()

Expand All @@ -24,20 +25,22 @@ def compare_command(
experiment_b: str = typer.Argument(
help="Second experiment ID or path.",
),
metrics: list[str] = typer.Option(
metrics: list[str] | None = typer.Option( # noqa: B008
None,
"--metrics",
"-m",
help="Specific metrics to compare (default: all).",
),
output_dir: str = typer.Option(
output_dir: str | None = typer.Option( # noqa: B008
None,
"--output-dir",
"-d",
help="Directory where reports are stored (default: ./reports).",
),
) -> None:
"""Compare two evaluation experiments side by side."""
ctx = get_context()

console.print("[bold blue]OpenAgent Eval[/bold blue] - Experiment Comparison")
console.print(f"[dim]Comparing: {experiment_a} vs {experiment_b}[/dim]\n")

Expand Down Expand Up @@ -72,4 +75,53 @@ def compare_command(
generator = ComparisonReport()
comparison_output = generator.generate(comparison)

# Handle JSON output
if ctx.json_output:
_output_json_comparison(data_a, data_b, experiment_a, experiment_b)
return

console.print(comparison_output)


def _output_json_comparison(
data_a: dict,
data_b: dict,
name_a: str,
name_b: str,
) -> None:
"""Output comparison as JSON.

Args:
data_a: First report data.
data_b: Second report data.
name_a: First experiment name.
name_b: Second experiment name.
"""
import json

output_data = {
"experiment_a": {
"name": name_a,
"report_id": data_a.get("report_id", "unknown"),
"created_at": data_a.get("created_at", "unknown"),
},
"experiment_b": {
"name": name_b,
"report_id": data_b.get("report_id", "unknown"),
"created_at": data_b.get("created_at", "unknown"),
},
}

# Add scores if available
if "scores" in data_a:
output_data["experiment_a"]["scores"] = data_a["scores"]
if "scores" in data_b:
output_data["experiment_b"]["scores"] = data_b["scores"]

# Add metrics if available
if "metrics" in data_a:
output_data["experiment_a"]["metrics"] = data_a["metrics"]
if "metrics" in data_b:
output_data["experiment_b"]["metrics"] = data_b["metrics"]

console.print(json.dumps(output_data, indent=2, default=str))
138 changes: 138 additions & 0 deletions openagent_eval/cli/commands/delete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
"""Delete command for OpenAgent Eval."""

from __future__ import annotations

from pathlib import Path

import typer
from rich.console import Console
from rich.prompt import Confirm

from openagent_eval.cli.utils.constants import DEFAULT_OUTPUT_DIR
from openagent_eval.reports.manager import ReportManager

console = Console()


def delete_command(
report_id: str = typer.Argument(
help="Report ID to delete, or 'all' to delete all reports.",
),
output_dir: str | None = typer.Option(
None,
"--output-dir",
"-d",
help="Directory where reports are stored (default: ./reports).",
),
force: bool = typer.Option(
False,
"--force",
"-f",
help="Skip confirmation prompt.",
),
) -> None:
"""Delete evaluation reports."""
console.print("[bold blue]OpenAgent Eval[/bold blue] - Delete Reports\n")

manager = ReportManager()
reports_dir = Path(output_dir) if output_dir else DEFAULT_OUTPUT_DIR

# Handle 'all' case
if report_id.lower() == "all":
_delete_all_reports(manager, reports_dir, force)
return

# Single report deletion
_delete_single_report(report_id, manager, reports_dir, force)


def _delete_single_report(
report_id: str,
manager: ReportManager,
reports_dir: Path,
force: bool,
) -> None:
"""Delete a single report.

Args:
report_id: Report ID to delete.
manager: Report manager instance.
reports_dir: Reports directory.
force: Skip confirmation.
"""
# Check if report exists
try:
data = manager.load_report(report_id, reports_dir)
except FileNotFoundError as exc:
console.print(f"[red]Error:[/red] Report not found: {report_id}")
raise typer.Exit(code=1) from exc

# Show report info
created_at = data.get("created_at", "unknown")
console.print(f"[bold]Report to delete:[/bold] {report_id}")
console.print(f"[dim]Created: {created_at}[/dim]\n")

# Confirm deletion
if not force and not Confirm.ask("Are you sure you want to delete this report?"):
console.print("[yellow]Aborted.[/yellow]")
return

# Delete the report
try:
report_path = reports_dir / f"{report_id}.json"
if report_path.exists():
report_path.unlink()
console.print(f"[green]OK[/green] Deleted report: {report_id}")
else:
console.print(f"[yellow]Warning:[/yellow] Report file not found: {report_path}")
except OSError as exc:
console.print(f"[red]Error:[/red] Failed to delete report: {exc}")
raise typer.Exit(code=1) from exc


def _delete_all_reports(
manager: ReportManager,
reports_dir: Path,
force: bool,
) -> None:
"""Delete all reports.

Args:
manager: Report manager instance.
reports_dir: Reports directory.
force: Skip confirmation.
"""
# List all reports
try:
reports = manager.list_reports(reports_dir)
except Exception as exc:
console.print(f"[red]Error:[/red] Failed to list reports: {exc}")
raise typer.Exit(code=1) from exc

if not reports:
console.print("[yellow]No reports found to delete.[/yellow]")
return

# Show count
console.print(f"[bold]Found {len(reports)} report(s)[/bold]\n")

# Confirm deletion
if not force and not Confirm.ask(
f"Are you sure you want to delete ALL {len(reports)} reports?"
):
console.print("[yellow]Aborted.[/yellow]")
return

# Delete each report
deleted = 0
for report in reports:
report_id = report.get("report_id", "unknown")
try:
report_path = reports_dir / f"{report_id}.json"
if report_path.exists():
report_path.unlink()
deleted += 1
except OSError:
console.print(f"[yellow]Warning:[/yellow] Failed to delete: {report_id}")

console.print(f"\n[green]OK[/green] Deleted {deleted} report(s)")
Loading
Loading