Skip to content

Commit a633044

Browse files
committed
fix(redis): coerce list fields in Redis aggregate path; add RecencyAggregationQuery.build_args helper; update tests to use build_args; whitespace fix
1 parent 5e1f7c5 commit a633044

File tree

4 files changed

+65
-30
lines changed

4 files changed

+65
-30
lines changed

CLAUDE.md

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,68 @@ This project uses Redis 8, which is the redis:8 docker image.
55
Do not use Redis Stack or other earlier versions of Redis.
66

77
## Frequently Used Commands
8-
Get started in a new environment by installing `uv`:
9-
```bash
10-
pip install uv
11-
```
128

9+
### Project Setup
10+
Get started in a new environment by installing `uv`:
1311
```bash
14-
# Development workflow
12+
pip install uv # Install uv (once)
1513
uv venv # Create a virtualenv (once)
16-
source .venv/bin/activate # Activate the virtualenv (start of terminal session)
1714
uv install --all-extras # Install dependencies
1815
uv sync --all-extras # Sync latest dependencies
16+
```
17+
18+
### Activate the virtual environment
19+
You MUST always activate the virtualenv before running commands:
20+
21+
```bash
22+
source .venv/bin/activate
23+
```
24+
25+
### Running Tests
26+
Always run tests before committing. You MUST have 100% of the tests in the
27+
code basepassing to commit.
28+
29+
Run all tests like this, including tests that require API keys in the
30+
environment:
31+
```bash
32+
uv run pytest --run-api-tests
33+
```
34+
35+
### Linting
36+
37+
```bash
1938
uv run ruff check # Run linting
2039
uv run ruff format # Format code
21-
uv run pytest --run-api-tests # Run all tests
40+
41+
### Managing Dependencies
2242
uv add <dependency> # Add a dependency to pyproject.toml and update lock file
2343
uv remove <dependency> # Remove a dependency from pyproject.toml and update lock file
2444

45+
### Running Servers
2546
# Server commands
2647
uv run agent-memory api # Start REST API server (default port 8000)
2748
uv run agent-memory mcp # Start MCP server (stdio mode)
2849
uv run agent-memory mcp --mode sse --port 9000 # Start MCP server (SSE mode)
2950

51+
### Database Operations
3052
# Database/Redis operations
3153
uv run agent-memory rebuild-index # Rebuild Redis search index
3254
uv run agent-memory migrate-memories # Run memory migrations
3355

56+
### Background Tasks
3457
# Background task management
3558
uv run agent-memory task-worker # Start background task worker
59+
# Schedule a specific task
3660
uv run agent-memory schedule-task "agent_memory_server.long_term_memory.compact_long_term_memories"
3761

62+
### Running All Containers
3863
# Docker development
3964
docker-compose up # Start full stack (API, MCP, Redis)
4065
docker-compose up redis # Start only Redis Stack
4166
docker-compose down # Stop all services
4267
```
4368

