fix(proxy): 规范化 Anthropic system 消息#3775
Conversation
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep it up! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
windows版本上 好像并没有生效 我的mac上是正常的 |
|
Here's a polished comment you can post on the PR: Impact on DeepSeek Prefix Cache Hit Rate This PR's system message normalization has a significant side effect on providers that use byte-prefix caching (DeepSeek, Moonshot, etc.). After this change, my cache hit rate dropped from ~95% to ~25% when routing Claude Code through CC Switch to DeepSeek. Root cause: DeepSeek's context cache requires strict byte-prefix matching from token 0. The current
Even a single character drift in the system message (which sits at the very start of the prefix) causes a full cache miss for all subsequent tokens — that's the entire conversation history being re-processed at full price ($0.14/M vs $0.014/M cached). Proposed fix: For byte-prefix-sensitive providers, instead of merging all fn normalize_openai_system_messages(messages: &mut Vec<Value>) {
let mut first_system_seen = false;
for msg in messages.iter_mut() {
if msg.get("role").and_then(|v| v.as_str()) == Some("system") {
if !first_system_seen {
first_system_seen = true;
// Keep the first system message — it's the stable prefix anchor
} else {
// Downgrade subsequent system messages to user role
// to avoid breaking the byte-prefix chain
msg["role"] = json!("user");
if let Some(text) = msg.get("content").and_then(|c| c.as_str()).cloned() {
msg["content"] = json!(format!("[System Instruction]\n{}", text));
}
}
}
}
// Ensure the first system message is at index 0
if first_system_seen {
if let Some(idx) = messages.iter().position(|m| {
m.get("role").and_then(|v| v.as_str()) == Some("system")
}) {
if idx > 0 {
let m = messages.remove(idx);
messages.insert(0, m);
}
}
}
}Why this works:
Important caveat: This strategy should be provider-aware. Claude's prompt cache uses segmented hashing with This aligns with the direction of #3841 (stripping |
The unrelated Windows test fixes from farion1231#3775 are kept; tool-thinking-history normalization is unchanged. Refs farion1231#4297
Summary / 概述
messages[].role = "system",将其移动到顶层system,避免 Claude Desktop / Anthropic-compatible proxy / DeepSeek 端点拒绝非标准 system 消息。normalize_anthropic_messages_for_provider,保留content[].thinking must be passed back场景的兼容处理。C:\...路径会触发转义解析错误,改为可跨平台解析的测试数据。Related Issue / 关联 Issue
未关联具体 Issue;对应近期 Codex / Claude / DeepSeek 协议兼容反馈。
Screenshots / 截图
不涉及 UI 变更。
Checklist / 检查清单
pnpm typecheckpasses / 通过 TypeScript 类型检查pnpm format:checkpasses / 通过代码格式检查cargo clippy --libpasses / 通过 Clippy 检查(剩余 warning 为仓库既有未触及项)Tests / 测试
cargo fmtcargo test proxy::providers::claude --lib:51 passedcargo test proxy::providers::transform_codex_chat --lib:48 passedcargo test proxy::providers::streaming_codex_chat --lib:12 passedcargo test --lib:1532 passed, 2 ignoredpnpm typecheckpnpm format:checkpnpm exec vitest run tests/config/codexChatProviderPresets.test.ts tests/components/ClaudeDesktopProviderForm.test.tsx tests/utils/providerConfigUtils.codex.test.ts:29 passed说明:
pnpm test:unit全量前端测试中tests/integration/App.test.tsx有 2 个既有集成测试失败(一个超时、一个重复provider-listDOM),与本次 Rust 协议修复无直接关联。