Skip to content

tito: vLLM backend kind — token-exact turns via render/generate/derender#149

Merged
Meirtz merged 2 commits into
masterfrom
feat/tito-vllm-backend
Jul 3, 2026
Merged

tito: vLLM backend kind — token-exact turns via render/generate/derender#149
Meirtz merged 2 commits into
masterfrom
feat/tito-vllm-backend

Conversation

@Meirtz

@Meirtz Meirtz commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Why

The TITO 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 unknown request fields (extra="allow"), so pointing the gateway at vLLM would "work" while quietly re-tokenizing messages server-side — exactly the retokenization drift TITO exists to prevent.

What

A backend-kind adapter layer, selected by backend_kind (TITOGatewayConfig / --backend-kind {sglang,vllm}, default sglang):

  • engine/upstream.pyUpstreamAdapter protocol (validate_request + chat_turn). The sglang flow moved here verbatim; session_app.py now runs prepare → adapter.chat_turn → checkpoint and knows no backend-specific fields.
  • vllm kind (requires vLLM ≥ 0.24.0, the first release with the derender endpoints) — the token-native three-call chain per turn:
    1. POST /v1/chat/completions/render — the agent's chat request → GenerateRequest, resolving sampling params server-side; its from-scratch token ids are discarded,
    2. POST /inference/v1/generate — runs on the session's exact accumulated prompt ids; the response carries the exact completion ids (stop/EOS included),
    3. POST /v1/chat/completions/derender — token output → chat completion via the server-side tool/reasoning parsers (chat_request + model + prompt_tokens forwarded).
  • Forcing mirrors the sglang path: non-streaming, logprobs on (per-token cross-check + recorded rollout logprobs), an explicit top_logprobs: null coerced to 0. finish_reason is 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.
  • Adapter request preconditions (vllm's required model) run before the session lock — phase 1 can commit a rollback, and a rejected retry must leave no side effects.
  • Reasoning dialect: message_matches treats reasoning_content (sglang/DeepSeek) and reasoning (vLLM) as one logical key and tolerates absence on either side (clients 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.
  • VLLM_BASE_URL joins 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 GenerateResponse has no usage field — the gateway counts tokens from choices[].token_ids and supplies prompt_tokens to derender itself; render copies stream from 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-usage response shape and the logprobs↔top_logprobs mapping), plus config/CLI/discovery coverage. Existing sglang tests unchanged and green.

🤖 Generated with Claude Code

Meirtz and others added 2 commits July 4, 2026 02:38
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>
@Meirtz
Meirtz merged commit 118067a into master Jul 3, 2026
5 checks passed
@Meirtz
Meirtz deleted the feat/tito-vllm-backend branch July 3, 2026 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant