Skip to content

Commit 29bab2e

Browse files
jona62Jonathan James
andauthored
feat: Add validation to check to get_or_create_memory to provide a truly idempotent experience (#227)
Co-authored-by: Jonathan James <[email protected]>
1 parent db5f2e0 commit 29bab2e

File tree

8 files changed

+1953
-93
lines changed

8 files changed

+1953
-93
lines changed

src/bedrock_agentcore_starter_toolkit/operations/memory/manager.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from .models.MemoryStrategy import MemoryStrategy
1717
from .models.MemorySummary import MemorySummary
1818
from .models.strategies import BaseStrategy
19+
from .strategy_validator import validate_existing_memory_strategies
1920

2021
logger = logging.getLogger(__name__)
2122

@@ -384,7 +385,7 @@ def get_or_create_memory(
384385
385386
Args:
386387
name: Memory name
387-
strategies: List of typed strategy objects or dictionary configurations
388+
strategies: Optional List of typed strategy objects or dictionary configurations
388389
description: Optional description
389390
event_expiry_days: How long to retain events (default: 90 days)
390391
memory_execution_role_arn: IAM role ARN for memory execution
@@ -393,6 +394,9 @@ def get_or_create_memory(
393394
Returns:
394395
Memory object, either newly created or existing
395396
397+
Raises:
398+
ValueError: If strategies are provided but existing memory has different strategies
399+
396400
Example:
397401
from bedrock_agentcore_starter_toolkit.operations.memory.models import SemanticStrategy
398402
@@ -424,6 +428,13 @@ def get_or_create_memory(
424428
else:
425429
logger.info("Memory already exists. Using existing memory ID: %s", memory_summary.id)
426430
memory = self.get_memory(memory_summary.id)
431+
432+
# Validate strategies if provided using deep comparison
433+
if strategies is not None:
434+
existing_strategies = memory.get("strategies", memory.get("memoryStrategies", []))
435+
memory_name = memory.get("name")
436+
validate_existing_memory_strategies(existing_strategies, strategies, memory_name)
437+
427438
return memory
428439
except ClientError as e:
429440
# Failed to create memory

src/bedrock_agentcore_starter_toolkit/operations/memory/models/strategies/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"""
3232

3333
from .base import BaseStrategy, ConsolidationConfig, ExtractionConfig, StrategyType
34-
from .custom import CustomSemanticStrategy
34+
from .custom import CustomSemanticStrategy, CustomSummaryStrategy, CustomUserPreferenceStrategy
3535
from .semantic import SemanticStrategy
3636
from .summary import SummaryStrategy
3737
from .user_preference import UserPreferenceStrategy
@@ -47,4 +47,6 @@
4747
"SummaryStrategy",
4848
"UserPreferenceStrategy",
4949
"CustomSemanticStrategy",
50+
"CustomSummaryStrategy",
51+
"CustomUserPreferenceStrategy",
5052
]

src/bedrock_agentcore_starter_toolkit/operations/memory/models/strategies/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from pydantic import BaseModel, ConfigDict, Field
77

88
if TYPE_CHECKING:
9-
from .custom import CustomSemanticStrategy
9+
from .custom import CustomSemanticStrategy, CustomSummaryStrategy, CustomUserPreferenceStrategy
1010
from .semantic import SemanticStrategy
1111
from .summary import SummaryStrategy
1212
from .user_preference import UserPreferenceStrategy
@@ -70,6 +70,8 @@ def to_dict(self) -> Dict[str, Any]:
7070
"SemanticStrategy",
7171
"SummaryStrategy",
7272
"CustomSemanticStrategy",
73+
"CustomSummaryStrategy",
74+
"CustomUserPreferenceStrategy",
7375
"UserPreferenceStrategy",
7476
Dict[str, Any], # Backward compatibility with dict-based strategies
7577
]

src/bedrock_agentcore_starter_toolkit/operations/memory/models/strategies/custom.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,122 @@ def _convert_consolidation_config(self) -> Dict[str, Any]:
7373
if self.consolidation_config.model_id is not None:
7474
config["modelId"] = self.consolidation_config.model_id
7575
return config
76+
77+
78+
class CustomSummaryStrategy(BaseStrategy):
79+
"""Custom summary strategy with configurable consolidation.
80+
81+
This strategy allows customization of consolidation using custom prompts and models.
82+
83+
Attributes:
84+
consolidation_config: Configuration for consolidation operations
85+
86+
Example:
87+
strategy = CustomSummaryStrategy(
88+
name="CustomSummary",
89+
description="Custom summary extraction with specific prompts",
90+
consolidation_config=ConsolidationConfig(
91+
append_to_prompt="Consolidate business insights",
92+
model_id="anthropic.claude-3-haiku-20240307-v1:0"
93+
),
94+
namespaces=["custom/{actorId}/{sessionId}"]
95+
)
96+
"""
97+
98+
consolidation_config: ConsolidationConfig = Field(..., description="Consolidation configuration")
99+
100+
def to_dict(self) -> Dict[str, Any]:
101+
"""Convert to dictionary format for API calls."""
102+
config = {
103+
"name": self.name,
104+
"configuration": {
105+
"summaryOverride": {
106+
"consolidation": self._convert_consolidation_config(),
107+
}
108+
},
109+
}
110+
111+
if self.description is not None:
112+
config["description"] = self.description
113+
114+
if self.namespaces is not None:
115+
config["namespaces"] = self.namespaces
116+
117+
return {"customMemoryStrategy": config}
118+
119+
def _convert_consolidation_config(self) -> Dict[str, Any]:
120+
"""Convert consolidation config to API format."""
121+
config = {}
122+
if self.consolidation_config.append_to_prompt is not None:
123+
config["appendToPrompt"] = self.consolidation_config.append_to_prompt
124+
if self.consolidation_config.model_id is not None:
125+
config["modelId"] = self.consolidation_config.model_id
126+
return config
127+
128+
129+
class CustomUserPreferenceStrategy(BaseStrategy):
130+
"""Custom userPreference strategy with configurable extraction and consolidation.
131+
132+
This strategy allows customization of both extraction and consolidation
133+
processes using custom prompts and models.
134+
135+
Attributes:
136+
extraction_config: Configuration for extraction operations
137+
consolidation_config: Configuration for consolidation operations
138+
139+
Example:
140+
strategy = CustomUserPreferenceStrategy(
141+
name="CustomUserPreference",
142+
description="Custom user preference extraction with specific prompts",
143+
extraction_config=ExtractionConfig(
144+
append_to_prompt="Extract key business insights",
145+
model_id="anthropic.claude-3-sonnet-20240229-v1:0"
146+
),
147+
consolidation_config=ConsolidationConfig(
148+
append_to_prompt="Consolidate business insights",
149+
model_id="anthropic.claude-3-haiku-20240307-v1:0"
150+
),
151+
namespaces=["custom/{actorId}/{sessionId}"]
152+
)
153+
"""
154+
155+
extraction_config: ExtractionConfig = Field(..., description="Extraction configuration")
156+
consolidation_config: ConsolidationConfig = Field(..., description="Consolidation configuration")
157+
158+
def to_dict(self) -> Dict[str, Any]:
159+
"""Convert to dictionary format for API calls."""
160+
config = {
161+
"name": self.name,
162+
"configuration": {
163+
"userPreferenceOverride": {
164+
"extraction": self._convert_extraction_config(),
165+
"consolidation": self._convert_consolidation_config(),
166+
}
167+
},
168+
}
169+
170+
if self.description is not None:
171+
config["description"] = self.description
172+
173+
if self.namespaces is not None:
174+
config["namespaces"] = self.namespaces
175+
176+
return {"customMemoryStrategy": config}
177+
178+
def _convert_extraction_config(self) -> Dict[str, Any]:
179+
"""Convert extraction config to API format."""
180+
config = {}
181+
if self.extraction_config.append_to_prompt is not None:
182+
config["appendToPrompt"] = self.extraction_config.append_to_prompt
183+
if self.extraction_config.model_id is not None:
184+
config["modelId"] = self.extraction_config.model_id
185+
return config
186+
187+
def _convert_consolidation_config(self) -> Dict[str, Any]:
188+
"""Convert consolidation config to API format."""
189+
config = {}
190+
if self.consolidation_config.append_to_prompt is not None:
191+
config["appendToPrompt"] = self.consolidation_config.append_to_prompt
192+
if self.consolidation_config.model_id is not None:
193+
config["modelId"] = self.consolidation_config.model_id
194+
return config

0 commit comments

Comments
 (0)