Skip to content

fix(router): support model aliases end to end - #1964

Merged
slin1237 merged 1 commit into
smg-project:mainfrom
junliu-mde:test/pd-alias-prefill-queue
Jul 26, 2026
Merged

fix(router): support model aliases end to end#1964
slin1237 merged 1 commit into
smg-project:mainfrom
junliu-mde:test/pd-alias-prefill-queue

Conversation

@junliu-mde

@junliu-mde junliu-mde commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • resolve model aliases once at each request entry: the regular HTTP router, the PD router, and the gRPC pipeline
  • use the canonical model ID for every per-model lookup downstream — hash rings, retry configs, policies, tokenizers, and metrics labels — since only get_by_model understands aliases
  • forward the canonical model ID to backends, which were registered under it and have never heard of the alias
  • accept aliases at the /v1/responses availability gate, which runs before canonicalization
  • hold the alias entry across the whole decision in every alias write, so a concurrent registration cannot erase or shadow an alias
  • resolve alias/model-ID collisions from both sides, so registration order does not change the outcome
  • keep model discovery and statistics canonical-only

Behavior change

A request addressed to an alias is answered with the canonical model ID, matching how the OpenAI API reports the model it actually ran.

Tests

  • registry: alias resolution, replacement, removal, handover to a remaining model, and collision with a registered model ID
  • /v1/responses gate: accepts aliases, still rejects unknown models and the unknown wildcard
  • gRPC PD pipeline: an alias is canonical before worker selection and tokenizer lookup
  • router selection for aliased PD models
  • HTTP integration, regular and PD: asserts the body the worker actually received carries the canonical model ID

Summary by CodeRabbit

  • New Features

    • Added model-alias support across HTTP and gRPC routing, including standard, prefill/decode, rerank, and audio transcription flows.
    • Alias model names are resolved at request entry and the canonical model is forwarded consistently to workers.
  • Bug Fixes

    • Unknown model names continue to be rejected with consistent 404 NOT_FOUND responses.
    • Worker alias availability and routing selection stay correct when workers are replaced or removed.
  • Tests

    • Added/extended unit and integration tests to verify alias canonicalization, correct request forwarding, and expected gateway/worker behavior across routes.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Model aliases are indexed and resolved by WorkerRegistry, canonicalized during gRPC and HTTP request handling, forwarded to workers as canonical model IDs, and covered by unit and integration tests across standard and PD routing.

Changes

Model Alias Routing

