feat(oss): port v3 pipeline with hybrid search, entity extraction, and additive scoring (#4805)
Co-authored-by: Soumil Rathi <soumilrathi@gmail.com> Co-authored-by: Saket Aryan <saketaryan2002@gmail.com> Co-authored-by: chaithanyak42 <chaithanya.kumar42a@gmail.com> Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
This commit is contained in:
co-authored by
Soumil Rathi
Saket Aryan
chaithanyak42
kartik-mem0
parent
57f944e18a
commit
a488e19044
+42
-24
@@ -29,6 +29,9 @@ warnings.filterwarnings("default", category=DeprecationWarning)
|
||||
# Setup user config
|
||||
setup_config()
|
||||
|
||||
# Entity parameters that must be passed via filters, not top-level
|
||||
ENTITY_PARAMS = frozenset({"user_id", "agent_id", "app_id", "run_id"})
|
||||
|
||||
|
||||
class MemoryClient:
|
||||
"""Client for interacting with the Mem0 API.
|
||||
@@ -162,12 +165,9 @@ class MemoryClient:
|
||||
elif not isinstance(messages, list):
|
||||
raise ValueError(f"messages must be str, dict, or list[dict], got {type(messages).__name__}")
|
||||
|
||||
# Force v1.1 format for all add operations
|
||||
kwargs["output_format"] = "v1.1"
|
||||
|
||||
kwargs = self._prepare_params(kwargs)
|
||||
payload = self._prepare_payload(messages, kwargs)
|
||||
response = self.client.post("/v1/memories/", json=payload)
|
||||
response = self.client.post("/v3/memories/", json=payload)
|
||||
response.raise_for_status()
|
||||
if "metadata" in kwargs:
|
||||
del kwargs["metadata"]
|
||||
@@ -204,8 +204,7 @@ class MemoryClient:
|
||||
|
||||
Args:
|
||||
options: Typed options for the get_all operation (GetAllMemoryOptions).
|
||||
**kwargs: Optional parameters for filtering (user_id, agent_id,
|
||||
app_id, top_k, page, page_size).
|
||||
**kwargs: Optional parameters for filtering (filters, page, page_size).
|
||||
|
||||
Returns:
|
||||
A dictionary containing memories in v1.1 format: {"results": [...]}
|
||||
@@ -218,6 +217,14 @@ class MemoryClient:
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
"""
|
||||
# Reject top-level entity params - must use filters instead
|
||||
invalid_keys = ENTITY_PARAMS & set(kwargs.keys())
|
||||
if invalid_keys:
|
||||
raise ValueError(
|
||||
f"Top-level entity parameters {invalid_keys} are not supported in get_all(). "
|
||||
f"Use filters={{'user_id': '...'}} instead."
|
||||
)
|
||||
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
params = self._prepare_params(kwargs)
|
||||
|
||||
@@ -267,11 +274,19 @@ class MemoryClient:
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
"""
|
||||
# Reject top-level entity params - must use filters instead
|
||||
invalid_keys = ENTITY_PARAMS & set(kwargs.keys())
|
||||
if invalid_keys:
|
||||
raise ValueError(
|
||||
f"Top-level entity parameters {invalid_keys} are not supported in search(). "
|
||||
f"Use filters={{'user_id': '...'}} instead."
|
||||
)
|
||||
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
params = self._prepare_params(kwargs)
|
||||
payload = {"query": query, **params}
|
||||
|
||||
response = self.client.post("/v2/memories/search/", json=payload)
|
||||
response = self.client.post("/v3/memories/search/", json=payload)
|
||||
response.raise_for_status()
|
||||
if "metadata" in kwargs:
|
||||
del kwargs["metadata"]
|
||||
@@ -283,12 +298,7 @@ class MemoryClient:
|
||||
"sync_type": "sync",
|
||||
},
|
||||
)
|
||||
result = response.json()
|
||||
|
||||
# Ensure v1.1 format (wrap raw list if needed)
|
||||
if isinstance(result, list):
|
||||
return {"results": result}
|
||||
return result
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
def update(
|
||||
@@ -1083,12 +1093,9 @@ class AsyncMemoryClient:
|
||||
elif not isinstance(messages, list):
|
||||
raise ValueError(f"messages must be str, dict, or list[dict], got {type(messages).__name__}")
|
||||
|
||||
# Force v1.1 format for all add operations
|
||||
kwargs["output_format"] = "v1.1"
|
||||
|
||||
kwargs = self._prepare_params(kwargs)
|
||||
payload = self._prepare_payload(messages, kwargs)
|
||||
response = await self.async_client.post("/v1/memories/", json=payload)
|
||||
response = await self.async_client.post("/v3/memories/", json=payload)
|
||||
response.raise_for_status()
|
||||
if "metadata" in kwargs:
|
||||
del kwargs["metadata"]
|
||||
@@ -1122,6 +1129,14 @@ class AsyncMemoryClient:
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
"""
|
||||
# Reject top-level entity params - must use filters instead
|
||||
invalid_keys = ENTITY_PARAMS & set(kwargs.keys())
|
||||
if invalid_keys:
|
||||
raise ValueError(
|
||||
f"Top-level entity parameters {invalid_keys} are not supported in get_all(). "
|
||||
f"Use filters={{'user_id': '...'}} instead."
|
||||
)
|
||||
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
params = self._prepare_params(kwargs)
|
||||
|
||||
@@ -1171,11 +1186,19 @@ class AsyncMemoryClient:
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
"""
|
||||
# Reject top-level entity params - must use filters instead
|
||||
invalid_keys = ENTITY_PARAMS & set(kwargs.keys())
|
||||
if invalid_keys:
|
||||
raise ValueError(
|
||||
f"Top-level entity parameters {invalid_keys} are not supported in search(). "
|
||||
f"Use filters={{'user_id': '...'}} instead."
|
||||
)
|
||||
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
params = self._prepare_params(kwargs)
|
||||
payload = {"query": query, **params}
|
||||
|
||||
response = await self.async_client.post("/v2/memories/search/", json=payload)
|
||||
response = await self.async_client.post("/v3/memories/search/", json=payload)
|
||||
response.raise_for_status()
|
||||
if "metadata" in kwargs:
|
||||
del kwargs["metadata"]
|
||||
@@ -1187,12 +1210,7 @@ class AsyncMemoryClient:
|
||||
"sync_type": "async",
|
||||
},
|
||||
)
|
||||
result = response.json()
|
||||
|
||||
# Ensure v1.1 format (wrap raw list if needed)
|
||||
if isinstance(result, list):
|
||||
return {"results": result}
|
||||
return result
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
async def update(
|
||||
|
||||
+20
-13
@@ -2,6 +2,9 @@
|
||||
|
||||
These models provide IDE autocompletion, runtime validation, and type safety.
|
||||
Methods accept both typed options and **kwargs for backward compatibility.
|
||||
|
||||
Identity fields (user_id, agent_id, app_id, run_id) must be passed inside
|
||||
the ``filters`` dict — the v3 API does not accept them at the top level.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
@@ -9,18 +12,16 @@ from typing import Any, Dict, List, Optional, Union
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class EntityOptions(BaseModel):
|
||||
"""Identity options for add/delete operations (top-level entity IDs)."""
|
||||
class AddMemoryOptions(BaseModel):
|
||||
"""Options for the add() method.
|
||||
|
||||
user_id: Optional[str] = Field(default=None, description="The user ID to associate with the memory")
|
||||
agent_id: Optional[str] = Field(default=None, description="The agent ID to associate with the memory")
|
||||
app_id: Optional[str] = Field(default=None, description="The app ID to associate with the memory")
|
||||
run_id: Optional[str] = Field(default=None, description="The run ID to associate with the memory")
|
||||
|
||||
|
||||
class AddMemoryOptions(EntityOptions):
|
||||
"""Options for the add() method."""
|
||||
Identity fields (user_id, agent_id, app_id, run_id) must be passed inside
|
||||
the ``filters`` dict — the v3 API does not accept them at the top level.
|
||||
"""
|
||||
|
||||
filters: Optional[Dict[str, Any]] = Field(
|
||||
default=None, description="Filters containing entity IDs (e.g. {'user_id': '...'})"
|
||||
)
|
||||
metadata: Optional[Dict[str, Any]] = Field(default=None, description="Additional metadata for the memory")
|
||||
infer: Optional[bool] = Field(default=None, description="Whether to infer memories from the input")
|
||||
custom_categories: Optional[List[Dict[str, Any]]] = Field(
|
||||
@@ -72,10 +73,16 @@ class GetAllMemoryOptions(BaseModel):
|
||||
categories: Optional[List[str]] = Field(default=None, description="Categories to filter by")
|
||||
|
||||
|
||||
class DeleteAllMemoryOptions(EntityOptions):
|
||||
"""Options for the delete_all() method."""
|
||||
class DeleteAllMemoryOptions(BaseModel):
|
||||
"""Options for the delete_all() method.
|
||||
|
||||
pass
|
||||
Identity fields (user_id, agent_id, app_id, run_id) must be passed inside
|
||||
the ``filters`` dict — the API does not accept them at the top level.
|
||||
"""
|
||||
|
||||
filters: Optional[Dict[str, Any]] = Field(
|
||||
default=None, description="Filters containing entity IDs (e.g. {'user_id': '...'})"
|
||||
)
|
||||
|
||||
|
||||
class UpdateMemoryOptions(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user