feat: same-turn memory retrieval in provider mode
prefetch() now reuses the in-flight background search when it matches the current query, or falls back to a synchronous search (capped at 3s, split across both requests in case-insensitive mode) so memories are injected on the same turn instead of one turn behind. - Track _prefetch_query to scope cached results to their query - Reset _prefetch_result when starting a new background search to prevent stale cross-turn injection after a timed-out join - Guard background thread writes against superseded queries - Add optional per-request timeout override to LocalMem0Client - Update AGENTS.md and README to describe the same-turn flow
This commit is contained in:
@@ -39,17 +39,19 @@ class LocalMem0Client:
|
||||
endpoint: str,
|
||||
json: Optional[Dict] = None,
|
||||
params: Optional[Dict] = None,
|
||||
timeout: Optional[float] = None,
|
||||
) -> Dict:
|
||||
"""Make HTTP request with error handling."""
|
||||
url = f"{self.base_url}{endpoint}"
|
||||
effective_timeout = timeout if timeout is not None else self.timeout
|
||||
try:
|
||||
resp = self.session.request(
|
||||
method, url, json=json, params=params, timeout=self.timeout
|
||||
method, url, json=json, params=params, timeout=effective_timeout
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
except requests.exceptions.Timeout:
|
||||
logger.error("Mem0 request timed out after %ss", self.timeout)
|
||||
logger.error("Mem0 request timed out after %ss", effective_timeout)
|
||||
raise
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
logger.error("Failed to connect to Mem0 server at %s: %s", self.base_url, e)
|
||||
@@ -66,6 +68,7 @@ class LocalMem0Client:
|
||||
user_id: Optional[str] = None,
|
||||
limit: int = 5,
|
||||
case_insensitive: bool = False,
|
||||
timeout: Optional[float] = None,
|
||||
) -> List[Dict]:
|
||||
"""Search memories by semantic similarity.
|
||||
|
||||
@@ -78,18 +81,19 @@ class LocalMem0Client:
|
||||
user_id: User identifier
|
||||
limit: Max results
|
||||
case_insensitive: If True, search with both original and lowercase query
|
||||
timeout: Optional per-request timeout override (seconds)
|
||||
"""
|
||||
if not case_insensitive:
|
||||
payload = {"query": query, "limit": limit}
|
||||
if user_id:
|
||||
payload["user_id"] = user_id
|
||||
result = self._request("POST", "/search", json=payload)
|
||||
result = self._request("POST", "/search", json=payload, timeout=timeout)
|
||||
return result.get("results", [])
|
||||
|
||||
# Case-insensitive mode: search with both original and lowercase
|
||||
# Fetch 2x limit to ensure we get top N after merging
|
||||
results_original = self._search_with_query(query, user_id, limit * 2)
|
||||
results_lower = self._search_with_query(query.lower(), user_id, limit * 2)
|
||||
results_original = self._search_with_query(query, user_id, limit * 2, timeout)
|
||||
results_lower = self._search_with_query(query.lower(), user_id, limit * 2, timeout)
|
||||
|
||||
# Merge and deduplicate, keeping highest score
|
||||
merged = {}
|
||||
@@ -109,12 +113,13 @@ class LocalMem0Client:
|
||||
query: str,
|
||||
user_id: Optional[str] = None,
|
||||
limit: int = 5,
|
||||
timeout: Optional[float] = None,
|
||||
) -> List[Dict]:
|
||||
"""Internal search helper for case-insensitive mode."""
|
||||
payload = {"query": query, "limit": limit}
|
||||
if user_id:
|
||||
payload["user_id"] = user_id
|
||||
result = self._request("POST", "/search", json=payload)
|
||||
result = self._request("POST", "/search", json=payload, timeout=timeout)
|
||||
return result.get("results", [])
|
||||
|
||||
def get_all(self, user_id: Optional[str] = None) -> List[Dict]:
|
||||
|
||||
Reference in New Issue
Block a user