Skip to content

Commit 47da8db

Browse files
authored
Merge pull request #982 from jupyter-naas/claude/vibrant-wu-4f37ba
feat(marketplace): add Amazon Bedrock AI module
2 parents 517db71 + f9c0051 commit 47da8db

10 files changed

Lines changed: 501 additions & 0 deletions

File tree

.claude/settings.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"sandbox": {
3+
"enabled": true,
4+
"network": {
5+
"allowedDomains": ["fonts.googleapis.com", "fonts.gstatic.com"]
6+
}
7+
}
8+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Bedrock Module
2+
3+
## Overview
4+
5+
The Bedrock module provides integration with **Amazon Bedrock**, AWS's fully managed
6+
service for accessing leading foundation models through a single API. It lets ABI agents
7+
invoke models from Anthropic (Claude), Meta (Llama), Amazon (Nova/Titan), Mistral, AI21,
8+
Cohere, and others without managing infrastructure.
9+
10+
## Provider: Amazon Web Services (AWS)
11+
12+
**Service**: Amazon Bedrock
13+
**Launched**: 2023
14+
**Focus**: Managed, serverless inference for foundation models with enterprise security,
15+
data isolation, and IAM-based access control.
16+
17+
## Why Bedrock?
18+
19+
- **Unified API** across multiple model providers.
20+
- **No data retention** by AWS for inference requests.
21+
- **VPC / PrivateLink** support for enterprise networking.
22+
- **IAM-based access control** and CloudTrail audit logging.
23+
- **Regional availability** lets you keep data in a specific geography.
24+
25+
## Configuration
26+
27+
Add the module to your config file:
28+
29+
```yaml
30+
modules:
31+
- module: naas_abi_marketplace.ai.bedrock
32+
enabled: true
33+
config:
34+
aws_access_key_id: "{{ secret.AWS_ACCESS_KEY_ID }}"
35+
aws_secret_access_key: "{{ secret.AWS_SECRET_ACCESS_KEY }}"
36+
aws_session_token: "{{ secret.AWS_SESSION_TOKEN }}" # optional
37+
region_name: "us-east-1"
38+
validate_on_load: true # optional, default true
39+
validation_model_id: null # optional, e.g. "anthropic.claude-3-5-haiku-20241022-v1:0"
40+
```
41+
42+
If `aws_access_key_id` / `aws_secret_access_key` are omitted, the underlying boto3 client
43+
falls back to the standard AWS credential resolution chain (environment variables,
44+
shared credentials file, ECS/EKS container credentials, EC2 IMDS, etc.).
45+
46+
## Startup validation
47+
48+
When `validate_on_load` is `true` (the default), the module verifies on load that:
49+
50+
1. `boto3` can resolve AWS credentials from any source in the chain,
51+
2. `sts:GetCallerIdentity` succeeds (credentials are usable),
52+
3. `bedrock:ListFoundationModels` succeeds in `region_name` (Bedrock is reachable
53+
and the principal has Bedrock permissions),
54+
4. *(Optional)* if `validation_model_id` is set, a 1-token `Converse` call is made
55+
against that model to prove model-level access has been granted in the Bedrock
56+
console.
57+
58+
On failure, the module raises `BedrockValidationError` and the application will not
59+
start — surfacing IAM / IMDS / region issues at deploy time instead of mid-conversation.
60+
61+
To disable (e.g. for offline development), set `validate_on_load: false` in config,
62+
or export `BEDROCK_SKIP_VALIDATION=1` in the environment.
63+
64+
Validation is also auto-skipped when `PYTEST_CURRENT_TEST` is set, so unit tests that
65+
import the module do not require live AWS access.
66+
67+
## Installation
68+
69+
This module depends on `langchain-aws`. Install the optional extra:
70+
71+
```bash
72+
uv pip install "naas-abi-marketplace[ai-bedrock]"
73+
```
74+
75+
## Available Models
76+
77+
| File | Bedrock Model ID |
78+
| --- | --- |
79+
| `models/claude_sonnet_4_bedrock.py` | `anthropic.claude-sonnet-4-20250514-v1:0` |
80+
| `models/claude_haiku_3_5_bedrock.py` | `anthropic.claude-3-5-haiku-20241022-v1:0` |
81+
| `models/llama_3_3_70b_bedrock.py` | `meta.llama3-3-70b-instruct-v1:0` |
82+
| `models/nova_pro_bedrock.py` | `amazon.nova-pro-v1:0` |
83+
84+
Each model is exposed as a `naas_abi_core.models.Model.ChatModel` and wraps
85+
`langchain_aws.ChatBedrockConverse` so it can be dropped into any LangChain / LangGraph
86+
pipeline.
87+
88+
## Agent
89+
90+
`agents/BedrockAgent.py` exposes a `create_agent()` factory returning an `IntentAgent`
91+
that defaults to Claude Sonnet 4 on Bedrock. Swap the imported model to use another
92+
foundation model.
93+
94+
## Requirements
95+
96+
- AWS account with **Bedrock access enabled** in the chosen region.
97+
- Each foundation model must be **explicitly granted access** in the AWS console
98+
(Bedrock → Model access).
99+
- IAM principal with `bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream`
100+
permissions.
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import logging
2+
import os
3+
from typing import Optional
4+
5+
from naas_abi_core.module.Module import (
6+
BaseModule,
7+
ModuleConfiguration,
8+
ModuleDependencies,
9+
)
10+
from naas_abi_core.services.object_storage.ObjectStorageService import (
11+
ObjectStorageService,
12+
)
13+
from pydantic import model_validator
14+
15+
logger = logging.getLogger(__name__)
16+
17+
18+
class BedrockValidationError(RuntimeError):
19+
"""Raised when the Bedrock module cannot authenticate or reach AWS Bedrock."""
20+
21+
22+
class ABIModule(BaseModule):
23+
dependencies: ModuleDependencies = ModuleDependencies(
24+
modules=[],
25+
services=[ObjectStorageService],
26+
)
27+
28+
class Configuration(ModuleConfiguration):
29+
"""
30+
Configuration example:
31+
32+
module: naas_abi_marketplace.ai.bedrock
33+
enabled: true
34+
config:
35+
aws_access_key_id: "{{ secret.AWS_ACCESS_KEY_ID }}"
36+
aws_secret_access_key: "{{ secret.AWS_SECRET_ACCESS_KEY }}"
37+
aws_session_token: "{{ secret.AWS_SESSION_TOKEN }}"
38+
region_name: "us-east-1"
39+
validate_on_load: true
40+
validation_model_id: null # optional: invoke this model to prove access
41+
"""
42+
43+
aws_access_key_id: Optional[str] = None
44+
aws_secret_access_key: Optional[str] = None
45+
aws_session_token: Optional[str] = None
46+
region_name: str = "us-east-1"
47+
datastore_path: str = "bedrock"
48+
49+
# When true (default), the module verifies on load that:
50+
# 1. boto3 can resolve AWS credentials,
51+
# 2. sts:GetCallerIdentity succeeds,
52+
# 3. bedrock:ListFoundationModels succeeds in `region_name`.
53+
# If `validation_model_id` is also set, a 1-token Converse call is made
54+
# against that model to prove model-level access.
55+
# On failure, BedrockValidationError is raised — the module will not load.
56+
validate_on_load: bool = True
57+
validation_model_id: Optional[str] = None
58+
59+
@model_validator(mode="after")
60+
def _validate_bedrock_access(self) -> "ABIModule.Configuration":
61+
if not self.validate_on_load:
62+
return self
63+
64+
# Auto-skip under pytest so test collection doesn't require AWS access.
65+
# Users who want to validate during tests can unset PYTEST_CURRENT_TEST
66+
# or explicitly set validate_on_load=true in a dedicated integration test.
67+
if os.environ.get("PYTEST_CURRENT_TEST") or os.environ.get(
68+
"BEDROCK_SKIP_VALIDATION"
69+
):
70+
logger.debug(
71+
"Bedrock module validation skipped (pytest or "
72+
"BEDROCK_SKIP_VALIDATION env var detected)."
73+
)
74+
return self
75+
76+
try:
77+
import boto3
78+
from botocore.exceptions import (
79+
BotoCoreError,
80+
ClientError,
81+
NoCredentialsError,
82+
)
83+
except ImportError as exc:
84+
raise BedrockValidationError(
85+
"boto3 is required for the Bedrock module. Install with "
86+
"`uv pip install \"naas-abi-marketplace[ai-bedrock]\"`."
87+
) from exc
88+
89+
# Only pass credentials explicitly if provided; otherwise let boto3
90+
# walk its credential chain (env -> shared file -> container -> IMDS).
91+
session_kwargs: dict = {"region_name": self.region_name}
92+
if self.aws_access_key_id:
93+
session_kwargs["aws_access_key_id"] = self.aws_access_key_id
94+
if self.aws_secret_access_key:
95+
session_kwargs["aws_secret_access_key"] = self.aws_secret_access_key
96+
if self.aws_session_token:
97+
session_kwargs["aws_session_token"] = self.aws_session_token
98+
99+
try:
100+
session = boto3.Session(**session_kwargs)
101+
except (BotoCoreError, ValueError) as exc:
102+
raise BedrockValidationError(
103+
f"Failed to build boto3 Session: {exc}"
104+
) from exc
105+
106+
# Step 1: credentials must resolve.
107+
creds = session.get_credentials()
108+
if creds is None:
109+
raise BedrockValidationError(
110+
"No AWS credentials could be resolved. Checked: explicit "
111+
"config, env vars, shared credentials file, container "
112+
"credentials endpoint, and EC2 IMDS. Attach an IAM role to "
113+
"the instance/task/pod or provide credentials in config."
114+
)
115+
method = getattr(creds, "method", "unknown")
116+
logger.info("Bedrock module: resolved AWS credentials via %s", method)
117+
118+
# Step 2: prove the credentials work.
119+
try:
120+
identity = session.client("sts").get_caller_identity()
121+
logger.info(
122+
"Bedrock module: authenticated as %s (account %s)",
123+
identity.get("Arn"),
124+
identity.get("Account"),
125+
)
126+
except (ClientError, BotoCoreError, NoCredentialsError) as exc:
127+
raise BedrockValidationError(
128+
f"sts:GetCallerIdentity failed — credentials resolved via "
129+
f"{method} but are not usable: {exc}"
130+
) from exc
131+
132+
# Step 3: prove Bedrock is reachable in the configured region.
133+
try:
134+
bedrock = session.client("bedrock", region_name=self.region_name)
135+
bedrock.list_foundation_models()
136+
logger.info(
137+
"Bedrock module: bedrock:ListFoundationModels succeeded in %s",
138+
self.region_name,
139+
)
140+
except ClientError as exc:
141+
code = exc.response.get("Error", {}).get("Code", "")
142+
if code in ("AccessDeniedException", "UnauthorizedOperation"):
143+
raise BedrockValidationError(
144+
f"Bedrock is reachable in {self.region_name} but the "
145+
f"caller lacks bedrock:ListFoundationModels permission: "
146+
f"{exc}"
147+
) from exc
148+
raise BedrockValidationError(
149+
f"bedrock:ListFoundationModels failed in "
150+
f"{self.region_name}: {exc}"
151+
) from exc
152+
except BotoCoreError as exc:
153+
raise BedrockValidationError(
154+
f"Could not reach Bedrock endpoint in {self.region_name} "
155+
f"(network/VPC endpoint issue?): {exc}"
156+
) from exc
157+
158+
# Step 4 (optional): prove a specific model is invokable.
159+
if self.validation_model_id:
160+
try:
161+
runtime = session.client(
162+
"bedrock-runtime", region_name=self.region_name
163+
)
164+
runtime.converse(
165+
modelId=self.validation_model_id,
166+
messages=[
167+
{"role": "user", "content": [{"text": "ping"}]}
168+
],
169+
inferenceConfig={"maxTokens": 1},
170+
)
171+
logger.info(
172+
"Bedrock module: validated invocation of %s",
173+
self.validation_model_id,
174+
)
175+
except (ClientError, BotoCoreError) as exc:
176+
raise BedrockValidationError(
177+
f"Failed to invoke validation model "
178+
f"'{self.validation_model_id}' in {self.region_name}. "
179+
f"Has model access been granted in the Bedrock console? "
180+
f"Error: {exc}"
181+
) from exc
182+
183+
return self
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from typing import Optional
2+
from naas_abi_core.services.agent.IntentAgent import (
3+
AgentConfiguration,
4+
AgentSharedState,
5+
Intent,
6+
IntentAgent,
7+
IntentType,
8+
)
9+
10+
NAME = "Bedrock"
11+
DESCRIPTION = (
12+
"Amazon Bedrock agent providing managed access to leading foundation models "
13+
"(Anthropic Claude, Meta Llama, Amazon Nova, and more) through a unified AWS API."
14+
)
15+
AVATAR_URL = "https://naasai-public.s3.eu-west-3.amazonaws.com/abi/assets/bedrock.png"
16+
SYSTEM_PROMPT = """<role>
17+
You are Bedrock, an AI assistant powered by foundation models served through Amazon Bedrock.
18+
</role>
19+
20+
<objective>
21+
Help users with reasoning, analysis, summarization, content generation, and code generation
22+
using models hosted on AWS Bedrock.
23+
</objective>
24+
25+
<context>
26+
You are available to authenticated users with AWS credentials configured to access Amazon
27+
Bedrock in the chosen region. If you cannot access the API, instruct the user to verify
28+
their AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, optional AWS_SESSION_TOKEN)
29+
and that the configured region exposes the requested foundation model.
30+
</context>
31+
32+
<tools>
33+
[TOOLS]
34+
</tools>
35+
36+
<operating_guidelines>
37+
- Maintain a clear, concise, and professional tone in all interactions.
38+
- Always include all relevant output and context from your tools in your responses.
39+
- Confirm actions and provide next steps when appropriate.
40+
</operating_guidelines>
41+
42+
<constraints>
43+
- Only operate on authenticated requests and available tools.
44+
- Do not speculate or fabricate tool responses—use provided data exclusively.
45+
- Never expose sensitive information such as AWS credentials in responses.
46+
</constraints>
47+
"""
48+
SUGGESTIONS: list = []
49+
50+
51+
def create_agent(
52+
agent_shared_state: Optional[AgentSharedState] = None,
53+
agent_configuration: Optional[AgentConfiguration] = None,
54+
) -> IntentAgent:
55+
from naas_abi_marketplace.ai.bedrock.models.claude_sonnet_4_bedrock import model
56+
57+
tools: list = []
58+
agents: list = []
59+
intents: list = [
60+
Intent(
61+
intent_value="use a foundation model on aws bedrock",
62+
intent_type=IntentType.AGENT,
63+
intent_target="call_model",
64+
),
65+
Intent(
66+
intent_value="run claude on bedrock",
67+
intent_type=IntentType.AGENT,
68+
intent_target="call_model",
69+
),
70+
Intent(
71+
intent_value="run llama on bedrock",
72+
intent_type=IntentType.AGENT,
73+
intent_target="call_model",
74+
),
75+
Intent(
76+
intent_value="run nova on bedrock",
77+
intent_type=IntentType.AGENT,
78+
intent_target="call_model",
79+
),
80+
]
81+
82+
system_prompt = SYSTEM_PROMPT.replace(
83+
"[TOOLS]", "\n".join([f"- {tool.name}: {tool.description}" for tool in tools])
84+
)
85+
if agent_configuration is None:
86+
agent_configuration = AgentConfiguration(
87+
system_prompt=system_prompt,
88+
)
89+
if agent_shared_state is None:
90+
agent_shared_state = AgentSharedState(thread_id="0")
91+
92+
return BedrockAgent(
93+
name=NAME,
94+
description=DESCRIPTION,
95+
chat_model=model,
96+
tools=tools,
97+
agents=agents,
98+
intents=intents,
99+
state=agent_shared_state,
100+
configuration=agent_configuration,
101+
)
102+
103+
104+
class BedrockAgent(IntentAgent):
105+
pass

0 commit comments

Comments
 (0)