fix(providers): report why a lazy SDK install did not land

_get_anthropic_sdk() swallowed every ensure_import("anthropic") failure and
_require_sdk() then told the user to "Install it with: hermes pm install
--extra anthropic". PM often HAS installed it: sync_venv succeeds into a new
dependency environment that only activates at process boot, and
ensure_import raises "installed; restart Hermes". A lazy-install guard
("this process is not running from the install's dependency environment")
was flattened the same way. Users were told to install something that was
installed, or given a command that doesn't address the actual refusal.

Keep the import as the decider, but remember the InstallError and put its
text in the ImportError. bedrock_adapter._require_boto3 had the identical
shape; azure_identity_adapter already propagates str(exc) and is the model.
This commit is contained in:
ethernet
2026-09-24 23:37:27 -04:00
parent 61c06ca3ce
commit 39c0a39100
4 changed files with 55 additions and 11 deletions

View File

@@ -34,20 +34,20 @@ from hermes_cli.version_info import get_version_info
# ``import anthropic`` is deliberately NOT at module top: the SDK costs ~220 ms of imports and
# every usage site is a cold user-triggered path. ``...`` = not yet tried; None = tried, missing.
_anthropic_sdk: Any = ...
# Why the lazy install did not make the SDK importable. A completed install that needs a restart
# (PM activates a new dependency environment only at boot) must not be reported as "install it".
_anthropic_install_error: Optional[Exception] = None
def _get_anthropic_sdk():
"""Return the ``anthropic`` SDK module, importing lazily. None if not installed."""
global _anthropic_sdk
global _anthropic_sdk, _anthropic_install_error
if _anthropic_sdk is ...:
try:
from pm import ensure_import
ensure_import("anthropic")
except ImportError:
pass
except Exception:
# InstallError — fall through to ImportError handling below
pass
except Exception as exc: # the import below decides; exc explains a miss
_anthropic_install_error = exc
try:
import anthropic as _sdk
_anthropic_sdk = _sdk
@@ -59,6 +59,9 @@ def _get_anthropic_sdk():
def _require_sdk(purpose: str, verb: str = "Install it with"):
"""``_get_anthropic_sdk()`` or ImportError naming the feature that needs it."""
sdk = _get_anthropic_sdk()
if sdk is None and _anthropic_install_error is not None:
raise ImportError(f"The 'anthropic' package is required for {purpose}: "
f"{_anthropic_install_error}") from _anthropic_install_error
if sdk is None:
raise ImportError(f"The 'anthropic' package is required for {purpose}. {verb}: "
f"{install_hint('anthropic')}")

View File

@@ -87,6 +87,7 @@ _MIN_BOTO3_VERSION = (1, 34, 59)
def _require_boto3():
"""Import boto3; converse_stream() needs >= 1.34.59 (a system boto3 can shadow the venv pin)."""
install_error = None
try:
# boto3 left [all] (PRs #24220, #24515); PM installs the [bedrock] extra on first use. This
# runs at the first client build, never at import: an import-time sync would rebuild the
@@ -94,14 +95,14 @@ def _require_boto3():
try:
from pm import ensure_import
ensure_import("bedrock")
except Exception as exc: # the import below reports the real failure
except Exception as exc: # the import below decides; exc explains a miss
logger.warning("boto3 lazy install did not complete: %s", exc)
install_error = exc
import boto3
except ImportError:
raise ImportError(
"The 'boto3' package is required for the AWS Bedrock provider. "
f"Run: {install_hint('bedrock')}"
)
# A completed install that needs a restart must not be reported as "install it".
reason = f": {install_error}" if install_error else f". Run: {install_hint('bedrock')}"
raise ImportError(f"The 'boto3' package is required for the AWS Bedrock provider{reason}") from install_error
try:
version = tuple(int(x) for x in boto3.__version__.split(".")[:3])
except (AttributeError, ValueError):