From 39bd0fb4b25555be348d30d29f67285595857780 Mon Sep 17 00:00:00 2001 From: Dongbumlee Date: Mon, 8 Jun 2026 13:59:36 -0700 Subject: [PATCH 1/2] fix: declare click as an explicit runtime dependency The CLI imports click directly for its pager and manual output paths (agentops explain, init explain, doctor explain). click was previously only available transitively via Typer, but Typer 0.26+ no longer depends on click, so a clean 'pip install agentops-accelerator' crashed every explain command with ModuleNotFoundError: No module named 'click'. Add click>=8.1,<9 to project.dependencies so the manual/pager paths work on a fresh install. Verified by building the wheel and installing it into a clean venv: click is pulled automatically and 'agentops explain' and 'agentops doctor explain' exit 0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 11 +++++++++++ pyproject.toml | 1 + 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38d0b81d..2e054d0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. This format follows [Keep a Changelog](https://keepachangelog.com/) and adheres to [Semantic Versioning](https://semver.org/). +## [Unreleased] + +### Fixed +- **`agentops explain` and every `explain` manual command no longer crash on a + clean install.** The CLI imports `click` directly for its pager and manual + output paths, but `click` was not a declared dependency. Newer Typer releases + (0.26+) no longer pull `click` transitively, so `pip install + agentops-accelerator` produced a `ModuleNotFoundError: No module named 'click'` + for `agentops explain`, `agentops init explain`, and `agentops doctor explain`. + `click>=8.1,<9` is now an explicit runtime dependency. + ## [0.3.11] - 2026-06-08 ### Fixed diff --git a/pyproject.toml b/pyproject.toml index 6974c5a3..3f616cb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ readme = "README.md" requires-python = ">=3.11" dependencies = [ "typer>=0.12,<1.0", + "click>=8.1,<9", "pydantic>=2,<3", "ruamel.yaml>=0.18,<1.0", "azure-ai-projects>=2.0.1,<3.0", From b8c447257fb5ec156f5effd4842285f2b4d949c4 Mon Sep 17 00:00:00 2001 From: Dongbumlee Date: Mon, 8 Jun 2026 17:34:12 -0700 Subject: [PATCH 2/2] fix: guard against None env_path in azd_eval_init to satisfy mypy set_env_values expects a Path, but location.env_path is Path | None. Return early when env_path is None, fixing the lint (mypy) gate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/agentops/services/azd_eval_init.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/agentops/services/azd_eval_init.py b/src/agentops/services/azd_eval_init.py index aae84d12..6caa267f 100644 --- a/src/agentops/services/azd_eval_init.py +++ b/src/agentops/services/azd_eval_init.py @@ -119,9 +119,7 @@ def run_azd_eval_init( effective_dataset, workspace=root, ) - command.extend( - ["--dataset", _command_path(effective_dataset, workspace=root)] - ) + command.extend(["--dataset", _command_path(effective_dataset, workspace=root)]) for evaluator in _azd_evaluators_from_config(resolved_config): command.extend(["--evaluator", evaluator]) @@ -147,7 +145,11 @@ def run_azd_eval_init( ) from exc if completed.returncode != 0: - detail = completed.stderr.strip() or completed.stdout.strip() or f"exit code {completed.returncode}" + detail = ( + completed.stderr.strip() + or completed.stdout.strip() + or f"exit code {completed.returncode}" + ) raise AzdBackendError(f"azd ai agent eval init failed: {detail}") recipe = find_eval_yaml(root) @@ -337,6 +339,8 @@ def _ensure_azd_project_env_metadata( if not location.found or location.env_path is None: location = ensure_azd_env(workspace, "sandbox") env_path = location.env_path + if env_path is None: + return updates = { "AZURE_AI_FOUNDRY_PROJECT_ENDPOINT": endpoint, "FOUNDRY_PROJECT_ENDPOINT": endpoint, @@ -475,7 +479,11 @@ def _azd_evaluators_from_config(config_path: Path) -> tuple[str, ...]: if not isinstance(raw_name, str) or not raw_name.strip(): continue name = raw_name.strip() - mapped = name if name.startswith("builtin.") else _EVALUATOR_NAME_TO_AZD.get(name) + mapped = ( + name + if name.startswith("builtin.") + else _EVALUATOR_NAME_TO_AZD.get(name) + ) if mapped and mapped not in names: names.append(mapped) return tuple(names) if names else _DEFAULT_AZD_EVALUATORS @@ -518,7 +526,9 @@ def _persist_recipe( data["eval_recipe"] = recipe_value if previous_execution in (None, "", "local", "auto"): data["execution"] = "azd" - config_updated = previous_recipe != recipe_value or data.get("execution") != previous_execution + config_updated = ( + previous_recipe != recipe_value or data.get("execution") != previous_execution + ) if config_updated: save_yaml(config_path, data) return AzdEvalInitResult(