mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(memory): read the complete scope in the substring search fallback (#6732)
* fix(memory): read the complete scope in the substring search fallback _search_substring iterated list_facts without paginating, so the adapter-less fallback only ever saw the default page of 100 facts. With 150 matching facts in a scope (max_facts allows up to 500), memory_search returned 100 results for top_k=150 and silently dropped every match past the first page; DeerMem.search keeps these results whenever they are non-empty, so its uncapped fallback never ran. Read the complete scope instead, as the scoped index rebuild already does. * test(memory): drop in-page fact-100 from the past-page assertion fact-100 was the last id inside list_facts' old first page (``matched[0:100]`` in insertion order), so its presence does not distinguish the fixed search from the pre-fix page; fact-101 and fact-150 (plus the ``len(results) == 150`` check) carry the regression signal. Keep a boundary note so the remaining ids are not misread.
This commit is contained in:
parent
0b78051840
commit
f4d10ef42d
@ -1974,8 +1974,11 @@ class FileMemoryStorage(MemoryStorage):
|
||||
return []
|
||||
results: list[dict[str, Any]] = []
|
||||
for scope in scopes:
|
||||
facts = self.list_facts(filters=filters, **self._scope_kwargs(scope))
|
||||
for fact in facts:
|
||||
# The ranking covers the whole scope, so read the complete scope
|
||||
# rather than a list_facts page.
|
||||
for fact in self.load(**self._scope_kwargs(scope)).get("facts", []):
|
||||
if filters and not all(key in fact and fact.get(key) == value for key, value in filters.items()):
|
||||
continue
|
||||
content = fact.get("content")
|
||||
if isinstance(content, str) and query_lower in content.lower():
|
||||
# 0.0 is a persisted confidence _normalize_fact accepts; only unset/null defaults.
|
||||
|
||||
@ -318,3 +318,29 @@ def test_substring_fallback_keeps_zero_confidence_weight(tmp_path: Path) -> None
|
||||
assert scores == {"zero": 0.0, "half": 0.5}
|
||||
finally:
|
||||
storage.close()
|
||||
|
||||
|
||||
def test_substring_fallback_searches_every_fact_of_a_large_scope(tmp_path: Path) -> None:
|
||||
"""The fallback must read the whole scope, not the first page of 100 facts.
|
||||
|
||||
With 150 facts (max_facts allows up to 500) and no retrieval adapter, a
|
||||
top_k of 200 returned only the first 100 matches: the fallback iterated
|
||||
list_facts' default page instead of the complete scope, so facts past
|
||||
the page silently vanished from search results.
|
||||
"""
|
||||
storage = FileMemoryStorage(DeerMemConfig(storage_path=str(tmp_path), max_facts=200))
|
||||
scope = {"userId": "alice", "agentName": "agent-a"}
|
||||
try:
|
||||
facts = [_fact(f"fact-{index:03d}", f"zeta common memory number {index:03d}") for index in range(1, 151)]
|
||||
storage.apply_changes({"upserts": facts}, user_id="alice", agent_name="agent-a")
|
||||
|
||||
results = storage.search_facts("zeta", scopes=[scope], top_k=200)
|
||||
|
||||
assert len(results) == 150
|
||||
returned_ids = {item["fact"]["id"] for item in results}
|
||||
# fact-001..fact-100 sat inside the old first page too (``matched[0:100]``
|
||||
# in insertion order), so fact-101 and fact-150 are the ids only a
|
||||
# complete-scope read can return.
|
||||
assert {"fact-101", "fact-150"} <= returned_ids
|
||||
finally:
|
||||
storage.close()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user