Skip to content

feat(api): honor provider-aware request routing - #54426

Closed
abundantbeing wants to merge 7 commits into
NousResearch:mainfrom
abundantbeing:fix/api-model-options
Closed

abundantbeing wants to merge 7 commits into
NousResearch:mainfrom
abundantbeing:fix/api-model-options

Conversation

@abundantbeing

@abundantbeing abundantbeing commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Honor provider-aware model, provider, and model_options fields across the API server's execution surfaces without mutating global configuration.

This branch now focuses only on execution-time routing. The provider/model inventory endpoint is isolated in #54689 so the two PRs no longer carry competing implementations.

Routing contract

Selection precedence is explicit and consistent:

  1. A persisted session /model override wins for that session.
  2. If the request's model matches a configured gateway.platforms.api_server.model_routes alias, the route's target and credentials are used.
  3. Otherwise, direct request model / provider values are resolved through the authenticated provider runtime.
  4. Global gateway defaults are the fallback.

model_options remains request-scoped regardless of which model/provider selection wins. A request provider that conflicts with a pinned route provider or route credentials fails closed with 400 rather than mixing credentials across providers.

Changes

  • carry model, provider, and model_options through session chat, Chat Completions, Responses, and /v1/runs
  • preserve model_routes target models, provider runtimes, route-specific API keys, and base URLs
  • preserve session /model selection and its provider/runtime credentials ahead of per-request selection
  • reject ambiguous route/provider combinations before starting a run
  • apply request-scoped reasoning and service-tier/fast options to AIAgent
  • document the accepted fields, precedence, and fail-closed behavior
  • add positive and conflict coverage across all four execution surfaces

Verification

240 passed, 1 skipped, 1 deselected
  tests/gateway/test_api_server.py
  tests/gateway/test_api_server_runs.py
  (excluding the environment-sensitive current-main health-detail assertion)

12 passed
  tests/gateway/test_session_api.py

Docusaurus production build: success for en and zh-Hans
Full PR diff secret scan: clean

@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have labels Jun 28, 2026
@alt-glitch

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Related to #52531 (a closed earlier attempt at the same authenticated GET /api/model/options REST endpoint), #40563 (merged desktop GUI sharing the same model-inventory plumbing), and #14352 (the provider-aware model-options feature request). Not marked a duplicate since #52531 is closed — this is a fresh resubmission of that surface.

@abundantbeing

Copy link
Copy Markdown
Contributor Author

Follow-up after triage pointed at #52531 / #40563 / #14352:

I re-ran this request path as a failing smoke first. Before the follow-up commit, /v1/chat/completions accepted model, provider, and model_options in the request body but _handle_chat_completions did not pass them into _run_agent:

  • requested_model <MISSING>
  • requested_provider <MISSING>
  • model_options <MISSING>

