fix(serve): suppress the query truncation banner when no nodes were cut (#2601)

This commit is contained in:
Ben Younes
2026-08-12 14:20:21 +01:00
committed by safishamsi
parent 4730ed9abb
commit b645a3a7f1
2 changed files with 37 additions and 0 deletions
+10
View File
@@ -1082,6 +1082,16 @@ def _subgraph_to_text(G: nx.Graph, nodes: set[str], edges: list[tuple], token_bu
total_nodes = sum(1 for l in lines if l.startswith("NODE "))
shown_nodes = output[:cut_at].count("\nNODE ") + (1 if output.startswith("NODE ") else 0)
cut_count = total_nodes - shown_nodes
# Nodes render before edges, so a char-budget overflow whose cut lands
# past the last NODE line drops only trailing edges — no whole node is
# lost. Announcing "showing N of N nodes … among the 0 cut nodes" then
# reads as a false truncation warning that teaches an agent to distrust a
# complete answer and burn follow-up narrowing calls for nodes that were
# never cut (#2601). When every node is shown the answer is complete:
# return the full output with no banner rather than silently dropping
# edges under a misleading notice.
if cut_count == 0:
return output
# Prominent notice at the TOP so a truncated answer can never be mistaken
# for a complete one — silence used to read as absence (#BUG2). The
# notice + end marker sit OUTSIDE char_budget by design (two bounded
+27
View File
@@ -1496,6 +1496,33 @@ def test_subgraph_to_text_no_notice_when_under_budget():
assert "TRUNCATED" not in text and "truncated" not in text
def test_subgraph_to_text_no_banner_when_only_edges_overflow():
"""#2601: nodes render before edges, so a budget overflow that only trims
trailing edges cuts zero whole nodes. The banner must not fire with a
misleading "showing N of N nodes … among the 0 cut nodes" that pushes an
agent to distrust a complete answer and issue pointless narrowing calls."""
import itertools
G = nx.Graph()
labels = [f"n{i}" for i in range(4)]
for lbl in labels:
G.add_node(lbl, label=lbl, source_file="f.py", source_location="L1", community="c")
edges = list(itertools.combinations(labels, 2))
for u, v in edges:
G.add_edge(u, v, relation="calls", confidence="high")
# Budget large enough for every NODE line but not the trailing EDGE lines.
text = _subgraph_to_text(G, set(G.nodes), edges, token_budget=60)
node_lines = [l for l in text.splitlines() if l.startswith("NODE ")]
edge_lines = [l for l in text.splitlines() if l.startswith("EDGE ")]
assert len(node_lines) == len(labels), "every node must still be shown"
assert "TRUNCATED" not in text and "truncated" not in text
assert "cut nodes" not in text
# A complete answer renders the whole subgraph: suppressing the banner must
# not silently truncate the trailing edges either (a `return output[:cut_at]`
# would drop them and still pass the assertions above).
assert len(edge_lines) == len(edges), "all edges must survive a complete answer"
def test_subgraph_to_text_order_is_deterministic():
"""Equal-degree nodes render in a stable order regardless of set iteration."""
G = nx.Graph()