Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ A memory layer for AI agents.
- **Dual Interface**: REST API and Model Context Protocol (MCP) server
- **Two-Tier Memory**: Working memory (session-scoped) and long-term memory (persistent)
- **Configurable Memory Strategies**: Customize how memories are extracted (discrete, summary, preferences, custom)
- **Semantic Search**: Vector-based similarity search with metadata filtering
- **Semantic, Keyword & Hybrid Search**: Vector-based similarity, full-text keyword, and combined hybrid search with metadata filtering
- **Flexible Backends**: Pluggable memory vector database factory system
- **Multi-Provider LLM Support**: OpenAI, Anthropic, AWS Bedrock, Ollama, Azure, Gemini via [LiteLLM](https://docs.litellm.ai/)
- **AI Integration**: Automatic topic extraction, entity recognition, and conversation summarization
Expand Down Expand Up @@ -268,7 +268,7 @@ See **[LLM Providers](https://redis.github.io/agent-memory-server/llm-providers/
```
Working Memory (Session-scoped) → Long-term Memory (Persistent)
↓ ↓
- Messages - Semantic search
- Messages - Semantic, keyword & hybrid search
- Structured memories - Topic modeling
- Summary of past messages - Entity recognition
- Metadata - Deduplication
Expand Down
23 changes: 19 additions & 4 deletions agent-memory-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ working_memory = WorkingMemory(
response = await client.put_working_memory("user-session-123", working_memory)

# Retrieve working memory
memory = await client.get_working_memory("user-session-123")
created, memory = await client.get_or_create_working_memory("user-session-123")

# Convenience method for data storage
await client.set_working_memory_data(
Expand Down Expand Up @@ -201,6 +201,21 @@ results = await client.search_long_term_memory(
user_id=UserId(eq="user-123"),
limit=20
)

# Keyword search - full-text matching
results = await client.search_long_term_memory(
text="science fiction",
search_mode="keyword",
limit=20
)

# Hybrid search - combines semantic and keyword matching
results = await client.search_long_term_memory(
text="science fiction",
search_mode="hybrid",
hybrid_alpha=0.7, # 0.0=keyword, 1.0=semantic
limit=20
)
```

## Enhanced Features
Expand Down Expand Up @@ -333,9 +348,9 @@ from agent_memory_client.exceptions import (
)

try:
memory = await client.get_working_memory("nonexistent-session")
except MemoryNotFoundError:
print("Session not found")
created, memory = await client.get_or_create_working_memory("nonexistent-session")
if created:
print("New session created")
except MemoryServerError as e:
print(f"Server error {e.status_code}: {e}")
except MemoryClientError as e:
Expand Down
17 changes: 16 additions & 1 deletion agent-memory-client/agent-memory-client-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,29 @@ await client.createLongTermMemory([
},
]);

// Search with filters
// Search with filters (default: semantic search)
const results = await client.searchLongTermMemory({
text: "science fiction",
topics: new Topics({ any: ["books", "entertainment"] }),
userId: new UserId({ eq: "user-123" }),
limit: 20,
});

// Keyword search - full-text matching
const keywordResults = await client.searchLongTermMemory({
text: "science fiction",
searchMode: "keyword",
limit: 20,
});

// Hybrid search - combines semantic and keyword matching
const hybridResults = await client.searchLongTermMemory({
text: "science fiction",
searchMode: "hybrid",
hybridAlpha: 0.7, // 0.0=keyword, 1.0=semantic
limit: 20,
});

// Get by ID
const memory = await client.getLongTermMemory("memory-id");

Expand Down
52 changes: 37 additions & 15 deletions agent-memory-client/agent_memory_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,7 @@ async def search_long_term_memory(
memory_type: Optional memory type filter
limit: Maximum number of results to return (default: 10)
offset: Offset for pagination (default: 0)
optimize_query: Whether to optimize the query for vector search using a fast model (default: True)
optimize_query: Whether to optimize the query for semantic (vector) search using a fast model; ignored for keyword and hybrid modes (default: False)

Returns:
MemoryRecordResults with matching memories and metadata
Expand Down Expand Up @@ -1233,7 +1233,7 @@ async def search_memory_tool(
offset: Offset for pagination (default: 0)
min_relevance: Optional minimum relevance score (0.0-1.0)
user_id: Optional user ID to filter memories by
optimize_query: Whether to optimize the query for vector search (default: False - LLMs typically provide already optimized queries)
optimize_query: Whether to optimize the query for semantic (vector) search; ignored for keyword and hybrid modes (default: False)

Returns:
Dict with 'memories' list and 'summary' for LLM consumption
Expand Down Expand Up @@ -1386,13 +1386,13 @@ async def handle_tool_calls(client, tool_calls):
"type": "function",
"function": {
"name": "search_memory",
"description": "Search long-term memory for relevant information using semantic vector search. Use this when you need to find previously stored information about the user, such as their preferences, past conversations, or important facts. Examples: 'Find information about user food preferences', 'What did they say about their job?', 'Look for travel preferences'. This searches only long-term memory, not current working memory - use get_working_memory for current session info. IMPORTANT: The result includes 'memories' with an 'id' field; use these IDs when calling edit_long_term_memory or delete_long_term_memories.",
"description": "Search long-term memory for relevant information using semantic, keyword, or hybrid search. Use this when you need to find previously stored information about the user, such as their preferences, past conversations, or important facts. Examples: 'Find information about user food preferences', 'What did they say about their job?', 'Look for travel preferences'. This searches only long-term memory, not current working memory - use get_or_create_working_memory for current session info. IMPORTANT: The result includes 'memories' with an 'id' field; use these IDs when calling edit_long_term_memory or delete_long_term_memories.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The query for vector search describing what information you're looking for",
"description": "The search query describing what information you're looking for",
},
"search_mode": {
"type": "string",
Expand Down Expand Up @@ -1451,7 +1451,7 @@ async def handle_tool_calls(client, tool_calls):
"optimize_query": {
"type": "boolean",
"default": False,
"description": "Whether to optimize the query for vector search (default: False - LLMs typically provide already optimized queries)",
"description": "Whether to optimize the query for semantic (vector) search; ignored for keyword and hybrid modes (default: False)",
},
},
"required": ["query"],
Expand Down Expand Up @@ -1875,7 +1875,7 @@ def get_update_memory_data_tool_schema(cls) -> ToolSchema:
"type": "function",
"function": {
"name": "update_working_memory_data",
"description": "Store or update structured session data (JSON objects) in working memory. Use this for complex session-specific information that needs to be accessed and modified during the conversation. Examples: Travel itinerary {'destination': 'Paris', 'dates': ['2024-03-15', '2024-03-20']}, project details {'name': 'Website Redesign', 'deadline': '2024-04-01', 'status': 'in_progress'}. Different from add_memory_to_working_memory which stores simple text facts.",
"description": "Store or update structured session data (JSON objects) in working memory. Use this for complex session-specific information that needs to be accessed and modified during the conversation. Examples: Travel itinerary {'destination': 'Paris', 'dates': ['2024-03-15', '2024-03-20']}, project details {'name': 'Website Redesign', 'deadline': '2024-04-01', 'status': 'in_progress'}. Different from lazily_create_long_term_memory which stores simple text facts for later promotion to long-term storage.",
"parameters": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -2689,12 +2689,18 @@ async def _resolve_search_memory(self, args: dict[str, Any]) -> dict[str, Any]:
topics = args.get("topics")
entities = args.get("entities")
memory_type = args.get("memory_type")
max_results = args.get("max_results", 5)
max_results = args.get("max_results", 10)
min_relevance = args.get("min_relevance")
user_id = args.get("user_id")
search_mode = args.get("search_mode", "semantic")
hybrid_alpha = args.get("hybrid_alpha")
text_scorer = args.get("text_scorer")

return await self.search_memory_tool(
query=query,
search_mode=search_mode,
hybrid_alpha=hybrid_alpha,
text_scorer=text_scorer,
topics=topics,
entities=entities,
memory_type=memory_type,
Expand All @@ -2706,7 +2712,7 @@ async def _resolve_search_memory(self, args: dict[str, Any]) -> dict[str, Any]:
async def _resolve_get_working_memory(
self, session_id: str, namespace: str | None, user_id: str | None = None
) -> dict[str, Any]:
"""Resolve get_working_memory function call."""
"""Resolve get_working_memory (deprecated) function call."""
return await self.get_working_memory_tool(
session_id=session_id,
namespace=namespace,
Expand All @@ -2731,7 +2737,7 @@ async def _resolve_add_memory(
namespace: str | None,
user_id: str | None = None,
) -> dict[str, Any]:
"""Resolve add_memory_to_working_memory function call."""
"""Resolve lazily_create_long_term_memory (formerly add_memory_to_working_memory) function call."""
text = args.get("text", "")
if not text:
raise ValueError("Text parameter is required for adding memory")
Expand Down Expand Up @@ -2790,11 +2796,11 @@ async def _resolve_get_long_term_memory(
async def _resolve_create_long_term_memory(
self, args: dict[str, Any], namespace: str | None, user_id: str | None = None
) -> dict[str, Any]:
"""Resolve create_long_term_memory function call."""
"""Resolve eagerly_create_long_term_memory (and deprecated create_long_term_memory alias) function call."""
memories_data = args.get("memories")
if not memories_data:
raise ValueError(
"memories parameter is required for create_long_term_memory"
"memories parameter is required for eagerly_create_long_term_memory"
)
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resolver explicitly supports the deprecated create_long_term_memory alias, but the error message only references eagerly_create_long_term_memory. This can be confusing when the caller invoked the deprecated name. Consider making the error message function-name-agnostic (e.g., just “memories parameter is required”) or mentioning both names.

Copilot uses AI. Check for mistakes.

# Convert dict memories to ClientMemoryRecord objects
Expand Down Expand Up @@ -2907,7 +2913,7 @@ async def resolve_function_calls(
# Handle multiple function calls
calls = [
{"name": "search_memory", "arguments": {"query": "user preferences"}},
{"name": "get_working_memory", "arguments": {}},
{"name": "get_or_create_working_memory", "arguments": {}},
]

results = await client.resolve_function_calls(calls, "session123")
Expand Down Expand Up @@ -3306,7 +3312,7 @@ async def memory_prompt(
context_window_max: Optional direct specification of context window tokens
long_term_search: Optional search parameters for long-term memory
user_id: Optional user ID for the session
optimize_query: Whether to optimize the query for vector search using a fast model (default: True)
optimize_query: Whether to optimize the query for semantic (vector) search using a fast model; ignored for keyword and hybrid modes (default: False)

Returns:
Dict with messages hydrated with relevant memory context
Expand Down Expand Up @@ -3392,6 +3398,9 @@ async def hydrate_memory_prompt(
user_id: dict[str, Any] | None = None,
distance_threshold: float | None = None,
memory_type: dict[str, Any] | None = None,
search_mode: SearchModeEnum | str = SearchModeEnum.SEMANTIC,
hybrid_alpha: float | None = None,
text_scorer: str | None = None,
limit: int = 10,
offset: int = 0,
optimize_query: bool = False,
Expand All @@ -3403,7 +3412,7 @@ async def hydrate_memory_prompt(
long-term memory search with the specified filters.

Args:
query: The query for vector search to find relevant context for
query: The search query to find relevant context for
session_id: Optional session ID filter (as dict)
namespace: Optional namespace filter (as dict)
topics: Optional topics filter (as dict)
Expand All @@ -3413,9 +3422,12 @@ async def hydrate_memory_prompt(
user_id: Optional user ID filter (as dict)
distance_threshold: Optional distance threshold
memory_type: Optional memory type filter (as dict)
search_mode: Search strategy to use ("semantic", "keyword", or "hybrid")
hybrid_alpha: Optional weight for vector similarity in hybrid search (0.0-1.0)
text_scorer: Optional Redis full-text scoring algorithm for keyword and hybrid search
limit: Maximum number of long-term memories to include
offset: Offset for pagination (default: 0)
optimize_query: Whether to optimize the query for vector search using a fast model (default: True)
optimize_query: Whether to optimize the query for semantic (vector) search using a fast model; ignored for keyword and hybrid modes (default: False)

Returns:
Dict with messages hydrated with relevant long-term memories
Expand Down Expand Up @@ -3443,6 +3455,16 @@ async def hydrate_memory_prompt(
long_term_search["distance_threshold"] = distance_threshold
if memory_type is not None:
long_term_search["memory_type"] = memory_type
normalized_search_mode = (
search_mode.value
if isinstance(search_mode, SearchModeEnum)
else str(search_mode)
)
long_term_search["search_mode"] = normalized_search_mode
if hybrid_alpha is not None:
long_term_search["hybrid_alpha"] = hybrid_alpha
if text_scorer is not None:
long_term_search["text_scorer"] = text_scorer

return await self.memory_prompt(
query=query,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def get_memory_tools(
tool_configs = {
"search_memory": {
"name": "search_memory",
"description": "Search long-term memory for relevant information using semantic search. Use this to recall past conversations, user preferences, or stored facts. Returns memories ranked by relevance with scores.",
"description": "Search long-term memory for relevant information using semantic, keyword, or hybrid search. Use this to recall past conversations, user preferences, or stored facts. Returns memories ranked by relevance with scores.",
"func": _create_search_memory_func(memory_client),
},
"get_or_create_working_memory": {
Expand Down Expand Up @@ -224,6 +224,9 @@ def _create_search_memory_func(client: MemoryAPIClient) -> Any:

async def search_memory(
query: str,
search_mode: Literal["semantic", "keyword", "hybrid"] = "semantic",
hybrid_alpha: float | None = None,
text_scorer: str | None = None,
topics: list[str] | None = None,
entities: list[str] | None = None,
memory_type: str | None = None,
Expand All @@ -234,6 +237,9 @@ async def search_memory(
"""Search long-term memory for relevant information."""
result = await client.search_memory_tool(
query=query,
search_mode=search_mode,
hybrid_alpha=hybrid_alpha,
text_scorer=text_scorer,
topics=topics,
entities=entities,
memory_type=memory_type,
Expand Down
4 changes: 2 additions & 2 deletions agent_memory_server/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ async def search_long_term_memory(

Args:
payload: Search payload with filter objects for precise queries
optimize_query: Whether to optimize the query for vector search using a fast model (default: False)
optimize_query: Whether to optimize the query for semantic (vector) search using a fast model; ignored for keyword and hybrid modes (default: False)

Returns:
List of search results
Expand Down Expand Up @@ -926,7 +926,7 @@ async def memory_prompt(

Args:
params: MemoryPromptRequest
optimize_query: Whether to optimize the query for vector search using a fast model (default: False)
optimize_query: Whether to optimize the query for semantic (vector) search using a fast model; ignored for keyword and hybrid modes (default: False)

Returns:
List of messages to send to an LLM, hydrated with relevant memory context
Expand Down
Loading
Loading