fix(serve): reserve budget for EDGE lines so query answers keep relations - #2376
fix(serve): reserve budget for EDGE lines so query answers keep relations#2376nicknikolakakis wants to merge 1 commit into
Conversation
…ions _subgraph_to_text appends every NODE line before the first EDGE line, then cuts the joined string at char_budget. On any subgraph whose nodes alone fill the budget, every edge is dropped and the query returns a bare node list: the relations the traversal was run to find never reach the caller. The truncation notice hid it. It tallied nodes only, so a cut that kept all nodes while dropping most edges reported "showing 61 of 61 nodes ... 0 cut nodes", which reads as a complete answer. Repro on a 61-node/60-edge star at the default budget of 2000: 61 nodes and 26 of 60 edges rendered, notice claimed 0 cut. On a real 1381-node docs graph, three 2-hop BFS questions traversed 84, 57 and 66 edges and rendered zero of them. Fix: when the output is over budget, charge the seed block first (BUG2 guarantee unchanged), then reserve half the remainder for edges and give the rest to nodes. Under budget the output is byte-identical, still every node followed by every edge. The notice and end marker now count cut edges alongside cut nodes. Same repro after the fix renders 42 nodes and 47 edges; the real graph returns 24 to 33 edges per question at the default budget.
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR reworks the truncation logic in _subgraph_to_text within graphify/serve.py. Instead of building a single flat list of node and edge lines and applying one cut at the character budget, it now tracks node and edge lines separately and, when over budget, reserves a portion of the remaining budget (after seed nodes) specifically for edge lines. The truncation notice and end marker are also updated to report counts of both cut nodes and cut edges rather than nodes only. The diff adds three new tests covering the edge-reservation behavior (edges surviving when nodes overrun the budget, the notice counting cut edges, and under-budget output remaining unchanged). The remaining changed symbols in the test file appear to be incidental to the edit surface rather than substantive modifications.
No blocking issues surfaced. 2 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 481 functions depend on the 292 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
_subgraph_to_text()— 20 callers, 1 callees
Verification — 481 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 344 function(s) in the blast radius were not formally verified this run
· 1 more finding(s) on lines outside this diff (see the check run).
The problem
_subgraph_to_textappends everyNODEline before the firstEDGEline, then cuts the joined string atchar_budget. When the nodes alone fill the budget, every edge is dropped andgraphify queryreturns a bare node list. The relations the traversal was run to find never reach the caller.The truncation notice hid this, because it tallied nodes only:
That output kept all 61 nodes and silently dropped 34 of 60 edges, while reporting
0 cut. It reads as a complete answer.Reproduction
A 61-node / 60-edge star at the default budget of 2000 renders 61 nodes and 26 of 60 edges, notice claims
0 cut.The synthetic case above is in the added tests. I also hit this on a private 1381-node documentation graph, where three separate 2-hop BFS questions traversed 84, 57 and 66 edges respectively and rendered zero of them at the default budget. Raising
--budgetto 10000 worked around it, which is what pointed at the ordering rather than the traversal.The fix
When the output is over budget:
#BUG2guarantee (the queried symbol always renders, and renders first) is unchanged.EDGElines.NODElines.Under budget nothing changes: the output stays byte-identical, still every node followed by every edge. The notice and the end marker now count cut edges alongside cut nodes.
After the fix the same star renders 42 nodes and 47 edges, and the private graph returns 24 to 33 edges per question at the default budget.
Tests
Three added to
tests/test_serve.py:test_subgraph_to_text_keeps_edges_when_nodes_overrun_budgettest_subgraph_to_text_truncation_notice_counts_cut_edgestest_subgraph_to_text_under_budget_output_is_unchanged_by_edge_reserveThe first two fail on
v8without the change and pass with it. The third is a guard that the under-budget path is untouched.Full suite: 3909 passed, 36 skipped. The 4 failures in
tests/test_ollama_retry_cap.pyare pre-existing in my environment (ModuleNotFoundError: No module named 'openai', an optional extra) and fail identically on a cleanv8checkout.Note on the 50/50 split
Half is a deliberate compromise rather than a tuned value. Edges are cheaper per line and carry both endpoint labels, so an argument exists for giving them more. Happy to change the ratio, make it a parameter, or interleave per seed if you prefer a different shape.