Layer / File(s) Summary
Worker registry alias index
model_gateway/src/worker/registry.rs
Adds alias indexing, resolution, alias-aware membership and lookup, collision handling, lifecycle updates, and registry tests.
gRPC request canonicalization
model_gateway/src/routers/grpc/context.rs, model_gateway/src/routers/grpc/pipeline.rs, model_gateway/src/routers/grpc/common/..., model_gateway/src/routers/grpc/router.rs
Canonicalizes models in RequestContext, updates embedded requests and metrics, and validates aliases during gRPC processing.
HTTP and PD canonical forwarding
model_gateway/src/routers/http/..., model_gateway/src/routers/router_manager.rs
Resolves aliases before routing and rewrites outbound HTTP and PD payloads with canonical model IDs.
Routing validation
model_gateway/tests/common/mock_worker.rs, model_gateway/tests/routing/*
Records worker payloads and tests canonical forwarding, PD routing, router selection, and unknown-model rejection.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Router
  participant WorkerRegistry
  participant Worker
  Client->>Router: request using model alias
  Router->>WorkerRegistry: resolve_model_alias(alias)
  WorkerRegistry-->>Router: canonical model ID
  Router->>Worker: forward request with canonical model
  Worker-->>Router: response
  Router-->>Client: response
Loading

Possibly related PRs

Suggested labels: openai

Suggested reviewers: key4ng, slin1237

Poem

I’m a rabbit routing names through the night,
An alias hops to its model just right.
Canonical packets race down the queue,
PD workers know exactly what to do.
Unknown names tumble, rejected and still.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding end-to-end model alias support in router paths.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions github-actions Bot added documentation Improvements or additions to documentation grpc gRPC client and router changes tests Test changes model-gateway Model gateway crate changes labels Jul 24, 2026
@mergify

mergify Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

⚠️ The sha of the head commit of this PR conflicts with #1961. Mergify cannot evaluate rules on this PR. Once #1961 is merged or closed, Mergify will resume processing this PR. ⚠️

@junliu-mde
junliu-mde force-pushed the test/pd-alias-prefill-queue branch from 9b65970 to 298a31f Compare July 24, 2026 18:39
@github-actions github-actions Bot removed the documentation Improvements or additions to documentation label Jul 24, 2026
@junliu-mde junliu-mde changed the title feat(router): support PD model aliases with bounded prefill concurrency fix(router): support model aliases in PD routing Jul 24, 2026
@junliu-mde

Copy link
Copy Markdown
Contributor Author

⚠️ The sha of the head commit of this PR conflicts with #1961. Mergify cannot evaluate rules on this PR. Once #1961 is merged or closed, Mergify will resume processing this PR. ⚠️

Pushed on wrong base... rebased

@junliu-mde
junliu-mde force-pushed the test/pd-alias-prefill-queue branch 2 times, most recently from 7a3ee6c to 210de10 Compare July 25, 2026 14:27
@junliu-mde junliu-mde changed the title fix(router): support model aliases in PD routing fix(router): support model aliases end to end Jul 25, 2026
@junliu-mde
junliu-mde marked this pull request as ready for review July 25, 2026 15:36
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 210de10c16

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +254 to +255
let canonical_model = self.worker_registry.resolve_model_alias(model_id);
let model_id = canonical_model.as_deref().unwrap_or(model_id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Canonicalize aliases on multipart transcription requests

When /v1/audio/transcriptions uses an alias, this canonicalization never runs because route_multipart_transcription bypasses route_typed_request. The alias-aware registry now finds the correct worker, but build_transcription_form still emits body.model at line 1088, so the backend receives the alias rather than the canonical model it serves and may reject the request. Resolve the alias on the multipart entry path and use that ID in the form.

Useful? React with 👍 / 👎.

Comment on lines +310 to +315
self.model_alias_index
.get(model_id)
.map(|workers| Arc::clone(&workers))
.and_then(|canonical_id| {
self.model_index
.get(canonical_id.as_ref())
.map(|workers| Arc::clone(&workers))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Canonicalize aliases before dispatching through OpenAIRouter

With IGW enabled and an external worker declaring an alias, this lookup lets RouterManager select OpenAIRouter, but that router never resolves the alias: for example, routers/openai/chat.rs patches the outbound payload with the original model_id at line 108, and the Responses and realtime paths likewise forward the client name. An upstream provider that only serves the canonical model therefore rejects an alias that the gateway now claims to route; canonicalize at the OpenAI router entry as done for the other routers.

Useful? React with 👍 / 👎.

Comment on lines +932 to +934
if let Some(canonical_model) = canonical_model {
super::set_request_model(&mut json_val, canonical_model);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return the canonical model from aliased rerank requests

For an aliased /v1/rerank request, this rewrite sends the canonical ID upstream but leaves the original typed request unchanged. After the worker responds, build_rerank_response constructs the public response using req.model.clone() at line 1056, so this endpoint reports the alias while the other newly canonicalized responses report the model actually run. Thread the canonical ID into the rerank response construction as well.

Useful? React with 👍 / 👎.

@junliu-mde
junliu-mde force-pushed the test/pd-alias-prefill-queue branch from 210de10 to ee39fcf Compare July 25, 2026 17:41

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee39fcfc17

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +313 to +315
self.model_index
.get(canonical_id.as_ref())
.map(|workers| Arc::clone(&workers))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Filter unrelated workers before selecting an alias router

When IGW has a self-hosted worker advertising alias A for canonical model C and an external-provider worker serving C but not A (a common hybrid-migration setup), this expands A to every worker indexed under C. RouterManager::select_router_for_workers then prioritizes the unrelated external worker and selects its provider router; that router filters candidates with supports_model(A), so the external worker is rejected and provider filtering can exclude the self-hosted worker, returning model-not-found instead of serving the advertised alias. Preserve the canonical expansion for backend dispatch, but restrict router-selection candidates to workers that support the client-supplied alias.

Useful? React with 👍 / 👎.

@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 `@model_gateway/src/routers/http/router.rs`:
- Around line 250-255: Add a shared WorkerRegistry helper for canonicalizing a
model ID, returning the resolved canonical value or the original input when no
alias exists, with a lifetime-safe return type. Replace the inline
resolve-and-shadow logic at model_gateway/src/routers/http/router.rs lines
250-255 and 547-553, and model_gateway/src/routers/http/pd_router.rs lines
293-299, with this helper while preserving each call site’s existing effective
model ID behavior.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c1561a2b-ffcb-4d5f-8b88-d3b79c280074

📥 Commits

Reviewing files that changed from the base of the PR and between 210de10 and ee39fcf.

📒 Files selected for processing (14)
  • model_gateway/src/routers/grpc/common/responses/utils.rs
  • model_gateway/src/routers/grpc/common/stages/dispatch_metadata.rs
  • model_gateway/src/routers/grpc/context.rs
  • model_gateway/src/routers/grpc/pipeline.rs
  • model_gateway/src/routers/grpc/router.rs
  • model_gateway/src/routers/http/mod.rs
  • model_gateway/src/routers/http/pd_router.rs
  • model_gateway/src/routers/http/router.rs
  • model_gateway/src/routers/router_manager.rs
  • model_gateway/src/worker/registry.rs
  • model_gateway/tests/common/mock_worker.rs
  • model_gateway/tests/routing/mod.rs
  • model_gateway/tests/routing/model_alias_test.rs
  • model_gateway/tests/routing/pd_routing_test.rs

Comment thread model_gateway/src/routers/http/router.rs
A worker registers under a canonical model ID and may declare aliases
next to it. Routing only ever matched the canonical ID, so a request that
named an alias found no workers and got a 404.

The registry now keeps an alias index next to the model index. It is
separate so aliases stay out of `/v1/models` and out of the statistics,
and `get_by_model` falls back to it. Every other per-model map — hash
rings, retry overrides, load balancing policies — stays keyed by the
canonical ID, so each request entry point resolves the alias exactly once
and passes the canonical ID from there on:

- the gRPC pipeline resolves in `RequestContext::new` and rewrites both
  `input.model_id` and the request's own `model` field, so worker
  selection, tokenizer lookup, parser selection and tool call ID format
  all read the canonical ID without resolving anything themselves;
- both HTTP routers resolve at the top of their request path and rewrite
  the `model` field of the body they forward. The backend was registered
  under the canonical ID and has never heard of the alias.

One visible consequence: the response reports the canonical model ID, not
the alias the client sent, matching how the OpenAI API answers with the
model it actually ran.

`/v1/responses` gates on the client-supplied name before the pipeline
canonicalizes it, so it gets `WorkerRegistry::contains_model`, which
accepts both spellings. The `unknown` wildcard is not a registered name
and stays rejected.

Alias conflicts are resolved so that a name is either a canonical model
ID or an alias, never both:

- an alias naming a registered model is refused, and an alias already
  recorded is dropped when a model later claims that name — otherwise the
  name would start resolving to a different model once the real one's
  last worker left;
- when two models declare the same alias the first registration wins, and
  losing that worker hands the alias to a remaining model that declares
  it rather than stranding it;
- the alias entry is held across that whole decision, so a concurrent
  registration cannot be erased between the check and the delete.

Per-model retry overrides are keyed by the canonical ID and are read
before the pipeline canonicalizes, so `GrpcRouter::resolve_retry_config`
resolves the alias itself. Without it a request naming an alias silently
took the router default instead of the worker's override, on chat,
generate, messages and completion alike.

Tests cover the registry rules, both HTTP request paths end to end
against the body the worker actually received, the multipart form, the
rerank response, the gRPC pipeline, the Responses canonicalization, the
retry override under an alias, and the Responses gate.

Signed-off-by: Jun Liu <jun.c.liu@rakuten.com>
@junliu-mde
junliu-mde force-pushed the test/pd-alias-prefill-queue branch from ee39fcf to 704ec6f Compare July 25, 2026 18:34

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
model_gateway/src/routers/http/router.rs (1)

1311-1336: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

route_rerank resolves the alias twice — a TOCTOU gap between the reported model and the one actually dispatched.

canonical_model is resolved here at line 1318, before route_typed_request awaits the worker round-trip; route_typed_request independently re-resolves the same alias internally (line 254) at a later point. If the registry's alias mapping changes in between (worker replace/remove), the model name build_rerank_response reports could diverge from the model that actually ran — the exact guarantee test_rerank_forwards_and_reports_the_canonical_model checks for. Resolving right before the response is built (after the await, on the success path) shrinks this window to effectively nothing.

🔧 Proposed fix: resolve after dispatch instead of before
     async fn route_rerank(
         &self,
         headers: Option<&HeaderMap>,
         _tenant_meta: &TenantRequestMeta,
         body: &RerankRequest,
         model_id: &str,
     ) -> Response {
-        let canonical_model = self.worker_registry.resolve_model_alias(model_id);
         let response = self
             .route_typed_request(headers, body, "/v1/rerank", model_id)
             .await;
         if response.status().is_success() {
+            let canonical_model = self.worker_registry.resolve_model_alias(model_id);
             match Self::build_rerank_response(body, canonical_model.as_deref(), response).await {
🤖 Prompt for 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.

In `@model_gateway/src/routers/http/router.rs` around lines 1311 - 1336, Move the
worker_registry.resolve_model_alias(model_id) call in route_rerank to the
successful response path after route_typed_request has awaited dispatch and
immediately before build_rerank_response. Keep the resolved canonical model
passed to build_rerank_response, ensuring the reported model reflects the latest
alias mapping.
🤖 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.

Outside diff comments:
In `@model_gateway/src/routers/http/router.rs`:
- Around line 1311-1336: Move the worker_registry.resolve_model_alias(model_id)
call in route_rerank to the successful response path after route_typed_request
has awaited dispatch and immediately before build_rerank_response. Keep the
resolved canonical model passed to build_rerank_response, ensuring the reported
model reflects the latest alias mapping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3f2b98b6-994c-46be-ad55-4060735ad963

📥 Commits

Reviewing files that changed from the base of the PR and between ee39fcf and 704ec6f.

📒 Files selected for processing (14)
  • model_gateway/src/routers/grpc/common/responses/utils.rs
  • model_gateway/src/routers/grpc/common/stages/dispatch_metadata.rs
  • model_gateway/src/routers/grpc/context.rs
  • model_gateway/src/routers/grpc/pipeline.rs
  • model_gateway/src/routers/grpc/router.rs
  • model_gateway/src/routers/http/mod.rs
  • model_gateway/src/routers/http/pd_router.rs
  • model_gateway/src/routers/http/router.rs
  • model_gateway/src/routers/router_manager.rs
  • model_gateway/src/worker/registry.rs
  • model_gateway/tests/common/mock_worker.rs
  • model_gateway/tests/routing/mod.rs
  • model_gateway/tests/routing/model_alias_test.rs
  • model_gateway/tests/routing/pd_routing_test.rs

@slin1237
slin1237 merged commit d4767a1 into smg-project:main Jul 26, 2026
48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

grpc gRPC client and router changes model-gateway Model gateway crate changes tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants