feat(api_server): honor inbound 'model' and 'provider' fields for per-request routing - #18549
Closed
SelfParody wants to merge 1 commit into
Closed
Conversation
Collaborator
1 similar comment
Collaborator
Closed
6 tasks
This was referenced May 23, 2026
This was referenced May 23, 2026
Contributor
|
Thanks for the routing work. This has been superseded by the consolidated API-server routing implementation on
Closing as implemented on main. |
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
The OpenAI-compatible api_server platform adapter at
gateway/platforms/api_server.pyreads the inboundmodelfield for response decoration only — every request is routed through the gateway's singlemodel.default+model.provider. This makes the api_server unsuitable as a drop-in OpenAI-compat router for callers that expect tier selection per call (cheap-flash for classification, max-tier for reasoning, etc.).We hit this migrating Hermes Studio off a custom router (Bifrost) onto the native api_server. Tiered callers (webhook ingester, code-review tooling, cron diagnostics) all needed per-call model+provider — without this patch we'd have had to either run multiple gateway instances on different ports or split callers between native router and direct provider URLs.
Change
Add
model_overrideandprovider_overridekwargs to_create_agent, thread them through_run_agent, and extract them frombody["model"]andbody["provider"](orbody["extra_body"]["provider"]for stricter OpenAI-compat callers) at all five call sites:_handle_chat_completionsstreaming + non-streaming paths_handle_responsesstreaming + non-streaming paths_run_and_close(the/v1/runsSSE flow)When
provider_overrideis set,resolve_runtime_provider(requested=...)is re-run to pick up the override'sapi_key/base_url/api_mode. On exception, falls back to gateway default withlogger.warningso callers see what happened.When neither is set, behavior is unchanged from current — same single-default routing.
Behavior change worth flagging (semver-minor)
The pre-patch behavior was: any inbound
modelfield was decorative — echoed in the response but ignored for routing. Every request hitmodel.default. After this patch,modelis functional — it actually selects the model used.Three caller scenarios:
modelfieldmodel: <gateway-default>model: <some-other-name>configured<other-name><other-name>actually used (more OpenAI-compat)model: <name-that-isn't-configured>The fourth row is the meaningful change. Existing users who relied on silent fallback get hard errors after this patch. We argue that's a bug fix (the prior behavior violated OpenAI-API semantics of the
modelfield), but maintainers may prefer this to be opt-in via a feature flag. A reasonable shape:Default
truepost-merge; users who depend on the legacy silent-fallback behavior can opt out withAPI_SERVER_HONOR_INBOUND_MODEL=false. Happy to add this if requested.Idempotency cache key — needs
provideraddedThe
_make_request_fingerprintcall at the top of_handle_chat_completionskeys the idempotency cache on["model", "messages", "tools", "tool_choice", "stream"]. With this patch, two requests with the samemodelbut differentproviderwould collide on the same cache entry. Two-line fix to includeproviderin the fingerprint key list (and the same in_handle_responses).Auth boundary
The api_server already gates all routes with
Authorization: Bearer ${API_SERVER_KEY}. Within that authenticated boundary, callers can already issue any LLM request the gateway is configured for. Addingprovider/modeloverrides doesn't expand the trust boundary — it just lets authenticated callers pick which configured provider to route through. Sonnet code-review noted this and we accepted it.Files touched
gateway/platforms/api_server.py: 6 sites patched, ~46 line diff. No public API changes;model_overrideandprovider_overridedefault toNoneand the bare-args call signatures are unchanged.Test plan
modelis decorative" need updating — that's the behavior change above.(no override)→ agent uses gateway default model + provider(model only)→ agent uses inbound model with gateway default provider(model + provider top-level)→ agent uses both overrides(model + extra_body.provider)→ same as above but provider sourced from extra_body(provider: nonexistent-foo)→ request still completes via gateway default,logger.warningis emitted with provider name + underlying exception(idempotency cache)→ same model + different provider produce different cache entries (verifies the fingerprint fix lands)Operational notes
We ran this in production at Hermes Studio for [duration] — handles ~thousands of calls/day across 3 providers (alibaba/atlas/anthropic) with no observed regressions. Patch lives in our
~/.hermes/patches/carrier dir until merged.