Skip to content

feat(router): support Decisions on the regular HTTP router - #2863

Merged
smg-project-bot merged 1 commit into
mainfrom
alex/openai-decisions
Oct 7, 2026
Merged

smg-project-bot merged 1 commit into
mainfrom
alex/openai-decisions

Conversation

@hello-alexmcc

@hello-alexmcc hello-alexmcc commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Problem

SMG cannot route OpenAI-compatible Decisions requests to local HTTP workers. SGLang 0.5.21 exposes different decision schemas, so callers currently have to bypass the gateway and adapt their requests.

Solution

Expose /v1/decisions through the regular HTTP router. Native Decisions backends receive the OpenAI request, including explicit extension values; SGLang workers receive a text-only translation through their internal /v1/systemone endpoint. Convert SGLang answers back into ordered OpenAI answers while preserving names, boolean/string values, score labels, and usage fields.

Builds on merged #2862 and targets main; the diff contains only routing and tests. Refs #2849. This implements the agreed OpenAI-compatible endpoint; it does not expose a separate public /v1/systemone route.

Changes

  • Regular routers/http/router.rs dispatch, server/trait wiring, and endpoint metrics.
  • Mixed-fleet selection restricted to the regular HTTP router, with clear unsupported responses for PD/gRPC/ZMQ configurations.
  • Backend adaptation after worker selection, preserving aliases, retries, auth, dispatch accounting, response limits, and non-success responses.
  • Adapter validation for malformed successes, omitted/duplicate answers, candidate types, and probability invariants.
  • Unit and HTTP boundary tests plus six SGLang e2e cases in e2e_test/router/test_decisions.py.

SGLang translation rejects image input and unsupported extensions instead of dropping them. Its confidence retains System One semantics, and unreported cache counters are represented as zero; numerical equivalence to OpenAI is not claimed. Prefill scoring has zero output/reasoning tokens. SGLang System One selects its own DP rank, so rank-specific cache affinity is unavailable. Other backends must implement native /v1/decisions themselves.

Test Plan

  • cargo test -p smg --test decisions_http_test — 17 passed, including native extension preservation, aliases, SGLang conversion/order/typed values, unsupported inputs/transports, upstream errors, and malformed successes.
  • cargo test -p smg --lib — 2,247 passed, five ignored during feature validation, including adapter, mixed-fleet, and explicit-field serialization tests.
  • cargo clippy -p openai-protocol -p smg -p smg-external-router --lib --bins --tests -- -D warnings -A clippy::nonminimal_bool — passed during feature validation. The exception is an existing unrelated lint at model_gateway/src/routers/common/kv_transfer.rs:389.
  • cargo +nightly fmt --all -- --check, git diff --check, and file hygiene hooks — passed.
  • Python native wheel build; binding tests: 337 passed, four skipped, 91.04% coverage. CPU harness: 84 passed, five skipped. Ruff and both mypy suites passed.
  • All six new E2E cases collected and selected through the real harness. Run inference with E2E_ENGINE=sglang E2E_RUNTIME=sglang pytest e2e_test/router/test_decisions.py -v on an e2e runner with an SGLang engine.

The feature diff is unchanged after updating onto merged #2859/#2860. Protocol and HTTP boundary tests were rerun after the update; both affected engine crate suites now pass (245 tests), and their strict Clippy check passes without suppressions. The subsequent rebase onto merged #2862 preserves the exact tested source tree. Engine inference was not run locally. Full-workspace checks still encounter unrelated Linux-only benchmark, /proc, and allocator assumptions; no full-workspace pass is claimed.

Checklist
  • cargo +nightly fmt passes
  • Full-workspace cargo clippy --all-targets --all-features -- -D warnings passes (scoped check and existing exception documented above)

Release note

The gateway serves OpenAI-compatible POST /v1/decisions on the regular HTTP router, translating for SGLang workers.

Numbers

metric before after setup
POST /v1/decisions through the regular HTTP router 404 (no route) 200; Decisions HTTP suite 17/17 cargo test -p smg --test decisions_http_test, mock HTTP workers
SGLang e2e Decisions cases 0 6 collected and selected e2e_test/router/test_decisions.py on the SGLang e2e lane

Refs: #2849

@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 8a1ad0ba-b442-491d-8cf7-73ae8ca5aaf1
📥 Commits

Reviewing files that changed from the base of the PR and between 6cef805 and 299ff5d.

📒 Files selected for processing (11)
  • crates/external_router/src/metrics.rs
  • e2e_test/router/test_decisions.py
  • model_gateway/src/routers/gateway.rs
  • model_gateway/src/routers/grpc/utils/metrics.rs
  • model_gateway/src/routers/http/decisions.rs
  • model_gateway/src/routers/http/mod.rs
  • model_gateway/src/routers/http/request_body.rs
  • model_gateway/src/routers/http/router.rs
  • model_gateway/src/routers/mod.rs
  • model_gateway/src/server.rs
  • model_gateway/tests/decisions_http_test.rs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.


📝 Summary

Summary by CodeRabbit

  • New Features
    • Added support for the OpenAI Decisions API at /v1/decisions, including routing requests to supported backends and translating SGLang requests and responses.
    • Added validation for decision questions and responses, with clear errors for unsupported inputs and malformed backend responses.
    • Preserved request fields and extensions when forwarding Decisions requests to compatible backends.

