Skip to content

feat(server): report declared model capabilities on /v1/models - #200

Open
elyasmnvidian wants to merge 1 commit into
mainfrom
emehtabuddin/models-capabilities
Open

feat(server): report declared model capabilities on /v1/models#200
elyasmnvidian wants to merge 1 commit into
mainfrom
emehtabuddin/models-capabilities

Conversation

@elyasmnvidian

@elyasmnvidian elyasmnvidian commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Context: supersedes the closed #105, which guarded context_window/tool_calling as null and named this operator-declared path as the follow-up. This PR implements it.

Problem

GET /v1/models reports null for every route's context_window and tool_calling, and an operator has no way to change it. Take an operator running a route over a model they know has a 1M-token context window:

[targets.deep]
id = "nvidia/deepseek-ai/deepseek-v4-pro"
llm_client = "primary"

[routes.smart]
id = "smart"
type = "passthrough"
target = "deep"

What happens

GET /v1/models lists the route with no capability values, so a client cannot learn the window from the listing:

{
  "id": "smart",
  "capabilities": {
    "streaming": true,
    "tool_calling": null,
    "context_window": null,
    "supported_inbound_formats": ["openai-chat-completions", "openai-responses", "anthropic-messages"]
  }
}

The route id (smart) is an operator alias, not a model, so the server has nothing to read a real window from — and guessing one from the target model name breaks the moment a route serves a model the guess does not know.

The fix

Let the operator declare each target's context_window and tool_calling in the server config. A route advertises the combination across the targets it can answer from: the smallest declared window (a route is only as capable as its most limited tier) and tool calling only when every tier supports it. A capability that no serving tier declares stays null — the server never invents a number. A classifier or stage-router judge target only scores the request, so it is left out of the advertised capabilities.

[targets.deep]
id = "nvidia/deepseek-ai/deepseek-v4-pro"
llm_client = "primary"
context_window = 1000000
tool_calling = true

Evidence

Declared → the endpoint reports the value; undeclared → it stays null; one declared tier plus one undeclared tier → the route reports null rather than over-claiming the declared tier's window:

// route "smart" over the declared target above
"capabilities": { "context_window": 1000000, "tool_calling": true, ... }

// a route over a target that declares no hints
"capabilities": { "context_window": null, "tool_calling": null, ... }

A declared context_window = 0 is rejected at config load. This is a config and contract change, so there is no performance number to report.

How tested

cargo clippy --workspace --all-targets -- -D warnings
cargo test -p switchyard-server
  • capabilities.rs unit tests: smallest-window plus tool-calling combination; a tier without tool calling makes the route report false; a missing field reports null while the other field still combines; no serving targets reports null.
  • config.rs: hints parse and route_capabilities combines them across serving tiers with the judge target excluded; a zero context_window is rejected.
  • tests/server.rs::models_endpoint_reports_declared_target_capabilities_and_null_when_undeclared: the full endpoint reports declared values, excludes the judge target, and reports null for undeclared and partially-declared routes.

Compatibility

context_window and tool_calling are new optional target fields. Existing configs keep working unchanged — a target that declares neither advertises null, exactly as today. No migration needed.

Summary by CodeRabbit

  • New Features

    • Model listings now expose configured context-window limits and tool-calling support.
    • Capabilities are automatically aggregated across serving targets, using the smallest context window and only advertising tool calling when universally supported.
    • Classifier and judge-only targets are excluded from advertised model capabilities.
  • Bug Fixes

    • Missing or incomplete capability declarations now correctly appear as unspecified.
    • Invalid zero-valued context windows are rejected.

@elyasmnvidian
elyasmnvidian force-pushed the emehtabuddin/models-capabilities branch 2 times, most recently from 6b812fc to 538fb3d Compare July 30, 2026 18:59
@elyasmnvidian
elyasmnvidian marked this pull request as ready for review July 30, 2026 20:20
@elyasmnvidian
elyasmnvidian requested a review from a team as a code owner July 30, 2026 20:20
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change adds declared model capabilities for context-window size and tool calling. Route construction aggregates serving-target declarations, excludes classifier and judge targets, stores capabilities with routes, and exposes them through /v1/models.

Changes

Model Capability Advertisement

Layer / File(s) Summary
Capability contract and aggregation
crates/switchyard-server/src/capabilities.rs
Adds optional context_window and tool_calling fields. Aggregation selects the smallest declared context window and requires complete tool-calling declarations. Missing declarations and empty routes produce None.
Route capability derivation
crates/switchyard-server/src/config.rs
Adds target capability hints, rejects zero context windows, aggregates serving-target capabilities, and excludes classifier and stage-router judge targets.
State and models endpoint integration
crates/switchyard-server/src/lib.rs, crates/switchyard-server/tests/server.rs
Stores capabilities with route algorithms, serializes them in /v1/models, updates test state builders, and adds endpoint coverage for declared, missing, and excluded target capabilities.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Poem

A rabbit checks each declared sign,
Smallest windows align.
Judges wait outside the route,
Models carry fields throughout.
Tests hop brightly: all is fine.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR preserves null capabilities without declarations and adds explicit operator declarations as the linked issue permits [#105].
Out of Scope Changes check ✅ Passed The configuration, aggregation, endpoint, API, and tests directly support declared capability reporting and compatibility requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes reporting declared model capabilities through the /v1/models endpoint.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch emehtabuddin/models-capabilities

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/switchyard-server/src/capabilities.rs`:
- Around line 23-40: Extend ModelCapabilities and ModelCapabilities::for_models
to infer and store a tool_calling capability alongside context_window, using the
established default for empty routes and the least-capable result across
model_ids. Update downstream GET /v1/models serialization to use this field
instead of hard-coding tool support, and add coverage for a model that does not
support tool calling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d1483d77-6999-4092-b018-2090c7a0ded0

📥 Commits

Reviewing files that changed from the base of the PR and between 6b9a168 and 538fb3d.

📒 Files selected for processing (4)
  • crates/switchyard-server/src/capabilities.rs
  • crates/switchyard-server/src/config.rs
  • crates/switchyard-server/src/lib.rs
  • crates/switchyard-server/tests/server.rs

Comment thread crates/switchyard-server/src/capabilities.rs Outdated
@elyasmnvidian
elyasmnvidian force-pushed the emehtabuddin/models-capabilities branch 3 times, most recently from a87624c to b02f431 Compare July 31, 2026 06:22
@elyasmnvidian elyasmnvidian changed the title fix(server): report model capabilities on /v1/models feat(server): report declared model capabilities on /v1/models Jul 31, 2026
@elyasmnvidian

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
@ayushag-nv
ayushag-nv force-pushed the emehtabuddin/models-capabilities branch from b02f431 to acf5ed6 Compare July 31, 2026 16:19
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