mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-01-31 09:43:07 +08:00
refactor(proxy): remove DeepSeek max_tokens clamp from transform layer
The max_tokens restriction was too aggressive and should be handled upstream or by the provider itself. Simplify anthropic_to_openai by removing provider parameter since model mapping is already done by proxy::model_mapper.
This commit is contained in:
@@ -305,9 +305,9 @@ impl ProviderAdapter for ClaudeAdapter {
|
|||||||
fn transform_request(
|
fn transform_request(
|
||||||
&self,
|
&self,
|
||||||
body: serde_json::Value,
|
body: serde_json::Value,
|
||||||
provider: &Provider,
|
_provider: &Provider,
|
||||||
) -> Result<serde_json::Value, ProxyError> {
|
) -> Result<serde_json::Value, ProxyError> {
|
||||||
super::transform::anthropic_to_openai(body, provider)
|
super::transform::anthropic_to_openai(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform_response(&self, body: serde_json::Value) -> Result<serde_json::Value, ProxyError> {
|
fn transform_response(&self, body: serde_json::Value) -> Result<serde_json::Value, ProxyError> {
|
||||||
|
|||||||
@@ -3,25 +3,11 @@
|
|||||||
//! 实现 Anthropic ↔ OpenAI 格式转换,用于 OpenRouter 支持
|
//! 实现 Anthropic ↔ OpenAI 格式转换,用于 OpenRouter 支持
|
||||||
//! 参考: anthropic-proxy-rs
|
//! 参考: anthropic-proxy-rs
|
||||||
|
|
||||||
use crate::provider::Provider;
|
|
||||||
use crate::proxy::error::ProxyError;
|
use crate::proxy::error::ProxyError;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
fn extract_base_url_from_provider(provider: &Provider) -> Option<&str> {
|
|
||||||
let settings = &provider.settings_config;
|
|
||||||
|
|
||||||
settings
|
|
||||||
.get("env")
|
|
||||||
.and_then(|env| env.get("ANTHROPIC_BASE_URL"))
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.or_else(|| settings.get("base_url").and_then(|v| v.as_str()))
|
|
||||||
.or_else(|| settings.get("baseURL").and_then(|v| v.as_str()))
|
|
||||||
.or_else(|| settings.get("apiBaseUrl").and_then(|v| v.as_str()))
|
|
||||||
.or_else(|| settings.get("apiEndpoint").and_then(|v| v.as_str()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Anthropic 请求 → OpenAI 请求
|
/// Anthropic 请求 → OpenAI 请求
|
||||||
pub fn anthropic_to_openai(body: Value, provider: &Provider) -> Result<Value, ProxyError> {
|
pub fn anthropic_to_openai(body: Value) -> Result<Value, ProxyError> {
|
||||||
let mut result = json!({});
|
let mut result = json!({});
|
||||||
|
|
||||||
// NOTE: 模型映射由上游统一处理(proxy::model_mapper),格式转换层只做结构转换。
|
// NOTE: 模型映射由上游统一处理(proxy::model_mapper),格式转换层只做结构转换。
|
||||||
@@ -60,30 +46,7 @@ pub fn anthropic_to_openai(body: Value, provider: &Provider) -> Result<Value, Pr
|
|||||||
|
|
||||||
// 转换参数
|
// 转换参数
|
||||||
if let Some(v) = body.get("max_tokens") {
|
if let Some(v) = body.get("max_tokens") {
|
||||||
// DeepSeek OpenAI Compatible API: max_tokens must be within [1, 8192]
|
result["max_tokens"] = v.clone();
|
||||||
// Ref: upstream error "the valid range of max_tokens is [1, 8192]"
|
|
||||||
const DEEPSEEK_MAX_TOKENS_MIN: u64 = 1;
|
|
||||||
const DEEPSEEK_MAX_TOKENS_MAX: u64 = 8192;
|
|
||||||
|
|
||||||
let is_deepseek = extract_base_url_from_provider(provider)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.contains("deepseek.com");
|
|
||||||
|
|
||||||
match (is_deepseek, v.as_u64()) {
|
|
||||||
(true, Some(max_tokens)) => {
|
|
||||||
let clamped = max_tokens.clamp(DEEPSEEK_MAX_TOKENS_MIN, DEEPSEEK_MAX_TOKENS_MAX);
|
|
||||||
if clamped != max_tokens {
|
|
||||||
log::warn!(
|
|
||||||
"[Transform] DeepSeek max_tokens 超出范围,已自动调整: {max_tokens} → {clamped}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
result["max_tokens"] = json!(clamped);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// 非 DeepSeek / 非数字类型:保持原样,让上游自行处理
|
|
||||||
result["max_tokens"] = v.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(v) = body.get("temperature") {
|
if let Some(v) = body.get("temperature") {
|
||||||
result["temperature"] = v.clone();
|
result["temperature"] = v.clone();
|
||||||
@@ -357,43 +320,15 @@ pub fn openai_to_anthropic(body: Value) -> Result<Value, ProxyError> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn create_provider(env_config: Value) -> Provider {
|
|
||||||
Provider {
|
|
||||||
id: "test".to_string(),
|
|
||||||
name: "Test Provider".to_string(),
|
|
||||||
settings_config: json!({"env": env_config}),
|
|
||||||
website_url: None,
|
|
||||||
category: None,
|
|
||||||
created_at: None,
|
|
||||||
sort_index: None,
|
|
||||||
notes: None,
|
|
||||||
meta: None,
|
|
||||||
icon: None,
|
|
||||||
icon_color: None,
|
|
||||||
in_failover_queue: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_openrouter_provider() -> Provider {
|
|
||||||
create_provider(json!({
|
|
||||||
"ANTHROPIC_BASE_URL": "https://openrouter.ai/api",
|
|
||||||
"ANTHROPIC_MODEL": "anthropic/claude-sonnet-4.5",
|
|
||||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "anthropic/claude-haiku-4.5",
|
|
||||||
"ANTHROPIC_DEFAULT_SONNET_MODEL": "anthropic/claude-sonnet-4.5",
|
|
||||||
"ANTHROPIC_DEFAULT_OPUS_MODEL": "anthropic/claude-opus-4.5"
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_anthropic_to_openai_simple() {
|
fn test_anthropic_to_openai_simple() {
|
||||||
let provider = create_openrouter_provider();
|
|
||||||
let input = json!({
|
let input = json!({
|
||||||
"model": "claude-3-opus",
|
"model": "claude-3-opus",
|
||||||
"max_tokens": 1024,
|
"max_tokens": 1024,
|
||||||
"messages": [{"role": "user", "content": "Hello"}]
|
"messages": [{"role": "user", "content": "Hello"}]
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = anthropic_to_openai(input, &provider).unwrap();
|
let result = anthropic_to_openai(input).unwrap();
|
||||||
assert_eq!(result["model"], "claude-3-opus");
|
assert_eq!(result["model"], "claude-3-opus");
|
||||||
assert_eq!(result["max_tokens"], 1024);
|
assert_eq!(result["max_tokens"], 1024);
|
||||||
assert_eq!(result["messages"][0]["role"], "user");
|
assert_eq!(result["messages"][0]["role"], "user");
|
||||||
@@ -402,7 +337,6 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_anthropic_to_openai_with_system() {
|
fn test_anthropic_to_openai_with_system() {
|
||||||
let provider = create_openrouter_provider();
|
|
||||||
let input = json!({
|
let input = json!({
|
||||||
"model": "claude-3-sonnet",
|
"model": "claude-3-sonnet",
|
||||||
"max_tokens": 1024,
|
"max_tokens": 1024,
|
||||||
@@ -410,7 +344,7 @@ mod tests {
|
|||||||
"messages": [{"role": "user", "content": "Hello"}]
|
"messages": [{"role": "user", "content": "Hello"}]
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = anthropic_to_openai(input, &provider).unwrap();
|
let result = anthropic_to_openai(input).unwrap();
|
||||||
assert_eq!(result["messages"][0]["role"], "system");
|
assert_eq!(result["messages"][0]["role"], "system");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result["messages"][0]["content"],
|
result["messages"][0]["content"],
|
||||||
@@ -421,7 +355,6 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_anthropic_to_openai_with_tools() {
|
fn test_anthropic_to_openai_with_tools() {
|
||||||
let provider = create_openrouter_provider();
|
|
||||||
let input = json!({
|
let input = json!({
|
||||||
"model": "claude-3-opus",
|
"model": "claude-3-opus",
|
||||||
"max_tokens": 1024,
|
"max_tokens": 1024,
|
||||||
@@ -433,14 +366,13 @@ mod tests {
|
|||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = anthropic_to_openai(input, &provider).unwrap();
|
let result = anthropic_to_openai(input).unwrap();
|
||||||
assert_eq!(result["tools"][0]["type"], "function");
|
assert_eq!(result["tools"][0]["type"], "function");
|
||||||
assert_eq!(result["tools"][0]["function"]["name"], "get_weather");
|
assert_eq!(result["tools"][0]["function"]["name"], "get_weather");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_anthropic_to_openai_tool_use() {
|
fn test_anthropic_to_openai_tool_use() {
|
||||||
let provider = create_openrouter_provider();
|
|
||||||
let input = json!({
|
let input = json!({
|
||||||
"model": "claude-3-opus",
|
"model": "claude-3-opus",
|
||||||
"max_tokens": 1024,
|
"max_tokens": 1024,
|
||||||
@@ -453,7 +385,7 @@ mod tests {
|
|||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = anthropic_to_openai(input, &provider).unwrap();
|
let result = anthropic_to_openai(input).unwrap();
|
||||||
let msg = &result["messages"][0];
|
let msg = &result["messages"][0];
|
||||||
assert_eq!(msg["role"], "assistant");
|
assert_eq!(msg["role"], "assistant");
|
||||||
assert!(msg.get("tool_calls").is_some());
|
assert!(msg.get("tool_calls").is_some());
|
||||||
@@ -462,7 +394,6 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_anthropic_to_openai_tool_result() {
|
fn test_anthropic_to_openai_tool_result() {
|
||||||
let provider = create_openrouter_provider();
|
|
||||||
let input = json!({
|
let input = json!({
|
||||||
"model": "claude-3-opus",
|
"model": "claude-3-opus",
|
||||||
"max_tokens": 1024,
|
"max_tokens": 1024,
|
||||||
@@ -474,7 +405,7 @@ mod tests {
|
|||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = anthropic_to_openai(input, &provider).unwrap();
|
let result = anthropic_to_openai(input).unwrap();
|
||||||
let msg = &result["messages"][0];
|
let msg = &result["messages"][0];
|
||||||
assert_eq!(msg["role"], "tool");
|
assert_eq!(msg["role"], "tool");
|
||||||
assert_eq!(msg["tool_call_id"], "call_123");
|
assert_eq!(msg["tool_call_id"], "call_123");
|
||||||
@@ -538,36 +469,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_model_mapping_from_provider() {
|
fn test_model_passthrough() {
|
||||||
let provider = create_provider(json!({
|
// 格式转换层只做结构转换,模型映射由上游 proxy::model_mapper 处理
|
||||||
"ANTHROPIC_MODEL": "gpt-4o-mini",
|
|
||||||
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-4o"
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 回归:格式转换层不能再二次做模型映射,否则会把已映射的 model 覆盖成默认模型
|
|
||||||
let input = json!({
|
let input = json!({
|
||||||
"model": "gpt-4o",
|
"model": "gpt-4o",
|
||||||
"max_tokens": 1024,
|
"max_tokens": 1024,
|
||||||
"messages": [{"role": "user", "content": "Hello"}]
|
"messages": [{"role": "user", "content": "Hello"}]
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = anthropic_to_openai(input, &provider).unwrap();
|
let result = anthropic_to_openai(input).unwrap();
|
||||||
assert_eq!(result["model"], "gpt-4o");
|
assert_eq!(result["model"], "gpt-4o");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_deepseek_max_tokens_clamp() {
|
|
||||||
let provider = create_provider(json!({
|
|
||||||
"ANTHROPIC_BASE_URL": "https://api.deepseek.com/v1",
|
|
||||||
}));
|
|
||||||
|
|
||||||
let input = json!({
|
|
||||||
"model": "deepseek-chat",
|
|
||||||
"max_tokens": 20000,
|
|
||||||
"messages": [{"role": "user", "content": "Hello"}]
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = anthropic_to_openai(input, &provider).unwrap();
|
|
||||||
assert_eq!(result["max_tokens"], 8192);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user