Skip to content

Report an unroutable classifier cell as no_route - #11

Merged
rabeckett merged 2 commits into
mainfrom
fix/classify-no-route
Sep 19, 2026
Merged

rabeckett merged 2 commits into
mainfrom
fix/classify-no-route

Conversation

@rabeckett

Copy link
Copy Markdown
Collaborator

Problem

/classify returned invalid_request when none of the offered models appeared in the classifier routing cell. That is not a caller mistake. The request is well formed and the router simply has nothing it can serve, which is exactly what no_route means.

/route already reports the same condition as no_route, and RoutingTable.route documents the rule the service follows:

Raise RoutingError for invalid requests or NoRouteError when no supported offered candidate has sufficient context capacity.

Classification was the one path that had drifted from that rule.

Why it matters

The asymmetry forced callers to branch on the endpoint to recognise a routine outcome. A client that handled no_route correctly for /route would misreport the identical situation from /classify as a contract violation, sending operators to look for a bug that does not exist.

This was found from the consuming side. A caller offering a model pool with no overlap against the router's tables saw "the router rejected the classify request" when the honest answer was "nothing here fits". Fixing it here means callers need no local mirror of the router's catalogue to produce a correct diagnosis, and the router stays the single authority on what it can serve.

Change

create_classification_plan raises NoRouteError instead of ClassificationError for an empty intersection.

Malformed input is unchanged and still invalid_request, covering a missing authored user message and an omitted effort for a model that requires one. Those are genuine caller errors with something to fix.

The CLI exits 3 rather than 2 for this case, which matches route and required no CLI change since NoRouteError is already handled ahead of the ValueError catch.

Contract coverage

The existing classify-no-supported-choice case offered models: []. The ordinary production shape of this failure is a non-empty pool whose entries are all unrecognised, and that went unrecorded, which is how the inconsistency survived. Added classify-unrecognised-choices to cover it.

openapi.yaml now documents the no_route outcome on /classify, mirroring the wording already used for /route.

Version

Bumped to 0.1.1 across pyproject.toml, __init__.py, openapi.yaml and the Dockerfile ARG, with the recorded discovery fixture updated to match. This is a behaviour change to a published HTTP contract, so it should not ship under the released 0.1.0.

Validation

  • pytest excluding docker: 409 passed
  • ruff format --check, ruff check, ty check: clean
  • Behaviour confirmed against a real container before and after, using the synthetic tables. Unrecognised pool now returns 422 no_route. A pool with partial overlap still returns 200 and ranks only the recognised entries.

Docker and release-marked suites are left to CI. They could not run in my sandbox because uv cannot reach PyPI from here.

/classify returned invalid_request when none of the offered models
appeared in the classifier routing cell. That condition is not a caller
mistake. The request is well formed and the router simply has nothing it
can serve, which is what no_route means. /route already reports the same
condition that way, and routing_table.route documents the rule the
service follows, RoutingError for invalid requests and NoRouteError when
no supported candidate remains. Classification was the one path that
drifted from it.

The asymmetry forced callers to branch on the endpoint to recognise a
routine outcome. A client that handled no_route correctly for /route
would misreport the identical situation from /classify as a contract
violation, pointing operators at a bug that does not exist.

Raise NoRouteError for the empty intersection so both endpoints share one
taxonomy. Malformed input keeps invalid_request, including a missing
authored user message and an omitted effort for a model that requires
one. The CLI now exits 3 rather than 2 for this case, matching route.

Add a contract case covering a non-empty pool whose models are all
unrecognised. The existing case offered no models at all, so the ordinary
production shape of this failure went unrecorded.
@rabeckett
rabeckett merged commit cec156b into main Sep 19, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant