Files
hermes-agent/agent/transports/bedrock.py
T

103 lines
4.0 KiB
Python

"""AWS Bedrock Converse API transport.
Delegates format conversion to agent/bedrock_adapter.py. Bedrock uses its own
boto3 client, so client construction and calls stay on AIAgent.
"""
from typing import Any, Dict, List, Optional
from agent.transports.base import ProviderTransport
from agent.transports.types import NormalizedResponse, ToolCall, Usage
class BedrockTransport(ProviderTransport):
"""Transport for api_mode='bedrock_converse'."""
# The adapter already maps inside normalize_converse_response; this serves raw-response access.
_STOP_REASON_MAP = {
"end_turn": "stop",
"tool_use": "tool_calls",
"max_tokens": "length",
"stop_sequence": "stop",
"guardrail_intervened": "content_filter",
"content_filtered": "content_filter",
}
@property
def api_mode(self) -> str:
return "bedrock_converse"
def convert_messages(self, messages: List[Dict[str, Any]], **kwargs) -> Any:
"""Convert OpenAI messages to Bedrock Converse format."""
from agent.bedrock_adapter import convert_messages_to_converse
return convert_messages_to_converse(messages)
def convert_tools(self, tools: List[Dict[str, Any]]) -> Any:
"""Convert OpenAI tool schemas to Bedrock Converse toolConfig."""
from agent.bedrock_adapter import convert_tools_to_converse
return convert_tools_to_converse(tools)
def build_kwargs(
self,
model: str,
messages: List[Dict[str, Any]],
tools: Optional[List[Dict[str, Any]]] = None,
**params,
) -> Dict[str, Any]:
"""Build converse() kwargs; params: max_tokens (4096), temperature, guardrail_config, region ('us-east-1')."""
from agent.bedrock_adapter import build_converse_kwargs
kwargs = build_converse_kwargs(
model=model,
messages=messages,
tools=tools,
max_tokens=params.get("max_tokens", 4096),
temperature=params.get("temperature"),
guardrail_config=params.get("guardrail_config"),
)
# Sentinel keys for dispatch — agent pops these before the boto3 call
kwargs["__bedrock_converse__"] = True
kwargs["__bedrock_region__"] = params.get("region", "us-east-1")
return kwargs
def normalize_response(self, response: Any, **kwargs) -> NormalizedResponse:
"""Normalize either a raw boto3 dict or an already-normalized SimpleNamespace with .choices."""
from agent.bedrock_adapter import normalize_converse_response
ns = response if hasattr(response, "choices") and response.choices else normalize_converse_response(response)
choice = ns.choices[0]
msg = choice.message
tool_calls = None
if msg.tool_calls:
tool_calls = [ToolCall(id=tc.id, name=tc.function.name, arguments=tc.function.arguments) for tc in msg.tool_calls]
usage = None
if hasattr(ns, "usage") and ns.usage:
usage = Usage.from_openai(ns.usage)
provider_data = {}
if getattr(msg, "reasoning_details", None):
provider_data["reasoning_details"] = msg.reasoning_details
if getattr(msg, "bedrock_content_blocks", None):
provider_data["bedrock_content_blocks"] = msg.bedrock_content_blocks
return NormalizedResponse(
content=msg.content,
tool_calls=tool_calls,
finish_reason=choice.finish_reason or "stop",
reasoning=getattr(msg, "reasoning", None) or getattr(msg, "reasoning_content", None),
usage=usage,
provider_data=provider_data or None,
)
def validate_response(self, response: Any) -> bool:
"""Raw Bedrock dict needs an 'output' key; a normalized namespace needs non-empty .choices."""
if isinstance(response, dict):
return "output" in response
return bool(getattr(response, "choices", None)) if response is not None else False
from agent.transports import register_transport # noqa: E402
register_transport("bedrock_converse", BedrockTransport)