bedrock-agentcore-starter-toolkit 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bedrock-agentcore-starter-toolkit might be problematic. Click here for more details.
- bedrock_agentcore_starter_toolkit/__init__.py +5 -0
- bedrock_agentcore_starter_toolkit/cli/cli.py +32 -0
- bedrock_agentcore_starter_toolkit/cli/common.py +44 -0
- bedrock_agentcore_starter_toolkit/cli/gateway/__init__.py +1 -0
- bedrock_agentcore_starter_toolkit/cli/gateway/commands.py +88 -0
- bedrock_agentcore_starter_toolkit/cli/runtime/__init__.py +1 -0
- bedrock_agentcore_starter_toolkit/cli/runtime/commands.py +651 -0
- bedrock_agentcore_starter_toolkit/cli/runtime/configuration_manager.py +133 -0
- bedrock_agentcore_starter_toolkit/notebook/__init__.py +5 -0
- bedrock_agentcore_starter_toolkit/notebook/runtime/__init__.py +1 -0
- bedrock_agentcore_starter_toolkit/notebook/runtime/bedrock_agentcore.py +239 -0
- bedrock_agentcore_starter_toolkit/operations/__init__.py +1 -0
- bedrock_agentcore_starter_toolkit/operations/gateway/README.md +277 -0
- bedrock_agentcore_starter_toolkit/operations/gateway/__init__.py +6 -0
- bedrock_agentcore_starter_toolkit/operations/gateway/client.py +456 -0
- bedrock_agentcore_starter_toolkit/operations/gateway/constants.py +152 -0
- bedrock_agentcore_starter_toolkit/operations/gateway/create_lambda.py +85 -0
- bedrock_agentcore_starter_toolkit/operations/gateway/create_role.py +90 -0
- bedrock_agentcore_starter_toolkit/operations/gateway/exceptions.py +13 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/__init__.py +26 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/configure.py +241 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/create_role.py +404 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +129 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +439 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/models.py +79 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/status.py +66 -0
- bedrock_agentcore_starter_toolkit/services/codebuild.py +332 -0
- bedrock_agentcore_starter_toolkit/services/ecr.py +84 -0
- bedrock_agentcore_starter_toolkit/services/runtime.py +473 -0
- bedrock_agentcore_starter_toolkit/utils/endpoints.py +32 -0
- bedrock_agentcore_starter_toolkit/utils/logging_config.py +72 -0
- bedrock_agentcore_starter_toolkit/utils/runtime/config.py +129 -0
- bedrock_agentcore_starter_toolkit/utils/runtime/container.py +310 -0
- bedrock_agentcore_starter_toolkit/utils/runtime/entrypoint.py +197 -0
- bedrock_agentcore_starter_toolkit/utils/runtime/logs.py +33 -0
- bedrock_agentcore_starter_toolkit/utils/runtime/policy_template.py +74 -0
- bedrock_agentcore_starter_toolkit/utils/runtime/schema.py +151 -0
- bedrock_agentcore_starter_toolkit/utils/runtime/templates/Dockerfile.j2 +44 -0
- bedrock_agentcore_starter_toolkit/utils/runtime/templates/dockerignore.template +68 -0
- bedrock_agentcore_starter_toolkit/utils/runtime/templates/execution_role_policy.json.j2 +98 -0
- bedrock_agentcore_starter_toolkit/utils/runtime/templates/execution_role_trust_policy.json.j2 +21 -0
- bedrock_agentcore_starter_toolkit-0.1.1.dist-info/METADATA +137 -0
- bedrock_agentcore_starter_toolkit-0.1.1.dist-info/RECORD +47 -0
- bedrock_agentcore_starter_toolkit-0.1.1.dist-info/entry_points.txt +2 -0
- bedrock_agentcore_starter_toolkit-0.1.1.dist-info/licenses/NOTICE.txt +190 -0
- bedrock_agentcore_starter_toolkit/init.py +0 -3
- bedrock_agentcore_starter_toolkit-0.0.1.dist-info/METADATA +0 -26
- bedrock_agentcore_starter_toolkit-0.0.1.dist-info/RECORD +0 -5
- {bedrock_agentcore_starter_toolkit-0.0.1.dist-info → bedrock_agentcore_starter_toolkit-0.1.1.dist-info}/WHEEL +0 -0
- /bedrock_agentcore_starter_toolkit-0.0.1.dist-info/licenses/LICENSE → /bedrock_agentcore_starter_toolkit-0.1.1.dist-info/licenses/LICENSE.txt +0 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""BedrockAgentCore service client for agent management."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
import urllib.parse
|
|
7
|
+
import uuid
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
import boto3
|
|
11
|
+
import requests
|
|
12
|
+
from botocore.exceptions import ClientError
|
|
13
|
+
|
|
14
|
+
from ..utils.endpoints import get_control_plane_endpoint, get_data_plane_endpoint
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def generate_session_id() -> str:
|
|
18
|
+
"""Generate session ID."""
|
|
19
|
+
return str(uuid.uuid4())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _handle_http_response(response) -> dict:
|
|
23
|
+
response.raise_for_status()
|
|
24
|
+
if "text/event-stream" in response.headers.get("content-type", ""):
|
|
25
|
+
return _handle_streaming_response(response)
|
|
26
|
+
else:
|
|
27
|
+
# Check if response has content
|
|
28
|
+
if not response.content:
|
|
29
|
+
raise ValueError("Empty response from agent endpoint")
|
|
30
|
+
|
|
31
|
+
return {"response": response.text}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _handle_aws_response(response) -> dict:
|
|
35
|
+
if "text/event-stream" in response.get("contentType", ""):
|
|
36
|
+
return _handle_streaming_response(response["response"])
|
|
37
|
+
else:
|
|
38
|
+
try:
|
|
39
|
+
events = []
|
|
40
|
+
for event in response.get("response", []):
|
|
41
|
+
events.append(event)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
events = [f"Error reading EventStream: {e}"]
|
|
44
|
+
|
|
45
|
+
response["response"] = events
|
|
46
|
+
return response
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _handle_streaming_response(response) -> Dict[str, Any]:
|
|
50
|
+
logger = logging.getLogger("bedrock_agentcore.stream")
|
|
51
|
+
logger.setLevel(logging.INFO)
|
|
52
|
+
|
|
53
|
+
content = []
|
|
54
|
+
for line in response.iter_lines(chunk_size=1):
|
|
55
|
+
if line:
|
|
56
|
+
line = line.decode("utf-8")
|
|
57
|
+
if line.startswith("data: "):
|
|
58
|
+
line = line[6:]
|
|
59
|
+
logger.info(line)
|
|
60
|
+
content.append(line)
|
|
61
|
+
|
|
62
|
+
return {"response": "\n".join(content)}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class BedrockAgentCoreClient:
|
|
66
|
+
"""Bedrock AgentCore client for agent management."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, region: str):
|
|
69
|
+
"""Initialize Bedrock AgentCore client.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
region: AWS region for the client
|
|
73
|
+
"""
|
|
74
|
+
self.region = region
|
|
75
|
+
self.logger = logging.getLogger(f"bedrock_agentcore.runtime.{region}")
|
|
76
|
+
|
|
77
|
+
# Get endpoint URLs and log them
|
|
78
|
+
control_plane_url = get_control_plane_endpoint(region)
|
|
79
|
+
data_plane_url = get_data_plane_endpoint(region)
|
|
80
|
+
|
|
81
|
+
self.logger.debug("Initializing Bedrock AgentCore client for region: %s", region)
|
|
82
|
+
self.logger.debug("Control plane: %s", control_plane_url)
|
|
83
|
+
self.logger.debug("Data plane: %s", data_plane_url)
|
|
84
|
+
|
|
85
|
+
self.client = boto3.client("bedrock-agentcore-control", region_name=region, endpoint_url=control_plane_url)
|
|
86
|
+
self.dataplane_client = boto3.client("bedrock-agentcore", region_name=region, endpoint_url=data_plane_url)
|
|
87
|
+
|
|
88
|
+
def create_agent(
|
|
89
|
+
self,
|
|
90
|
+
agent_name: str,
|
|
91
|
+
image_uri: str,
|
|
92
|
+
execution_role_arn: str,
|
|
93
|
+
network_config: Optional[Dict] = None,
|
|
94
|
+
authorizer_config: Optional[Dict] = None,
|
|
95
|
+
protocol_config: Optional[Dict] = None,
|
|
96
|
+
env_vars: Optional[Dict] = None,
|
|
97
|
+
auto_update_on_conflict: bool = False,
|
|
98
|
+
) -> Dict[str, str]:
|
|
99
|
+
"""Create new agent."""
|
|
100
|
+
self.logger.info("Creating agent '%s' with image URI: %s", agent_name, image_uri)
|
|
101
|
+
try:
|
|
102
|
+
# Build parameters dict, only including optional configs when present
|
|
103
|
+
params = {
|
|
104
|
+
"agentRuntimeName": agent_name,
|
|
105
|
+
"agentRuntimeArtifact": {"containerConfiguration": {"containerUri": image_uri}},
|
|
106
|
+
"roleArn": execution_role_arn,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if network_config is not None:
|
|
110
|
+
params["networkConfiguration"] = network_config
|
|
111
|
+
|
|
112
|
+
if authorizer_config is not None:
|
|
113
|
+
params["authorizerConfiguration"] = authorizer_config
|
|
114
|
+
|
|
115
|
+
if protocol_config is not None:
|
|
116
|
+
params["protocolConfiguration"] = protocol_config
|
|
117
|
+
|
|
118
|
+
if env_vars is not None:
|
|
119
|
+
params["environmentVariables"] = env_vars
|
|
120
|
+
|
|
121
|
+
resp = self.client.create_agent_runtime(**params)
|
|
122
|
+
agent_id = resp["agentRuntimeId"]
|
|
123
|
+
agent_arn = resp["agentRuntimeArn"]
|
|
124
|
+
self.logger.info("Successfully created agent '%s' with ID: %s, ARN: %s", agent_name, agent_id, agent_arn)
|
|
125
|
+
return {"id": agent_id, "arn": agent_arn}
|
|
126
|
+
except ClientError as e:
|
|
127
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
128
|
+
if error_code == "ConflictException":
|
|
129
|
+
if not auto_update_on_conflict:
|
|
130
|
+
self.logger.error("Agent '%s' already exists and auto_update_on_conflict is disabled", agent_name)
|
|
131
|
+
raise
|
|
132
|
+
|
|
133
|
+
self.logger.info("Agent '%s' already exists, searching for existing agent...", agent_name)
|
|
134
|
+
|
|
135
|
+
# Find existing agent by name
|
|
136
|
+
existing_agent = self.find_agent_by_name(agent_name)
|
|
137
|
+
|
|
138
|
+
if not existing_agent:
|
|
139
|
+
raise RuntimeError(
|
|
140
|
+
f"ConflictException occurred but couldn't find existing agent '{agent_name}'. "
|
|
141
|
+
f"This might be a permissions issue or the agent name might be different."
|
|
142
|
+
) from e
|
|
143
|
+
|
|
144
|
+
# Extract existing agent details
|
|
145
|
+
existing_agent_id = existing_agent["agentRuntimeId"]
|
|
146
|
+
existing_agent_arn = existing_agent["agentRuntimeArn"]
|
|
147
|
+
|
|
148
|
+
self.logger.info("Found existing agent ID: %s, updating instead...", existing_agent_id)
|
|
149
|
+
|
|
150
|
+
# Update the existing agent
|
|
151
|
+
self.update_agent(
|
|
152
|
+
existing_agent_id,
|
|
153
|
+
image_uri,
|
|
154
|
+
execution_role_arn,
|
|
155
|
+
network_config,
|
|
156
|
+
authorizer_config,
|
|
157
|
+
protocol_config,
|
|
158
|
+
env_vars,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Return the existing agent info (keeping the original ID and ARN)
|
|
162
|
+
return {"id": existing_agent_id, "arn": existing_agent_arn}
|
|
163
|
+
else:
|
|
164
|
+
# Re-raise other ClientErrors
|
|
165
|
+
raise
|
|
166
|
+
except Exception as e:
|
|
167
|
+
self.logger.error("Failed to create agent '%s': %s", agent_name, str(e))
|
|
168
|
+
raise
|
|
169
|
+
|
|
170
|
+
def update_agent(
|
|
171
|
+
self,
|
|
172
|
+
agent_id: str,
|
|
173
|
+
image_uri: str,
|
|
174
|
+
execution_role_arn: str,
|
|
175
|
+
network_config: Optional[Dict] = None,
|
|
176
|
+
authorizer_config: Optional[Dict] = None,
|
|
177
|
+
protocol_config: Optional[Dict] = None,
|
|
178
|
+
env_vars: Optional[Dict] = None,
|
|
179
|
+
) -> Dict[str, str]:
|
|
180
|
+
"""Update existing agent."""
|
|
181
|
+
self.logger.info("Updating agent ID '%s' with image URI: %s", agent_id, image_uri)
|
|
182
|
+
try:
|
|
183
|
+
# Build parameters dict, only including optional configs when present
|
|
184
|
+
params = {
|
|
185
|
+
"agentRuntimeId": agent_id,
|
|
186
|
+
"agentRuntimeArtifact": {"containerConfiguration": {"containerUri": image_uri}},
|
|
187
|
+
"roleArn": execution_role_arn,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if network_config is not None:
|
|
191
|
+
params["networkConfiguration"] = network_config
|
|
192
|
+
|
|
193
|
+
if authorizer_config is not None:
|
|
194
|
+
params["authorizerConfiguration"] = authorizer_config
|
|
195
|
+
|
|
196
|
+
if protocol_config is not None:
|
|
197
|
+
params["protocolConfiguration"] = protocol_config
|
|
198
|
+
|
|
199
|
+
if env_vars is not None:
|
|
200
|
+
params["environmentVariables"] = env_vars
|
|
201
|
+
|
|
202
|
+
resp = self.client.update_agent_runtime(**params)
|
|
203
|
+
agent_arn = resp["agentRuntimeArn"]
|
|
204
|
+
self.logger.info("Successfully updated agent ID '%s', ARN: %s", agent_id, agent_arn)
|
|
205
|
+
return {"id": agent_id, "arn": agent_arn}
|
|
206
|
+
except Exception as e:
|
|
207
|
+
self.logger.error("Failed to update agent ID '%s': %s", agent_id, str(e))
|
|
208
|
+
raise
|
|
209
|
+
|
|
210
|
+
def list_agents(self, max_results: int = 100) -> list:
|
|
211
|
+
"""List all agent runtimes, handling pagination."""
|
|
212
|
+
all_agents = []
|
|
213
|
+
next_token = None
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
while True:
|
|
217
|
+
params = {"maxResults": max_results}
|
|
218
|
+
if next_token:
|
|
219
|
+
params["nextToken"] = next_token
|
|
220
|
+
|
|
221
|
+
response = self.client.list_agent_runtimes(**params)
|
|
222
|
+
agents = response.get("agentRuntimes", [])
|
|
223
|
+
all_agents.extend(agents)
|
|
224
|
+
|
|
225
|
+
next_token = response.get("nextToken")
|
|
226
|
+
if not next_token:
|
|
227
|
+
break
|
|
228
|
+
|
|
229
|
+
return all_agents
|
|
230
|
+
except Exception as e:
|
|
231
|
+
self.logger.error("Failed to list agents: %s", str(e))
|
|
232
|
+
raise
|
|
233
|
+
|
|
234
|
+
def find_agent_by_name(self, agent_name: str) -> Optional[Dict]:
|
|
235
|
+
"""Find an agent by name, reusing list_agents method."""
|
|
236
|
+
try:
|
|
237
|
+
# Get all agents using the existing method
|
|
238
|
+
all_agents = self.list_agents()
|
|
239
|
+
|
|
240
|
+
# Search for the specific agent by name
|
|
241
|
+
for agent in all_agents:
|
|
242
|
+
if agent.get("agentRuntimeName") == agent_name:
|
|
243
|
+
return agent
|
|
244
|
+
|
|
245
|
+
return None # Agent not found
|
|
246
|
+
except Exception as e:
|
|
247
|
+
self.logger.error("Failed to search for agent '%s': %s", agent_name, str(e))
|
|
248
|
+
raise
|
|
249
|
+
|
|
250
|
+
def create_or_update_agent(
|
|
251
|
+
self,
|
|
252
|
+
agent_id: Optional[str],
|
|
253
|
+
agent_name: str,
|
|
254
|
+
image_uri: str,
|
|
255
|
+
execution_role_arn: str,
|
|
256
|
+
network_config: Optional[Dict] = None,
|
|
257
|
+
authorizer_config: Optional[Dict] = None,
|
|
258
|
+
protocol_config: Optional[Dict] = None,
|
|
259
|
+
env_vars: Optional[Dict] = None,
|
|
260
|
+
auto_update_on_conflict: bool = False,
|
|
261
|
+
) -> Dict[str, str]:
|
|
262
|
+
"""Create or update agent."""
|
|
263
|
+
if agent_id:
|
|
264
|
+
return self.update_agent(
|
|
265
|
+
agent_id, image_uri, execution_role_arn, network_config, authorizer_config, protocol_config, env_vars
|
|
266
|
+
)
|
|
267
|
+
return self.create_agent(
|
|
268
|
+
agent_name,
|
|
269
|
+
image_uri,
|
|
270
|
+
execution_role_arn,
|
|
271
|
+
network_config,
|
|
272
|
+
authorizer_config,
|
|
273
|
+
protocol_config,
|
|
274
|
+
env_vars,
|
|
275
|
+
auto_update_on_conflict,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
def wait_for_agent_endpoint_ready(self, agent_id: str, endpoint_name: str = "DEFAULT", max_wait: int = 120) -> str:
|
|
279
|
+
"""Wait for agent endpoint to be ready.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
agent_id: Agent ID to wait for
|
|
283
|
+
endpoint_name: Endpoint name, defaults to "DEFAULT"
|
|
284
|
+
max_wait: Maximum wait time in seconds
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Agent endpoint ARN when ready
|
|
288
|
+
"""
|
|
289
|
+
start_time = time.time()
|
|
290
|
+
|
|
291
|
+
while time.time() - start_time < max_wait:
|
|
292
|
+
try:
|
|
293
|
+
resp = self.client.get_agent_runtime_endpoint(
|
|
294
|
+
agentRuntimeId=agent_id,
|
|
295
|
+
endpointName=endpoint_name,
|
|
296
|
+
)
|
|
297
|
+
status = resp.get("status", "UNKNOWN")
|
|
298
|
+
|
|
299
|
+
if status == "READY":
|
|
300
|
+
return resp["agentRuntimeEndpointArn"]
|
|
301
|
+
elif status in ["CREATE_FAILED", "UPDATE_FAILED"]:
|
|
302
|
+
raise Exception(
|
|
303
|
+
f"Agent endpoint {status.lower().replace('_', ' ')}: {resp.get('failureReason', 'Unknown')}"
|
|
304
|
+
)
|
|
305
|
+
elif status not in ["CREATING", "UPDATING"]:
|
|
306
|
+
pass
|
|
307
|
+
except self.client.exceptions.ResourceNotFoundException:
|
|
308
|
+
pass
|
|
309
|
+
except Exception as e:
|
|
310
|
+
if "ResourceNotFoundException" not in str(e):
|
|
311
|
+
raise
|
|
312
|
+
time.sleep(2)
|
|
313
|
+
return (
|
|
314
|
+
f"Endpoint is taking longer than {max_wait} seconds to be ready, "
|
|
315
|
+
f"please check status and try to invoke after some time"
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def get_agent_runtime(self, agent_id: str) -> Dict:
|
|
319
|
+
"""Get agent runtime details.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
agent_id: Agent ID to get details for
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
Agent runtime details
|
|
326
|
+
"""
|
|
327
|
+
return self.client.get_agent_runtime(agentRuntimeId=agent_id)
|
|
328
|
+
|
|
329
|
+
def get_agent_runtime_endpoint(self, agent_id: str, endpoint_name: str = "DEFAULT") -> Dict:
|
|
330
|
+
"""Get agent runtime endpoint details.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
agent_id: Agent ID to get endpoint for
|
|
334
|
+
endpoint_name: Endpoint name, defaults to "DEFAULT"
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
Agent endpoint details
|
|
338
|
+
"""
|
|
339
|
+
return self.client.get_agent_runtime_endpoint(
|
|
340
|
+
agentRuntimeId=agent_id,
|
|
341
|
+
endpointName=endpoint_name,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def invoke_endpoint(
|
|
345
|
+
self,
|
|
346
|
+
agent_arn: str,
|
|
347
|
+
payload: str,
|
|
348
|
+
session_id: str,
|
|
349
|
+
endpoint_name: str = "DEFAULT",
|
|
350
|
+
user_id: Optional[str] = None,
|
|
351
|
+
) -> Dict:
|
|
352
|
+
"""Invoke agent endpoint."""
|
|
353
|
+
req = {
|
|
354
|
+
"agentRuntimeArn": agent_arn,
|
|
355
|
+
"qualifier": endpoint_name,
|
|
356
|
+
"runtimeSessionId": session_id,
|
|
357
|
+
"payload": payload,
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if user_id:
|
|
361
|
+
req["runtimeUserId"] = user_id
|
|
362
|
+
|
|
363
|
+
response = self.dataplane_client.invoke_agent_runtime(**req)
|
|
364
|
+
return _handle_aws_response(response)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class HttpBedrockAgentCoreClient:
|
|
368
|
+
"""Bedrock AgentCore client for agent management using HTTP requests with bearer token."""
|
|
369
|
+
|
|
370
|
+
def __init__(self, region: str):
|
|
371
|
+
"""Initialize HttpBedrockAgentCoreClient.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
region: AWS region for the client
|
|
375
|
+
"""
|
|
376
|
+
self.region = region
|
|
377
|
+
self.dp_endpoint = get_data_plane_endpoint(region)
|
|
378
|
+
self.logger = logging.getLogger(f"bedrock_agentcore.http_runtime.{region}")
|
|
379
|
+
|
|
380
|
+
self.logger.debug("Initializing HTTP Bedrock AgentCore client for region: %s", region)
|
|
381
|
+
self.logger.debug("Data plane: %s", self.dp_endpoint)
|
|
382
|
+
|
|
383
|
+
def invoke_endpoint(
|
|
384
|
+
self,
|
|
385
|
+
agent_arn: str,
|
|
386
|
+
payload,
|
|
387
|
+
session_id: str,
|
|
388
|
+
bearer_token: Optional[str],
|
|
389
|
+
endpoint_name: str = "DEFAULT",
|
|
390
|
+
) -> Dict:
|
|
391
|
+
"""Invoke agent endpoint using HTTP request with bearer token.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
agent_arn: Agent ARN to invoke
|
|
395
|
+
payload: Payload to send (dict or string)
|
|
396
|
+
session_id: Session ID for the request
|
|
397
|
+
bearer_token: Bearer token for authentication
|
|
398
|
+
endpoint_name: Endpoint name, defaults to "DEFAULT"
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Response from the agent endpoint
|
|
402
|
+
"""
|
|
403
|
+
# Escape agent ARN for URL
|
|
404
|
+
escaped_arn = urllib.parse.quote(agent_arn, safe="")
|
|
405
|
+
|
|
406
|
+
# Build URL
|
|
407
|
+
url = f"{self.dp_endpoint}/runtimes/{escaped_arn}/invocations"
|
|
408
|
+
# Headers
|
|
409
|
+
headers = {
|
|
410
|
+
"Authorization": f"Bearer {bearer_token}",
|
|
411
|
+
"Content-Type": "application/json",
|
|
412
|
+
"X-Amzn-Bedrock-AgentCore-Runtime-Session-Id": session_id,
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
# Parse the payload string back to JSON object to send properly
|
|
416
|
+
# This ensures consistent payload structure between boto3 and HTTP clients
|
|
417
|
+
try:
|
|
418
|
+
body = json.loads(payload) if isinstance(payload, str) else payload
|
|
419
|
+
except json.JSONDecodeError:
|
|
420
|
+
# Fallback for non-JSON strings - wrap in payload object
|
|
421
|
+
self.logger.warning("Failed to parse payload as JSON, wrapping in payload object")
|
|
422
|
+
body = {"payload": payload}
|
|
423
|
+
|
|
424
|
+
try:
|
|
425
|
+
# Make request with timeout
|
|
426
|
+
response = requests.post(
|
|
427
|
+
url,
|
|
428
|
+
params={"qualifier": endpoint_name},
|
|
429
|
+
headers=headers,
|
|
430
|
+
json=body,
|
|
431
|
+
timeout=100,
|
|
432
|
+
stream=True,
|
|
433
|
+
)
|
|
434
|
+
return _handle_http_response(response)
|
|
435
|
+
except requests.exceptions.RequestException as e:
|
|
436
|
+
self.logger.error("Failed to invoke agent endpoint: %s", str(e))
|
|
437
|
+
raise
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
class LocalBedrockAgentCoreClient:
|
|
441
|
+
"""Local Bedrock AgentCore client for invoking endpoints."""
|
|
442
|
+
|
|
443
|
+
def __init__(self, endpoint: str):
|
|
444
|
+
"""Initialize the local client with the given endpoint."""
|
|
445
|
+
self.endpoint = endpoint
|
|
446
|
+
self.logger = logging.getLogger("bedrock_agentcore.http_local")
|
|
447
|
+
|
|
448
|
+
def invoke_endpoint(self, session_id: str, payload: str, workload_access_token: str):
|
|
449
|
+
"""Invoke the endpoint with the given parameters."""
|
|
450
|
+
from bedrock_agentcore.runtime.models import ACCESS_TOKEN_HEADER, SESSION_HEADER
|
|
451
|
+
|
|
452
|
+
url = f"{self.endpoint}/invocations"
|
|
453
|
+
|
|
454
|
+
headers = {
|
|
455
|
+
"Content-Type": "application/json",
|
|
456
|
+
ACCESS_TOKEN_HEADER: workload_access_token,
|
|
457
|
+
SESSION_HEADER: session_id,
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
try:
|
|
461
|
+
body = json.loads(payload) if isinstance(payload, str) else payload
|
|
462
|
+
except json.JSONDecodeError:
|
|
463
|
+
# Fallback for non-JSON strings - wrap in payload object
|
|
464
|
+
self.logger.warning("Failed to parse payload as JSON, wrapping in payload object")
|
|
465
|
+
body = {"payload": payload}
|
|
466
|
+
|
|
467
|
+
try:
|
|
468
|
+
# Make request with timeout
|
|
469
|
+
response = requests.post(url, headers=headers, json=body, timeout=100, stream=True)
|
|
470
|
+
return _handle_http_response(response)
|
|
471
|
+
except requests.exceptions.RequestException as e:
|
|
472
|
+
self.logger.error("Failed to invoke agent endpoint: %s", str(e))
|
|
473
|
+
raise
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Endpoint utilities for BedrockAgentCore services."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
# Environment-configurable constants with fallback defaults
|
|
6
|
+
DP_ENDPOINT_OVERRIDE = os.getenv("BEDROCK_AGENTCORE_DP_ENDPOINT")
|
|
7
|
+
CP_ENDPOINT_OVERRIDE = os.getenv("BEDROCK_AGENTCORE_CP_ENDPOINT")
|
|
8
|
+
DEFAULT_REGION = os.getenv("AWS_REGION", "us-west-2")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_data_plane_endpoint(region: str = DEFAULT_REGION) -> str:
|
|
12
|
+
"""Get the data plane endpoint URL for BedrockAgentCore services.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
region: AWS region to use. Defaults to DEFAULT_REGION.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
The data plane endpoint URL, either from environment override or constructed URL.
|
|
19
|
+
"""
|
|
20
|
+
return DP_ENDPOINT_OVERRIDE or f"https://bedrock-agentcore.{region}.amazonaws.com"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_control_plane_endpoint(region: str = DEFAULT_REGION) -> str:
|
|
24
|
+
"""Get the control plane endpoint URL for BedrockAgentCore services.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
region: AWS region to use. Defaults to DEFAULT_REGION.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
The control plane endpoint URL, either from environment override or constructed URL.
|
|
31
|
+
"""
|
|
32
|
+
return CP_ENDPOINT_OVERRIDE or f"https://bedrock-agentcore-control.{region}.amazonaws.com"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Centralized logging configuration for bedrock-agentcore-starter-toolkit."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
_LOGGING_CONFIGURED = False
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def setup_toolkit_logging(mode: str = "sdk") -> None:
|
|
9
|
+
"""Setup logging for bedrock-agentcore-starter-toolkit.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
mode: "cli" or "sdk" (defaults to "sdk")
|
|
13
|
+
"""
|
|
14
|
+
global _LOGGING_CONFIGURED
|
|
15
|
+
if _LOGGING_CONFIGURED:
|
|
16
|
+
return # Already configured, prevent duplicates
|
|
17
|
+
|
|
18
|
+
if mode == "cli":
|
|
19
|
+
_setup_cli_logging()
|
|
20
|
+
elif mode == "sdk":
|
|
21
|
+
_setup_sdk_logging()
|
|
22
|
+
else:
|
|
23
|
+
raise ValueError(f"Invalid logging mode: {mode}. Must be 'cli' or 'sdk'")
|
|
24
|
+
|
|
25
|
+
_LOGGING_CONFIGURED = True
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _setup_cli_logging() -> None:
|
|
29
|
+
"""Setup logging for CLI usage with RichHandler."""
|
|
30
|
+
try:
|
|
31
|
+
from rich.logging import RichHandler
|
|
32
|
+
|
|
33
|
+
from ..cli.common import console
|
|
34
|
+
|
|
35
|
+
FORMAT = "%(message)s"
|
|
36
|
+
logging.basicConfig(
|
|
37
|
+
level="INFO",
|
|
38
|
+
format=FORMAT,
|
|
39
|
+
handlers=[RichHandler(show_time=False, show_path=False, show_level=False, console=console)],
|
|
40
|
+
force=True, # Override any existing configuration
|
|
41
|
+
)
|
|
42
|
+
except ImportError:
|
|
43
|
+
# Fallback if rich is not available
|
|
44
|
+
_setup_basic_logging()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _setup_sdk_logging() -> None:
|
|
48
|
+
"""Setup logging for SDK usage (notebooks, scripts, imports) with StreamHandler."""
|
|
49
|
+
# Configure logger for ALL toolkit modules (ensures all operation logs appear)
|
|
50
|
+
toolkit_logger = logging.getLogger("bedrock_agentcore_starter_toolkit")
|
|
51
|
+
|
|
52
|
+
if not toolkit_logger.handlers:
|
|
53
|
+
handler = logging.StreamHandler()
|
|
54
|
+
handler.setFormatter(logging.Formatter("%(message)s"))
|
|
55
|
+
toolkit_logger.addHandler(handler)
|
|
56
|
+
toolkit_logger.setLevel(logging.INFO)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _setup_basic_logging() -> None:
|
|
60
|
+
"""Setup basic logging as fallback."""
|
|
61
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s", force=True)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def is_logging_configured() -> bool:
|
|
65
|
+
"""Check if toolkit logging has been configured."""
|
|
66
|
+
return _LOGGING_CONFIGURED
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def reset_logging_config() -> None:
|
|
70
|
+
"""Reset logging configuration state (for testing)."""
|
|
71
|
+
global _LOGGING_CONFIGURED
|
|
72
|
+
_LOGGING_CONFIGURED = False
|