fix(bedrock): check boto3 version >= 1.34.59 before using converse_stream

converse() and converse_stream() were added in boto3 1.34.59. When Hermes
is installed editable into system Python (e.g. Ubuntu 24.04 ships 1.34.46),
the system boto3 takes precedence and calls to converse_stream fail with
AttributeError. Add an early version check in _require_boto3() that raises
a clear RuntimeError with upgrade instructions.
This commit is contained in:
liuhao1024 2026-06-15 19:08:44 +08:00 committed by Teknium
parent f79b109f4f
commit 2cddc9c895
2 changed files with 68 additions and 2 deletions

View file

@ -58,17 +58,34 @@ _bedrock_runtime_client_cache: Dict[str, Any] = {}
_bedrock_control_client_cache: Dict[str, Any] = {}
_MIN_BOTO3_VERSION = (1, 34, 59)
def _require_boto3():
"""Import boto3, raising a clear error if not installed."""
"""Import boto3, raising a clear error if not installed or too old."""
try:
import boto3
return boto3
except ImportError:
raise ImportError(
"The 'boto3' package is required for the AWS Bedrock provider. "
"Install it with: pip install boto3\n"
"Or install Hermes with Bedrock support: pip install -e '.[bedrock]'"
)
# converse() / converse_stream() were added in boto3 1.34.59.
# When Hermes is installed editable into system Python, the system boto3
# (e.g. Ubuntu 24.04 ships 1.34.46) may take precedence over the venv
# version pinned in pyproject.toml.
try:
version = tuple(int(x) for x in boto3.__version__.split(".")[:3])
except (AttributeError, ValueError):
return boto3 # can't parse — don't block on version check
if version < _MIN_BOTO3_VERSION:
raise RuntimeError(
f"boto3 {boto3.__version__} does not support converse_stream "
f"(minimum 1.34.59 required). Upgrade with: "
f"pip install --upgrade boto3"
)
return boto3
def _get_bedrock_runtime_client(region: str):

View file

@ -1663,3 +1663,52 @@ class TestCallConverseStreamIamFallback:
assert result.choices[0].message.content == "hi"
# Not a stale connection — client stays cached.
assert _bedrock_runtime_client_cache.get("us-east-1") is client
# ---------------------------------------------------------------------------
# boto3 version check
# ---------------------------------------------------------------------------
class TestRequireBoto3VersionCheck:
"""Test that _require_boto3() rejects boto3 versions older than 1.34.59."""
def test_raises_runtime_error_when_boto3_too_old(self):
"""boto3 < 1.34.59 should raise RuntimeError with upgrade instructions."""
from agent.bedrock_adapter import _require_boto3
fake_boto3 = MagicMock()
fake_boto3.__version__ = "1.34.46"
with patch.dict("sys.modules", {"boto3": fake_boto3}):
with pytest.raises(RuntimeError, match="does not support converse_stream"):
_require_boto3()
def test_accepts_boto3_at_minimum_version(self):
"""boto3 == 1.34.59 should be accepted."""
from agent.bedrock_adapter import _require_boto3
fake_boto3 = MagicMock()
fake_boto3.__version__ = "1.34.59"
with patch.dict("sys.modules", {"boto3": fake_boto3}):
result = _require_boto3()
assert result is fake_boto3
def test_accepts_newer_boto3(self):
"""boto3 > 1.34.59 should be accepted."""
from agent.bedrock_adapter import _require_boto3
fake_boto3 = MagicMock()
fake_boto3.__version__ = "1.42.89"
with patch.dict("sys.modules", {"boto3": fake_boto3}):
result = _require_boto3()
assert result is fake_boto3
def test_accepts_boto3_with_unparseable_version(self):
"""If version string can't be parsed, don't block on version check."""
from agent.bedrock_adapter import _require_boto3
fake_boto3 = MagicMock()
fake_boto3.__version__ = "dev"
with patch.dict("sys.modules", {"boto3": fake_boto3}):
result = _require_boto3()
assert result is fake_boto3