bedrock-agentcore-starter-toolkit 0.1.26__py3-none-any.whl → 0.1.27__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/cli/cli.py +9 -1
- bedrock_agentcore_starter_toolkit/cli/runtime/commands.py +250 -7
- bedrock_agentcore_starter_toolkit/cli/runtime/configuration_manager.py +31 -7
- bedrock_agentcore_starter_toolkit/notebook/runtime/bedrock_agentcore.py +240 -2
- bedrock_agentcore_starter_toolkit/operations/memory/manager.py +20 -33
- bedrock_agentcore_starter_toolkit/operations/memory/models/strategies/base.py +2 -0
- bedrock_agentcore_starter_toolkit/operations/memory/models/strategies/self_managed.py +107 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/__init__.py +4 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/configure.py +120 -5
- bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +0 -53
- bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +213 -16
- bedrock_agentcore_starter_toolkit/operations/runtime/models.py +19 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/status.py +30 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/stop_session.py +123 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/vpc_validation.py +196 -0
- bedrock_agentcore_starter_toolkit/services/runtime.py +43 -1
- bedrock_agentcore_starter_toolkit/utils/runtime/schema.py +44 -1
- {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/METADATA +8 -8
- {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/RECORD +23 -20
- {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/WHEEL +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/entry_points.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/licenses/LICENSE.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/licenses/NOTICE.txt +0 -0
|
@@ -6,12 +6,14 @@ from typing import Any, Dict, List, Literal, Optional
|
|
|
6
6
|
|
|
7
7
|
from ...operations.runtime import (
|
|
8
8
|
configure_bedrock_agentcore,
|
|
9
|
+
destroy_bedrock_agentcore,
|
|
9
10
|
get_status,
|
|
10
11
|
invoke_bedrock_agentcore,
|
|
11
12
|
launch_bedrock_agentcore,
|
|
13
|
+
stop_runtime_session,
|
|
12
14
|
validate_agent_name,
|
|
13
15
|
)
|
|
14
|
-
from ...operations.runtime.models import ConfigureResult, LaunchResult, StatusResult
|
|
16
|
+
from ...operations.runtime.models import ConfigureResult, DestroyResult, LaunchResult, StatusResult
|
|
15
17
|
|
|
16
18
|
# Setup centralized logging for SDK usage (notebooks, scripts, imports)
|
|
17
19
|
from ...utils.logging_config import setup_toolkit_logging
|
|
@@ -48,8 +50,13 @@ class Runtime:
|
|
|
48
50
|
region: Optional[str] = None,
|
|
49
51
|
protocol: Optional[Literal["HTTP", "MCP", "A2A"]] = None,
|
|
50
52
|
disable_otel: bool = False,
|
|
51
|
-
memory_mode: Literal["NO_MEMORY", "STM_ONLY", "STM_AND_LTM"] = "
|
|
53
|
+
memory_mode: Literal["NO_MEMORY", "STM_ONLY", "STM_AND_LTM"] = "NO_MEMORY",
|
|
52
54
|
non_interactive: bool = True,
|
|
55
|
+
vpc_enabled: bool = False,
|
|
56
|
+
vpc_subnets: Optional[List[str]] = None,
|
|
57
|
+
vpc_security_groups: Optional[List[str]] = None,
|
|
58
|
+
idle_timeout: Optional[int] = None,
|
|
59
|
+
max_lifetime: Optional[int] = None,
|
|
53
60
|
) -> ConfigureResult:
|
|
54
61
|
"""Configure Bedrock AgentCore from notebook using an entrypoint file.
|
|
55
62
|
|
|
@@ -75,6 +82,11 @@ class Runtime:
|
|
|
75
82
|
- "STM_ONLY": Short-term memory only (default)
|
|
76
83
|
- "STM_AND_LTM": Short-term + long-term memory with strategy extraction
|
|
77
84
|
non_interactive: Skip interactive prompts and use defaults (default: True)
|
|
85
|
+
vpc_enabled: Enable VPC networking mode (requires vpc_subnets and vpc_security_groups)
|
|
86
|
+
vpc_subnets: List of VPC subnet IDs (required if vpc_enabled=True)
|
|
87
|
+
vpc_security_groups: List of VPC security group IDs (required if vpc_enabled=True)
|
|
88
|
+
idle_timeout: Idle runtime session timeout in seconds (60-28800)
|
|
89
|
+
max_lifetime: Maximum instance lifetime in seconds (60-28800)
|
|
78
90
|
|
|
79
91
|
Returns:
|
|
80
92
|
ConfigureResult with configuration details
|
|
@@ -83,6 +95,14 @@ class Runtime:
|
|
|
83
95
|
# Default: STM only (backward compatible)
|
|
84
96
|
runtime.configure(entrypoint='handler.py')
|
|
85
97
|
|
|
98
|
+
# With VPC networking
|
|
99
|
+
runtime.configure(
|
|
100
|
+
entrypoint='handler.py',
|
|
101
|
+
vpc_enabled=True,
|
|
102
|
+
vpc_subnets=['subnet-abc123', 'subnet-def456'],
|
|
103
|
+
vpc_security_groups=['sg-xyz789']
|
|
104
|
+
)
|
|
105
|
+
|
|
86
106
|
# Explicitly enable LTM
|
|
87
107
|
runtime.configure(entrypoint='handler.py', memory_mode='STM_AND_LTM')
|
|
88
108
|
|
|
@@ -91,10 +111,60 @@ class Runtime:
|
|
|
91
111
|
|
|
92
112
|
# Invalid - raises error
|
|
93
113
|
runtime.configure(entrypoint='handler.py', disable_memory=True, memory_mode='STM_AND_LTM')
|
|
114
|
+
|
|
115
|
+
# With lifecycle settings
|
|
116
|
+
runtime.configure(
|
|
117
|
+
entrypoint='handler.py',
|
|
118
|
+
idle_timeout=1800, # 30 minutes
|
|
119
|
+
max_lifetime=7200 # 2 hours
|
|
120
|
+
)
|
|
94
121
|
"""
|
|
95
122
|
if protocol and protocol.upper() not in ["HTTP", "MCP", "A2A"]:
|
|
96
123
|
raise ValueError("protocol must be either HTTP or MCP or A2A")
|
|
97
124
|
|
|
125
|
+
# Validate VPC configuration
|
|
126
|
+
if vpc_enabled:
|
|
127
|
+
if not vpc_subnets or not vpc_security_groups:
|
|
128
|
+
raise ValueError(
|
|
129
|
+
"VPC mode requires both vpc_subnets and vpc_security_groups.\n"
|
|
130
|
+
"Example: runtime.configure(entrypoint='handler.py', vpc_enabled=True, "
|
|
131
|
+
"vpc_subnets=['subnet-abc123', 'subnet-def456'], "
|
|
132
|
+
"vpc_security_groups=['sg-xyz789'])"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Validate subnet ID format - UPDATED
|
|
136
|
+
for subnet_id in vpc_subnets:
|
|
137
|
+
if not subnet_id.startswith("subnet-"):
|
|
138
|
+
raise ValueError(f"Invalid subnet ID format: {subnet_id}\nSubnet IDs must start with 'subnet-'")
|
|
139
|
+
if len(subnet_id) < 15: # "subnet-" + 8 chars minimum
|
|
140
|
+
raise ValueError(
|
|
141
|
+
f"Invalid subnet ID format: {subnet_id}\n"
|
|
142
|
+
f"Subnet ID is too short. Expected: subnet-xxxxxxxx (at least 8 hex chars)"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Validate security group ID format - UPDATED
|
|
146
|
+
for sg_id in vpc_security_groups:
|
|
147
|
+
if not sg_id.startswith("sg-"):
|
|
148
|
+
raise ValueError(
|
|
149
|
+
f"Invalid security group ID format: {sg_id}\nSecurity group IDs must start with 'sg-'"
|
|
150
|
+
)
|
|
151
|
+
if len(sg_id) < 11: # "sg-" + 8 chars minimum
|
|
152
|
+
raise ValueError(
|
|
153
|
+
f"Invalid security group ID format: {sg_id}\n"
|
|
154
|
+
f"Security group ID is too short. Expected: sg-xxxxxxxx (at least 8 hex chars)"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
log.info(
|
|
158
|
+
"VPC mode enabled with %d subnets and %d security groups", len(vpc_subnets), len(vpc_security_groups)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
elif vpc_subnets or vpc_security_groups:
|
|
162
|
+
raise ValueError(
|
|
163
|
+
"vpc_subnets and vpc_security_groups require vpc_enabled=True.\n"
|
|
164
|
+
"Use: runtime.configure(entrypoint='handler.py', vpc_enabled=True, "
|
|
165
|
+
"vpc_subnets=[...], vpc_security_groups=[...])"
|
|
166
|
+
)
|
|
167
|
+
|
|
98
168
|
# Parse entrypoint to get agent name
|
|
99
169
|
file_path, file_name = parse_entrypoint(entrypoint)
|
|
100
170
|
agent_name = agent_name or file_name
|
|
@@ -152,6 +222,11 @@ class Runtime:
|
|
|
152
222
|
region=region,
|
|
153
223
|
protocol=protocol.upper() if protocol else None,
|
|
154
224
|
non_interactive=non_interactive,
|
|
225
|
+
vpc_enabled=vpc_enabled,
|
|
226
|
+
vpc_subnets=vpc_subnets,
|
|
227
|
+
vpc_security_groups=vpc_security_groups,
|
|
228
|
+
idle_timeout=idle_timeout,
|
|
229
|
+
max_lifetime=max_lifetime,
|
|
155
230
|
)
|
|
156
231
|
|
|
157
232
|
self._config_path = result.config_path
|
|
@@ -319,6 +394,36 @@ class Runtime:
|
|
|
319
394
|
)
|
|
320
395
|
return result.response
|
|
321
396
|
|
|
397
|
+
def stop_session(self, session_id: Optional[str] = None) -> Dict[str, Any]:
|
|
398
|
+
"""Stop an active runtime session.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
session_id: Optional session ID to stop. If not provided, uses tracked session.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Dictionary with stop session result details
|
|
405
|
+
|
|
406
|
+
Raises:
|
|
407
|
+
ValueError: If no session ID provided or found, or agent not configured
|
|
408
|
+
"""
|
|
409
|
+
if not self._config_path:
|
|
410
|
+
log.warning("Agent not configured")
|
|
411
|
+
log.info("Call .configure() first to set up your agent")
|
|
412
|
+
raise ValueError("Must configure first. Call .configure() first.")
|
|
413
|
+
|
|
414
|
+
result = stop_runtime_session(
|
|
415
|
+
config_path=self._config_path,
|
|
416
|
+
session_id=session_id,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
log.info("Session stopped: %s", result.session_id)
|
|
420
|
+
return {
|
|
421
|
+
"session_id": result.session_id,
|
|
422
|
+
"agent_name": result.agent_name,
|
|
423
|
+
"status_code": result.status_code,
|
|
424
|
+
"message": result.message,
|
|
425
|
+
}
|
|
426
|
+
|
|
322
427
|
def status(self) -> StatusResult:
|
|
323
428
|
"""Get Bedrock AgentCore status including config and runtime details.
|
|
324
429
|
|
|
@@ -335,6 +440,76 @@ class Runtime:
|
|
|
335
440
|
log.info("Retrieved Bedrock AgentCore status for: %s", self.name or "Bedrock AgentCore")
|
|
336
441
|
return result
|
|
337
442
|
|
|
443
|
+
def destroy(
|
|
444
|
+
self,
|
|
445
|
+
dry_run: bool = False,
|
|
446
|
+
delete_ecr_repo: bool = False,
|
|
447
|
+
) -> DestroyResult:
|
|
448
|
+
"""Destroy Bedrock AgentCore resources from notebook.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
dry_run: If True, only show what would be destroyed without actually doing it
|
|
452
|
+
delete_ecr_repo: If True, also delete the ECR repository after removing images
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
DestroyResult with details of what was destroyed or would be destroyed
|
|
456
|
+
|
|
457
|
+
Example:
|
|
458
|
+
# Preview what would be destroyed
|
|
459
|
+
result = runtime.destroy(dry_run=True)
|
|
460
|
+
|
|
461
|
+
# Destroy resources (keeping ECR repository)
|
|
462
|
+
result = runtime.destroy()
|
|
463
|
+
|
|
464
|
+
# Destroy resources including ECR repository
|
|
465
|
+
result = runtime.destroy(delete_ecr_repo=True)
|
|
466
|
+
"""
|
|
467
|
+
if not self._config_path:
|
|
468
|
+
log.warning("Configuration not found")
|
|
469
|
+
log.info("Call .configure() first to set up your agent")
|
|
470
|
+
log.info("Example: runtime.configure(entrypoint='my_agent.py')")
|
|
471
|
+
raise ValueError("Must configure first. Call .configure() first.")
|
|
472
|
+
|
|
473
|
+
if dry_run:
|
|
474
|
+
log.info("🔍 Dry run mode: showing what would be destroyed")
|
|
475
|
+
else:
|
|
476
|
+
log.info("🗑️ Destroying Bedrock AgentCore resources")
|
|
477
|
+
if delete_ecr_repo:
|
|
478
|
+
log.info(" • Including ECR repository deletion")
|
|
479
|
+
|
|
480
|
+
try:
|
|
481
|
+
result = destroy_bedrock_agentcore(
|
|
482
|
+
config_path=self._config_path,
|
|
483
|
+
agent_name=self.name,
|
|
484
|
+
dry_run=dry_run,
|
|
485
|
+
force=True, # Always force in notebook interface to avoid interactive prompts
|
|
486
|
+
delete_ecr_repo=delete_ecr_repo,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
# Log summary
|
|
490
|
+
if dry_run:
|
|
491
|
+
log.info("Dry run completed. Would destroy %d resources", len(result.resources_removed))
|
|
492
|
+
else:
|
|
493
|
+
log.info("Destroy completed. Removed %d resources", len(result.resources_removed))
|
|
494
|
+
|
|
495
|
+
# Clear our internal state if destruction was successful and not a dry run
|
|
496
|
+
if not result.errors:
|
|
497
|
+
self._config_path = None
|
|
498
|
+
self.name = None
|
|
499
|
+
|
|
500
|
+
# Log warnings and errors
|
|
501
|
+
for warning in result.warnings:
|
|
502
|
+
log.warning("⚠️ %s", warning)
|
|
503
|
+
|
|
504
|
+
for error in result.errors:
|
|
505
|
+
log.error("❌ %s", error)
|
|
506
|
+
|
|
507
|
+
return result
|
|
508
|
+
|
|
509
|
+
except Exception as e:
|
|
510
|
+
log.error("Destroy operation failed: %s", str(e))
|
|
511
|
+
raise
|
|
512
|
+
|
|
338
513
|
def help_deployment_modes(self):
|
|
339
514
|
"""Display information about available deployment modes and migration guidance."""
|
|
340
515
|
print("\n🚀 Bedrock AgentCore Deployment Modes:")
|
|
@@ -370,3 +545,66 @@ class Runtime:
|
|
|
370
545
|
print(" runtime.launch() # Uses CodeBuild by default")
|
|
371
546
|
print(' runtime.invoke({"prompt": "Hello"})')
|
|
372
547
|
print()
|
|
548
|
+
|
|
549
|
+
def help_vpc_networking(self):
|
|
550
|
+
"""Display information about VPC networking configuration."""
|
|
551
|
+
print("\n🔒 VPC Networking for Bedrock AgentCore")
|
|
552
|
+
print("=" * 50)
|
|
553
|
+
|
|
554
|
+
print("\n📋 What is VPC Networking?")
|
|
555
|
+
print(" VPC (Virtual Private Cloud) mode allows your agent to:")
|
|
556
|
+
print(" • Access private resources (databases, internal APIs)")
|
|
557
|
+
print(" • Run in isolated network environments")
|
|
558
|
+
print(" • Comply with enterprise security requirements")
|
|
559
|
+
|
|
560
|
+
print("\n⚙️ Prerequisites:")
|
|
561
|
+
print(" You must have existing AWS resources:")
|
|
562
|
+
print(" • VPC with private subnets")
|
|
563
|
+
print(" • Security groups with appropriate rules")
|
|
564
|
+
print(" • (Optional) NAT Gateway for internet access")
|
|
565
|
+
print(" • (Optional) VPC endpoints for AWS services")
|
|
566
|
+
|
|
567
|
+
print("\n🚀 Basic Usage:")
|
|
568
|
+
print(" runtime.configure(")
|
|
569
|
+
print(" entrypoint='my_agent.py',")
|
|
570
|
+
print(" vpc_enabled=True,")
|
|
571
|
+
print(" vpc_subnets=['subnet-abc123', 'subnet-def456'],")
|
|
572
|
+
print(" vpc_security_groups=['sg-xyz789']")
|
|
573
|
+
print(" )")
|
|
574
|
+
print(" runtime.launch()")
|
|
575
|
+
|
|
576
|
+
print("\n📝 Requirements:")
|
|
577
|
+
print(" • All subnets must be in the same VPC")
|
|
578
|
+
print(" • Security groups must be in the same VPC as subnets")
|
|
579
|
+
print(" • Use subnets from multiple AZs for high availability")
|
|
580
|
+
print(" • Security groups must allow outbound HTTPS (443) traffic")
|
|
581
|
+
|
|
582
|
+
print("\n⚠️ Important Notes:")
|
|
583
|
+
print(" • Network configuration is IMMUTABLE after agent creation")
|
|
584
|
+
print(" • Cannot migrate existing PUBLIC agents to VPC mode")
|
|
585
|
+
print(" • Create a new agent if you need to change network settings")
|
|
586
|
+
print(" • Without NAT gateway, agent cannot pull container images")
|
|
587
|
+
|
|
588
|
+
print("\n🔍 Security Group Requirements:")
|
|
589
|
+
print(" Your security groups must allow:")
|
|
590
|
+
print(" • Outbound HTTPS (443) - for AWS API calls")
|
|
591
|
+
print(" • Outbound to your private resources (as needed)")
|
|
592
|
+
print(" • Inbound rules are typically not required")
|
|
593
|
+
|
|
594
|
+
print("\n💡 Example with All Features:")
|
|
595
|
+
print(" runtime.configure(")
|
|
596
|
+
print(" entrypoint='my_agent.py',")
|
|
597
|
+
print(" execution_role='arn:aws:iam::123456789012:role/MyRole',")
|
|
598
|
+
print(" vpc_enabled=True,")
|
|
599
|
+
print(" vpc_subnets=['subnet-abc123', 'subnet-def456'],")
|
|
600
|
+
print(" vpc_security_groups=['sg-xyz789'],")
|
|
601
|
+
print(" memory_mode='STM_AND_LTM'")
|
|
602
|
+
print(" )")
|
|
603
|
+
|
|
604
|
+
print("\n📚 Related Commands:")
|
|
605
|
+
print(" runtime.status() # View network configuration")
|
|
606
|
+
print(" runtime.help_deployment_modes() # Deployment options")
|
|
607
|
+
|
|
608
|
+
print("\n🔗 More Information:")
|
|
609
|
+
print(" See AWS VPC documentation for networking setup")
|
|
610
|
+
print()
|
|
@@ -9,6 +9,7 @@ from typing import Any, Dict, List, Optional, Union
|
|
|
9
9
|
import boto3
|
|
10
10
|
from botocore.config import Config as BotocoreConfig
|
|
11
11
|
from botocore.exceptions import ClientError
|
|
12
|
+
from rich.console import Console
|
|
12
13
|
|
|
13
14
|
from .constants import MemoryStatus, MemoryStrategyStatus, OverrideType, StrategyType
|
|
14
15
|
from .models import convert_strategies_to_dicts
|
|
@@ -32,6 +33,7 @@ class MemoryManager:
|
|
|
32
33
|
region_name: Optional[str] = None,
|
|
33
34
|
boto3_session: Optional[boto3.Session] = None,
|
|
34
35
|
boto_client_config: Optional[BotocoreConfig] = None,
|
|
36
|
+
console: Optional[Console] = None,
|
|
35
37
|
):
|
|
36
38
|
"""Initialize MemoryManager with AWS region.
|
|
37
39
|
|
|
@@ -42,12 +44,14 @@ class MemoryManager:
|
|
|
42
44
|
parameter is also specified, validation will ensure they match.
|
|
43
45
|
boto_client_config: Optional boto3 client configuration. If provided, will be
|
|
44
46
|
merged with default configuration including user agent.
|
|
47
|
+
console: Optional Rich console instance for output (creates new if not provided)
|
|
45
48
|
|
|
46
49
|
Raises:
|
|
47
50
|
ValueError: If region_name parameter conflicts with boto3_session region.
|
|
48
51
|
"""
|
|
49
52
|
session = boto3_session or boto3.Session()
|
|
50
53
|
session_region = session.region_name
|
|
54
|
+
self.console = console or Console()
|
|
51
55
|
|
|
52
56
|
# Validate region consistency if both are provided
|
|
53
57
|
if region_name and boto3_session and session_region and region_name != session_region:
|
|
@@ -280,32 +284,7 @@ class MemoryManager:
|
|
|
280
284
|
if memory_id is None:
|
|
281
285
|
memory_id = ""
|
|
282
286
|
logger.info("Created memory %s, waiting for ACTIVE status...", memory_id)
|
|
283
|
-
|
|
284
|
-
start_time = time.time()
|
|
285
|
-
while time.time() - start_time < max_wait:
|
|
286
|
-
elapsed = int(time.time() - start_time)
|
|
287
|
-
|
|
288
|
-
try:
|
|
289
|
-
status = self.get_memory_status(memory_id)
|
|
290
|
-
|
|
291
|
-
if status == MemoryStatus.ACTIVE.value:
|
|
292
|
-
logger.info("Memory %s is now ACTIVE (took %d seconds)", memory_id, elapsed)
|
|
293
|
-
return memory
|
|
294
|
-
elif status == MemoryStatus.FAILED.value:
|
|
295
|
-
# Get failure reason if available
|
|
296
|
-
response = self._control_plane_client.get_memory(memoryId=memory_id)
|
|
297
|
-
failure_reason = response["memory"].get("failureReason", "Unknown")
|
|
298
|
-
raise RuntimeError("Memory creation failed: %s" % failure_reason)
|
|
299
|
-
else:
|
|
300
|
-
logger.debug("Memory status: %s (%d seconds elapsed)", status, elapsed)
|
|
301
|
-
|
|
302
|
-
except ClientError as e:
|
|
303
|
-
logger.error("Error checking memory status: %s", e)
|
|
304
|
-
raise
|
|
305
|
-
|
|
306
|
-
time.sleep(poll_interval)
|
|
307
|
-
|
|
308
|
-
raise TimeoutError(f"Memory {memory_id} did not become ACTIVE within {max_wait} seconds")
|
|
287
|
+
return self._wait_for_memory_active(memory_id, max_wait, poll_interval)
|
|
309
288
|
|
|
310
289
|
def create_memory_and_wait(
|
|
311
290
|
self,
|
|
@@ -1008,6 +987,8 @@ class MemoryManager:
|
|
|
1008
987
|
)
|
|
1009
988
|
|
|
1010
989
|
start_time = time.time()
|
|
990
|
+
last_status_print = 0
|
|
991
|
+
status_print_interval = 10 # Print status every 10 seconds
|
|
1011
992
|
|
|
1012
993
|
while time.time() - start_time < max_wait:
|
|
1013
994
|
elapsed = int(time.time() - start_time)
|
|
@@ -1029,13 +1010,18 @@ class MemoryManager:
|
|
|
1029
1010
|
self._check_strategies_terminal_state(strategies)
|
|
1030
1011
|
)
|
|
1031
1012
|
|
|
1032
|
-
#
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1013
|
+
# Print status update every 10 seconds
|
|
1014
|
+
if elapsed - last_status_print >= status_print_interval:
|
|
1015
|
+
if strategies:
|
|
1016
|
+
active_count = len([s for s in strategy_statuses if s == "ACTIVE"])
|
|
1017
|
+
self.console.log(
|
|
1018
|
+
f" ⏳ Memory: {memory_status}, "
|
|
1019
|
+
f"Strategies: {active_count}/{len(strategies)} active "
|
|
1020
|
+
f"({elapsed}s elapsed)"
|
|
1021
|
+
)
|
|
1022
|
+
else:
|
|
1023
|
+
self.console.log(f" ⏳ Memory: {memory_status} ({elapsed}s elapsed)")
|
|
1024
|
+
last_status_print = elapsed
|
|
1039
1025
|
|
|
1040
1026
|
# Check if memory is ACTIVE and all strategies are in terminal states
|
|
1041
1027
|
if memory_status == MemoryStatus.ACTIVE.value and all_strategies_terminal:
|
|
@@ -1048,6 +1034,7 @@ class MemoryManager:
|
|
|
1048
1034
|
memory_id,
|
|
1049
1035
|
elapsed,
|
|
1050
1036
|
)
|
|
1037
|
+
self.console.log(f" ✅ Memory is ACTIVE (took {elapsed}s)")
|
|
1051
1038
|
return Memory(memory)
|
|
1052
1039
|
|
|
1053
1040
|
# Wait before next check
|
|
@@ -7,6 +7,7 @@ from pydantic import BaseModel, ConfigDict, Field
|
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from .custom import CustomSemanticStrategy, CustomSummaryStrategy, CustomUserPreferenceStrategy
|
|
10
|
+
from .self_managed import SelfManagedStrategy
|
|
10
11
|
from .semantic import SemanticStrategy
|
|
11
12
|
from .summary import SummaryStrategy
|
|
12
13
|
from .user_preference import UserPreferenceStrategy
|
|
@@ -73,5 +74,6 @@ StrategyType = Union[
|
|
|
73
74
|
"CustomSummaryStrategy",
|
|
74
75
|
"CustomUserPreferenceStrategy",
|
|
75
76
|
"UserPreferenceStrategy",
|
|
77
|
+
"SelfManagedStrategy",
|
|
76
78
|
Dict[str, Any], # Backward compatibility with dict-based strategies
|
|
77
79
|
]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Self managed memory strategy implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Union
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from .base import BaseStrategy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MessageBasedTrigger(BaseModel):
|
|
11
|
+
"""Trigger configuration based on message."""
|
|
12
|
+
|
|
13
|
+
message_count: int = Field(default=6, description="Number of messages that trigger memory processing.")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TokenBasedTrigger(BaseModel):
|
|
17
|
+
"""Trigger configuration based on tokens."""
|
|
18
|
+
|
|
19
|
+
token_count: int = Field(default=5000, description="Number of tokens that trigger memory processing.")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TimeBasedTrigger(BaseModel):
|
|
23
|
+
"""Trigger configuration based on time."""
|
|
24
|
+
|
|
25
|
+
idle_session_timeout: int = Field(
|
|
26
|
+
default=20, description="Idle session timeout (seconds) that triggers memory processing."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class InvocationConfig(BaseModel):
|
|
31
|
+
"""Configuration to invoke customer-owned memory processing pipeline."""
|
|
32
|
+
|
|
33
|
+
topic_arn: str = Field(..., description="The ARN of the SNS topic for job notifications.")
|
|
34
|
+
payload_delivery_bucket_name: str = Field(..., description="S3 bucket name for event payload delivery.")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SelfManagedStrategy(BaseStrategy):
|
|
38
|
+
"""Self-managed memory strategy with custom processing pipeline.
|
|
39
|
+
|
|
40
|
+
This strategy allows complete control over memory processing through
|
|
41
|
+
customer-owned pipelines triggered by configurable conditions.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
trigger_conditions: List of conditions that trigger memory processing
|
|
45
|
+
invocation_config: Configuration for invoking memory processing pipeline
|
|
46
|
+
historical_context_window_size: Number of historical messages to include
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
strategy = SelfManagedStrategy(
|
|
50
|
+
name="SelfManagedStrategy",
|
|
51
|
+
description="Self-managed processing with SNS notifications",
|
|
52
|
+
trigger_conditions=[
|
|
53
|
+
MessageBasedTrigger(message_count=10),
|
|
54
|
+
TokenBasedTrigger(token_count=8000)
|
|
55
|
+
],
|
|
56
|
+
invocation_config=InvocationConfig(
|
|
57
|
+
topic_arn="arn:aws:sns:us-east-1:123456789012:memory-processing",
|
|
58
|
+
payload_delivery_bucket_name="my-memory-bucket"
|
|
59
|
+
),
|
|
60
|
+
historical_context_window_size=6
|
|
61
|
+
)
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
trigger_conditions: List[Union[MessageBasedTrigger, TokenBasedTrigger, TimeBasedTrigger]] = Field(
|
|
65
|
+
default_factory=list
|
|
66
|
+
)
|
|
67
|
+
invocation_config: InvocationConfig
|
|
68
|
+
historical_context_window_size: int = Field(
|
|
69
|
+
default=4, description="Number of historical messages to include in processing context."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
73
|
+
"""Convert to dictionary format for API calls."""
|
|
74
|
+
config = {
|
|
75
|
+
"name": self.name,
|
|
76
|
+
"configuration": {
|
|
77
|
+
"selfManagedConfiguration": {
|
|
78
|
+
"triggerConditions": self._convert_trigger_conditions(),
|
|
79
|
+
"invocationConfiguration": self._convert_invocation_config(),
|
|
80
|
+
"historicalContextWindowSize": self.historical_context_window_size,
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if self.description is not None:
|
|
86
|
+
config["description"] = self.description
|
|
87
|
+
|
|
88
|
+
return {"customMemoryStrategy": config}
|
|
89
|
+
|
|
90
|
+
def _convert_trigger_conditions(self) -> List[Dict[str, Any]]:
|
|
91
|
+
"""Convert trigger conditions to API format."""
|
|
92
|
+
conditions = []
|
|
93
|
+
for condition in self.trigger_conditions:
|
|
94
|
+
if isinstance(condition, MessageBasedTrigger):
|
|
95
|
+
conditions.append({"messageBasedTrigger": {"messageCount": condition.message_count}})
|
|
96
|
+
elif isinstance(condition, TokenBasedTrigger):
|
|
97
|
+
conditions.append({"tokenBasedTrigger": {"tokenCount": condition.token_count}})
|
|
98
|
+
elif isinstance(condition, TimeBasedTrigger):
|
|
99
|
+
conditions.append({"timeBasedTrigger": {"idleSessionTimeout": condition.idle_session_timeout}})
|
|
100
|
+
return conditions
|
|
101
|
+
|
|
102
|
+
def _convert_invocation_config(self) -> Dict[str, Any]:
|
|
103
|
+
"""Convert invocation config to API format."""
|
|
104
|
+
return {
|
|
105
|
+
"topicArn": self.invocation_config.topic_arn,
|
|
106
|
+
"payloadDeliveryBucketName": self.invocation_config.payload_delivery_bucket_name,
|
|
107
|
+
}
|
|
@@ -18,8 +18,10 @@ from .models import (
|
|
|
18
18
|
LaunchResult,
|
|
19
19
|
StatusConfigInfo,
|
|
20
20
|
StatusResult,
|
|
21
|
+
StopSessionResult,
|
|
21
22
|
)
|
|
22
23
|
from .status import get_status
|
|
24
|
+
from .stop_session import stop_runtime_session
|
|
23
25
|
|
|
24
26
|
__all__ = [
|
|
25
27
|
"configure_bedrock_agentcore",
|
|
@@ -31,6 +33,7 @@ __all__ = [
|
|
|
31
33
|
"infer_agent_name",
|
|
32
34
|
"launch_bedrock_agentcore",
|
|
33
35
|
"invoke_bedrock_agentcore",
|
|
36
|
+
"stop_runtime_session",
|
|
34
37
|
"get_status",
|
|
35
38
|
"ConfigureResult",
|
|
36
39
|
"DestroyResult",
|
|
@@ -38,4 +41,5 @@ __all__ = [
|
|
|
38
41
|
"LaunchResult",
|
|
39
42
|
"StatusResult",
|
|
40
43
|
"StatusConfigInfo",
|
|
44
|
+
"StopSessionResult",
|
|
41
45
|
]
|