Merge pull request #614 from fabro-sh/fix/forward-structured-reasoning-effort

Forward reasoning effort for structured completions
This commit is contained in:
Bryan Helmkamp 2026-07-24 09:00:49 -04:00 • committed by GitHub
commit a86d34a25a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 83 additions and 16 deletions

View file

@ -139,22 +139,23 @@ async fn create_completion(
let msg_id = Ulid::new().to_string();
if let Some(schema) = req.schema {
// Structured output uses generate_object for JSON parsing logic
let mut params =
GenerateParams::new(&request.model, std::sync::Arc::new(client.clone()))
.messages(request.messages);
if let Some(ref p) = request.provider {
params = params.provider(p);
}
if let Some(temp) = request.temperature {
params = params.temperature(temp);
}
if let Some(max_tokens) = request.max_tokens {
params = params.max_tokens(max_tokens);
}
if let Some(top_p) = request.top_p {
params = params.top_p(top_p);
}
// Structured output uses generate_object for JSON parsing logic.
// tools/tool_choice are not forwarded: GenerateParams carries
// executable Arc<Tool>s, not wire ToolDefinitions, and
// generate_object sets response_format from the schema itself.
let params = GenerateParams {
messages: Some(request.messages),
provider: request.provider,
temperature: request.temperature,
top_p: request.top_p,
max_tokens: request.max_tokens,
stop_sequences: request.stop_sequences,
reasoning_effort: request.reasoning_effort,
speed: request.speed,
metadata: request.metadata,
provider_options: request.provider_options,
..GenerateParams::new(request.model, std::sync::Arc::new(client.clone()))
};
match generate_object(params, schema).await {
Ok(result) => {
// `result.finish_reason` / `result.usage` resolve through

View file

@ -15368,6 +15368,72 @@ reasoning = false
completion.assert();
}
#[tokio::test]
async fn create_completion_structured_output_forwards_reasoning_effort() {
let upstream = MockServer::start();
let completion = upstream.mock(|when, then| {
when.method(POST)
.path("/chat/completions")
.json_body_includes(r#"{"model":"kimi-k3","reasoning_effort":"high"}"#);
then.status(200)
.header("content-type", "application/json")
.json_body(json!({
"id": "chatcmpl-kimi-structured",
"model": "kimi-k3",
"choices": [{
"message": {
"role": "assistant",
"content": "{\"answer\":42}"
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 4,
"total_tokens": 14
}
}));
});
let state = TestAppStateBuilder::new()
.provider_base_url("kimi", upstream.base_url())
.vault_entries([(EnvVars::KIMI_API_KEY, "test-kimi-api-key")])
.build();
let app = crate::test_support::build_test_router(state);
let req = Request::builder()
.method("POST")
.uri(api("/completions"))
.header("content-type", "application/json")
.body(Body::from(
serde_json::json!({
"provider": "kimi",
"model": "kimi-k3",
"reasoning_effort": "high",
"stream": false,
"schema": {
"type": "object",
"properties": {
"answer": {"type": "integer"}
},
"required": ["answer"]
},
"messages": [
{
"role": "user",
"content": [{"kind": "text", "data": "Return the answer."}]
}
]
})
.to_string(),
))
.unwrap();
let response = app.oneshot(req).await.unwrap();
let body = response_json!(response, StatusCode::OK).await;
assert_eq!(body["output"], json!({"answer": 42}));
completion.assert();
}
#[tokio::test]
async fn demo_list_runs_returns_run_list_items() {
let state = test_app_state();