mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-07 17:09:13 -10:00
fix(client): forward reasoning controls for custom DeepSeek routes
Custom DeepSeek Chat Completions requests silently dropped configured effort. Reuse the existing DeepSeek mapper so configured low/high/max and the thinking toggle reach streaming and ordinary HTTP requests. Extend the existing request capture check across direct/custom routes and both transports. Validation: 12 focused Rust checks passed, including 24 HTTP capture cases. The rebuilt CLI passed 6 loopback cases (11 HTTP requests), including max effort, enabled thinking, 65,536/384,000-token output limits and reasoning replay after a tool result. External network access was blocked during CLI captures; no provider calls were made. npm test: 1,312 passed, 7 skipped, 0 failed. npm run check:web passed after regenerating the existing generated assets. Rust and CLI evidence came from the shared checkout with concurrent catalog work retained; unrelated test-only formatting changed during compilation. No pristine-release or provider-semantic claim is made.
This commit is contained in:
1 parent
35b11285ae
commit
008d21d69f
2 files changed
+88
-32
No files matched your search
@@ -583,7 +583,13 @@ pub(super) fn apply_route_reasoning_controls(
|
||||
model: &str,
|
||||
effort: Option<&str>,
|
||||
) {
|
||||
apply_reasoning_effort(body, effort, provider);
|
||||
let reasoning_provider =
|
||||
if provider == ProviderKind::Custom && requires_reasoning_content(model) {
|
||||
ProviderKind::Deepseek
|
||||
} else {
|
||||
provider
|
||||
};
|
||||
apply_reasoning_effort(body, effort, reasoning_provider);
|
||||
apply_modelstudio_route_reasoning_controls(body, provider, base_url, model, effort);
|
||||
apply_minimax_route_reasoning_controls(body, provider, base_url, model, effort);
|
||||
apply_inkling_reasoning_effort(body, provider, model, effort);
|
||||
|
||||
@@ -46,30 +46,55 @@
|
||||
);
|
||||
}
|
||||
|
||||
async fn capture_deepseek_chat_body_for_effort(effort: Option<&str>) -> Value {
|
||||
async fn capture_deepseek_chat_body_for_effort(
|
||||
effort: Option<&str>,
|
||||
custom: bool,
|
||||
stream: bool,
|
||||
) -> Value {
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("POST"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
let model = if custom {
|
||||
"deepseek/deepseek-v4.1-flash"
|
||||
} else {
|
||||
"deepseek-v4-pro"
|
||||
};
|
||||
let response = if stream {
|
||||
ResponseTemplate::new(200)
|
||||
.insert_header("content-type", "text/event-stream")
|
||||
.set_body_string(format!(
|
||||
"data: {}\n\ndata: [DONE]\n\n",
|
||||
json!({
|
||||
"id": "chatcmpl-deepseek-effort-ladder",
|
||||
"object": "chat.completion.chunk",
|
||||
"model": model,
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"delta": {"content": "ok"},
|
||||
"finish_reason": "stop"
|
||||
}],
|
||||
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2}
|
||||
})
|
||||
))
|
||||
} else {
|
||||
ResponseTemplate::new(200).set_body_json(json!({
|
||||
"id": "chatcmpl-deepseek-effort-ladder",
|
||||
"object": "chat.completion",
|
||||
"model": "deepseek-v4-pro",
|
||||
"model": model,
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"message": {"role": "assistant", "content": "ok"},
|
||||
"finish_reason": "stop"
|
||||
}],
|
||||
"usage": {
|
||||
"prompt_tokens": 1,
|
||||
"completion_tokens": 1,
|
||||
"total_tokens": 2
|
||||
}
|
||||
})))
|
||||
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2}
|
||||
}))
|
||||
};
|
||||
Mock::given(method("POST"))
|
||||
.respond_with(response)
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let request = MessageRequest {
|
||||
model: "deepseek-v4-pro".to_string(),
|
||||
model: model.to_string(),
|
||||
messages: vec![Message {
|
||||
role: Role::User,
|
||||
content: vec![ContentBlock::Text {
|
||||
@@ -83,24 +108,46 @@
|
||||
tool_choice: None,
|
||||
metadata: None,
|
||||
thinking: None,
|
||||
reasoning_effort: effort.map(str::to_string),
|
||||
stream: Some(false),
|
||||
reasoning_effort: effort.map(|effort| {
|
||||
crate::reasoning_preference::ReasoningEffort::parse_strict(effort)
|
||||
.expect("valid capture effort")
|
||||
.as_setting_for_route(
|
||||
ProviderKind::Deepseek,
|
||||
&server.uri(),
|
||||
model,
|
||||
)
|
||||
.to_string()
|
||||
}),
|
||||
stream: Some(stream),
|
||||
temperature: None,
|
||||
top_p: None,
|
||||
};
|
||||
let client =
|
||||
deepseek_request_boundary_client(crate::config::DEFAULT_DEEPSEEK_BASE_URL, server.uri());
|
||||
client
|
||||
.create_message(request)
|
||||
.await
|
||||
.expect("non-streaming request succeeds");
|
||||
let client = if custom {
|
||||
custom_mock_client_for_identity(&server, "aicraft")
|
||||
} else {
|
||||
deepseek_request_boundary_client(crate::config::DEFAULT_DEEPSEEK_BASE_URL, server.uri())
|
||||
};
|
||||
if stream {
|
||||
let mut events = client
|
||||
.create_message_stream(request)
|
||||
.await
|
||||
.expect("streaming request succeeds");
|
||||
while let Some(event) = events.next().await {
|
||||
event.expect("streaming event succeeds");
|
||||
}
|
||||
} else {
|
||||
client
|
||||
.create_message(request)
|
||||
.await
|
||||
.unwrap_or_else(|error| panic!("custom={custom} effort={effort:?}: {error}"));
|
||||
}
|
||||
|
||||
let requests = server.received_requests().await.expect("recorded request");
|
||||
assert_eq!(requests.len(), 1);
|
||||
serde_json::from_slice(&requests[0].body).expect("captured request JSON")
|
||||
}
|
||||
|
||||
/// Request-body capture per effort level on the first-party DeepSeek chat
|
||||
/// Request-body capture per effort level on direct and custom DeepSeek chat
|
||||
/// route: the wire must carry the documented low/high/max ladder and the
|
||||
/// thinking toggle, never an invented value (#52).
|
||||
#[tokio::test]
|
||||
@@ -113,17 +160,20 @@
|
||||
(Some("off"), None, Some("disabled")),
|
||||
(None, None, None),
|
||||
] {
|
||||
let body = capture_deepseek_chat_body_for_effort(effort).await;
|
||||
assert_eq!(
|
||||
body.get("reasoning_effort").and_then(Value::as_str),
|
||||
expected_effort,
|
||||
"reasoning_effort on the wire for {effort:?}: {body}"
|
||||
);
|
||||
assert_eq!(
|
||||
body.pointer("/thinking/type").and_then(Value::as_str),
|
||||
expected_thinking,
|
||||
"thinking on the wire for {effort:?}: {body}"
|
||||
);
|
||||
for (custom, stream) in [(false, false), (false, true), (true, false), (true, true)] {
|
||||
let body = capture_deepseek_chat_body_for_effort(effort, custom, stream).await;
|
||||
assert_eq!(body["max_tokens"], 64);
|
||||
assert_eq!(
|
||||
body.get("reasoning_effort").and_then(Value::as_str),
|
||||
expected_effort,
|
||||
"reasoning_effort on the wire for {effort:?}: {body}"
|
||||
);
|
||||
assert_eq!(
|
||||
body.pointer("/thinking/type").and_then(Value::as_str),
|
||||
expected_thinking,
|
||||
"thinking on the wire for {effort:?}: {body}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user