Skip to content

Commit c58a937

Browse files
committed
Implement nested value retrieval and metadata matching in StepBasedSearchStrategy
This commit adds two new methods to the StepBasedSearchStrategy class: _get_nested_value for extracting values from nested dictionaries using a dot-separated path, and _metadata_matches for checking if a memory object matches a given metadata filter. Additionally, the filtering logic in _filter_memories is updated to utilize the new _metadata_matches method. The scoring calculations in the memory processing logic are also enhanced to avoid division by zero and improve step weight application. Test suite updates include adjustments to metadata filters for improved validation coverage.
1 parent 59ea427 commit c58a937

File tree

2 files changed

+85
-11
lines changed

2 files changed

+85
-11
lines changed

memory/search/strategies/step.py

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,67 @@ def _get_memory_step(self, memory: Dict[str, Any]) -> Optional[int]:
314314

315315
return None
316316

317+
def _get_nested_value(self, obj: Dict[str, Any], path: str) -> Any:
318+
"""Get value from a nested dictionary using a dot-separated path.
319+
320+
Args:
321+
obj: Dictionary to extract value from
322+
path: Dot-separated path to the value (e.g., "content.metadata.importance")
323+
324+
Returns:
325+
Value found at the path, or None if not found
326+
"""
327+
if not path or not isinstance(obj, dict):
328+
return None
329+
330+
parts = path.split('.')
331+
current = obj
332+
333+
for part in parts:
334+
if not isinstance(current, dict) or part not in current:
335+
return None
336+
current = current[part]
337+
338+
return current
339+
340+
def _metadata_matches(self, memory: Dict[str, Any], metadata_filter: Dict[str, Any]) -> bool:
341+
"""Check if a memory matches the metadata filter.
342+
343+
Args:
344+
memory: Memory object to check
345+
metadata_filter: Metadata filter to apply
346+
347+
Returns:
348+
True if memory matches filter, False otherwise
349+
"""
350+
for key, filter_value in metadata_filter.items():
351+
memory_value = self._get_nested_value(memory, key)
352+
if memory_value is None:
353+
# Try to get from non-nested metadata
354+
memory_value = memory.get("metadata", {}).get(key)
355+
356+
# No matching value found
357+
if memory_value is None:
358+
logger.debug(f"No value found for key {key} in memory")
359+
return False
360+
361+
# Handle list/array values - check if filter_value is a subset of memory_value
362+
if isinstance(filter_value, list):
363+
if not isinstance(memory_value, list):
364+
# Convert to list if memory_value is a single value
365+
memory_value = [memory_value]
366+
367+
# Check if all items in filter_value are in memory_value
368+
if not all(item in memory_value for item in filter_value):
369+
logger.debug(f"List match failed for {key}: filter={filter_value}, memory={memory_value}")
370+
return False
371+
# For other types, do a direct comparison
372+
elif memory_value != filter_value:
373+
logger.debug(f"Value mismatch for {key}: filter={filter_value}, memory={memory_value}")
374+
return False
375+
376+
return True
377+
317378
def _filter_memories(
318379
self,
319380
memories: List[Dict[str, Any]],
@@ -364,11 +425,8 @@ def _filter_memories(
364425
continue
365426

366427
# Apply metadata filter
367-
if metadata_filter:
368-
memory_metadata = memory.get("metadata", {})
369-
if not all(
370-
memory_metadata.get(k) == v for k, v in metadata_filter.items()
371-
):
428+
if metadata_filter and len(metadata_filter) > 0:
429+
if not self._metadata_matches(memory, metadata_filter):
372430
continue
373431

374432
filtered.append(memory)
@@ -412,13 +470,24 @@ def _score_memories(
412470
# Normalize step distance (closer = higher score)
413471
# Adjust max_distance based on your simulation scale
414472
max_distance = step_params.get("step_range", 100) * 2
415-
normalized_distance = min(step_distance / max_distance, 1.0)
473+
# Avoid division by zero
474+
if max_distance > 0:
475+
normalized_distance = min(step_distance / max_distance, 1.0)
476+
else:
477+
normalized_distance = 1.0 if step_distance > 0 else 0.0
416478

417-
# Higher score for closer steps
479+
# Higher score for closer steps (1.0 for exact match, decreasing as distance increases)
418480
step_score = 1.0 - normalized_distance
419481

420-
# Apply step weight
421-
step_score = step_score * step_weight
482+
# Apply step weight (higher weight emphasizes proximity more)
483+
step_score = pow(step_score, 1.0 / max(step_weight, 0.001))
484+
485+
# Log the scoring calculations for debugging
486+
logger.debug(
487+
f"Memory {memory.get('id', 'unknown')}: step={memory_step}, "
488+
f"ref={reference_step}, distance={step_distance}, "
489+
f"normalized_distance={normalized_distance}, score={step_score}"
490+
)
422491

423492
# Create a copy of the memory to avoid modifying the original
424493
memory_copy = memory.copy()

validation/search/step/step_test_suite.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def run_basic_tests(self) -> None:
6262
"test-agent-step-search-stm-1",
6363
"test-agent-step-search-stm-2"
6464
],
65+
tier="stm",
6566
memory_checksum_map=self.memory_checksum_map,
6667
)
6768

@@ -153,7 +154,10 @@ def run_advanced_tests(self) -> None:
153154
"test-agent-step-search-ltm-1",
154155
"test-agent-step-search-ltm-2"
155156
],
156-
metadata_filter={"content.metadata.type": "system", "content.metadata.importance": "high"},
157+
metadata_filter={
158+
"content.metadata.type": "state",
159+
"content.metadata.importance": "high"
160+
},
157161
memory_checksum_map=self.memory_checksum_map,
158162
)
159163

@@ -264,7 +268,8 @@ def run_edge_case_tests(self) -> None:
264268
{"start_step": 100, "end_step": 200},
265269
expected_memory_ids=[
266270
"test-agent-step-search-stm-1",
267-
"test-agent-step-search-stm-2"
271+
"test-agent-step-search-stm-2",
272+
"test-agent-step-search-im-1"
268273
],
269274
metadata_filter={},
270275
memory_checksum_map=self.memory_checksum_map,

0 commit comments

Comments
 (0)