fix(communities): bound the label_propagation loop so build_communities terminates - #1844
Open
Dhimmo wants to merge 1 commit into
Open
fix(communities): bound the label_propagation loop so build_communities terminates#1844Dhimmo wants to merge 1 commit into
Dhimmo wants to merge 1 commit into
Conversation
…es terminates `label_propagation` loops `while True` and exits only on convergence, but label propagation is not guaranteed to converge. On balanced graph shapes the assignments oscillate indefinitely and the loop never returns. This is not theoretical. On a ~1.2k-entity / 649-edge graph, `build_communities` ran CPU-bound for over four hours producing nothing before it was killed by hand. Because the call is synchronous and CPU-bound, it also blocks the event loop, so a client-side cancellation cannot be processed while it spins. The minimal reproduction is two nodes: with `edge_count > 1`, `candidate_rank > 1` holds, so each node adopts its neighbour's community rather than falling back to the monotonic `max(candidate, curr)` tie-break, and the two labels swap on every pass forever. A balanced triangle behaves the same way — no plurality winner exists, so nothing ever settles. Bound the loop at one relabelling pass per node (floor of 10 for small graphs). A run that has not settled by then is oscillating rather than converging slowly, so we log a warning and return the partition reached so far. Label propagation is a heuristic, not an exact algorithm, so a partition from a truncated run is a legitimate result; spinning forever is not. The convergence exit path (`no_change`) is untouched, so graphs that do settle are unaffected. Both copies of the algorithm are fixed: the one in `driver/operations/graph_utils.py` used by all four drivers, and the second in `utils/maintenance/community_operations.py`, which is reachable from the public `Graphiti.build_communities` API via `get_community_clusters`. Tests cover both implementations: the two non-converging shapes terminate within a timeout, a converging graph still exits via `no_change` without warning, and the warning fires when the cap is reached. Against unpatched `main` the termination tests hang indefinitely; with this change the suite completes in about 0.1s. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
I have read the CLA Document and I hereby sign the CLA behalf on myself, e-mail: example@example.com or I have read the CLA Document and I hereby sign the CLA behalf of my company, e-mail: example@example.com Signature is valid for 6 months. This bot will be retriggered when the Contributor License Agreement comment has been provided. Posted by the CLA Assistant Lite bot. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's the problem
label_propagationloopswhile Trueand exits only on convergence — but label propagation isn't guaranteed to converge. On balanced graph shapes the assignments oscillate indefinitely and the loop never returns.This isn't theoretical. On a ~1.2k-entity / 649-edge graph,
build_communitiesran CPU-bound for over four hours producing nothing before I killed it by hand. Because the call is synchronous and CPU-bound it also blocks the event loop, so a client-side cancellation can't be processed while it spins.Minimal reproduction
Two nodes are enough:
With
edge_count > 1,candidate_rank > 1holds, so each node adopts its neighbour's community rather than falling back to the monotonicmax(candidate, curr)tie-break — and the two labels swap on every pass, forever. A balanced triangle (three nodes, alledge_count=2) behaves the same way: no plurality winner exists, so nothing ever settles.The fix
Bound the loop at one relabelling pass per node, with a floor of 10 for small graphs. A run that hasn't settled by then is oscillating rather than converging slowly, so we log a warning and return the partition reached so far.
Label propagation is a heuristic, not an exact algorithm, so a partition from a truncated run is a legitimate result — spinning forever is not. The convergence exit path (
no_change) is untouched, so graphs that do settle behave exactly as before.Both copies of the algorithm are fixed:
graphiti_core/driver/operations/graph_utils.py— used by all four driversgraphiti_core/utils/maintenance/community_operations.py— reachable from the publicGraphiti.build_communitiesAPI viaget_community_clustersTests
tests/test_label_propagation_termination.py, parametrised over both implementations:no_change, with no warning loggedAgainst unpatched
mainthe termination tests hang indefinitely; with this change the suite completes in about 0.1s.ruff formatandruff checkare clean.Notes
Happy to adjust the budget heuristic or make the cap configurable if you'd prefer that shape. I've been running an equivalent fix locally against 0.29.2 and 0.30.1.