Walkthrough

Adds a protected /v1/decisions endpoint with gateway dispatch to supported HTTP routers. The HTTP router forwards Decisions requests, translates requests for SGLang workers, validates and converts their responses, and records the route with a dedicated metrics label.

Changes

Decisions API

Layer / File(s) Summary
SGLang translation and response conversion
model_gateway/src/routers/http/decisions.rs, model_gateway/src/routers/http/mod.rs
Adds SGLang request translation and validation for predicate, choice, and score questions. Converts valid upstream responses to the public Decisions format and rejects malformed or oversized responses. Tests cover mapping, validation, and response conversion.
HTTP routing and request forwarding
model_gateway/src/routers/http/request_body.rs, model_gateway/src/routers/http/router.rs, model_gateway/tests/decisions_http_test.rs
Routes Decisions requests to workers. Uses field-preserving serialization for Decisions requests and applies the SGLang adapter when the worker uses SGLang. Tests cover Generic passthrough, SGLang conversion, and error responses.
Endpoint registration and router selection
model_gateway/src/server.rs, model_gateway/src/routers/mod.rs, model_gateway/src/routers/gateway.rs, model_gateway/src/routers/grpc/utils/metrics.rs, crates/external_router/src/metrics.rs, e2e_test/router/test_decisions.py
Registers the protected endpoint and adds gateway selection for supported HTTP routers. Maps /v1/decisions to its metrics label. Adds gateway and end-to-end tests for routing and request behavior.

Priority: ➖ Normal

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

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant DecisionsClient
  participant GatewayServer
  participant Gateway
  participant HttpRouter
  participant SglangDecisionAdapter
  participant SGLangWorker
  DecisionsClient->>GatewayServer: POST /v1/decisions
  GatewayServer->>Gateway: Route validated Decisions request
  Gateway->>HttpRouter: Dispatch to selected HTTP router
  HttpRouter->>SglangDecisionAdapter: Translate request
  SglangDecisionAdapter->>SGLangWorker: Send request to /v1/systemone
  SGLangWorker->>SglangDecisionAdapter: Return scoring response
  SglangDecisionAdapter->>HttpRouter: Convert Decisions response
  HttpRouter->>DecisionsClient: Return response
Loading

Merge Risk: ⚪ Minimal · up to 299ff

This change adds a /v1/decisions endpoint for regular HTTP workers, and SGLang requests are translated and validated. No concrete defect was found that should block merging.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.40% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 99 functions across 11 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: adding Decisions support to the regular HTTP router.
Description check ✅ Passed The description directly explains the Decisions endpoint, routing behavior, SGLang adaptation, validation, tests, and known limitations.
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.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@github-actions github-actions Bot added grpc gRPC client and router changes tests Test changes model-gateway Model gateway crate changes labels Oct 7, 2026
Comment on lines +234 to +239
let code = value
.as_ref()
.and_then(|value| value.pointer("/error/code"))
.and_then(Value::as_str)
.unwrap_or("upstream_decisions_error");
let mut response = error::create_error(parts.status, code, message);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Nit: This passes the backend's error.code into error::create_error, which stores it in the GatewayErrorCode extension and the X-SMG-Error-Code header. Line 276 then copies that extension onto the outgoing response, so record_router_upstream_response in route_typed_request uses a string the worker chose as a Prometheus label. crates/external_router/src/error.rs:27-30 says only gateway-minted codes should get this extension, and the extract_error_code_ignores_header_without_gateway_extension test protects that rule.

What goes wrong:

  • A worker that returns many distinct error.code strings (for example, codes with request IDs in them) adds a new label value for each one.
  • Every upstream Decisions error that doesn't have a string code is now labelled as the gateway error upstream_decisions_error. Other HTTP routes leave the label empty for upstream errors.

Suggested fix: when the body already has /error/message, build the response from the upstream JSON and status without calling create_error. For the fallback, wrap the detail/text message with a fixed code. Never take the code from the worker.

}
// Structured text evidence is rendered by System One as ordered JSON,
// preserving message/part boundaries and roles. Images are rejected above.
// safety_identifier is consumed at the gateway boundary, not a backend field.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Nit: Nothing in the gateway reads safety_identifier. The only other reference in model_gateway/src is the gRPC Responses path. So on SGLang workers the field is silently dropped, while native workers receive it. That conflicts with how this adapter handles every other unsupported field ("rejects ... instead of dropping them"). Either reword the comment to say the field is intentionally dropped for SGLang because System One has no equivalent, or log/record the drop so it's visible. As written, the comment suggests some gateway-side handling that doesn't exist.

Base automatically changed from alex/openai-decisions-protocol to main October 7, 2026 21:51
Signed-off-by: Alex McC <319643551+hello-alexmcc@users.noreply.github.com>
@hello-alexmcc
hello-alexmcc force-pushed the alex/openai-decisions branch from d6ae835 to 299ff5d Compare October 7, 2026 21:53
@smg-project-bot
smg-project-bot merged commit 1418d35 into main Oct 7, 2026
48 of 62 checks passed
@smg-project-bot
smg-project-bot deleted the alex/openai-decisions branch October 7, 2026 22:58
@slin1237 slin1237 mentioned this pull request Oct 8, 2026
1 of 4 tasks
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