mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-05-07 06:07:18 +08:00
Stop sending prompt cache keys on Claude chat conversions
Responses conversions still use promptCacheKey, but chat completions now stay a pure shape transform. This keeps Claude -> chat requests aligned with providers that do not understand the field and keeps stream checks consistent with production behavior. Constraint: Issue #1919 requires removing prompt_cache_key from Claude -> OpenAI Chat requests Rejected: Add a runtime toggle for chat injection | requested behavior is unconditional removal Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep promptCacheKey limited to Claude -> Responses conversions unless a provider-specific contract is proven Tested: cargo test anthropic_to_openai Tested: cargo test anthropic_to_responses_with_cache_key Tested: cargo test transform_claude_request_for_api_format_responses Not-tested: Full src-tauri test suite Related: #1919
This commit is contained in:
@@ -282,9 +282,9 @@ pub struct ProviderMeta {
|
||||
/// 是否将 base_url 视为完整 API 端点(不拼接 endpoint 路径)
|
||||
#[serde(rename = "isFullUrl", skip_serializing_if = "Option::is_none")]
|
||||
pub is_full_url: Option<bool>,
|
||||
/// Prompt cache key for OpenAI-compatible endpoints.
|
||||
/// When set, injected into converted requests to improve cache hit rate.
|
||||
/// If not set, provider ID is used automatically during format conversion.
|
||||
/// Prompt cache key for OpenAI Responses-compatible endpoints.
|
||||
/// When set, injected into converted Responses requests to improve cache hit rate.
|
||||
/// If not set, provider ID is used automatically during Claude -> Responses conversion.
|
||||
#[serde(rename = "promptCacheKey", skip_serializing_if = "Option::is_none")]
|
||||
pub prompt_cache_key: Option<String>,
|
||||
/// 累加模式应用中,该 provider 是否已写入 live config。
|
||||
|
||||
@@ -81,14 +81,13 @@ pub fn transform_claude_request_for_api_format(
|
||||
provider: &Provider,
|
||||
api_format: &str,
|
||||
) -> Result<serde_json::Value, ProxyError> {
|
||||
let cache_key = provider
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.prompt_cache_key.as_deref())
|
||||
.unwrap_or(&provider.id);
|
||||
|
||||
match api_format {
|
||||
"openai_responses" => {
|
||||
let cache_key = provider
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.prompt_cache_key.as_deref())
|
||||
.unwrap_or(&provider.id);
|
||||
// Codex OAuth (ChatGPT Plus/Pro 反代) 需要在请求体里强制 store: false
|
||||
// + include: ["reasoning.encrypted_content"],由 transform 层统一处理。
|
||||
let is_codex_oauth = provider
|
||||
@@ -102,7 +101,7 @@ pub fn transform_claude_request_for_api_format(
|
||||
is_codex_oauth,
|
||||
)
|
||||
}
|
||||
"openai_chat" => super::transform::anthropic_to_openai(body, Some(cache_key)),
|
||||
"openai_chat" => super::transform::anthropic_to_openai(body),
|
||||
_ => Ok(body),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,10 +71,8 @@ pub fn resolve_reasoning_effort(body: &Value) -> Option<&'static str> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Anthropic 请求 → OpenAI 请求
|
||||
///
|
||||
/// `cache_key`: optional prompt_cache_key to inject for improved cache routing
|
||||
pub fn anthropic_to_openai(body: Value, cache_key: Option<&str>) -> Result<Value, ProxyError> {
|
||||
/// Anthropic 请求 → OpenAI Chat Completions 请求
|
||||
pub fn anthropic_to_openai(body: Value) -> Result<Value, ProxyError> {
|
||||
let mut result = json!({});
|
||||
|
||||
// NOTE: 模型映射由上游统一处理(proxy::model_mapper),格式转换层只做结构转换。
|
||||
@@ -175,11 +173,6 @@ pub fn anthropic_to_openai(body: Value, cache_key: Option<&str>) -> Result<Value
|
||||
result["tool_choice"] = v.clone();
|
||||
}
|
||||
|
||||
// Inject prompt_cache_key for improved cache routing on OpenAI-compatible endpoints
|
||||
if let Some(key) = cache_key {
|
||||
result["prompt_cache_key"] = json!(key);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@@ -569,7 +562,7 @@ mod tests {
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert_eq!(result["model"], "claude-3-opus");
|
||||
assert_eq!(result["max_tokens"], 1024);
|
||||
assert_eq!(result["messages"][0]["role"], "user");
|
||||
@@ -585,7 +578,7 @@ mod tests {
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert_eq!(result["messages"][0]["role"], "system");
|
||||
assert_eq!(
|
||||
result["messages"][0]["content"],
|
||||
@@ -607,7 +600,7 @@ mod tests {
|
||||
}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert_eq!(result["tools"][0]["type"], "function");
|
||||
assert_eq!(result["tools"][0]["function"]["name"], "get_weather");
|
||||
}
|
||||
@@ -627,7 +620,7 @@ mod tests {
|
||||
]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert_eq!(result["messages"].as_array().unwrap().len(), 2);
|
||||
assert_eq!(result["messages"][0]["role"], "system");
|
||||
assert_eq!(
|
||||
@@ -651,7 +644,7 @@ mod tests {
|
||||
}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
let msg = &result["messages"][0];
|
||||
assert_eq!(msg["role"], "assistant");
|
||||
assert!(msg.get("tool_calls").is_some());
|
||||
@@ -671,7 +664,7 @@ mod tests {
|
||||
}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
let msg = &result["messages"][0];
|
||||
assert_eq!(msg["role"], "tool");
|
||||
assert_eq!(msg["tool_call_id"], "call_123");
|
||||
@@ -743,31 +736,19 @@ mod tests {
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert_eq!(result["model"], "gpt-4o");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anthropic_to_openai_with_cache_key() {
|
||||
fn test_anthropic_to_openai_does_not_inject_prompt_cache_key() {
|
||||
let input = json!({
|
||||
"model": "claude-3-opus",
|
||||
"max_tokens": 1024,
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, Some("provider-123")).unwrap();
|
||||
assert_eq!(result["prompt_cache_key"], "provider-123");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anthropic_to_openai_no_cache_key() {
|
||||
let input = json!({
|
||||
"model": "claude-3-opus",
|
||||
"max_tokens": 1024,
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert!(result.get("prompt_cache_key").is_none());
|
||||
}
|
||||
|
||||
@@ -793,7 +774,7 @@ mod tests {
|
||||
}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
// System message cache_control preserved
|
||||
assert_eq!(result["messages"][0]["cache_control"]["type"], "ephemeral");
|
||||
// Text block cache_control preserved
|
||||
@@ -1047,7 +1028,7 @@ mod tests {
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert!(result.get("reasoning_effort").is_none());
|
||||
}
|
||||
|
||||
@@ -1060,7 +1041,7 @@ mod tests {
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert_eq!(result["reasoning_effort"], "medium");
|
||||
}
|
||||
|
||||
@@ -1073,7 +1054,7 @@ mod tests {
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert_eq!(result["reasoning_effort"], "xhigh");
|
||||
}
|
||||
|
||||
@@ -1086,7 +1067,7 @@ mod tests {
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert_eq!(result["reasoning_effort"], "low");
|
||||
}
|
||||
|
||||
@@ -1099,7 +1080,7 @@ mod tests {
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert_eq!(result["reasoning_effort"], "xhigh");
|
||||
}
|
||||
|
||||
@@ -1111,7 +1092,7 @@ mod tests {
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert!(result.get("reasoning_effort").is_none());
|
||||
}
|
||||
|
||||
@@ -1124,7 +1105,7 @@ mod tests {
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert!(
|
||||
result.get("max_tokens").is_none(),
|
||||
"{model} should not have max_tokens"
|
||||
@@ -1144,7 +1125,7 @@ mod tests {
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
});
|
||||
|
||||
let result = anthropic_to_openai(input, None).unwrap();
|
||||
let result = anthropic_to_openai(input).unwrap();
|
||||
assert_eq!(result["max_tokens"], 1024);
|
||||
assert!(result.get("max_completion_tokens").is_none());
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ impl StreamCheckService {
|
||||
anthropic_to_responses(anthropic_body, Some(&provider.id), is_codex_oauth)
|
||||
.map_err(|e| AppError::Message(format!("Failed to build test request: {e}")))?
|
||||
} else if is_openai_chat {
|
||||
anthropic_to_openai(anthropic_body, Some(&provider.id))
|
||||
anthropic_to_openai(anthropic_body)
|
||||
.map_err(|e| AppError::Message(format!("Failed to build test request: {e}")))?
|
||||
} else {
|
||||
anthropic_body
|
||||
|
||||
@@ -166,7 +166,7 @@ export interface ProviderMeta {
|
||||
apiKeyField?: ClaudeApiKeyField;
|
||||
// 是否将 base_url 视为完整 API 端点(代理直接使用此 URL,不拼接路径)
|
||||
isFullUrl?: boolean;
|
||||
// Prompt cache key for OpenAI-compatible endpoints (improves cache hit rate)
|
||||
// Prompt cache key for OpenAI Responses-compatible endpoints (improves cache hit rate)
|
||||
promptCacheKey?: string;
|
||||
// 供应商类型(用于识别 Copilot 等特殊供应商)
|
||||
providerType?: string;
|
||||
|
||||
Reference in New Issue
Block a user