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
8 changes: 8 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,11 @@ otel:
server:
host: 0.0.0.0
port: 8000

# ---------------------------------------------------------------------------
# AWS settings
# ---------------------------------------------------------------------------
# Override: AWS_BEDROCK_REGION

settings:
aws_bedrock_region: us-east-1
7 changes: 7 additions & 0 deletions core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,13 @@ def get_redis_url() -> str:
return _require_env(url_env)


def get_bedrock_region() -> str:
"""Return the AWS region for Bedrock calls. Reads AWS_BEDROCK_REGION env var, falls back to config.yaml settings.aws_bedrock_region, then us-east-1."""
raw = _load_yaml()
yaml_region = raw.get("settings", {}).get("aws_bedrock_region", "us-east-1")
return os.environ.get("AWS_BEDROCK_REGION", yaml_region)


def get_redis_mode() -> str:
"""
Return the Redis messaging mode: "streams" or "pubsub".
Expand Down
50 changes: 49 additions & 1 deletion core/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,52 @@ def _get_run_tree(): # type: ignore[misc]
# Fallback model used when Anthropic returns 529 Overloaded for the primary model.
_HAIKU_FALLBACK = "claude-haiku-4-5-20251001"

# Bedrock model ID mapping from Helix internal names to Bedrock ARN base IDs.
_BEDROCK_MODEL_IDS = {
"claude-haiku-4-5": "anthropic.claude-haiku-4-5-20251001-v1:0",
"claude-sonnet-4-6": "anthropic.claude-sonnet-4-6-20250514-v1:0",
"claude-opus-4-6": "anthropic.claude-opus-4-6-20250514-v1:0",
}
# Cross-region inference profile prefix by AWS region prefix.
_BEDROCK_REGION_PREFIXES = {"us": "us.", "eu": "eu.", "ap": "ap."}


# ---------------------------------------------------------------------------
# Bedrock backend
# ---------------------------------------------------------------------------

def _bedrock_model_id(model: str, region: str) -> str:
base = _BEDROCK_MODEL_IDS.get(model, model)
prefix = _BEDROCK_REGION_PREFIXES.get(region.split("-")[0], "")
return prefix + base


def _get_bedrock_client(region: str):
import anthropic
return anthropic.AnthropicBedrock(aws_region=region)


async def _complete_bedrock(
config: AgentConfig, prompt: str, system: str
) -> tuple[str, dict]:
from core.config import get_bedrock_region
region = get_bedrock_region()
bedrock_model = _bedrock_model_id(config.model, region)
client = _get_bedrock_client(region)
kwargs: dict = {
"model": bedrock_model,
"max_tokens": _MAX_TOKENS,
"messages": [{"role": "user", "content": prompt}],
}
if system:
kwargs["system"] = system
response = await asyncio.to_thread(client.messages.create, **kwargs)
usage = {
"input_tokens": response.usage.input_tokens,
"output_tokens": response.usage.output_tokens,
}
return response.content[0].text, usage


# ---------------------------------------------------------------------------
# Anthropic backend
Expand Down Expand Up @@ -520,6 +566,8 @@ async def complete(

if resolved.provider == "anthropic":
response, usage = await _complete_anthropic(resolved, prompt, system)
elif resolved.provider == "bedrock":
response, usage = await _complete_bedrock(resolved, prompt, system)
elif resolved.provider == "openrouter":
response, usage = await _complete_openrouter(resolved, prompt, system)
elif resolved.provider == "ollama":
Expand All @@ -533,7 +581,7 @@ async def complete(
else:
raise ValueError(
f"Unknown provider '{resolved.provider}' for agent '{agent}'. "
"Must be 'anthropic', 'openrouter', 'ollama', 'claude-code', 'opencode', or 'goose'."
"Must be 'anthropic', 'bedrock', 'openrouter', 'ollama', 'claude-code', 'opencode', or 'goose'."
)

span.set_attribute("helix.input_tokens", usage.get("input_tokens", 0))
Expand Down
22 changes: 19 additions & 3 deletions core/preflight.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,27 @@
logger = logging.getLogger(__name__)


def _agent_in_config(agent: str) -> bool:
from core.config import get_agent_config
try:
get_agent_config(agent)
return True
except KeyError:
return False


def check_required_env():
has_llm = os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("OPENROUTER_API_KEY")
if not has_llm:
from core.config import get_agent_config
has_anthropic = bool(os.environ.get("ANTHROPIC_API_KEY"))
has_openrouter = bool(os.environ.get("OPENROUTER_API_KEY"))
has_bedrock = any(
get_agent_config(a).provider == "bedrock"
for a in ("crash_handler", "qa", "dev")
if _agent_in_config(a)
)
if not (has_anthropic or has_openrouter or has_bedrock):
raise RuntimeError(
"No LLM provider key set. Set ANTHROPIC_API_KEY or OPENROUTER_API_KEY in your .env file."
"No LLM provider key set. Set ANTHROPIC_API_KEY or OPENROUTER_API_KEY, or configure provider: bedrock in config.yaml."
)
for var in ("SENTRY_WEBHOOK_SECRET", "ROLLBAR_ACCESS_TOKEN", "GITHUB_TOKEN"):
if not os.environ.get(var):
Expand Down
Loading
Loading