Skip to content

Commit 72a16be

Browse files
authored
feat: adding function to delete all memory records in namespace (#148)
feat: adding function to delete all memory records in namespace
1 parent 90f04bf commit 72a16be

File tree

2 files changed

+198
-0
lines changed

2 files changed

+198
-0
lines changed

src/bedrock_agentcore/memory/session.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,51 @@ def list_actor_sessions(self, actor_id: str) -> List[SessionSummary]:
10061006
logger.error(" ❌ Error listing sessions: %s", e)
10071007
raise
10081008

1009+
def delete_all_long_term_memories_in_namespace(self, namespace: str) -> Dict[str, Any]:
1010+
"""Delete all long-term memory records within a specific namespace.
1011+
1012+
This method retrieves all memory records in the specified namespace and performs
1013+
batch deletion operations using the AWS Bedrock AgentCore API, processing in chunks of 100.
1014+
1015+
Args:
1016+
namespace: The namespace prefix to delete memories from
1017+
1018+
Returns:
1019+
Dictionary containing batch deletion results with successfulRecords and failedRecords
1020+
"""
1021+
logger.info("🗑️ Deleting all long-term memories in namespace '%s'...", namespace)
1022+
1023+
# Retrieve all memory records in the specified namespace
1024+
memory_records = self.list_long_term_memory_records(namespace_prefix=namespace)
1025+
logger.info(" -> Found %d memory records to delete", len(memory_records))
1026+
1027+
if not memory_records:
1028+
logger.info(" ✅ No records found to delete")
1029+
return {"successfulRecords": [], "failedRecords": []}
1030+
1031+
# Format record IDs for batch deletion API
1032+
memory_record_ids = [{"memoryRecordId": record["memoryRecordId"]} for record in memory_records]
1033+
1034+
all_successful = []
1035+
all_failed = []
1036+
1037+
# Process in chunks of 100
1038+
for i in range(0, len(memory_record_ids), 100):
1039+
chunk = memory_record_ids[i : i + 100]
1040+
try:
1041+
result = self._data_plane_client.batch_delete_memory_records(memoryId=self._memory_id, records=chunk)
1042+
all_successful.extend(result.get("successfulRecords", []))
1043+
all_failed.extend(result.get("failedRecords", []))
1044+
except ClientError as e:
1045+
logger.error(" ❌ Error deleting chunk: %s", e)
1046+
raise
1047+
1048+
logger.info(" ✅ Successfully deleted %d records", len(all_successful))
1049+
if all_failed:
1050+
logger.warning(" ⚠️ Failed to delete %d records", len(all_failed))
1051+
1052+
return {"successfulRecords": all_successful, "failedRecords": all_failed}
1053+
10091054
def create_memory_session(self, actor_id: str, session_id: str = None) -> "MemorySession":
10101055
"""Creates a new MemorySession instance."""
10111056
session_id = session_id or str(uuid.uuid4())

tests/bedrock_agentcore/memory/test_session.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,6 +1452,159 @@ def test_delete_memory_record_client_error(self):
14521452
with pytest.raises(ClientError):
14531453
manager.delete_memory_record(record_id="invalid-record")
14541454

1455+
def test_delete_all_long_term_memories_in_namespace_success(self):
1456+
"""Test delete_all_long_term_memories_in_namespace successful execution."""
1457+
with patch("boto3.Session") as mock_session_class:
1458+
mock_session = MagicMock()
1459+
mock_session.region_name = "us-west-2"
1460+
mock_client_instance = MagicMock()
1461+
mock_session.client.return_value = mock_client_instance
1462+
mock_session_class.return_value = mock_session
1463+
1464+
manager = MemorySessionManager(memory_id="testMemory-1234567890", region_name="us-west-2")
1465+
1466+
# Mock list_long_term_memory_records
1467+
mock_records = [
1468+
{"memoryRecordId": "rec-1", "content": {"text": "Memory 1"}},
1469+
{"memoryRecordId": "rec-2", "content": {"text": "Memory 2"}},
1470+
]
1471+
with patch.object(manager, "list_long_term_memory_records", return_value=mock_records):
1472+
# Mock batch_delete_memory_records response
1473+
mock_response = {
1474+
"successfulRecords": [
1475+
{"memoryRecordId": "rec-1", "status": "SUCCEEDED"},
1476+
{"memoryRecordId": "rec-2", "status": "SUCCEEDED"},
1477+
],
1478+
"failedRecords": [],
1479+
}
1480+
mock_client_instance.batch_delete_memory_records.return_value = mock_response
1481+
1482+
result = manager.delete_all_long_term_memories_in_namespace("test/namespace")
1483+
1484+
assert len(result["successfulRecords"]) == 2
1485+
assert len(result["failedRecords"]) == 0
1486+
1487+
# Verify API call
1488+
mock_client_instance.batch_delete_memory_records.assert_called_once_with(
1489+
memoryId="testMemory-1234567890",
1490+
records=[{"memoryRecordId": "rec-1"}, {"memoryRecordId": "rec-2"}],
1491+
)
1492+
1493+
def test_delete_all_long_term_memories_in_namespace_empty(self):
1494+
"""Test delete_all_long_term_memories_in_namespace with no records."""
1495+
with patch("boto3.Session") as mock_session_class:
1496+
mock_session = MagicMock()
1497+
mock_session.region_name = "us-west-2"
1498+
mock_client_instance = MagicMock()
1499+
mock_session.client.return_value = mock_client_instance
1500+
mock_session_class.return_value = mock_session
1501+
1502+
manager = MemorySessionManager(memory_id="testMemory-1234567890", region_name="us-west-2")
1503+
1504+
# Mock empty list_long_term_memory_records
1505+
with patch.object(manager, "list_long_term_memory_records", return_value=[]):
1506+
result = manager.delete_all_long_term_memories_in_namespace("empty/namespace")
1507+
1508+
assert result == {"successfulRecords": [], "failedRecords": []}
1509+
# Should not call batch_delete_memory_records
1510+
mock_client_instance.batch_delete_memory_records.assert_not_called()
1511+
1512+
def test_delete_all_long_term_memories_in_namespace_client_error(self):
1513+
"""Test delete_all_long_term_memories_in_namespace with ClientError."""
1514+
with patch("boto3.Session") as mock_session_class:
1515+
mock_session = MagicMock()
1516+
mock_session.region_name = "us-west-2"
1517+
mock_client_instance = MagicMock()
1518+
mock_session.client.return_value = mock_client_instance
1519+
mock_session_class.return_value = mock_session
1520+
1521+
manager = MemorySessionManager(memory_id="testMemory-1234567890", region_name="us-west-2")
1522+
1523+
# Mock list_long_term_memory_records
1524+
mock_records = [{"memoryRecordId": "rec-1", "content": {"text": "Memory 1"}}]
1525+
with patch.object(manager, "list_long_term_memory_records", return_value=mock_records):
1526+
# Mock ClientError
1527+
error_response = {"Error": {"Code": "ValidationException", "Message": "Invalid request"}}
1528+
mock_client_instance.batch_delete_memory_records.side_effect = ClientError(
1529+
error_response, "BatchDeleteMemoryRecords"
1530+
)
1531+
1532+
with pytest.raises(ClientError):
1533+
manager.delete_all_long_term_memories_in_namespace("test/namespace")
1534+
1535+
def test_delete_all_long_term_memories_in_namespace_over_100_records(self):
1536+
"""Test deleting more than 100 records in namespace."""
1537+
with patch("boto3.Session") as mock_session_class:
1538+
mock_session = MagicMock()
1539+
mock_session.region_name = "us-west-2"
1540+
mock_client_instance = MagicMock()
1541+
mock_session.client.return_value = mock_client_instance
1542+
mock_session_class.return_value = mock_session
1543+
1544+
manager = MemorySessionManager(memory_id="testMemory-1234567890", region_name="us-west-2")
1545+
1546+
# Mock 150 memory records
1547+
mock_records = [{"memoryRecordId": f"rec-{i}", "content": {"text": f"Memory {i}"}} for i in range(150)]
1548+
with patch.object(manager, "list_long_term_memory_records", return_value=mock_records):
1549+
# Mock batch_delete_memory_records responses for each chunk
1550+
mock_client_instance.batch_delete_memory_records.side_effect = [
1551+
{"successfulRecords": [{"memoryRecordId": f"rec-{i}"} for i in range(100)], "failedRecords": []},
1552+
{
1553+
"successfulRecords": [{"memoryRecordId": f"rec-{i}"} for i in range(100, 150)],
1554+
"failedRecords": [],
1555+
},
1556+
]
1557+
1558+
result = manager.delete_all_long_term_memories_in_namespace("test/namespace")
1559+
1560+
# Verify two batch calls were made
1561+
assert mock_client_instance.batch_delete_memory_records.call_count == 2
1562+
assert len(result["successfulRecords"]) == 150
1563+
assert len(result["failedRecords"]) == 0
1564+
1565+
# Verify first batch had 100 records
1566+
first_batch = mock_client_instance.batch_delete_memory_records.call_args_list[0][1]["records"]
1567+
assert len(first_batch) == 100
1568+
1569+
# Verify second batch had 50 records
1570+
second_batch = mock_client_instance.batch_delete_memory_records.call_args_list[1][1]["records"]
1571+
assert len(second_batch) == 50
1572+
1573+
def test_delete_all_long_term_memories_in_namespace_partial_failure(self):
1574+
"""Test delete_all_long_term_memories_in_namespace with some failed records."""
1575+
with patch("boto3.Session") as mock_session_class:
1576+
mock_session = MagicMock()
1577+
mock_session.region_name = "us-west-2"
1578+
mock_client_instance = MagicMock()
1579+
mock_session.client.return_value = mock_client_instance
1580+
mock_session_class.return_value = mock_session
1581+
1582+
manager = MemorySessionManager(memory_id="testMemory-1234567890", region_name="us-west-2")
1583+
1584+
# Mock list_long_term_memory_records
1585+
mock_records = [
1586+
{"memoryRecordId": "rec-1", "content": {"text": "Memory 1"}},
1587+
{"memoryRecordId": "rec-2", "content": {"text": "Memory 2"}},
1588+
{"memoryRecordId": "rec-3", "content": {"text": "Memory 3"}},
1589+
]
1590+
with patch.object(manager, "list_long_term_memory_records", return_value=mock_records):
1591+
# Mock batch_delete_memory_records response with partial failure
1592+
mock_response = {
1593+
"successfulRecords": [
1594+
{"memoryRecordId": "rec-1", "status": "SUCCEEDED"},
1595+
{"memoryRecordId": "rec-3", "status": "SUCCEEDED"},
1596+
],
1597+
"failedRecords": [{"memoryRecordId": "rec-2", "status": "FAILED", "errorMessage": "Access denied"}],
1598+
}
1599+
mock_client_instance.batch_delete_memory_records.return_value = mock_response
1600+
1601+
result = manager.delete_all_long_term_memories_in_namespace("test/namespace")
1602+
1603+
assert len(result["successfulRecords"]) == 2
1604+
assert len(result["failedRecords"]) == 1
1605+
assert result["failedRecords"][0]["memoryRecordId"] == "rec-2"
1606+
assert result["failedRecords"][0]["errorMessage"] == "Access denied"
1607+
14551608
def test_list_actor_sessions_success(self):
14561609
"""Test list_actor_sessions successful execution."""
14571610
with patch("boto3.Session") as mock_session_class:

0 commit comments

Comments
 (0)