Skip to content

[codex] Fix DeepSeek Claude trailing system messages - #5413

Closed
chensunny wants to merge 1 commit into
QuantumNous:mainfrom
chensunny:codex/fix-deepseek-trailing-system
Closed

[codex] Fix DeepSeek Claude trailing system messages#5413
chensunny wants to merge 1 commit into
QuantumNous:mainfrom
chensunny:codex/fix-deepseek-trailing-system

Conversation

@chensunny

@chensunny chensunny commented Jun 10, 2026

Copy link
Copy Markdown

Summary

  • Normalize Claude messages[].role == "system" entries in the DeepSeek Claude request conversion path before forwarding to DeepSeek's Anthropic-compatible upstream.
  • Fold trailing system reminders into the previous user turn, and fold assistant/system/user reminders into the next user turn.
  • Keep native Claude adaptor behavior unchanged.

Root Cause

Claude Code /effort ultracode can append a trailing system message after the pending user turn. DeepSeek's Anthropic-compatible upstream may treat that shape as having no pending user turn and return an empty assistant end_turn.

Impact

DeepSeek Anthropic-compatible relay requests avoid empty assistant turns for this request shape while preserving the existing native Claude relay behavior.

Validation

  • go test ./relay/channel/deepseek -count=1

Notes

Summary by CodeRabbit

  • New Features

    • Added Claude system message normalization for DeepSeek requests, which merges system messages into adjacent user messages for improved message handling.
  • Tests

    • Added comprehensive test coverage for the system message normalization logic to verify proper merging behavior in various message configurations.

@chensunny
chensunny marked this pull request as ready for review June 10, 2026 08:05
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR implements Claude system-message normalization for DeepSeek Anthropic-compatible relay. It prevents empty assistant responses caused by trailing system messages by merging system content into adjacent user messages before forwarding to non-native-Claude upstreams.

Changes

Claude system message normalization

Layer / File(s) Summary
System message normalization implementation
relay/channel/deepseek/claude_system_normalize.go
New module with core normalization routine that buffers consecutive system messages and merges them into adjacent user messages, handling both string and structured content with intelligent fallback when parsing multi-part content fails.
Adaptor integration and tests
relay/channel/deepseek/adaptor.go, relay/channel/deepseek/adaptor_test.go
ConvertClaudeRequest now calls normalizeClaudeSystemMessagesForNonNativeUpstream before DeepSeek thinking suffix logic; two unit tests verify merging of trailing and interleaved system messages into user message content.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • QuantumNous/new-api#1832: Both PRs modify relay/channel/deepseek/adaptor.go's ConvertClaudeRequest to extend how Claude requests are prepared before DeepSeek forwarding.
  • QuantumNous/new-api#4428: Both PRs modify ConvertClaudeRequest request transformation pipeline, with this PR adding system-message normalization that runs before the existing thinking-suffix logic.
  • QuantumNous/new-api#1522: Both PRs extend Adaptor.ConvertClaudeRequest, building on that method's functional implementation.

Suggested reviewers

  • Calcium-Ion

Poem

🐰 A rabbit hops through message streams so fine,
Where system notes and users intertwine—
They merge with grace, no empty turns remain,
DeepSeek now answers through the rabbit's lane! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[codex] Fix DeepSeek Claude trailing system messages' directly addresses the main change: normalizing Claude system messages in DeepSeek requests. It is concise, clear, and accurately reflects the primary objective from PR #5411.
Linked Issues check ✅ Passed The PR fully implements the objectives from issue #5411: normalizing trailing/interleaved system messages for DeepSeek Anthropic-compatible upstreams by merging them into adjacent user messages, while preserving native Claude behavior. Tests validate both trailing and interleaved system message normalization scenarios.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the DeepSeek channel's Claude request normalization: the adaptor applies normalization logic, a new module implements the normalization with helper functions, and new tests validate the behavior. No unrelated modifications were introduced.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
relay/channel/deepseek/claude_system_normalize.go (2)

