Skip to content

Commit 9d90155

Browse files
committed
fix(core): don't zero edge weight when recovering from a partial write
Follow-up to the edge-weight fix, from a Codex review of #3373: the weight filter excluded sources found in existing_full_source_ids, but that set can come from relation_chunks_storage, a SEPARATE store from the graph edge. If a prior attempt upserted relation_chunks but crashed before upsert_edge, there is no stored scalar weight (already_weights is empty), so filtering those sources recovered the edge with weight 0. Only apply the exclusion when there IS a stored scalar (already_weights non-empty); otherwise sum all contributions. Regression test: an edge with relation_chunks holding the source but no graph edge yet recovers with weight 1, not 0.
1 parent cc5d8c4 commit 9d90155

2 files changed

Lines changed: 72 additions & 5 deletions

File tree

lightrag/operate.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2510,15 +2510,23 @@ async def _merge_edges_then_upsert(
25102510
# per reprocess (the #3367 sibling of description accumulation).
25112511
# Genuinely new sources still add their weight, preserving legitimate
25122512
# multi-document growth.
2513-
already_source_id_set = set(existing_full_source_ids)
2514-
weight = sum(
2515-
[
2513+
#
2514+
# Guard on already_weights (i.e. an existing edge with a stored scalar):
2515+
# existing_full_source_ids can come from relation_chunks_storage, which
2516+
# is a SEPARATE store from the graph edge. If a prior attempt upserted
2517+
# relation_chunks but crashed before upsert_edge, there is no stored
2518+
# weight yet — filtering those sources would recover the edge with
2519+
# weight 0. With no stored scalar nothing is double-counted, so sum all.
2520+
if already_weights:
2521+
already_source_id_set = set(existing_full_source_ids)
2522+
new_weight_terms = [
25162523
dp["weight"]
25172524
for dp in edges_data
25182525
if dp.get("source_id") not in already_source_id_set
25192526
]
2520-
+ already_weights
2521-
)
2527+
else:
2528+
new_weight_terms = [dp["weight"] for dp in edges_data]
2529+
weight = sum(new_weight_terms + already_weights)
25222530

25232531
# 6.2 Finalize keywords by merging existing and new keywords
25242532
all_keywords = set()

tests/extraction/test_summary_description_dedup.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
_handle_entity_relation_summary,
2222
_merge_edges_then_upsert,
2323
_merge_nodes_then_upsert,
24+
make_relation_chunk_key,
2425
)
2526
from lightrag.constants import GRAPH_FIELD_SEP
2627

@@ -257,3 +258,61 @@ def _edge(src: str) -> dict:
257258
await _merge_edges_then_upsert("A", "B", [_edge(src)], g, None, None, cfg)
258259

259260
assert await g.get_edge("A", "B") is not None
261+
262+
263+
class _MemKV:
264+
"""No-op-ish KV that just serves a preset get_by_id."""
265+
266+
def __init__(self, data: dict):
267+
self.data = dict(data)
268+
269+
async def get_by_id(self, key):
270+
return self.data.get(key)
271+
272+
async def upsert(self, data):
273+
self.data.update(data)
274+
275+
276+
@pytest.mark.offline
277+
@pytest.mark.asyncio
278+
async def test_edge_weight_recovers_to_one_when_edge_missing_but_chunks_stored():
279+
"""Partial-write recovery: relation_chunks_storage already has the source but
280+
the graph edge does not exist yet (a crash between the two writes). The weight
281+
filter must NOT treat those sources as already-weighted — already_weights is
282+
empty, so filtering them would recover the edge with weight 0. With no stored
283+
scalar, sum all contributions (weight = 1, not 0)."""
284+
g = _MemGraph()
285+
cfg = _cfg()
286+
for name in ("A", "B"):
287+
await g.upsert_node(
288+
name,
289+
{
290+
"entity_id": name,
291+
"description": name,
292+
"source_id": "c1",
293+
"entity_type": "X",
294+
"file_path": "f",
295+
},
296+
)
297+
rcs = _MemKV({make_relation_chunk_key("A", "B"): {"chunk_ids": ["c1"]}})
298+
await _merge_edges_then_upsert(
299+
"A",
300+
"B",
301+
[
302+
{
303+
"weight": 1.0,
304+
"source_id": "c1",
305+
"description": "rel",
306+
"keywords": "k",
307+
"file_path": "f",
308+
}
309+
],
310+
g,
311+
None,
312+
None,
313+
cfg,
314+
relation_chunks_storage=rcs,
315+
)
316+
edge = await g.get_edge("A", "B")
317+
assert edge is not None
318+
assert edge["weight"] == 1.0

0 commit comments

Comments
 (0)