69+
### Committing Changes
4470
IMPORTANT: This project uses `pre-commit`. You should run `pre-commit`
4571
before committing:
4672
```bash

agent_memory_server/utils/redis_query.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ def from_vector_query(
4343
*,
4444
filter_expression: Any | None = None,
4545
) -> RecencyAggregationQuery:
46-
return cls(vq.query, filter_expression=filter_expression)
46+
agg = cls(vq.query)
47+
if filter_expression is not None:
48+
agg.filter(filter_expression)
49+
return agg
4750

4851
def load_default_fields(self) -> RecencyAggregationQuery:
4952
self.load(self.DEFAULT_RETURN_FIELDS)
@@ -60,15 +63,13 @@ def apply_recency(
6063
hl_la = float(params.get("half_life_last_access_days", 7.0))
6164
hl_cr = float(params.get("half_life_created_days", 30.0))
6265

63-
self.apply(
64-
f"max(0, ({now_ts} - @last_accessed)/86400.0)", AS="days_since_access"
65-
).apply(
66-
f"max(0, ({now_ts} - @created_at)/86400.0)", AS="days_since_created"
67-
).apply(f"pow(2, -@days_since_access/{hl_la})", AS="freshness").apply(
68-
f"pow(2, -@days_since_created/{hl_cr})", AS="novelty"
69-
).apply(f"{wf}*@freshness+{wa}*@novelty", AS="recency").apply(
70-
"1-(@__vector_score/2)", AS="sim"
71-
).apply(f"{w_sem}*@sim+{w_rec}*@recency", AS="boosted_score")
66+
self.apply(days_since_access=f"max(0, ({now_ts} - @last_accessed)/86400.0)")
67+
self.apply(days_since_created=f"max(0, ({now_ts} - @created_at)/86400.0)")
68+
self.apply(freshness=f"pow(2, -@days_since_access/{hl_la})")
69+
self.apply(novelty=f"pow(2, -@days_since_created/{hl_cr})")
70+
self.apply(recency=f"{wf}*@freshness+{wa}*@novelty")
71+
self.apply(sim="1-(@__vector_score/2)")
72+
self.apply(boosted_score=f"{w_sem}*@sim+{w_rec}*@recency")
7273

7374
return self
7475

@@ -79,3 +80,7 @@ def sort_by_boosted_desc(self) -> RecencyAggregationQuery:
7980
def paginate(self, offset: int, limit: int) -> RecencyAggregationQuery:
8081
self.limit(offset, limit)
8182
return self
83+
84+
# Compatibility helper for tests that inspect the built query
85+
def build_args(self) -> list:
86+
return super().build_args()

agent_memory_server/vectorstore_adapter.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -376,13 +376,13 @@ def parse_datetime(dt_val: str | float | None) -> datetime | None:
376376
updated_at=updated_at,
377377
pinned=pinned_bool,
378378
access_count=access_count_val,
379-
topics=metadata.get("topics"),
380-
entities=metadata.get("entities"),
379+
topics=self._parse_list_field(metadata.get("topics")),
380+
entities=self._parse_list_field(metadata.get("entities")),
381381
memory_hash=metadata.get("memory_hash"),
382382
discrete_memory_extracted=metadata.get("discrete_memory_extracted", "f"),
383383
memory_type=metadata.get("memory_type", "message"),
384384
persisted_at=persisted_at,
385-
extracted_from=metadata.get("extracted_from"),
385+
extracted_from=self._parse_list_field(metadata.get("extracted_from")),
386386
event_date=event_date,
387387
dist=score,
388388
)
@@ -898,14 +898,14 @@ async def search_memories(
898898
vector_field_name="vector",
899899
filter_expression=redis_filter,
900900
distance_threshold=float(distance_threshold),
901-
k=limit,
901+
num_results=limit,
902902
)
903903
else:
904904
knn = VectorQuery(
905905
vector=embedding_vector,
906906
vector_field_name="vector",
907907
filter_expression=redis_filter,
908-
k=limit,
908+
num_results=limit,
909909
)
910910

911911
# Aggregate with APPLY/SORTBY boosted score via helper

tests/test_recency_aggregation.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ async def test_recency_aggregation_query_builds_and_paginates():
1212
from redisvl.query import VectorQuery
1313

1414
dummy_vec = [0.0, 0.0, 0.0]
15-
vq = VectorQuery(vector=dummy_vec, vector_field_name="vector", k=10)
15+
vq = VectorQuery(vector=dummy_vec, vector_field_name="vector", num_results=10)
1616

1717
# Build aggregation
1818
agg = (
@@ -33,13 +33,13 @@ async def test_recency_aggregation_query_builds_and_paginates():
3333
.paginate(5, 7)
3434
)
3535

36-
# Implementation detail: AggregationQuery has a private builder we can sanity-check
37-
# We only assert key substrings to avoid coupling to exact formatting
38-
qs = agg._build_query_string() # type: ignore[attr-defined]
39-
assert "APPLY" in qs
40-
assert "boosted_score" in qs
41-
assert "SORTBY" in qs
42-
assert "LIMIT" in qs
36+
# Validate the aggregate request contains APPLY, SORTBY, and LIMIT via build_args
37+
args = agg.build_args()
38+
args_str = " ".join(map(str, args))
39+
assert "APPLY" in args_str
40+
assert "boosted_score" in args_str
41+
assert "SORTBY" in args_str
42+
assert "LIMIT" in args_str
4343

4444

4545
@pytest.mark.asyncio
@@ -82,6 +82,10 @@ def __init__(self, rows):
8282

8383
mock_vectorstore = MagicMock()
8484
mock_vectorstore._index = mock_index
85+
# If the adapter falls back, ensure awaited LC call is defined
86+
mock_vectorstore.asimilarity_search_with_relevance_scores = AsyncMock(
87+
return_value=[]
88+
)
8589

8690
# Mock embeddings
8791
mock_embeddings = MagicMock()

0 commit comments

Comments
 (0)