Skip to content

fix(reasoning): support DeepSeek V4 Flash 0731 effort encoding - #2025

Draft
key4ng wants to merge 6 commits into
mainfrom
codex/deepseek-v4-flash-0731
Draft

fix(reasoning): support DeepSeek V4 Flash 0731 effort encoding#2025
key4ng wants to merge 6 commits into
mainfrom
codex/deepseek-v4-flash-0731

Conversation

@key4ng

@key4ng key4ng commented Aug 1, 2026

Copy link
Copy Markdown
Member

Description

Problem

DeepSeek V4 Flash 0731 defines a three-level low / high / max reasoning-effort encoding, but SMG's native Rust renderer did not consistently receive the OpenAI-compatible Chat Completions and Responses API effort fields. DeepSeek V4 models were also not automatically selecting the appropriate reasoning parser, and regular Responses conversion could merge reasoning into output text or lose modern usage details on some completion paths.

Solution

Map the official OpenAI request fields into the native DeepSeek V4 renderer while keeping an explicit native chat_template_kwargs.reasoning_effort override. Use the 0731 effort encoding for the DeepSeek V4 renderer, keep thinking disabled when no effort is requested, and automatically register the DeepSeek V4 reasoning parser. Preserve separated reasoning and modern Responses API usage details in non-streaming, streaming, and tool-loop completion paths.

Changes

  • Accept Chat Completions reasoning_effort and Responses API reasoning.effort.
  • Map public effort values into DeepSeek V4 low, high, and max buckets.
  • Respect an explicit native chat_template_kwargs.reasoning_effort value and reject invalid native buckets.
  • Render the official 0731 high/max effort prefixes in the Rust DeepSeek V4 encoder.
  • Keep thinking disabled by default when neither public nor native effort is supplied.
  • Automatically select the DeepSeek V4 reasoning parser from model identity.
  • Keep Chat and Responses rendering behavior in parity, including stateful Responses tool loops.
  • Preserve cached-token and reasoning-token usage details in the modern Responses API shape.

Test Plan

  • cargo test -p smg responses --lib — 51 passed.
  • cargo test --workspace -- --skip dsml_one_shot_and_incremental_decode_match --skip test_router_with_tracing — passed, including unit, integration, and doc tests.
  • cargo +nightly fmt --all -- --check — passed.
  • pre-commit run --all-files — every hook passed except Clippy, which could not start because the local macOS environment has no pkg-config/OpenCV installation.
  • The excluded dsml_one_shot_and_incremental_decode_match is a pre-existing untracked debug probe requiring DSV4_TOKENIZER.
  • The excluded upstream test_router_with_tracing was run twice independently and consistently received zero OTLP spans; this branch does not modify tracing or observability code.

Live B300 validation used the original deepseek-ai/DeepSeek-V4-Flash weights with TP2 on GPUs 4–5. Because the deployed SMG 1.9.0 image predates this patch, the benchmark injected the exact official 0731 prefixes that the new Rust unit tests verify:

Prompt Effort Completion tokens Reasoning chars Finish
12-coin balance puzzle low 4,472 10,270 stop
12-coin balance puzzle high 8,192 27,138 length
12-coin balance puzzle max 5,038 14,190 stop
Decimal comparison low 186 312 stop
Decimal comparison high 242 400 stop
Decimal comparison max 170 300 stop

The original Flash weights clearly respond to the 0731 effort prompts. Reasoning length is task-dependent rather than strictly monotonic, so these prefixes should be understood as instructions, not hard token budgets.

Checklist
  • cargo +nightly fmt passes
  • cargo clippy --all-targets --all-features -- -D warnings passes (blocked locally by missing pkg-config/OpenCV)
  • (Optional) Documentation updated
  • (Optional) Please join us on Slack #sig-smg to discuss, review, and merge PRs

key4ng added 3 commits August 1, 2026 12:26
Signed-off-by: key4ng <rukeyang@gmail.com>
Signed-off-by: key4ng <rukeyang@gmail.com>
Signed-off-by: key4ng <rukeyang@gmail.com>
@github-actions github-actions Bot added tokenizer Tokenizer related changes grpc gRPC client and router changes tests Test changes reasoning-parser Reasoning parser changes protocols Protocols crate changes model-gateway Model gateway crate changes labels Aug 1, 2026
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e280f2c0-e768-45f5-b153-6dafdc4f5d4f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added DeepSeek V4 support, including automatic model detection and reasoning extraction.
    • Added low, high, xhigh, and max reasoning-effort options across Chat and Responses APIs.
    • Preserved explicit template reasoning settings and improved effort precedence handling.
    • Added support for separated and streamed reasoning.
  • Improvements

    • Updated usage reporting with cached-input and reasoning-token details.
    • Reasoning preferences now consistently enable thinking across supported effort levels.
    • Improved reasoning-effort handling across tool calls and streaming responses.

Walkthrough

