Skip to content

Commit efcf4da

Browse files
Merge branch 'aws:main' into feat/agentcore-tool-search
2 parents 13a0bef + 7d6d4f4 commit efcf4da

31 files changed

Lines changed: 3418 additions & 75 deletions

.github/workflows/integration-testing.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ jobs:
7575
with:
7676
result-encoding: string
7777
script: |
78+
let username;
7879
try {
79-
let username;
8080
if (context.payload.pull_request) {
8181
username = context.payload.pull_request.user.login;
8282
} else {
@@ -99,7 +99,7 @@ jobs:
9999
return "auto-approve"
100100
}
101101
} catch (error) {
102-
console.log(`${username} does not have write access. Requiring Manual Approval to run PR Checks.`)
102+
console.log(`Permission check failed for ${username}. Requiring Manual Approval to run PR Checks. Error: ${error.message}`);
103103
return "manual-approval"
104104
}
105105

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
# Changelog
22

3+
## [1.12.0] - 2026-05-28
4+
5+
### Added
6+
- feat: add async support to MemorySessionManager (#478) (76edb16)
7+
8+
### Fixed
9+
- fix: out-of-scope variable in catch block (#497) (4054115)
10+
11+
### Other Changes
12+
- add metadata support for LTM (#481) (80c4b11)
13+
14+
## [1.11.0] - 2026-05-22
15+
16+
### Fixed
17+
- fix: stop retrying after successful payment signing is rejected by merchant (#492) (0b2b34f)
18+
19+
### Other Changes
20+
- fix(payments): drop unsupported paymentConnectorId + add http_request plugin tool + EIP-3009 timing fix (#493) (d5428b2)
21+
- feat(evaluation): add DatasetClient and dataset management service provider (#491) (29287c2)
22+
- test: add OTEL span content leakage integration tests (#485) (c311682)
23+
324
## [1.10.0] - 2026-05-19
425

526
### Added

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "bedrock-agentcore"
7-
version = "1.10.0"
7+
version = "1.12.0"
88
description = "An SDK for using Bedrock AgentCore"
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/bedrock_agentcore/evaluation/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
EvaluatorOutput,
77
custom_code_based_evaluator,
88
)
9+
from bedrock_agentcore.evaluation.dataset_client import DatasetClient
910
from bedrock_agentcore.evaluation.runner.batch.batch_evaluation_models import (
1011
BatchEvaluationResult,
1112
BatchEvaluationRunConfig,
@@ -21,6 +22,7 @@
2122
BatchEvaluationRunner,
2223
)
2324
from bedrock_agentcore.evaluation.runner.dataset_providers import (
25+
DatasetManagementServiceProvider,
2426
DatasetProvider,
2527
FileDatasetProvider,
2628
)
@@ -77,6 +79,7 @@
7779
"AgentInvokerOutput",
7880
"CloudWatchAgentSpanCollector",
7981
"Dataset",
82+
"DatasetClient",
8083
"DatasetProvider",
8184
"EvaluationClient",
8285
"EvaluationResult",
@@ -86,6 +89,7 @@
8689
"EvaluatorOutput",
8790
"EvaluatorResult",
8891
"FileDatasetProvider",
92+
"DatasetManagementServiceProvider",
8993
"Input",
9094
"OnDemandEvaluationDatasetRunner",
9195
"ReferenceInputs",
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
"""DatasetClient for managing evaluation datasets."""
2+
3+
import logging
4+
from typing import Any, Dict, Optional
5+
6+
import boto3
7+
from botocore.config import Config
8+
9+
from bedrock_agentcore._utils.config import WaitConfig
10+
from bedrock_agentcore._utils.polling import wait_until, wait_until_deleted
11+
from bedrock_agentcore._utils.snake_case import accept_snake_case_kwargs, convert_kwargs
12+
from bedrock_agentcore._utils.user_agent import build_user_agent_suffix
13+
14+
logger = logging.getLogger(__name__)
15+
16+
17+
class DatasetClient:
18+
"""Client for managing evaluation datasets.
19+
20+
Provides pass-through access to all dataset management APIs on the
21+
bedrock-agentcore-control service, plus *_and_wait helpers for async operations.
22+
23+
Example::
24+
25+
client = DatasetClient(region_name="us-west-2")
26+
27+
# Create a dataset and wait for ACTIVE
28+
dataset = client.create_dataset_and_wait(
29+
datasetName="my-dataset",
30+
schemaType="AGENTCORE_EVALUATION_PREDEFINED_V1",
31+
source={"inlineExamples": {"examples": [...]}},
32+
)
33+
34+
# Pass-through to any dataset API
35+
client.list_datasets(maxResults=10)
36+
client.add_dataset_examples(datasetId="ds-123", examples=[...])
37+
"""
38+
39+
_ALLOWED_CP_METHODS = {
40+
# Dataset CRUD
41+
"create_dataset",
42+
"get_dataset",
43+
"list_datasets",
44+
"update_dataset",
45+
"delete_dataset",
46+
# Version management
47+
"create_dataset_version",
48+
"list_dataset_versions",
49+
# Examples management
50+
"add_dataset_examples",
51+
"update_dataset_examples",
52+
"delete_dataset_examples",
53+
"list_dataset_examples",
54+
}
55+
56+
def __init__(
57+
self,
58+
region_name: Optional[str] = None,
59+
integration_source: Optional[str] = None,
60+
boto3_session: Optional[boto3.Session] = None,
61+
):
62+
"""Initialize the DatasetClient.
63+
64+
Args:
65+
region_name: AWS region. Falls back to boto3 session region or us-west-2.
66+
integration_source: Optional integration framework identifier for telemetry.
67+
boto3_session: Optional boto3 Session. If not provided, a default is created.
68+
"""
69+
session = boto3_session if boto3_session else boto3.Session()
70+
self.region_name = region_name or session.region_name or "us-west-2"
71+
self.integration_source = integration_source
72+
73+
user_agent_extra = build_user_agent_suffix(integration_source)
74+
client_config = Config(user_agent_extra=user_agent_extra)
75+
76+
self._cp_client = session.client(
77+
"bedrock-agentcore-control",
78+
region_name=self.region_name,
79+
config=client_config,
80+
)
81+
82+
logger.info("Initialized DatasetClient in region %s", self.region_name)
83+
84+
def __getattr__(self, name: str):
85+
"""Dynamically forward allowlisted method calls to the boto3 client."""
86+
if "_cp_client" not in self.__dict__:
87+
raise AttributeError(name)
88+
89+
if name in self._ALLOWED_CP_METHODS and hasattr(self._cp_client, name):
90+
method = getattr(self._cp_client, name)
91+
logger.debug("Forwarding method '%s' to _cp_client", name)
92+
return accept_snake_case_kwargs(method)
93+
94+
raise AttributeError(
95+
f"'{self.__class__.__name__}' object has no attribute '{name}'. "
96+
f"Method not found on control plane client. "
97+
f"Available methods can be found in the boto3 documentation for "
98+
f"'bedrock-agentcore-control' service."
99+
)
100+
101+
# *_and_wait methods
102+
# -------------------------------------------------------------------------
103+
104+
def create_dataset_and_wait(
105+
self,
106+
wait_config: Optional[WaitConfig] = None,
107+
**kwargs,
108+
) -> Dict[str, Any]:
109+
"""Create a dataset and wait for it to reach ACTIVE status.
110+
111+
Args:
112+
wait_config: Optional WaitConfig for polling behavior.
113+
**kwargs: Arguments forwarded to the create_dataset API.
114+
115+
Returns:
116+
Dataset details when ACTIVE.
117+
118+
Raises:
119+
RuntimeError: If the dataset reaches CREATE_FAILED status.
120+
TimeoutError: If the dataset doesn't become ACTIVE within max_wait.
121+
"""
122+
response = self._cp_client.create_dataset(**convert_kwargs(kwargs))
123+
dataset_id = response["datasetId"]
124+
return wait_until(
125+
lambda: self._cp_client.get_dataset(datasetId=dataset_id),
126+
"ACTIVE",
127+
{"CREATE_FAILED"},
128+
wait_config,
129+
)
130+
131+
def delete_dataset_and_wait(
132+
self,
133+
wait_config: Optional[WaitConfig] = None,
134+
**kwargs,
135+
) -> Optional[Dict[str, Any]]:
136+
"""Delete a dataset (or a single version) and wait for completion.
137+
138+
- Full delete (no ``datasetVersion``): polls until ``GetDataset``
139+
raises ``ResourceNotFoundException``. Fails on ``DELETE_FAILED``.
140+
- Version-specific delete (``datasetVersion`` provided): the dataset
141+
itself is not removed. Polls ``GetDataset`` until status returns to
142+
``ACTIVE``. Fails on ``UPDATE_FAILED``. Returns the dataset details.
143+
144+
Args:
145+
wait_config: Optional WaitConfig for polling behavior.
146+
**kwargs: Arguments forwarded to the delete_dataset API.
147+
148+
Raises:
149+
RuntimeError: On ``DELETE_FAILED`` or ``UPDATE_FAILED``.
150+
TimeoutError: If the operation does not finish within ``max_wait``.
151+
"""
152+
converted = convert_kwargs(kwargs)
153+
response = self._cp_client.delete_dataset(**converted)
154+
dataset_id = response["datasetId"]
155+
156+
if "datasetVersion" in converted:
157+
return wait_until(
158+
lambda: self._cp_client.get_dataset(datasetId=dataset_id),
159+
"ACTIVE",
160+
{"UPDATE_FAILED"},
161+
wait_config,
162+
)
163+
164+
wait_until_deleted(
165+
lambda: self._cp_client.get_dataset(datasetId=dataset_id),
166+
failed={"DELETE_FAILED"},
167+
wait_config=wait_config,
168+
)
169+
return None
170+
171+
def create_dataset_version_and_wait(
172+
self,
173+
wait_config: Optional[WaitConfig] = None,
174+
**kwargs,
175+
) -> Dict[str, Any]:
176+
"""Create a dataset version and wait for the dataset to reach ACTIVE status.
177+
178+
Args:
179+
wait_config: Optional WaitConfig for polling behavior.
180+
**kwargs: Arguments forwarded to the create_dataset_version API.
181+
182+
Returns:
183+
Dataset details when ACTIVE.
184+
185+
Raises:
186+
RuntimeError: If the dataset reaches UPDATE_FAILED status.
187+
TimeoutError: If the dataset doesn't become ACTIVE within max_wait.
188+
"""
189+
response = self._cp_client.create_dataset_version(**convert_kwargs(kwargs))
190+
dataset_id = response["datasetId"]
191+
return wait_until(
192+
lambda: self._cp_client.get_dataset(datasetId=dataset_id),
193+
"ACTIVE",
194+
{"UPDATE_FAILED"},
195+
wait_config,
196+
)
197+
198+
def add_examples_and_wait(
199+
self,
200+
wait_config: Optional[WaitConfig] = None,
201+
**kwargs,
202+
) -> Dict[str, Any]:
203+
"""Add examples to a dataset and wait for ACTIVE status.
204+
205+
Args:
206+
wait_config: Optional WaitConfig for polling behavior.
207+
**kwargs: Arguments forwarded to the add_dataset_examples API.
208+
209+
Returns:
210+
Dataset details when ACTIVE.
211+
212+
Raises:
213+
RuntimeError: If the dataset reaches UPDATE_FAILED status.
214+
TimeoutError: If the dataset doesn't become ACTIVE within max_wait.
215+
"""
216+
response = self._cp_client.add_dataset_examples(**convert_kwargs(kwargs))
217+
dataset_id = response["datasetId"]
218+
return wait_until(
219+
lambda: self._cp_client.get_dataset(datasetId=dataset_id),
220+
"ACTIVE",
221+
{"UPDATE_FAILED"},
222+
wait_config,
223+
)
224+
225+
def update_examples_and_wait(
226+
self,
227+
wait_config: Optional[WaitConfig] = None,
228+
**kwargs,
229+
) -> Dict[str, Any]:
230+
"""Update examples in a dataset and wait for ACTIVE status.
231+
232+
Args:
233+
wait_config: Optional WaitConfig for polling behavior.
234+
**kwargs: Arguments forwarded to the update_dataset_examples API.
235+
236+
Returns:
237+
Dataset details when ACTIVE.
238+
239+
Raises:
240+
RuntimeError: If the dataset reaches UPDATE_FAILED status.
241+
TimeoutError: If the dataset doesn't become ACTIVE within max_wait.
242+
"""
243+
response = self._cp_client.update_dataset_examples(**convert_kwargs(kwargs))
244+
dataset_id = response["datasetId"]
245+
return wait_until(
246+
lambda: self._cp_client.get_dataset(datasetId=dataset_id),
247+
"ACTIVE",
248+
{"UPDATE_FAILED"},
249+
wait_config,
250+
)
251+
252+
def delete_examples_and_wait(
253+
self,
254+
wait_config: Optional[WaitConfig] = None,
255+
**kwargs,
256+
) -> Dict[str, Any]:
257+
"""Delete examples from a dataset and wait for ACTIVE status.
258+
259+
Args:
260+
wait_config: Optional WaitConfig for polling behavior.
261+
**kwargs: Arguments forwarded to the delete_dataset_examples API.
262+
263+
Returns:
264+
Dataset details when ACTIVE.
265+
266+
Raises:
267+
RuntimeError: If the dataset reaches UPDATE_FAILED status.
268+
TimeoutError: If the dataset doesn't become ACTIVE within max_wait.
269+
"""
270+
response = self._cp_client.delete_dataset_examples(**convert_kwargs(kwargs))
271+
dataset_id = response["datasetId"]
272+
return wait_until(
273+
lambda: self._cp_client.get_dataset(datasetId=dataset_id),
274+
"ACTIVE",
275+
{"UPDATE_FAILED"},
276+
wait_config,
277+
)

0 commit comments

Comments
 (0)