Skip to content

fix(server): re-inject generated reasoning into content-only history - #2116

Merged
inureyes merged 2 commits into
mainfrom
fix/issue-2110-reasoning-reinjection
Oct 5, 2026
Merged

inureyes merged 2 commits into
mainfrom
fix/issue-2110-reasoning-reinjection

Conversation

@inureyes

@inureyes inureyes commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Summary

A client that echoes only content (the OpenAI SDK default) re-rendered Jamba-Reasoning's earlier user turn without its thinking instruction, so every follow-up missed the prompt cache. While the prompt cache is on, /v1/chat/completions (streaming and non-streaming) now records the exact reasoning_content each plain reply returned and fills it back into a later content-only assistant turn before rendering, as if the client had echoed it.

Soundness

  • Key: BLAKE3 over model id, template_sig, resolved session (prompt_cache_key, user, or the anonymous bucket), a digest of every preceding message as the client sent it, and the trimmed reply content. A trace is only found by a request that could itself have produced it; editing the reply, any earlier message, kwargs, tools or session misses, and a miss changes nothing.
  • Echoed reasoning always wins; tool-call turns and inline <think> content are never filled.
  • Reasoning-only replies (cut off inside the thinking block, empty content) are recorded too; the prefix digest keeps them tied to their own conversation.
  • Bounded: only a 32-byte key and the text are kept, 16 MiB and 4096 entries with LRU eviction, one trace at most an eighth of the budget. MLXCEL_REASONING_ECHO_MAX_BYTES (0 disables) is documented.
  • Off under cache_prompt: false, --reasoning-format none/deepseek-legacy, and --skip-chat-parsing. Not wired into /v1/responses, /v1/messages, prompt inspection, or the router front.

Validation

Model (content-only, 3 turns, max_tokens 2048) cached_tokens before after
jamba-v0.1-4bit, non-streaming 0 / 0 / 0 0 / 41 / 89
jamba-v0.1-4bit, streaming not run 0 / 41 / 91
qwen3-0.6b-4bit 0 / 0 / 32 0 / 0 / 32 (identical outputs)

"Before" is the same binary with the store disabled (MLXCEL_REASONING_ECHO_MAX_BYTES=0); the issue records 0 on 33c4505. qwen3.5-0.8b-4bit hit max_tokens in thinking on every turn and was identical both ways (0/0/36).

  • New unit and route tests (the route test fails with the store disabled)
  • server::chat_request, server::prompt_cache, server::routes, reasoning filters, clippy -D warnings, fmt

Closes #2110

Closes #2110

@inureyes inureyes added type:bug Bug fixes, error corrections, or issue resolutions priority:medium Medium priority area:inference Generation, sampling, decoding (incl. speculative, DRY) status:review Under review labels Oct 5, 2026
inureyes added a commit that referenced this pull request Oct 5, 2026
@inureyes inureyes added status:done Completed and removed status:review Under review labels Oct 5, 2026
Templates such as AI21 Jamba-Reasoning keep a thinking instruction on an earlier user turn only when the following assistant message carries reasoning_content. A client that echoes only content (the OpenAI SDK default) re-rendered that turn shorter than it was generated, so the history-boundary snapshot never prefixed the follow-up and every turn missed the prompt cache.

The chat route now records the exact reasoning_content each plain reply returned (streaming and non-streaming, including replies cut off inside the thinking block) in a bounded store and fills it back into a later content-only assistant turn before rendering, as if the client had echoed it. The key covers model id, template_sig, resolved session, a digest of every preceding message as the client sent it, and the trimmed reply content, so any edit misses and a miss changes nothing. Echoed reasoning always wins. The store keeps only a 32-byte key and the text, is capped at 16 MiB and 4096 entries with LRU eviction (MLXCEL_REASONING_ECHO_MAX_BYTES, 0 disables), and runs only while the prompt cache is on.

Validated with new unit and route tests (the route test fails with the store disabled), the chat_request, prompt_cache and routes filters, clippy and fmt. On GB10, jamba-v0.1-4bit content-only 3-turn chat went from cached_tokens 0/0/0 to 0/41/89; qwen3-0.6b-4bit is unchanged.

Closes #2110
@inureyes
inureyes force-pushed the fix/issue-2110-reasoning-reinjection branch from 1fbbc15 to 6e27ae7 Compare October 5, 2026 14:19
@inureyes
inureyes merged commit 680f64e into main Oct 5, 2026
50 of 51 checks passed
@inureyes
inureyes deleted the fix/issue-2110-reasoning-reinjection branch October 5, 2026 14:36
inureyes added a commit that referenced this pull request Oct 6, 2026
…#2121)

PR #2116 re-injected server-generated reasoning into content-only assistant turns on /v1/chat/completions only, so Jamba-Reasoning missed the prompt cache on every turn of /v1/responses and /v1/messages, and the #1144 warm-up never ran for it: the probe rendered the reply without reasoning and the unfilled request, failed the extension check, and bailed.

- /v1/responses and /v1/messages fill a render-only copy of the translated request and record the reply after it finishes, non-streaming and streaming, through new render_request and record_reply helpers. Responses records the reasoning item (previous_response_id chains replay no reasoning item, so they are covered too); Messages records the thinking block only when extended thinking was enabled, since otherwise the client never receives it.
- The chat routes record before the warm-up. record now returns whether it stored the trace, and the warm-up renders the filled request plus the reply with that reasoning only when stored. render_next_turn_history takes the reply reasoning and picks the raw or typed render path with the same predicate as prepare_chat_request_with_cache. The streamed flush tail now reaches the warm-up reply.

Route tests for both endpoints fail with MLXCEL_REASONING_ECHO_MAX_BYTES=0; warm-up tests check the target prefixes the filled follow-up. On GB10 with jamba-v0.1-4bit, non-streaming cached tokens went 0/0/0 to 0/52/102 (responses) and 0/51/101 (messages), and chat turn 2 hit the 91-token warm-up entry.

Closes #2118
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:inference Generation, sampling, decoding (incl. speculative, DRY) priority:medium Medium priority status:done Completed type:bug Bug fixes, error corrections, or issue resolutions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: reasoning-model prompt cache misses when clients do not echo reasoning

1 participant