Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
78 changes: 71 additions & 7 deletions src/agentops/cli/config_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import typer

from agentops.cli._planned import _planned_command
from agentops.utils.logging import get_logger

log = get_logger(__name__)
Expand All @@ -15,15 +14,80 @@


@config_app.command("validate")
def cmd_config_validate() -> None:
"""Validate configuration files (planned)."""
_planned_command("agentops config validate")
def cmd_config_validate(
config: Path | None = typer.Option(
None,
"--config",
"-c",
help="Path to run.yaml (default: .agentops/run.yaml).",
),
directory: Path = typer.Option(
Path("."),
"--dir",
help="Workspace directory.",
),
) -> None:
"""Validate configuration files (run.yaml, bundle, dataset, data)."""
from agentops.services.validate import validate_config

try:
result = validate_config(config_path=config, directory=directory)
except FileNotFoundError as exc:
typer.echo(f"Error: {exc}", err=True)
raise typer.Exit(code=1) from exc

typer.echo(f"Files checked: {result.files_checked}")
for issue in result.issues:
marker = "ERROR" if issue.severity == "error" else "WARN"
typer.echo(f" [{marker}] {issue.file.name}: {issue.message}")

if result.passed:
typer.echo("Validation: PASSED")
else:
typer.echo("Validation: FAILED")
raise typer.Exit(code=1)


@config_app.command("show")
def cmd_config_show() -> None:
"""Show merged runtime config (planned)."""
_planned_command("agentops config show")
def cmd_config_show(
config: Path | None = typer.Option(
None,
"--config",
"-c",
help="Path to run.yaml (default: .agentops/run.yaml).",
),
directory: Path = typer.Option(
Path("."),
"--dir",
help="Workspace directory.",
),
) -> None:
"""Show resolved configuration from a run.yaml file."""
from agentops.services.validate import show_config

try:
result = show_config(config_path=config, directory=directory)
except (FileNotFoundError, ValueError) as exc:
typer.echo(f"Error: {exc}", err=True)
raise typer.Exit(code=1) from exc

typer.echo(f"Run config: {result.run_config_path}")
typer.echo(f"Bundle: {result.bundle_name} ({result.bundle_path})")
typer.echo(f"Dataset: {result.dataset_name} ({result.dataset_path})")
if result.data_path:
rows = f" ({result.data_rows} rows)" if result.data_rows is not None else ""
typer.echo(f"Data: {result.data_path}{rows}")
typer.echo(f"Backend: {result.backend_type} (target={result.target})")
if result.model:
typer.echo(f"Model: {result.model}")
if result.agent_id:
typer.echo(f"Agent: {result.agent_id}")
typer.echo(f"Thresholds: {result.thresholds}")
typer.echo("")
typer.echo("Evaluators:")
for e in result.evaluators:
status = "enabled" if e["enabled"] else "disabled"
typer.echo(f" {e['name']} (source={e['source']}, {status})")


@config_app.command("cicd")
Expand Down
58 changes: 52 additions & 6 deletions src/agentops/cli/dataset_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

from pathlib import Path

import typer

from agentops.cli._planned import _planned_command
Expand All @@ -10,15 +12,59 @@


@dataset_app.command("validate")
def cmd_dataset_validate() -> None:
"""Validate dataset files (planned)."""
_planned_command("agentops dataset validate")
def cmd_dataset_validate(
dataset: Path = typer.Argument(help="Path to dataset YAML config file."),
) -> None:
"""Validate a dataset config and its JSONL data file."""
from agentops.services.validate import validate_dataset

try:
result = validate_dataset(dataset_path=dataset)
except (FileNotFoundError, ValueError) as exc:
typer.echo(f"Error: {exc}", err=True)
raise typer.Exit(code=1) from exc

typer.echo(f"Files checked: {result.files_checked}")
for issue in result.issues:
marker = "ERROR" if issue.severity == "error" else "WARN"
typer.echo(f" [{marker}] {issue.file.name}: {issue.message}")

if result.passed:
typer.echo("Validation: PASSED")
else:
typer.echo("Validation: FAILED")
raise typer.Exit(code=1)