I then restored the missing behavior from the earlier closed attempt (#52531): API requests now carry requested_model, requested_provider, and model_options through _run_agent / _create_agent, and the idempotency fingerprint includes those fields so cached responses cannot cross model/provider selections.

Fresh verification after the new commit:

  • request-path smoke passed: model=MiniMax-M3, provider=minimax, and model_options={reasoning:{enabled:false},fast:false} reached _run_agent
  • python -m pytest tests/gateway/test_api_server.py -o addopts= -q174 passed, 112 warnings

So this PR is no longer just the inventory endpoint; it now covers the display + actual request execution path.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for carrying the inventory endpoint forward. The endpoint is still absent from the gateway API server on current main, so the feature remains useful.

Problems

  • This branch predates model_routes (4a09b69). Current main resolves configured routes in Chat/Responses and applies route credentials in gateway/platforms/api_server.py:1320-1363. Please integrate request overrides with that pipeline and define precedence against a session /model override.
  • /v1/runs is a separate agent-entry path: it resolves only model_routes and directly calls _create_agent (gateway/platforms/api_server.py:4336-4361). The PR does not propagate provider or model_options there.
  • The advertised API needs docs; current endpoint references omit /api/model/options (website/docs/user-guide/features/api-server.md:197-222).

Suggested changes

  • Add precedence and end-to-end coverage across session chat, Chat Completions, Responses, and Runs.
  • Document the authenticated endpoint and capability flag.

Automated hermes-sweeper review.

Comment thread gateway/platforms/api_server.py
@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
@abundantbeing abundantbeing changed the title feat(api): expose provider-aware model options feat(api): honor provider-aware request routing Jul 15, 2026
@abundantbeing

Copy link
Copy Markdown
Contributor Author

Updated against current main and addressed the routing review in full:

  • integrated request model / provider / model_options with current model_routes, including route-specific provider runtime and credentials
  • made precedence explicit: session /model → selected route alias → direct request selection → global defaults
  • fail closed with 400 when an explicit provider conflicts with a route's provider or pinned route credentials
  • propagated the same contract through session chat (streaming and non-streaming), Chat Completions, Responses, and /v1/runs
  • added success-path and conflict coverage for every execution surface
  • documented accepted fields, precedence, and conflict behavior
  • removed the duplicate inventory endpoint from this branch; fix(api-server): expose model options inventory #54689 now owns /api/model/options, keeping both PRs independently reviewable

Verification on branch head 731e52fb6:

  • broad API/runs sweep: 240 passed, 1 skipped, 1 deselected
  • session API: 12 passed
  • focused routing gate after scope split: 30 passed
  • Docusaurus production build: success for en and zh-Hans
  • full PR diff/credential scan: clean

The one deselected health-detail assertion is environment-sensitive and reproduces on current-main-based lanes; it is unrelated to this routing diff. Ready for re-review.

@abundantbeing

Copy link
Copy Markdown
Contributor Author

Proposed merge topology so the routing PRs do not diverge:

Combined precedence should be:

  1. active confirmed session runtime lock
  2. persisted session /model selection when no lock is active
  3. configured model_routes alias
  4. direct per-request model/provider
  5. global default

model_options remains request-scoped.

One edge case should be decided explicitly: a /model command issued after an active Browser lock must either deliberately replace or clear that lock, or return a conflict. It should never create two silently competing persistent selections.

My intended merge order is #54426 before #61236, followed by rebasing #61236 onto the accepted resolver shape.

# Conflicts:
#	gateway/platforms/api_server.py
@abundantbeing

Copy link
Copy Markdown
Contributor Author

Refreshed against current upstream main (d9165d7a6) with a normal merge commit — no force-push or history rewrite.

Scope remains execution routing only. The resolution preserves current-main profile scoping, checkpoint kwargs, model_routes, and session /model precedence while carrying request provider/model/options through session chat, Chat Completions, Responses, and Runs.

Fresh verification:

  • API/runs/session/profile adjacency: 285 passed, 1 skipped, 1 known current-main health assertion deselected
  • combined inventory + routing + session-lock contract: 1,228 passed, 1 skipped, 1 known current-main health assertion deselected
  • Docusaurus production build: passed for en and zh-Hans
  • Ruff, Python compile, current-main diff check, merge simulation, and high-confidence secret scan: passed

GitHub now reports this head as mergeable. #54689 combines cleanly with it. This should merge before #61236 so the durable-lock PR can take one final refresh onto the accepted resolver.

teknium1 pushed a commit that referenced this pull request Jul 24, 2026
Carry model, provider, and model_options through the API server's
execution surfaces (session chat, Chat Completions, Responses, /v1/runs)
without mutating global configuration. Precedence: session /model
override -> model_routes alias -> direct request selection -> global
defaults. Conflicting route/provider mixes fail closed with 400.
model_options stays request-scoped regardless of which selection wins.

Salvaged from PR #54426 by @abundantbeing.
teknium1 added a commit that referenced this pull request Jul 24, 2026
Follow-ups on the salvaged #54426 routing contract:

- Bare `model` without `provider` on the OpenAI-compatible endpoints
  (/v1/chat/completions, /v1/responses) is now opt-in via
  gateway.platforms.api_server.direct_model_requests (default off) —
  generic OpenAI clients hardcode model names ('gpt-4o', ...) and
  existing deployments rely on those falling back to the gateway
  default. Explicit `provider` requests and the Hermes-native
  session-chat + /v1/runs surfaces are always honored.
  Idea credit: PR #22825 by @mssteuer.
- A model_routes alias with no `model` key can no longer leak the
  alias string as the executing model name (defensive; parse-time
  validation already drops such routes).
- Fix mis-indented _run_agent call args in _handle_session_chat_stream.
- Docs: document the opt-in flag.
@teknium1

Copy link
Copy Markdown
Collaborator

Merged via PR #70853 — your commits were reapplied onto current main with your authorship preserved in git log (d66a82000c). Thanks for the thorough iteration on this: the execution-routing scope split, explicit precedence contract, and fail-closed conflict handling all landed as designed.

One follow-up we added on top: bare model values (no provider) on the OpenAI-compatible endpoints are gated behind an opt-in direct_model_requests config flag so generic clients hardcoding model names keep falling back to the gateway default. Explicit-provider requests and the session-chat//v1/runs surfaces are always honored, so the browser-extension picker flow you built this for is unaffected.

#61236 can now rebase onto the merged resolver shape as you proposed.

randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
Carry model, provider, and model_options through the API server's
execution surfaces (session chat, Chat Completions, Responses, /v1/runs)
without mutating global configuration. Precedence: session /model
override -> model_routes alias -> direct request selection -> global
defaults. Conflicting route/provider mixes fail closed with 400.
model_options stays request-scoped regardless of which selection wins.

Salvaged from PR NousResearch#54426 by @abundantbeing.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
Follow-ups on the salvaged NousResearch#54426 routing contract:

- Bare `model` without `provider` on the OpenAI-compatible endpoints
  (/v1/chat/completions, /v1/responses) is now opt-in via
  gateway.platforms.api_server.direct_model_requests (default off) —
  generic OpenAI clients hardcode model names ('gpt-4o', ...) and
  existing deployments rely on those falling back to the gateway
  default. Explicit `provider` requests and the Hermes-native
  session-chat + /v1/runs surfaces are always honored.
  Idea credit: PR NousResearch#22825 by @mssteuer.
- A model_routes alias with no `model` key can no longer leak the
  alias string as the executing model name (defensive; parse-time
  validation already drops such routes).
- Fix mis-indented _run_agent call args in _handle_session_chat_stream.
- Docs: document the opt-in flag.
prmartinow pushed a commit to prmartinow/hermes-agent that referenced this pull request Aug 26, 2026
Carry model, provider, and model_options through the API server's
execution surfaces (session chat, Chat Completions, Responses, /v1/runs)
without mutating global configuration. Precedence: session /model
override -> model_routes alias -> direct request selection -> global
defaults. Conflicting route/provider mixes fail closed with 400.
model_options stays request-scoped regardless of which selection wins.

Salvaged from PR NousResearch#54426 by @abundantbeing.
prmartinow pushed a commit to prmartinow/hermes-agent that referenced this pull request Aug 26, 2026
Follow-ups on the salvaged NousResearch#54426 routing contract:

- Bare `model` without `provider` on the OpenAI-compatible endpoints
  (/v1/chat/completions, /v1/responses) is now opt-in via
  gateway.platforms.api_server.direct_model_requests (default off) —
  generic OpenAI clients hardcode model names ('gpt-4o', ...) and
  existing deployments rely on those falling back to the gateway
  default. Explicit `provider` requests and the Hermes-native
  session-chat + /v1/runs surfaces are always honored.
  Idea credit: PR NousResearch#22825 by @mssteuer.
- A model_routes alias with no `model` key can no longer leak the
  alias string as the executing model name (defensive; parse-time
  validation already drops such routes).
- Fix mis-indented _run_agent call args in _handle_session_chat_stream.
- Docs: document the opt-in flag.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
Carry model, provider, and model_options through the API server's
execution surfaces (session chat, Chat Completions, Responses, /v1/runs)
without mutating global configuration. Precedence: session /model
override -> model_routes alias -> direct request selection -> global
defaults. Conflicting route/provider mixes fail closed with 400.
model_options stays request-scoped regardless of which selection wins.

Salvaged from PR NousResearch#54426 by @abundantbeing.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
Follow-ups on the salvaged NousResearch#54426 routing contract:

- Bare `model` without `provider` on the OpenAI-compatible endpoints
  (/v1/chat/completions, /v1/responses) is now opt-in via
  gateway.platforms.api_server.direct_model_requests (default off) —
  generic OpenAI clients hardcode model names ('gpt-4o', ...) and
  existing deployments rely on those falling back to the gateway
  default. Explicit `provider` requests and the Hermes-native
  session-chat + /v1/runs surfaces are always honored.
  Idea credit: PR NousResearch#22825 by @mssteuer.
- A model_routes alias with no `model` key can no longer leak the
  alias string as the executing model name (defensive; parse-time
  validation already drops such routes).
- Fix mis-indented _run_agent call args in _handle_session_chat_stream.
- Docs: document the opt-in flag.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants