tito: vLLM backend kind — token-exact turns via render/generate/derender#149
Merged
Conversation
The gateway's token-recording chat flow was hardwired to sglang's dialect
(input_ids request extension + meta_info.output_token_logprobs). vLLM's
chat-completions endpoint silently ignores an input_ids field (extra="allow"),
so a naive port would break token exactness without any error.
Split the backend dialect into engine/upstream.py adapters selected by a new
backend_kind config (--backend-kind {sglang,vllm}, default sglang):
- sglang: the previous single-call flow, moved verbatim.
- vllm (>= 0.24.0, the first release with derender): the token-native
three-call chain — /v1/chat/completions/render resolves sampling params
server-side (its from-scratch token ids are discarded), /inference/v1/generate
runs on the session's exact accumulated prompt ids, and
/v1/chat/completions/derender rebuilds the chat completion through the
server-side tool/reasoning parsers. finish_reason is rewritten to
"tool_calls" for parity with the chat endpoint, logprobs are forced on as
the per-token cross-check, and upstream errors pass through verbatim.
session_app.py now runs prepare -> adapter.chat_turn -> checkpoint and knows
no backend-specific fields. VLLM_BASE_URL joins the discovery env vars.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- validate_request hook: adapter request preconditions (vllm's required model field) now raise BEFORE the session lock — phase 1 can commit a rollback, so a rejected diverging retry used to truncate the trajectory and brick the original branch. - Coerce an explicit top_logprobs:null to 0 (openai-python serializes an explicitly-passed None): render maps sampling logprobs from top_logprobs, so a surviving null made generate omit the logprobs block and 502 every turn of the session. - Reasoning dialect: message_matches treats reasoning_content (sglang / DeepSeek) and reasoning (vLLM) as one logical key and tolerates absence on either side — clients routinely drop the field on echo. The vllm adapter mirrors reasoning into reasoning_content on the stored trajectory copy so templates and the mismatch audit can read it; the wire response keeps vLLM's key verbatim. - Test fidelity: the fake replica now resolves sampling logprobs from top_logprobs and omits the generate logprobs block when null, like real vLLM; the tool-call test carries tools and pins the derender chat_request round-trip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Why
The TITO gateway's token-recording chat flow was hardwired to sglang's dialect (
input_idsrequest extension +meta_info.output_token_logprobs). vLLM's chat-completions endpoint silently ignores unknown request fields (extra="allow"), so pointing the gateway at vLLM would "work" while quietly re-tokenizingmessagesserver-side — exactly the retokenization drift TITO exists to prevent.What
A backend-kind adapter layer, selected by
backend_kind(TITOGatewayConfig/--backend-kind {sglang,vllm}, defaultsglang):engine/upstream.py—UpstreamAdapterprotocol (validate_request+chat_turn). The sglang flow moved here verbatim;session_app.pynow runs prepare →adapter.chat_turn→ checkpoint and knows no backend-specific fields.vllmkind (requires vLLM ≥ 0.24.0, the first release with the derender endpoints) — the token-native three-call chain per turn:POST /v1/chat/completions/render— the agent's chat request →GenerateRequest, resolving sampling params server-side; its from-scratch token ids are discarded,POST /inference/v1/generate— runs on the session's exact accumulated prompt ids; the response carries the exact completion ids (stop/EOS included),POST /v1/chat/completions/derender— token output → chat completion via the server-side tool/reasoning parsers (chat_request+model+prompt_tokensforwarded).logprobson (per-token cross-check + recorded rollout logprobs), an explicittop_logprobs: nullcoerced to 0.finish_reasonis rewritten to"tool_calls"for parity with vLLM's own chat endpoint (derender passes it through verbatim). Upstream errors pass through verbatim; malformed 200 bodies surface as clean 502s.model) run before the session lock — phase 1 can commit a rollback, and a rejected retry must leave no side effects.message_matchestreatsreasoning_content(sglang/DeepSeek) andreasoning(vLLM) as one logical key and tolerates absence on either side (clients drop the field on echo); the vllm adapter mirrorsreasoningintoreasoning_contenton the stored trajectory copy so templates and the mismatch audit can read it.VLLM_BASE_URLjoins the discovery env vars; README documents the backend kinds and the vLLM server parser flags.All wire shapes were verified line-by-line against the vLLM v0.24.0 release tag (notably: the released
GenerateResponsehas nousagefield — the gateway counts tokens fromchoices[].token_idsand suppliesprompt_tokensto derender itself; render copiesstreamfrom the chat request — re-forced off before generate).Tests
25 new tests: an HTTP-level suite (
test_gateway_vllm_http.py) driving the real FastAPI app against a v0.24.0-faithful fake replica (render/generate/derender, including the no-usageresponse shape and the logprobs↔top_logprobs mapping), plus config/CLI/discovery coverage. Existing sglang tests unchanged and green.🤖 Generated with Claude Code