@dataset_app.command("describe")
def cmd_dataset_describe() -> None:
"""Describe dataset schema and shape (planned)."""
_planned_command("agentops dataset describe")
def cmd_dataset_describe(
dataset: Path = typer.Argument(help="Path to dataset YAML config file."),
) -> None:
"""Describe dataset schema, fields, and row count."""
from agentops.services.validate import describe_dataset

try:
desc = describe_dataset(dataset_path=dataset)
except (FileNotFoundError, ValueError) as exc:
typer.echo(f"Error: {exc}", err=True)
raise typer.Exit(code=1) from exc

typer.echo(f"Dataset: {desc.name}")
if desc.description:
typer.echo(f"Description: {desc.description}")
typer.echo(f"Source: {desc.source_type}")
typer.echo(f"Format: {desc.format_type}")
typer.echo(f"Input field: {desc.input_field}")
typer.echo(f"Expected field: {desc.expected_field}")
if desc.context_field:
typer.echo(f"Context field: {desc.context_field}")
if desc.data_path:
typer.echo(f"Data file: {desc.data_path}")
typer.echo(f"Rows: {desc.row_count}")
if desc.fields:
typer.echo(f"Fields: {', '.join(desc.fields)}")
if desc.metadata:
typer.echo(f"Metadata: {desc.metadata}")


@dataset_app.command("import")
Expand Down
21 changes: 21 additions & 0 deletions src/agentops/services/_workspace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Shared workspace resolution for CLI services."""

from __future__ import annotations

from pathlib import Path

_DEFAULT_AGENTOPS_DIR = ".agentops"


def resolve_workspace(directory: Path) -> Path:
"""Resolve the .agentops workspace directory.

Raises:
FileNotFoundError: If no .agentops directory exists.
"""
workspace = (directory / _DEFAULT_AGENTOPS_DIR).resolve()
if not workspace.is_dir():
raise FileNotFoundError(
f"No .agentops workspace found at {workspace}. Run 'agentops init' first."
)
return workspace
26 changes: 5 additions & 21 deletions src/agentops/services/browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,7 @@

from agentops.core.config_loader import load_bundle_config
from agentops.core.models import RunResult


# ---------------------------------------------------------------------------
# Workspace resolution
# ---------------------------------------------------------------------------

_DEFAULT_AGENTOPS_DIR = ".agentops"


def _resolve_workspace(directory: Path) -> Path:
"""Resolve the .agentops workspace directory."""
workspace = (directory / _DEFAULT_AGENTOPS_DIR).resolve()
if not workspace.is_dir():
raise FileNotFoundError(
f"No .agentops workspace found at {workspace}. Run 'agentops init' first."
)
return workspace
from agentops.services._workspace import resolve_workspace


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -54,7 +38,7 @@ class BundleListResult:

def list_bundles(directory: Path = Path(".")) -> BundleListResult:
"""List all bundle YAML files in the workspace."""
workspace = _resolve_workspace(directory)
workspace = resolve_workspace(directory)
bundles_dir = workspace / "bundles"

if not bundles_dir.is_dir():
Expand Down Expand Up @@ -103,7 +87,7 @@ class BundleDetail:

def show_bundle(bundle_name: str, directory: Path = Path(".")) -> BundleDetail:
"""Load and return full details of a bundle by name."""
workspace = _resolve_workspace(directory)
workspace = resolve_workspace(directory)
bundles_dir = workspace / "bundles"

# Try exact filename first, then search by bundle name
Expand Down Expand Up @@ -190,7 +174,7 @@ class RunListResult:

def list_runs(directory: Path = Path(".")) -> RunListResult:
"""List all past evaluation runs in the workspace."""
workspace = _resolve_workspace(directory)
workspace = resolve_workspace(directory)
results_dir = workspace / "results"

if not results_dir.is_dir():
Expand Down Expand Up @@ -266,7 +250,7 @@ class RunDetail:

def show_run(run_id: str, directory: Path = Path(".")) -> RunDetail:
"""Load and return full details of a past run."""
workspace = _resolve_workspace(directory)
workspace = resolve_workspace(directory)
results_dir = workspace / "results"

run_dir = (results_dir / run_id).resolve()
Expand Down
Loading