mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-12 12:09:50 +08:00
fix(gateway): detect DeepSeek custom relay models
This commit is contained in:
@@ -291,6 +291,7 @@ pub(crate) fn apply_provider_request_routing_policy_to_decision_with_websocket_m
|
|||||||
crate::ai_serving::openai_responses_reasoning_replay_policy(
|
crate::ai_serving::openai_responses_reasoning_replay_policy(
|
||||||
transport.provider.provider_type.as_str(),
|
transport.provider.provider_type.as_str(),
|
||||||
transport.endpoint.base_url.as_str(),
|
transport.endpoint.base_url.as_str(),
|
||||||
|
provider_model.as_str(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ pub(crate) async fn resolve_local_same_format_provider_candidate_payload_parts(
|
|||||||
let reasoning_replay_policy = openai_responses_reasoning_replay_policy(
|
let reasoning_replay_policy = openai_responses_reasoning_replay_policy(
|
||||||
prepared.transport.provider.provider_type.as_str(),
|
prepared.transport.provider.provider_type.as_str(),
|
||||||
prepared.transport.endpoint.base_url.as_str(),
|
prepared.transport.endpoint.base_url.as_str(),
|
||||||
|
prepared.mapped_model.as_str(),
|
||||||
);
|
);
|
||||||
let redaction = resolve_provider_chat_pii_redaction(
|
let redaction = resolve_provider_chat_pii_redaction(
|
||||||
state,
|
state,
|
||||||
|
|||||||
@@ -15,11 +15,25 @@ pub(crate) fn is_deepseek_provider(provider_type: &str, base_url: &str) -> bool
|
|||||||
host == "deepseek.com" || host.ends_with(".deepseek.com")
|
host == "deepseek.com" || host.ends_with(".deepseek.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_deepseek_model(provider_model: &str) -> bool {
|
||||||
|
let provider_model = provider_model.trim().to_ascii_lowercase();
|
||||||
|
let leaf = provider_model
|
||||||
|
.rsplit(['/', ':'])
|
||||||
|
.next()
|
||||||
|
.unwrap_or(provider_model.as_str());
|
||||||
|
leaf == "deepseek" || leaf.starts_with("deepseek-") || leaf.starts_with("deepseek_")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_deepseek_upstream(provider_type: &str, base_url: &str, provider_model: &str) -> bool {
|
||||||
|
is_deepseek_provider(provider_type, base_url) || is_deepseek_model(provider_model)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn openai_responses_reasoning_replay_policy(
|
pub(crate) fn openai_responses_reasoning_replay_policy(
|
||||||
provider_type: &str,
|
provider_type: &str,
|
||||||
base_url: &str,
|
base_url: &str,
|
||||||
|
provider_model: &str,
|
||||||
) -> crate::ai_serving::OpenAiResponsesReasoningReplayPolicy {
|
) -> crate::ai_serving::OpenAiResponsesReasoningReplayPolicy {
|
||||||
if is_deepseek_provider(provider_type, base_url) {
|
if is_deepseek_upstream(provider_type, base_url, provider_model) {
|
||||||
crate::ai_serving::OpenAiResponsesReasoningReplayPolicy::DeepSeekOpaque
|
crate::ai_serving::OpenAiResponsesReasoningReplayPolicy::DeepSeekOpaque
|
||||||
} else {
|
} else {
|
||||||
crate::ai_serving::OpenAiResponsesReasoningReplayPolicy::OpenAiItemIds
|
crate::ai_serving::OpenAiResponsesReasoningReplayPolicy::OpenAiItemIds
|
||||||
@@ -33,7 +47,11 @@ pub(crate) fn apply_deepseek_tool_call_thinking_compat(
|
|||||||
provider_api_format: &str,
|
provider_api_format: &str,
|
||||||
original_request_body: Option<&Value>,
|
original_request_body: Option<&Value>,
|
||||||
) {
|
) {
|
||||||
if !is_deepseek_provider(provider_type, base_url) {
|
let provider_model = provider_request_body
|
||||||
|
.get("model")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.unwrap_or_default();
|
||||||
|
if !is_deepseek_upstream(provider_type, base_url, provider_model) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,11 +320,35 @@ mod tests {
|
|||||||
));
|
));
|
||||||
assert!(!is_deepseek_provider("custom", "ftp://api.deepseek.com/v1"));
|
assert!(!is_deepseek_provider("custom", "ftp://api.deepseek.com/v1"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
openai_responses_reasoning_replay_policy("custom", "https://api.deepseek.com/v1"),
|
openai_responses_reasoning_replay_policy(
|
||||||
|
"custom",
|
||||||
|
"https://api.deepseek.com/v1",
|
||||||
|
"deepseek-v4-flash",
|
||||||
|
),
|
||||||
crate::ai_serving::OpenAiResponsesReasoningReplayPolicy::DeepSeekOpaque
|
crate::ai_serving::OpenAiResponsesReasoningReplayPolicy::DeepSeekOpaque
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
openai_responses_reasoning_replay_policy("openai", "https://api.openai.com/v1"),
|
openai_responses_reasoning_replay_policy(
|
||||||
|
"openai",
|
||||||
|
"https://api.openai.com/v1",
|
||||||
|
"gpt-5.6-sol",
|
||||||
|
),
|
||||||
|
crate::ai_serving::OpenAiResponsesReasoningReplayPolicy::OpenAiItemIds
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
openai_responses_reasoning_replay_policy(
|
||||||
|
"custom",
|
||||||
|
"https://api.b.ai/v1",
|
||||||
|
"deepseek-v4-flash",
|
||||||
|
),
|
||||||
|
crate::ai_serving::OpenAiResponsesReasoningReplayPolicy::DeepSeekOpaque
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
openai_responses_reasoning_replay_policy(
|
||||||
|
"custom",
|
||||||
|
"https://api.b.ai/v1",
|
||||||
|
"not-deepseek-compatible",
|
||||||
|
),
|
||||||
crate::ai_serving::OpenAiResponsesReasoningReplayPolicy::OpenAiItemIds
|
crate::ai_serving::OpenAiResponsesReasoningReplayPolicy::OpenAiItemIds
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -330,8 +372,11 @@ mod tests {
|
|||||||
"input": reasoning_items.clone(),
|
"input": reasoning_items.clone(),
|
||||||
"future_request_field": {"preserve": true}
|
"future_request_field": {"preserve": true}
|
||||||
});
|
});
|
||||||
let replay_policy =
|
let replay_policy = openai_responses_reasoning_replay_policy(
|
||||||
openai_responses_reasoning_replay_policy("custom", "https://api.deepseek.com/v1");
|
"custom",
|
||||||
|
"https://api.deepseek.com/v1",
|
||||||
|
"deepseek-v4-flash",
|
||||||
|
);
|
||||||
let mut provider_body = crate::ai_serving::build_standard_request_body_with_model_directives_and_request_headers_and_reasoning_replay_policy(
|
let mut provider_body = crate::ai_serving::build_standard_request_body_with_model_directives_and_request_headers_and_reasoning_replay_policy(
|
||||||
&request,
|
&request,
|
||||||
"openai:responses",
|
"openai:responses",
|
||||||
@@ -373,7 +418,11 @@ mod tests {
|
|||||||
crate::ai_serving::strip_incompatible_openai_responses_reasoning_items_with_policy(
|
crate::ai_serving::strip_incompatible_openai_responses_reasoning_items_with_policy(
|
||||||
&mut deepseek,
|
&mut deepseek,
|
||||||
"openai:responses",
|
"openai:responses",
|
||||||
openai_responses_reasoning_replay_policy("custom", "https://api.deepseek.com/v1"),
|
openai_responses_reasoning_replay_policy(
|
||||||
|
"custom",
|
||||||
|
"https://api.deepseek.com/v1",
|
||||||
|
"deepseek-v4-flash",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
@@ -383,7 +432,11 @@ mod tests {
|
|||||||
crate::ai_serving::strip_incompatible_openai_responses_reasoning_items_with_policy(
|
crate::ai_serving::strip_incompatible_openai_responses_reasoning_items_with_policy(
|
||||||
&mut openai,
|
&mut openai,
|
||||||
"openai:responses",
|
"openai:responses",
|
||||||
openai_responses_reasoning_replay_policy("openai", "https://api.openai.com/v1"),
|
openai_responses_reasoning_replay_policy(
|
||||||
|
"openai",
|
||||||
|
"https://api.openai.com/v1",
|
||||||
|
"gpt-5.6-sol",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
66
|
66
|
||||||
);
|
);
|
||||||
@@ -417,6 +470,52 @@ mod tests {
|
|||||||
assert_eq!(body["messages"][1]["reasoning_content"], "");
|
assert_eq!(body["messages"][1]["reasoning_content"], "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_relay_deepseek_model_adds_chat_thinking_compat() {
|
||||||
|
let mut body = json!({
|
||||||
|
"model": "deepseek-v4-flash",
|
||||||
|
"messages": [
|
||||||
|
{"role": "user", "content": "inspect the repository"},
|
||||||
|
{"role": "assistant", "content": null, "tool_calls": [{
|
||||||
|
"id": "call_1",
|
||||||
|
"type": "function",
|
||||||
|
"function": {"name": "inspect", "arguments": "{}"}
|
||||||
|
}]},
|
||||||
|
{"role": "tool", "tool_call_id": "call_1", "content": "done"}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
apply_deepseek_tool_call_thinking_compat(
|
||||||
|
&mut body,
|
||||||
|
"custom",
|
||||||
|
"https://api.b.ai/v1",
|
||||||
|
"openai:chat",
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(body["thinking"]["type"], "enabled");
|
||||||
|
assert_eq!(body["messages"][1]["reasoning_content"], "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_relay_non_deepseek_model_is_not_rewritten() {
|
||||||
|
let original = json!({
|
||||||
|
"model": "not-deepseek-compatible",
|
||||||
|
"messages": [{"role": "assistant", "content": "done"}]
|
||||||
|
});
|
||||||
|
let mut body = original.clone();
|
||||||
|
|
||||||
|
apply_deepseek_tool_call_thinking_compat(
|
||||||
|
&mut body,
|
||||||
|
"custom",
|
||||||
|
"https://api.b.ai/v1",
|
||||||
|
"openai:chat",
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(body, original);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn openai_chat_deepseek_honors_disabled_thinking() {
|
fn openai_chat_deepseek_honors_disabled_thinking() {
|
||||||
let original = json!({"reasoning_effort": "none"});
|
let original = json!({"reasoning_effort": "none"});
|
||||||
|
|||||||
@@ -597,6 +597,7 @@ pub(crate) async fn resolve_local_standard_candidate_payload_parts(
|
|||||||
let reasoning_replay_policy = openai_responses_reasoning_replay_policy(
|
let reasoning_replay_policy = openai_responses_reasoning_replay_policy(
|
||||||
transport.provider.provider_type.as_str(),
|
transport.provider.provider_type.as_str(),
|
||||||
transport.endpoint.base_url.as_str(),
|
transport.endpoint.base_url.as_str(),
|
||||||
|
prepared_candidate.mapped_model.as_str(),
|
||||||
);
|
);
|
||||||
let redaction = resolve_provider_chat_pii_redaction(
|
let redaction = resolve_provider_chat_pii_redaction(
|
||||||
state,
|
state,
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ fn finalize_openai_chat_provider_request_body(
|
|||||||
openai_responses_reasoning_replay_policy(
|
openai_responses_reasoning_replay_policy(
|
||||||
transport.provider.provider_type.as_str(),
|
transport.provider.provider_type.as_str(),
|
||||||
transport.endpoint.base_url.as_str(),
|
transport.endpoint.base_url.as_str(),
|
||||||
|
mapped_model,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.err()
|
.err()
|
||||||
|
|||||||
+1
@@ -438,6 +438,7 @@ pub(crate) async fn resolve_local_openai_responses_candidate_payload_parts_with_
|
|||||||
let reasoning_replay_policy = openai_responses_reasoning_replay_policy(
|
let reasoning_replay_policy = openai_responses_reasoning_replay_policy(
|
||||||
transport.provider.provider_type.as_str(),
|
transport.provider.provider_type.as_str(),
|
||||||
transport.endpoint.base_url.as_str(),
|
transport.endpoint.base_url.as_str(),
|
||||||
|
mapped_model.as_str(),
|
||||||
);
|
);
|
||||||
let redaction = resolve_provider_chat_pii_redaction(
|
let redaction = resolve_provider_chat_pii_redaction(
|
||||||
state,
|
state,
|
||||||
|
|||||||
@@ -1005,6 +1005,7 @@ pub(crate) async fn maybe_build_responses_websocket_decision(
|
|||||||
reasoning_replay_policy: openai_responses_reasoning_replay_policy(
|
reasoning_replay_policy: openai_responses_reasoning_replay_policy(
|
||||||
transport.provider.provider_type.as_str(),
|
transport.provider.provider_type.as_str(),
|
||||||
transport.endpoint.base_url.as_str(),
|
transport.endpoint.base_url.as_str(),
|
||||||
|
mapped_model.as_str(),
|
||||||
),
|
),
|
||||||
model_directive_patch: input
|
model_directive_patch: input
|
||||||
.model_directive_policy
|
.model_directive_policy
|
||||||
|
|||||||
@@ -3177,6 +3177,7 @@ async fn provider_query_execute_standard_test_candidate(
|
|||||||
crate::ai_serving::openai_responses_reasoning_replay_policy(
|
crate::ai_serving::openai_responses_reasoning_replay_policy(
|
||||||
transport.provider.provider_type.as_str(),
|
transport.provider.provider_type.as_str(),
|
||||||
transport.endpoint.base_url.as_str(),
|
transport.endpoint.base_url.as_str(),
|
||||||
|
request_model,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else {
|
else {
|
||||||
@@ -3238,6 +3239,7 @@ async fn provider_query_execute_standard_test_candidate(
|
|||||||
crate::ai_serving::openai_responses_reasoning_replay_policy(
|
crate::ai_serving::openai_responses_reasoning_replay_policy(
|
||||||
transport.provider.provider_type.as_str(),
|
transport.provider.provider_type.as_str(),
|
||||||
transport.endpoint.base_url.as_str(),
|
transport.endpoint.base_url.as_str(),
|
||||||
|
request_model,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.is_err()
|
.is_err()
|
||||||
|
|||||||
Reference in New Issue
Block a user