The changes standardize reasoning-effort handling across protocols, tokenizers, parsers, and gRPC routes. They add DeepSeek V4 support, preserve native template precedence, update reasoning propagation, and consolidate modern Responses usage serialization.

Changes

Reasoning effort and DeepSeek V4

Layer / File(s) Summary
Protocol and parser contracts
crates/protocols/src/chat.rs, crates/protocols/src/responses.rs, crates/reasoning_parser/src/factory.rs
Recognized non-none effort values now enable thinking. Responses effort supports none, xhigh, and max, preserves omission, and rejects unknown values. DeepSeek V4 parser detection is registered and tested.
DeepSeek V4 tokenizer resolution
crates/tokenizer/src/chat_template.rs, crates/tokenizer/src/encoders/deepseek_v4.rs, crates/tokenizer/src/huggingface.rs, crates/tokenizer/src/factory.rs, crates/tokenizer/tests/deepseek_renderer_detection.rs
The tokenizer maps public and native effort values to Low, High, and Max behavior. Native template values take precedence, invalid values return errors, and 0731 reasoning prefixes are selected during thinking mode.
Gateway reasoning propagation
model_gateway/src/routers/grpc/harmony/builder.rs, model_gateway/src/routers/grpc/regular/responses/*, model_gateway/src/routers/grpc/utils/*, model_gateway/src/workflow/tokenizer_registration.rs
Gateway routes forward reasoning settings, support the expanded effort values, preserve effort across tool-loop requests, and verify DeepSeek V4 prompt parity and tokenizer encoding.
Responses usage serialization
model_gateway/src/routers/grpc/common/responses/*, model_gateway/src/routers/grpc/regular/responses/conversions.rs, model_gateway/src/routers/grpc/regular/responses/streaming.rs
Shared helpers now produce modern Responses usage objects and completed-event payloads with cached-input and reasoning-token details.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ResponsesConversion
  participant ChatTemplateParams
  participant HuggingFaceTokenizer
  participant DeepSeekV4Encoder
  Client->>ResponsesConversion: submit reasoning_effort
  ResponsesConversion->>ChatTemplateParams: forward public and native effort
  ChatTemplateParams->>HuggingFaceTokenizer: apply chat template
  HuggingFaceTokenizer->>DeepSeekV4Encoder: resolve effort precedence
  DeepSeekV4Encoder-->>HuggingFaceTokenizer: select reasoning prefix
  HuggingFaceTokenizer-->>Client: render encoded prompt
Loading

Suggested reviewers: catherinesue, slin1237

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: DeepSeek V4 Flash 0731 reasoning-effort encoding support.
Description check ✅ Passed The description directly explains the DeepSeek V4 effort encoding, parser, rendering, and Responses API changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/deepseek-v4-flash-0731

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thorough review of all 17 changed files. No issues found — approving.

What changed:

  • thinking_from_reasoning_effort now explicitly enables thinking for all known non-none OpenAI levels (previously only none/minimal were handled, others returned None).
  • ResponseReasoningParam.effort no longer defaults to Medium — an empty reasoning object leaves effort unspecified.
  • DeepSeek V4 renderer gains 0731 effort encoding with three native buckets (low/high/max) mapped from the six OpenAI levels.
  • resolve_deepseek_v4_reasoning_effort cleanly separates three effort sources with correct precedence: explicit native (template_reasoning_effort) > merged kwargs (template_kwargs) when no public effort > public OpenAI effort via from_openai.
  • response_completed_usage unifies the streaming/non-streaming/tool-loop wire format to always include input_tokens_details and output_tokens_details.
  • responses_to_chat now sets separate_reasoning: true and stream_reasoning: true, fixing reasoning content bleeding into output text.
  • Harmony builder maps xhigh/max to its highest supported effort, consistent across Chat and Responses paths (verified by chat_and_responses_reasoning_efforts_build_the_same_system_message).

Verified:

  • ReasoningEffort serde: XHigh correctly renames to xhigh (not x_high from snake_case), confirmed by round-trip test.
  • from_openai("none")Low is harmless since thinking_from_reasoning_effort("none")Some(false) disables thinking, so the effort prefix is never emitted.
  • factory.rs change from from_file to from_file_with_chat_template(_, None) is safe: auto-detect is a fallback for non-standard extensions, embedded templates in tokenizer_config.json still load, and V4 uses a native renderer that bypasses Jinja entirely.
  • All behavioral changes are intentional and covered by updated/new tests.

0 🔴 Important · 0 🟡 Nit · 0 🟣 Pre-existing

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/tokenizer/src/encoders/deepseek_v4.rs`:
- Around line 35-42: Update resolve_deepseek_v4_reasoning_effort to distinguish
an omitted effort from an explicitly supplied unsupported value: preserve the
existing from_openai mapping for recognized names, but return a validation error
instead of treating None as omitted when a value such as "turbo" is provided.
Adjust the corresponding test to expect rejection rather than successful
rendering without an effort prefix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 675c2244-ed02-4482-a498-cb92dfcb1698

📥 Commits

Reviewing files that changed from the base of the PR and between 2271d60 and f2c82be.

📒 Files selected for processing (17)
  • crates/protocols/src/chat.rs
  • crates/protocols/src/responses.rs
  • crates/reasoning_parser/src/factory.rs
  • crates/tokenizer/src/chat_template.rs
  • crates/tokenizer/src/encoders/deepseek_v4.rs
  • crates/tokenizer/src/factory.rs
  • crates/tokenizer/src/huggingface.rs
  • crates/tokenizer/tests/deepseek_renderer_detection.rs
  • model_gateway/src/routers/grpc/common/responses/mod.rs
  • model_gateway/src/routers/grpc/common/responses/streaming.rs
  • model_gateway/src/routers/grpc/harmony/builder.rs
  • model_gateway/src/routers/grpc/regular/responses/common.rs
  • model_gateway/src/routers/grpc/regular/responses/conversions.rs
  • model_gateway/src/routers/grpc/regular/responses/streaming.rs
  • model_gateway/src/routers/grpc/utils/chat_utils.rs
  • model_gateway/src/routers/grpc/utils/parsers.rs
  • model_gateway/src/workflow/tokenizer_registration.rs

Comment on lines +35 to +42
/// Map OpenAI-compatible effort names into the three native 0731 buckets.
pub fn from_openai(value: &str) -> Option<Self> {
match value {
"none" | "minimal" | "low" => Some(Self::Low),
"medium" | "high" => Some(Self::High),
"xhigh" | "max" => Some(Self::Max),
_ => None,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🔴 Important: Reject unsupported public effort values.

Line 36 returns None for an unsupported public value such as "turbo". resolve_deepseek_v4_reasoning_effort then treats that result as omitted effort, so the renderer emits no effort prefix instead of rejecting the request. Keep from_openai as a mapper if needed, but return an error when a supplied public value does not map to a DeepSeek V4 bucket. Update the test that expects "turbo" to succeed.

As per coding guidelines, do not silently fall back to None or a default when configuration validation should fail loudly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tokenizer/src/encoders/deepseek_v4.rs` around lines 35 - 42, Update
resolve_deepseek_v4_reasoning_effort to distinguish an omitted effort from an
explicitly supplied unsupported value: preserve the existing from_openai mapping
for recognized names, but return a validation error instead of treating None as
omitted when a value such as "turbo" is provided. Adjust the corresponding test
to expect rejection rather than successful rendering without an effort prefix.

Source: Coding guidelines

@key4ng
key4ng marked this pull request as draft August 1, 2026 20:02
key4ng added 2 commits August 1, 2026 13:04
Signed-off-by: key4ng <rukeyang@gmail.com>
Signed-off-by: key4ng <rukeyang@gmail.com>
) -> Option<bool> {
resolve_thinking_pref(
extract_thinking_from_kwargs(kwargs, tokenizer),
extract_template_effort_thinking(kwargs, tokenizer),

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: The doc comment on resolve_user_thinking (lines 75-77) is now stale — it describes a two-tier precedence (explicit kwarg → reasoning_effort) but the implementation now has three tiers with this new middle layer. The internal resolve_thinking_pref doc was updated but the public-facing one was not.

Consider updating lines 75-77 to:

/// Resolve the user's effective thinking preference, honoring an explicit
/// template thinking kwarg first, then a native template effort for renderers
/// that support it, then the protocol-level OpenAI `reasoning_effort` mapping.

@key4ng

key4ng commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

Live B300 parity validation completed against the original DeepSeek-V4-Flash model (TP2 gRPC backend), using PR commit 86b9a70f.

Compared Chat Completions requests using:

{"reasoning_effort":"low|max"}

and:

{"chat_template_kwargs":{"reasoning_effort":"low|max"}}

Results with identical prompts and deterministic decoding:

effort request shape prompt tokens completion tokens reasoning chars answer chars
low top-level 21 125 255 75
low template kwargs 21 125 255 75
max top-level 113 102 157 107
max template kwargs 113 102 157 107

For each effort, the two request shapes produced identical SHA-256 hashes for both reasoning_content and content.

The live test caught and fixed two separate gaps:

  1. Native template effort selected the effort bucket but did not initially enable thinking.
  2. After thinking was enabled, native template effort did not initially mark the response parser as starting inside reasoning, so reasoning leaked into content.

Both paths now use the same precedence: explicit chat_template_kwargs.thinking > native template effort > top-level OpenAI effort. The native-effort response behavior is gated specifically to the DeepSeek V4 renderer.

Focused verification: 19/19 DeepSeek renderer tests, 10/10 DeepSeek V4 encoder tests, 51/51 Responses tests, parser-helper tests, rustfmt, diff check, and tokenizer-library Clippy all pass.

Signed-off-by: key4ng <rukeyang@gmail.com>
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 protocols Protocols crate changes reasoning-parser Reasoning parser changes tests Test changes tokenizer Tokenizer related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant