fix(path): prefer full-token label matches when resolving path endpoints#1785
Closed
CJNA wants to merge 1 commit into
Closed
fix(path): prefer full-token label matches when resolving path endpoints#1785CJNA wants to merge 1 commit into
CJNA wants to merge 1 commit into
Conversation
graphify path committed to _score_nodes()[0] for each endpoint. The
full-query bonus tier only fires when the query equals or prefixes a
label, so a query that is a token subset of the intended label (query
"Reject-everything judge" vs. label "Degenerate Reject-Everything
Judge") gets no bonus, and a node prefix-matching one rare token
("Rejection Summary") can out-score it on IDF alone — on a real 27k-node
graph the intended node ranked 94th at 1/24th the head score, behind an
exact 3-way tie of disconnected doc-island leaves, producing a false
"No path found".
Add _pick_scored_endpoint(): scan the score-ordered list and take the
first candidate whose label contains every query token, falling back to
scored[0] when none does (or when the head already full-matches, where
it is a no-op). Wire it into both endpoint resolvers — the path CLI and
the MCP shortest-path tool — and suppress the close-runner-up ambiguity
warning when the picked endpoint is not the raw score head, since the
head's margin didn't decide anything there.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
safishamsi
pushed a commit
that referenced
this pull request
Jul 11, 2026
…nts (#1785) `graphify path` committed each endpoint to _score_nodes()[0]. The full-query bonus tier only fires when the query equals/prefixes a label, so a query that is a token subset of the intended label ("Reject-everything judge" vs "Degenerate Reject-Everything Judge") got no bonus and a node prefix-matching one rare token ("Rejection Summary") could out-score it on IDF alone — anchoring the path on an unrelated, often disconnected node and returning a false "No path found". _pick_scored_endpoint() scans the score-ordered list and takes the first candidate whose label contains EVERY query token, falling back to scored[0] when none does — so when the head already full-matches (the common case) resolution is unchanged. Wired into both the `path` CLI and the MCP _tool_shortest_path. The close-runner-up ambiguity warning now fires only when the picked endpoint is the raw score head (a full-token override was chosen on coverage, not score, so the head's margin is irrelevant). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
|
Merged on v8, authored to you. Verified on a synthetic version of your repro: with distractors prefix-matching only the |
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.
Problem
graphify pathresolves each endpoint by committing to_score_nodes()[0]. The full-query bonus tier in_score_nodesonly fires when the query equals or prefixes a label, so a query that is a token subset of the intended label gets no bonus, and a node prefix-matching a single rare token can out-score it on IDF alone.Real-world repro (27k-node graph):
The head of the scored list was an exact 3-way tie of disconnected doc-island leaves ("Rejection Summary", "Rejected Alternatives", "Rejected / not surfaced (with reason)" — each matching only the
rejecttoken as a prefix), while the intended node "Degenerate Reject-Everything Judge" — which contains every query token — ranked 94th at 1/24th the head score. The path anchored on a degree-1 leaf in another component → false "No path found".Fix
_pick_scored_endpoint(G, scored, query)inserve.py: scan the score-ordered list and take the first candidate whose label contains every query token; fall back toscored[0]when none does. When the head already full-matches (the common case — including every whole-query-tier hit), the pick is exactlyscored[0], so existing resolutions are unchanged.Wired into both endpoint resolvers:
cli.pypathcommandserve.py_tool_shortest_path(MCP)Also: the close-runner-up "ambiguous" warning is now emitted only when the picked endpoint is the raw score head — when the full-token override chose a different node, the head's margin didn't decide anything and the warning was misleading.
After
Tests
test_endpoint_prefers_full_token_match— minimal deterministic graph reproducing the misranking (decoy "Rejection Summary" out-scores the full match via IDF; verified the decoy genuinely wins under current scoring before the fix); asserts the path resolves through the full-match node.test_endpoint_falls_back_to_score_head— query with no full-token candidate behaves byte-identically to the oldscored[0]pick.tests/test_serve.py+tests/test_path_cli.py: 84 passed.ruff checkclean on all touched files.🤖 Generated with Claude Code