fix(oss): parallelize entity boost searches in Memory.search (#5377)

This commit is contained in:
Kartik
2026-06-05 21:03:42 +05:30
committed by GitHub
parent 7ac8ab154b
commit d817aa9c12
5 changed files with 575 additions and 43 deletions
+75 -30
View File
@@ -1,4 +1,5 @@
import asyncio
import concurrent.futures
import gc
import hashlib
import json
@@ -1464,34 +1465,55 @@ class Memory(MemoryBase):
memory_boosts = {}
try:
for _, entity_text in deduped:
entity_embedding = self.embedding_model.embed(entity_text, "search")
matches = self.entity_store.search(
query=entity_text,
vectors=entity_embedding,
top_k=500,
filters=search_filters,
entity_texts = [text for _, text in deduped]
embeddings = self.embedding_model.embed_batch(entity_texts, "search")
if len(embeddings) != len(entity_texts):
logger.warning(
"embed_batch returned %d vectors for %d texts — skipping entity boost",
len(embeddings),
len(entity_texts),
)
return memory_boosts
entity_store = self.entity_store
def _search_entity(entity_text, embedding):
return entity_store.search(
query=entity_text, vectors=embedding, top_k=500, filters=search_filters
)
for match in matches:
similarity = match.score if hasattr(match, 'score') else 0.0
if similarity < 0.5:
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
futures = {
pool.submit(_search_entity, text, emb): text
for text, emb in zip(entity_texts, embeddings)
}
for future in concurrent.futures.as_completed(futures):
try:
matches = future.result()
except Exception as e:
logger.warning("Entity boost search failed for one entity: %s", e)
continue
payload = match.payload if hasattr(match, 'payload') else {}
linked_memory_ids = payload.get("linked_memory_ids", [])
if not isinstance(linked_memory_ids, list):
continue
for match in matches:
similarity = match.score if hasattr(match, 'score') else 0.0
if similarity < 0.5:
continue
# Spread-attenuated boost: entities linking to many memories get attenuated
num_linked = max(len(linked_memory_ids), 1)
memory_count_weight = 1.0 / (1.0 + 0.001 * ((num_linked - 1) ** 2))
boost = similarity * ENTITY_BOOST_WEIGHT * memory_count_weight
payload = match.payload if hasattr(match, 'payload') else {}
linked_memory_ids = payload.get("linked_memory_ids", [])
if not isinstance(linked_memory_ids, list):
continue
for memory_id in linked_memory_ids:
if memory_id:
memory_key = str(memory_id)
memory_boosts[memory_key] = max(memory_boosts.get(memory_key, 0.0), boost)
num_linked = max(len(linked_memory_ids), 1)
memory_count_weight = 1.0 / (1.0 + 0.001 * ((num_linked - 1) ** 2))
boost = similarity * ENTITY_BOOST_WEIGHT * memory_count_weight
for memory_id in linked_memory_ids:
if memory_id:
memory_key = str(memory_id)
memory_boosts[memory_key] = max(memory_boosts.get(memory_key, 0.0), boost)
except Exception as e:
logger.warning(f"Entity boost computation failed: {e}")
@@ -2872,15 +2894,38 @@ class AsyncMemory(MemoryBase):
memory_boosts = {}
try:
for _, entity_text in deduped:
entity_embedding = await asyncio.to_thread(self.embedding_model.embed, entity_text, "search")
matches = await asyncio.to_thread(
self.entity_store.search,
query=entity_text,
vectors=entity_embedding,
top_k=500,
filters=search_filters,
entity_texts = [text for _, text in deduped]
embeddings = await asyncio.to_thread(self.embedding_model.embed_batch, entity_texts, "search")
if len(embeddings) != len(entity_texts):
logger.warning(
"embed_batch returned %d vectors for %d texts — skipping entity boost",
len(embeddings),
len(entity_texts),
)
return memory_boosts
sem = asyncio.Semaphore(4)
async def _search_entity(entity_text, embedding):
async with sem:
return await asyncio.to_thread(
self.entity_store.search,
query=entity_text,
vectors=embedding,
top_k=500,
filters=search_filters,
)
results = await asyncio.gather(
*(_search_entity(text, emb) for text, emb in zip(entity_texts, embeddings)),
return_exceptions=True,
)
for matches in results:
if isinstance(matches, BaseException):
logger.warning("Entity boost search failed for one entity: %s", matches)
continue
for match in matches:
similarity = match.score if hasattr(match, 'score') else 0.0