Skip to content

Commit 20ae946

Browse files
authored
Merge pull request #25 from redis-developer/long-term-memory-interface
Add a long-term memory interface
2 parents 5a41a2f + 0c6fae6 commit 20ae946

33 files changed

+6496
-1609
lines changed

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ docker-compose down # Stop all services
4242
IMPORTANT: This project uses `pre-commit`. You should run `pre-commit`
4343
before committing:
4444
```bash
45+
uv run pre-commit install # Install the hooks first
4546
uv run pre-commit run --all-files
4647
```
4748

@@ -68,7 +69,7 @@ Working Memory (Session-scoped) → Long-term Memory (Persistent)
6869
```python
6970
# Correct - Use RedisVL queries
7071
from redisvl.query import VectorQuery, FilterQuery
71-
query = VectorQuery(vector=embedding, vector_field_name="embedding", return_fields=["text"])
72+
query = VectorQuery(vector=embedding, vector_field_name="vector", return_fields=["text"])
7273

7374
# Avoid - Direct redis client searches
7475
# redis.ft().search(...) # Don't do this

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ A Redis-powered memory server built for AI agents and applications. It manages b
1414
- **Long-Term Memory**
1515

1616
- Persistent storage for memories across sessions
17+
- **Pluggable Vector Store Backends** - Support for multiple vector databases through LangChain VectorStore interface:
18+
- **Redis** (default) - RedisStack with RediSearch
19+
- **Chroma** - Open-source vector database
20+
- **Pinecone** - Managed vector database service
21+
- **Weaviate** - Open-source vector search engine
22+
- **Qdrant** - Vector similarity search engine
23+
- **Milvus** - Cloud-native vector database
24+
- **PostgreSQL/PGVector** - PostgreSQL with vector extensions
25+
- **LanceDB** - Embedded vector database
26+
- **OpenSearch** - Open-source search and analytics suite
1727
- Semantic search to retrieve memories with advanced filtering system
1828
- Filter by session, user ID, namespace, topics, entities, timestamps, and more
1929
- Supports both exact match and semantic similarity search
@@ -87,6 +97,8 @@ Configure servers and workers using environment variables. Includes background t
8797

8898
For complete configuration details, see [Configuration Guide](docs/configuration.md).
8999

100+
For vector store backend options and setup, see [Vector Store Backends](docs/vector-store-backends.md).
101+
90102
## License
91103

92104
Apache 2.0 License - see [LICENSE](LICENSE) file for details.

agent-memory-client/agent_memory_client/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
memory management capabilities for AI agents and applications.
66
"""
77

8-
__version__ = "0.9.0b4"
8+
__version__ = "0.9.0b5"
99

1010
from .client import MemoryAPIClient, MemoryClientConfig, create_memory_client
1111
from .exceptions import (

agent-memory-client/agent_memory_client/client.py

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66

77
import asyncio
88
import re
9-
from collections.abc import AsyncIterator
9+
from collections.abc import AsyncIterator, Sequence
1010
from typing import TYPE_CHECKING, Any, Literal, TypedDict
1111

1212
if TYPE_CHECKING:
1313
from typing_extensions import Self
1414

1515
import httpx
16-
import ulid
1716
from pydantic import BaseModel
17+
from ulid import ULID
1818

1919
from .exceptions import MemoryClientError, MemoryServerError, MemoryValidationError
2020
from .filters import (
@@ -416,7 +416,7 @@ async def set_working_memory_data(
416416
async def add_memories_to_working_memory(
417417
self,
418418
session_id: str,
419-
memories: list[ClientMemoryRecord | MemoryRecord],
419+
memories: Sequence[ClientMemoryRecord | MemoryRecord],
420420
namespace: str | None = None,
421421
replace: bool = False,
422422
) -> WorkingMemoryResponse:
@@ -459,14 +459,14 @@ async def add_memories_to_working_memory(
459459

460460
# Determine final memories list
461461
if replace or not existing_memory:
462-
final_memories = memories
462+
final_memories = list(memories)
463463
else:
464-
final_memories = existing_memory.memories + memories
464+
final_memories = existing_memory.memories + list(memories)
465465

466466
# Auto-generate IDs for memories that don't have them
467467
for memory in final_memories:
468468
if not memory.id:
469-
memory.id = str(ulid.ULID())
469+
memory.id = str(ULID())
470470

471471
# Create new working memory with the memories
472472
working_memory = WorkingMemory(
@@ -482,7 +482,7 @@ async def add_memories_to_working_memory(
482482
return await self.put_working_memory(session_id, working_memory)
483483

484484
async def create_long_term_memory(
485-
self, memories: list[ClientMemoryRecord | MemoryRecord]
485+
self, memories: Sequence[ClientMemoryRecord | MemoryRecord]
486486
) -> AckResponse:
487487
"""
488488
Create long-term memories for later retrieval.
@@ -541,6 +541,29 @@ async def create_long_term_memory(
541541
self._handle_http_error(e.response)
542542
raise
543543

544+
async def delete_long_term_memories(self, memory_ids: Sequence[str]) -> AckResponse:
545+
"""
546+
Delete long-term memories.
547+
548+
Args:
549+
memory_ids: List of memory IDs to delete
550+
551+
Returns:
552+
AckResponse indicating success
553+
"""
554+
params = {"memory_ids": list(memory_ids)}
555+
556+
try:
557+
response = await self._client.delete(
558+
"/v1/long-term-memory",
559+
params=params,
560+
)
561+
response.raise_for_status()
562+
return AckResponse(**response.json())
563+
except httpx.HTTPStatusError as e:
564+
self._handle_http_error(e.response)
565+
raise
566+
544567
async def search_long_term_memory(
545568
self,
546569
text: str,
@@ -666,8 +689,8 @@ async def search_long_term_memory(
666689
async def search_memory_tool(
667690
self,
668691
query: str,
669-
topics: list[str] | None = None,
670-
entities: list[str] | None = None,
692+
topics: Sequence[str] | None = None,
693+
entities: Sequence[str] | None = None,
671694
memory_type: str | None = None,
672695
max_results: int = 5,
673696
min_relevance: float | None = None,
@@ -722,8 +745,8 @@ async def search_memory_tool(
722745
from .filters import Entities, MemoryType, Topics
723746

724747
# Convert simple parameters to filter objects
725-
topics_filter = Topics(any=topics) if topics else None
726-
entities_filter = Entities(any=entities) if entities else None
748+
topics_filter = Topics(any=list(topics)) if topics else None
749+
entities_filter = Entities(any=list(entities)) if entities else None
727750
memory_type_filter = MemoryType(eq=memory_type) if memory_type else None
728751
user_id_filter = UserId(eq=user_id) if user_id else None
729752

@@ -940,8 +963,8 @@ async def add_memory_tool(
940963
session_id: str,
941964
text: str,
942965
memory_type: str,
943-
topics: list[str] | None = None,
944-
entities: list[str] | None = None,
966+
topics: Sequence[str] | None = None,
967+
entities: Sequence[str] | None = None,
945968
namespace: str | None = None,
946969
user_id: str | None = None,
947970
) -> dict[str, Any]:
@@ -982,8 +1005,8 @@ async def add_memory_tool(
9821005
memory = ClientMemoryRecord(
9831006
text=text,
9841007
memory_type=MemoryTypeEnum(memory_type),
985-
topics=topics,
986-
entities=entities,
1008+
topics=list(topics) if topics else None,
1009+
entities=list(entities) if entities else None,
9871010
namespace=namespace or self.config.default_namespace,
9881011
user_id=user_id,
9891012
)
@@ -1172,7 +1195,7 @@ def get_update_memory_data_tool_schema(cls) -> dict[str, Any]:
11721195
}
11731196

11741197
@classmethod
1175-
def get_all_memory_tool_schemas(cls) -> list[dict[str, Any]]:
1198+
def get_all_memory_tool_schemas(cls) -> Sequence[dict[str, Any]]:
11761199
"""
11771200
Get all memory-related tool schemas for easy LLM integration.
11781201
@@ -1200,7 +1223,7 @@ def get_all_memory_tool_schemas(cls) -> list[dict[str, Any]]:
12001223
]
12011224

12021225
@classmethod
1203-
def get_all_memory_tool_schemas_anthropic(cls) -> list[dict[str, Any]]:
1226+
def get_all_memory_tool_schemas_anthropic(cls) -> Sequence[dict[str, Any]]:
12041227
"""
12051228
Get all memory-related tool schemas in Anthropic format.
12061229
@@ -1470,11 +1493,11 @@ async def resolve_tool_call(
14701493

14711494
async def resolve_tool_calls(
14721495
self,
1473-
tool_calls: list[dict[str, Any]],
1496+
tool_calls: Sequence[dict[str, Any]],
14741497
session_id: str,
14751498
namespace: str | None = None,
14761499
user_id: str | None = None,
1477-
) -> list[ToolCallResolutionResult]:
1500+
) -> Sequence[ToolCallResolutionResult]:
14781501
"""
14791502
Resolve multiple tool calls from any LLM provider format.
14801503
@@ -1713,11 +1736,11 @@ async def _resolve_update_memory_data(
17131736

17141737
async def resolve_function_calls(
17151738
self,
1716-
function_calls: list[dict[str, Any]],
1739+
function_calls: Sequence[dict[str, Any]],
17171740
session_id: str,
17181741
namespace: str | None = None,
17191742
user_id: str | None = None,
1720-
) -> list[ToolCallResolutionResult]:
1743+
) -> Sequence[ToolCallResolutionResult]:
17211744
"""
17221745
Resolve multiple function calls in batch.
17231746
@@ -1765,7 +1788,7 @@ async def resolve_function_calls(
17651788
async def promote_working_memories_to_long_term(
17661789
self,
17671790
session_id: str,
1768-
memory_ids: list[str] | None = None,
1791+
memory_ids: Sequence[str] | None = None,
17691792
namespace: str | None = None,
17701793
) -> AckResponse:
17711794
"""
@@ -1805,10 +1828,10 @@ async def promote_working_memories_to_long_term(
18051828

18061829
async def bulk_create_long_term_memories(
18071830
self,
1808-
memory_batches: list[list[ClientMemoryRecord | MemoryRecord]],
1831+
memory_batches: Sequence[Sequence[ClientMemoryRecord | MemoryRecord]],
18091832
batch_size: int = 100,
18101833
delay_between_batches: float = 0.1,
1811-
) -> list[AckResponse]:
1834+
) -> Sequence[AckResponse]:
18121835
"""
18131836
Create multiple batches of memories with proper rate limiting.
18141837
@@ -2104,6 +2127,8 @@ async def memory_prompt(
21042127
"""
21052128
Hydrate a user query with memory context and return a prompt ready to send to an LLM.
21062129
2130+
NOTE: `long_term_search` uses the same filter options as `search_long_term_memories`.
2131+
21072132
Args:
21082133
query: The input text to find relevant context for
21092134
session_id: Optional session ID to include session messages
@@ -2163,9 +2188,17 @@ async def memory_prompt(
21632188

21642189
# Add long-term search parameters if provided
21652190
if long_term_search is not None:
2191+
if "namespace" not in long_term_search:
2192+
if namespace is not None:
2193+
long_term_search["namespace"] = {"eq": namespace}
2194+
elif self.config.default_namespace is not None:
2195+
long_term_search["namespace"] = {
2196+
"eq": self.config.default_namespace
2197+
}
21662198
payload["long_term_search"] = long_term_search
21672199

21682200
try:
2201+
print("Payload: ", payload)
21692202
response = await self._client.post(
21702203
"/v1/memory/prompt",
21712204
json=payload,

agent-memory-client/agent_memory_client/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
from enum import Enum
1010
from typing import Any, Literal, TypedDict
1111

12-
import ulid
1312
from pydantic import BaseModel, Field
13+
from ulid import ULID
1414

1515
# Model name literals for model-specific window sizes
1616
ModelNameLiteral = Literal[
@@ -122,7 +122,7 @@ class ClientMemoryRecord(MemoryRecord):
122122
"""A memory record with a client-provided ID"""
123123

124124
id: str = Field(
125-
default_factory=lambda: str(ulid.ULID()),
125+
default_factory=lambda: str(ULID()),
126126
description="Client-provided ID generated by the client (ULID)",
127127
)
128128

agent_memory_server/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Redis Agent Memory Server - A memory system for conversational AI."""
22

3-
__version__ = "0.9.0b4"
3+
__version__ = "0.9.0b5"

0 commit comments

Comments
 (0)