fix(serve): honest overshoot notice when edges push a complete answer over budget (#2784)

An unconditional early return once no whole node line was cut returned the full
edge section unchecked, so a query could emit ~6x the requested budget with no
indication — and the truncation banner advised raising the budget, the exact
trigger. When the node set fits but appending edges overruns the budget, prepend
an honest 'complete answer over budget' notice (real counts, estimated vs
requested tokens) without truncating edges, preserving the #2601 completeness
guarantee. The genuinely-fits case is byte-identical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
AromalBiju1
2026-08-17 15:40:56 +01:00
committed by safishamsi
co-authored by Claude Opus 4.8
parent 7722e23900
commit 7d01244a03
2 changed files with 67 additions and 4 deletions
+22 -4
View File
@@ -1087,11 +1087,29 @@ def _subgraph_to_text(G: nx.Graph, nodes: set[str], edges: list[tuple], token_bu
# 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.
# never cut (#2601). When every node is shown the answer is complete, so
# edges are never dropped either (returning output[:cut_at] here would
# silently truncate them) — but that completeness guarantee is exactly
# why a query can quietly cost 4-6x its requested budget once the last
# node crosses the fit line (#2784): the check above only ever compared
# the FULL output (nodes+edges) against char_budget, so this branch was
# already known to be over budget, yet said nothing about it. Report the
# real size instead of silence — still the complete, non-truncated
# answer, just an honest one.
if cut_count == 0:
return output
if len(output) <= char_budget:
return output
total_edges = sum(1 for l in lines if l.startswith("EDGE "))
est_tokens = len(output) // 3
return (
f"[i] Complete answer over budget: all {total_nodes} nodes and "
f"{total_edges} edges shown (~{est_tokens} tokens vs the "
f"requested ~{token_budget}-token budget). Edges are never "
f"dropped once every node fits, so this is already the full "
f"answer — raising --budget further will not shrink it. Narrow "
f"with context_filter=['call'] or use get_node for a specific "
f"symbol to reduce size instead.\n\n"
) + 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
+45
View File
@@ -1523,6 +1523,51 @@ def test_subgraph_to_text_no_banner_when_only_edges_overflow():
assert len(edge_lines) == len(edges), "all edges must survive a complete answer"
def test_subgraph_to_text_overshoot_notice_when_edges_exceed_budget():
"""#2784: once every node fits, edges are never dropped (#2601) — but that
used to mean the char_budget check silently stopped applying, so a query
could cost 4-6x its requested budget with zero indication. The complete
answer must still be returned whole, but the overshoot must be visible and
must not repeat the "raise the budget" advice that caused the blow-up."""
import itertools
G = nx.Graph()
labels = [f"n{i}" for i in range(20)]
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")
node_chars = len("\n".join(f"NODE {l} [src=f.py loc=L1 community=c]" for l in labels))
budget = (node_chars // 3) + 5 # fits every node, nowhere near every edge
text = _subgraph_to_text(G, set(G.nodes), edges, token_budget=budget)
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)
assert len(edge_lines) == len(edges), "complete answer: edges must not be dropped"
assert "Complete answer over budget" in text
assert str(len(labels)) in text and str(len(edges)) in text
assert "raise" not in text.lower(), "must not repeat the advice that caused the overshoot"
assert "TRUNCATED" not in text and "truncated" not in text
def test_subgraph_to_text_no_overshoot_notice_when_edges_fit_too():
"""The honest-overshoot notice is additive: a complete answer that already
fits the budget must render exactly as before, with no notice at all."""
import itertools
G = nx.Graph()
labels = [f"n{i}" for i in range(3)]
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")
text = _subgraph_to_text(G, set(G.nodes), edges, token_budget=2000)
assert "Complete answer over budget" not in text
assert "TRUNCATED" not in text and "truncated" not in text
def test_subgraph_to_text_order_is_deterministic():
"""Equal-degree nodes render in a stable order regardless of set iteration."""
G = nx.Graph()