mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(memory): rank capacity eviction per record, not per fact_id (#7454)
* fix(memory): rank capacity eviction per record, not per fact_id select_facts_for_capacity keyed every score by fact_id, so an imported batch that repeats an id collapsed into a single dict entry: the last record's score won, and both the ranking pass and the evicted audit read that one value for every position. A 0.9-confidence record could therefore be evicted while its own 0.1-confidence duplicate survived. The import path keeps caller-supplied ids without checking them for uniqueness, and any over-cap import lands here. Score by position instead: a parallel list next to the indexed facts, so ranking and the audit read each record's own score. The public ``scores`` dict stays keyed by fact_id with last-write-wins semantics, keeping the DeerMem benchmark contract and the callers that never read it unchanged. Red on main: test_confidence_policy_scores_duplicate_fact_ids_per_record fails with the highest-confidence record evicted; green on this branch. * fix(utils): keep share-less UNC roots out of the extended-length prefix extended_length_path prefixed every absolute UNC path with \\?\UNC\, but Win32 only accepts \\?\UNC\server\share\... — a share-less root (\\server, \\server\, or a bare \) is not a valid device path, so every Win32 call handed the prefixed spelling fails. Only prefix when both the server and the share are present; otherwise return the ordinary spelling unchanged. Red on main: the \\server, \\server\, //, and //server cases of test_windows_extended_spelling assert the forged prefix; green on this branch.
This commit is contained in:
parent
2ce9bdc003
commit
346ff7b253
@ -179,9 +179,13 @@ def select_facts_for_capacity(
|
||||
usage = usage or {}
|
||||
indexed_facts = list(enumerate(facts))
|
||||
scores: dict[str, FactEvictionScore] = {}
|
||||
# Per-position scores: the input list may repeat a fact_id, and the public
|
||||
# ``scores`` dict keeps only the last write per id, so ranking and the
|
||||
# evicted audit must read each record's own score, not the shared id's.
|
||||
scored: list[FactEvictionScore] = []
|
||||
for index, fact in indexed_facts:
|
||||
fact_id = str(fact.get("id") or f"__missing_{index}")
|
||||
scores[fact_id] = _score_fact(
|
||||
score = _score_fact(
|
||||
fact,
|
||||
policy=policy,
|
||||
usage=usage.get(fact_id),
|
||||
@ -192,6 +196,8 @@ def select_facts_for_capacity(
|
||||
confirmation_half_life_days=confirmation_half_life_days,
|
||||
access_half_life_days=access_half_life_days,
|
||||
)
|
||||
scores[fact_id] = score
|
||||
scored.append(score)
|
||||
|
||||
if len(indexed_facts) <= max_facts:
|
||||
return FactEvictionDecision(
|
||||
@ -203,7 +209,7 @@ def select_facts_for_capacity(
|
||||
|
||||
ranked = sorted(
|
||||
indexed_facts,
|
||||
key=lambda item: (-scores[str(item[1].get("id") or f"__missing_{item[0]}")].value, item[0]),
|
||||
key=lambda item: (-scored[item[0]].value, item[0]),
|
||||
)
|
||||
reserved_count = 0
|
||||
selected_indexes: set[int] = set()
|
||||
@ -227,7 +233,7 @@ def select_facts_for_capacity(
|
||||
if index in selected_indexes:
|
||||
continue
|
||||
fact_id = str(fact.get("id") or f"__missing_{index}")
|
||||
score = scores[fact_id]
|
||||
score = scored[index]
|
||||
evicted.append(
|
||||
EvictedFact(
|
||||
fact_id=fact_id,
|
||||
|
||||
@ -24,7 +24,13 @@ def extended_length_path(path: Path) -> Path:
|
||||
return path
|
||||
absolute = os.path.abspath(path)
|
||||
if absolute.startswith("\\\\"):
|
||||
return Path("\\\\?\\UNC\\" + absolute[2:])
|
||||
# Win32 only accepts \\?\UNC\server\share\...; a share-less UNC root
|
||||
# (\\server, \\server\, or a bare \) is not a valid device path, so
|
||||
# keep its ordinary spelling instead of forging an invalid one.
|
||||
server, separator, share = absolute[2:].partition("\\")
|
||||
if server and separator and share:
|
||||
return Path("\\\\?\\UNC\\" + absolute[2:])
|
||||
return Path(absolute)
|
||||
return Path("\\\\?\\" + absolute)
|
||||
|
||||
|
||||
|
||||
@ -17,6 +17,10 @@ from deerflow.utils import host_paths
|
||||
(r"C:\shelf\..\documents", r"\\?\C:\documents"),
|
||||
("C:/shelf/documents", r"\\?\C:\shelf\documents"),
|
||||
(r"\\server\share\shelf", r"\\?\UNC\server\share\shelf"),
|
||||
(r"\\server", r"\\server"),
|
||||
("\\\\server\\", "\\\\server\\"),
|
||||
(r"//", r"\\"),
|
||||
(r"//server", r"\\server"),
|
||||
(r"\\?\C:\shelf", r"\\?\C:\shelf"),
|
||||
(r"\\?\UNC\server\share\shelf", r"\\?\UNC\server\share\shelf"),
|
||||
(r"\\.\C:\shelf", r"\\.\C:\shelf"),
|
||||
|
||||
@ -79,6 +79,31 @@ def test_confidence_policy_coerces_non_float_confidence() -> None:
|
||||
assert [fact["id"] for fact in decision.kept] == ["b", "c"]
|
||||
|
||||
|
||||
def test_confidence_policy_scores_duplicate_fact_ids_per_record() -> None:
|
||||
"""Duplicate ids must not collide: rank and audit each record by its own score."""
|
||||
facts = [
|
||||
_fact("dup", confidence=0.9),
|
||||
_fact("dup", confidence=0.1),
|
||||
_fact("other", confidence=0.5),
|
||||
]
|
||||
|
||||
decision = select_facts_for_capacity(
|
||||
facts,
|
||||
max_facts=1,
|
||||
policy=EVICTION_POLICY_CONFIDENCE,
|
||||
now=NOW,
|
||||
)
|
||||
|
||||
# The highest-confidence record wins even though its id repeats.
|
||||
assert [fact["id"] for fact in decision.kept] == ["dup"]
|
||||
assert decision.kept[0]["confidence"] == 0.9
|
||||
assert [item.fact_id for item in decision.evicted] == ["dup", "other"]
|
||||
assert [item.score for item in decision.evicted] == [0.1, 0.5]
|
||||
# Public contract unchanged: scores stays keyed by fact_id (last write wins).
|
||||
assert decision.scores["dup"].value == pytest.approx(0.1)
|
||||
assert set(decision.scores) == {"dup", "other"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"policy",
|
||||
[EVICTION_POLICY_CONFIDENCE, EVICTION_POLICY_HYBRID_V1],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user