38-40: 💤 Low value

Edge case: Trailing system messages lost if no user message exists.

If normalizedMessages contains only assistant messages (no user messages), mergePendingSystemTextIntoLastUser will silently discard any buffered system text. While Claude API typically requires at least one user message, defensive handling could log a warning or raise an error when trailing system messages cannot be merged.

🤖 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 `@relay/channel/deepseek/claude_system_normalize.go` around lines 38 - 40, When
pending system text remains but there is no existing user message to merge into
(i.e., len(pendingSystems)>0 and mergePendingSystemTextIntoLastUser would drop
the text), detect this by checking normalizedMessages for any user-role message;
if none, do not call mergePendingSystemTextIntoLastUser — instead log a warning
and append a new user message containing strings.Join(pendingSystems, "\n\n") to
normalizedMessages (or alternatively return an explicit error), ensuring
pendingSystems is not silently discarded; reference
mergePendingSystemTextIntoLastUser, pendingSystems, and normalizedMessages when
making the change.

62-66: 💤 Low value

Non-text content blocks in system messages are silently dropped.

Only ContentTypeText blocks are extracted from structured system messages. If a system message contains images or other media, they will be silently discarded during normalization. This is likely intentional for text-only merging, but consider documenting this behavior if it's not obvious to future maintainers.

🤖 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 `@relay/channel/deepseek/claude_system_normalize.go` around lines 62 - 66, The
loop over contents only appends text blocks (contents, dto.ContentTypeText,
builder.WriteString) which silently drops non-text system content; update
claude_system_normalize.go by explicitly documenting this behavior (add a clear
comment above the for loop mentioning that non-text content like
images/attachments in structured system messages are intentionally ignored
during normalization) or, if you prefer to preserve non-text, implement handling
for other ContentType values (e.g., serialize or log them) and reference the
same symbols (contents, ContentTypeText, GetText, builder.WriteString) so future
maintainers understand the choice.
🤖 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.

Nitpick comments:
In `@relay/channel/deepseek/claude_system_normalize.go`:
- Around line 38-40: When pending system text remains but there is no existing
user message to merge into (i.e., len(pendingSystems)>0 and
mergePendingSystemTextIntoLastUser would drop the text), detect this by checking
normalizedMessages for any user-role message; if none, do not call
mergePendingSystemTextIntoLastUser — instead log a warning and append a new user
message containing strings.Join(pendingSystems, "\n\n") to normalizedMessages
(or alternatively return an explicit error), ensuring pendingSystems is not
silently discarded; reference mergePendingSystemTextIntoLastUser,
pendingSystems, and normalizedMessages when making the change.
- Around line 62-66: The loop over contents only appends text blocks (contents,
dto.ContentTypeText, builder.WriteString) which silently drops non-text system
content; update claude_system_normalize.go by explicitly documenting this
behavior (add a clear comment above the for loop mentioning that non-text
content like images/attachments in structured system messages are intentionally
ignored during normalization) or, if you prefer to preserve non-text, implement
handling for other ContentType values (e.g., serialize or log them) and
reference the same symbols (contents, ContentTypeText, GetText,
builder.WriteString) so future maintainers understand the choice.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 58ce4b3c-76db-48c8-97fb-8f504cf718e2

📥 Commits

Reviewing files that changed from the base of the PR and between d2576dd and af11bbe.

📒 Files selected for processing (3)
  • relay/channel/deepseek/adaptor.go
  • relay/channel/deepseek/adaptor_test.go
  • relay/channel/deepseek/claude_system_normalize.go

@seefs001

Copy link
Copy Markdown
Collaborator

原生 claude -> deepseek官方 路径的话,这种事情应由Provider去做而不是NewAPI

@seefs001 seefs001 closed this Jun 10, 2026
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.

Bug: Claude Code ultracode trailing system message causes DeepSeek Anthropic-compatible relay to return empty response

2 participants