Repository navigation
[sgl-router] Messages: align /v1/messages with the engine's Messages adapter - #43566
Merged
Conversation
…adapter - Streams that fail mid-flight still send message_start, close the open content block, and end with error + message_stop. - 5xx error messages are replaced with "Internal server error"; 4xx messages lose traceback lines and are capped at 500 characters, on both the buffered and the streaming paths. - Only temperature, top_p, top_k, chat_template_kwargs and continue_final_message are forwarded as-is, so max_completion_tokens can no longer override max_tokens. - thinking: enabled requires budget_tokens >= 1024; disabled rejects budget_tokens and display; adaptive rejects budget_tokens. - redacted_thinking in assistant history is a 400. - Thinking blocks carry no empty signature. - A non-JSON upstream chunk ends the stream with an error, [DONE] completes the message, and argument deltas with no open tool_use block are dropped. - tool_result accepts the legacy id field and forwards is_error results unchanged; tool_reference blocks become their own tool messages, and tools keep defer_loading. - input_schema defaults to type object, tool_choice names must match a custom tool, effort accepts minimal and maps xhigh to max, message_delta usage carries output_tokens only, the buffered reply echoes the engine's model, and a blank system prompt is skipped.
ShangmingCai
merged commit Oct 12, 2026
22f66dc
into
sgl-project:feat/router-anthropic-messages
48 of 54 checks passed
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.
Follow-up to #42316: brings the router's
/v1/messagesin line with the engine's own Messages adapter (python/sglang/srt/entrypoints/anthropic/). Where the router behaves differently on purpose, the reason is the Anthropic Messages API contract (listed below).Fixes
message_start(if not yet sent),content_block_stopfor the open block, thenerror, thenmessage_stop, matching_flush_on_error. Strict SDK stream parsers reject a stream that leaves a block open."Internal server error". 4xx messages loseTraceback/File "/…"lines and are capped at 500 characters (_scrub_error_message). This applies to buffered errors and to in-band stream errors. An Anthropic-shaped upstream envelope keeps itserror.typefor 4xx only.temperature,top_p,top_k,chat_template_kwargsandcontinue_final_message. As a resultmax_completion_tokenscan no longer overridemax_tokens.thinkingvalidation.enabledrequiresbudget_tokens >= 1024.disabledrejectsbudget_tokensanddisplay.adaptiverejectsbudget_tokens.displaymust besummarizedoromitted. All of these return 400.redacted_thinkingin assistant history returns 400 instead of being dropped."signature": "", in both buffered replies andcontent_block_start.data:payload that is not valid JSON ends the stream through the envelope in (1) withStream processing error.[DONE]completes the message right away; the router no longer waits for EOF.tool_useblock are dropped with a warning. Before, they opened an unnamed block.tool_result. The legacyidfield is accepted whentool_use_idis absent.is_error: truecontent is forwarded unchanged; the router no longer adds anError:prefix.tool_referenceblocks insidetool_resultbecome{"type":"tool_reference","name":…}parts.tool_call_id, because templates expand references only at the start of a tool message.defer_loading.input_schemawithouttypegets"type": "object".tool_choice: {type: "tool"}name that is not a forwarded custom tool returns 400.output_config.effortacceptsminimal, andxhighis sent asmax.message_delta.usagecarries onlyoutput_tokens.modelechoes the engine's response.systemprompt is skipped.New tests:
stream.rs:[DONE]finalization without EOF;request.rs:thinkingshapes,tool_result/tool_referencegrouping, tool definitions.mod.rs: scrubbing.tests/proxy/messages_api.rs: an end-to-end malformed-chunk stream through the proxy.Existing tests that asserted the old behavior were updated: the
Error:prefix, the blanket pass-through, the empty signature, andmessage_deltainput usage.Intentional differences from the engine adapter
stop_reason.stop_sequencesentry givesstop_reason: "stop_sequence"and echoes the sequence instop_sequence.content_filtermaps to"refusal".end_turn.Error types. The router follows the documented status mapping: 402
billing_error, 413request_too_large, 504timeout_error, 529overloaded_error. The engine adapter maps 504 toapi_error.Stricter validation. These all return 400, where the engine adapter accepts or drops them:
role: "system"turns (the Messages API has onlyuser/assistant; system is top-level);systemblocks;image/tool_result/search_resultblocks in assistant turns;tool_resultwith neithertool_use_idnorid;tool_referencewithout a name;tool_result.A bare-string
stop_sequencesis still accepted.Assistant text. Several text blocks are joined with
\ninto one string, andcontentisnullwhen the turn has only tool calls. The engine adapter sends a content-part array. A plain string is the representation every chat template renders for assistant turns.Turn merging and prefill. Consecutive same-role turns are merged, and a final assistant turn is sent with
continue_final_message, as the Messages API documents.Forwarded extensions.
chat_template_kwargsandcontinue_final_messagepass through because the router README documents them as the way to reach template knobs.Undocumented chat fields. Other top-level chat-extension fields (for example a top-level
response_format,seed, ormin_p) are no longer passed to the chat backend. Only the allowlist above is forwarded; structured output goes throughoutput_config.format.Truncated streams. A stream that reaches
[DONE]or EOF without afinish_reasonends with anapi_errorevent. The engine adapter reportsend_turnin this case if any content was produced.Stream errors without a code. An in-band stream error with no
codeis treated as 5xx and scrubbed.Known gap
The router sends prior-turn
thinkingasreasoning_content, and setsthinking/enable_thinkingtemplate kwargs for thethinkingparameter. The engine adapter does two things the router can't:Both depend on the engine's reasoning parser, which the router doesn't know. The README already points such models at
output_config.effort/chat_template_kwargs.Verification
Run on an EC2 CPU devbox (c7i.4xlarge, Rust 1.92 from
rust-toolchain.toml) at head6eae98943b40eb119025f8604b4e5bde7b9c35b4, fromexperimental/sgl-router, with the same commands aspr-test-sgl-router.yml:cargo +nightly fmt --all -- --check: passcargo fmt --all -- --check: passcargo clippy --locked --workspace --all-targets -- -D warnings: pass, no warningscargo test --locked --workspace -- --skip parity_matrix: all suites passsgl_routerlib: 1059 passedcomponent: 127 passedproxy: 186 passed, including all 7messages_apitestspre-commiton the changed files: passCI States
Latest PR Test (Base): ❌ Run #38061741758
Latest PR Test (Extra): ❌ Run #38061741628
Latest PR Test (AMD ROCm 10): ➖ No AMD PR run found for this commit.