From b5bd63b585b106767dc38d8daefadeecf245bcf4 Mon Sep 17 00:00:00 2001 From: dudukee Date: Wed, 14 Oct 2026 11:42:48 +0800 Subject: [PATCH] fix(openai): use a catalog-supported warmup reasoning effort (#425) Scheduled account warmup sent reasoning.effort=none, but the Codex model catalog lists low as the lowest supported level for every model an account can see. Upstream answers HTTP 200 with an SSE error event (unsupported_value on reasoning.effort), which the warmup terminal check records as response_failed, so the cycle ends with warmed_up=0 and never starts the 5h window. Send low instead and cover the upstream payload with a regression test. --- .../openai/src/credential/quota/warmup.rs | 6 +-- .../openai/tests/credential/quota/warmup.rs | 39 ++++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/backend/crates/providers/openai/src/credential/quota/warmup.rs b/backend/crates/providers/openai/src/credential/quota/warmup.rs index 9bf45da8c..2a5583df8 100644 --- a/backend/crates/providers/openai/src/credential/quota/warmup.rs +++ b/backend/crates/providers/openai/src/credential/quota/warmup.rs @@ -164,10 +164,8 @@ impl CodexCredentialQuotaService { "service_tier".to_owned(), Value::String("default".to_owned()), ); - body.insert( - "reasoning".to_owned(), - serde_json::json!({"effort": "none"}), - ); + // 目录里各模型的最低档都是 low,none 会收到上游 error 终态并让整次预热记为失败 + body.insert("reasoning".to_owned(), serde_json::json!({"effort": "low"})); body.insert("text".to_owned(), serde_json::json!({"verbosity": "low"})); let upstream_request = CodexResponsesRequest::from_body(body); diff --git a/backend/crates/providers/openai/tests/credential/quota/warmup.rs b/backend/crates/providers/openai/tests/credential/quota/warmup.rs index 8aad5877b..c1c3aa3bc 100644 --- a/backend/crates/providers/openai/tests/credential/quota/warmup.rs +++ b/backend/crates/providers/openai/tests/credential/quota/warmup.rs @@ -1,4 +1,4 @@ -//! 验证额度预热按 SSE 终态判定结果并跳过过期凭据 +//! 验证额度预热按 SSE 终态判定结果、请求档位并跳过过期凭据 use super::*; @@ -40,6 +40,43 @@ async fn warmup_consumes_completed_sse_and_persists_rate_limit_event() { assert!(store.quota_json("acct_warmup_completed").is_some()); } +#[tokio::test] +async fn warmup_sends_the_lowest_reasoning_effort_supported_by_the_catalog() { + let server = MockServer::start().await; + let sse = concat!( + "event: response.completed\n", + "data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_warmup_effort\",\"status\":\"completed\",\"output\":[]}}\n\n", + "data: [DONE]\n\n", + ); + Mock::given(method("POST")) + .and(path("/codex/responses")) + .respond_with(ResponseTemplate::new(200).set_body_raw(sse, "text/event-stream")) + .expect(1) + .mount(&server) + .await; + let store = Arc::new(MemoryAccountStore::default()); + create_account(&store, "acct_warmup_effort").await; + let service = quota_service_with_base_url( + &store, + reqwest::Client::builder() + .no_proxy() + .build() + .expect("client"), + server.uri(), + ); + + let summary = service.execute_warmup("test-model").await.expect("warmup"); + + assert_eq!(summary.warmed_up, 1); + let requests = server.received_requests().await.expect("recorded requests"); + let decoded = + zstd::stream::decode_all(requests[0].body.as_slice()).expect("zstd body should decode"); + let body: serde_json::Value = + serde_json::from_slice(&decoded).expect("warmup body should be valid JSON"); + // 上游按目录拒绝目录外的档位,none 曾让全部定时预热只留下 error 终态 + assert_eq!(body["reasoning"]["effort"], "low"); +} + #[tokio::test] async fn warmup_rejects_failed_sse_despite_successful_http_headers() { let server = MockServer::start().await;