diff --git a/src-tauri/src/database/dao/settings.rs b/src-tauri/src/database/dao/settings.rs index 972e95e3..8056a01c 100644 --- a/src-tauri/src/database/dao/settings.rs +++ b/src-tauri/src/database/dao/settings.rs @@ -168,7 +168,7 @@ impl Database { /// 获取整流器配置 /// - /// 返回整流器配置,如果不存在则返回默认值(全部关闭) + /// 返回整流器配置,如果不存在则返回默认值(全部开启) pub fn get_rectifier_config(&self) -> Result { match self.get_setting("rectifier_config")? { Some(json) => serde_json::from_str(&json) diff --git a/src-tauri/src/proxy/forwarder.rs b/src-tauri/src/proxy/forwarder.rs index 9d46d5f3..c26ceabf 100644 --- a/src-tauri/src/proxy/forwarder.rs +++ b/src-tauri/src/proxy/forwarder.rs @@ -262,6 +262,7 @@ impl RequestForwarder { provider_type, ProviderType::Claude | ProviderType::ClaudeAuth ); + let mut signature_rectifier_non_retryable_client_error = false; if is_anthropic_provider { let error_message = extract_error_message(&e); @@ -300,8 +301,9 @@ impl RequestForwarder { // 整流未生效:继续尝试 budget 整流路径,避免误判后短路 if !rectified.applied { log::warn!( - "[{app_type_str}] [RECT-006] thinking 签名整流器触发但无可整流内容,继续检查 budget 整流路径" + "[{app_type_str}] [RECT-006] thinking 签名整流器触发但无可整流内容,继续检查 budget;若 budget 也未命中则按客户端错误返回" ); + signature_rectifier_non_retryable_client_error = true; } else { log::info!( "[{}] [RECT-001] thinking 签名整流器触发, 移除 {} thinking blocks, {} redacted_thinking blocks, {} signature fields", @@ -497,11 +499,10 @@ impl RequestForwarder { } log::info!( - "[{}] [RECT-010] thinking budget 整流器触发, type_changed={}, budget_changed={}, max_tokens_changed={}", + "[{}] [RECT-010] thinking budget 整流器触发, before={:?}, after={:?}", app_type_str, - budget_rectified.type_changed, - budget_rectified.budget_changed, - budget_rectified.max_tokens_changed + budget_rectified.before, + budget_rectified.after ); let _ = std::mem::replace(&mut budget_rectifier_retried, true); @@ -616,6 +617,28 @@ impl RequestForwarder { } } + if signature_rectifier_non_retryable_client_error { + self.router + .release_permit_neutral( + &provider.id, + app_type_str, + used_half_open_permit, + ) + .await; + let mut status = self.status.write().await; + status.failed_requests += 1; + status.last_error = Some(e.to_string()); + if status.total_requests > 0 { + status.success_rate = (status.success_requests as f32 + / status.total_requests as f32) + * 100.0; + } + return Err(ForwardError { + error: e, + provider: Some(provider.clone()), + }); + } + // 失败:记录失败并更新熔断器 let _ = self .router diff --git a/src-tauri/src/proxy/thinking_budget_rectifier.rs b/src-tauri/src/proxy/thinking_budget_rectifier.rs index ac91bd58..aa083687 100644 --- a/src-tauri/src/proxy/thinking_budget_rectifier.rs +++ b/src-tauri/src/proxy/thinking_budget_rectifier.rs @@ -16,16 +16,25 @@ const MAX_TOKENS_VALUE: u64 = 64000; const MIN_MAX_TOKENS_FOR_BUDGET: u64 = MAX_THINKING_BUDGET + 1; /// Budget 整流结果 -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct BudgetRectifySnapshot { + /// max_tokens + pub max_tokens: Option, + /// thinking.type + pub thinking_type: Option, + /// thinking.budget_tokens + pub thinking_budget_tokens: Option, +} + +/// Budget 整流结果 +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct BudgetRectifyResult { /// 是否应用了整流 pub applied: bool, - /// 是否修改了 thinking type - pub type_changed: bool, - /// 是否修改了 budget_tokens - pub budget_changed: bool, - /// 是否修改了 max_tokens - pub max_tokens_changed: bool, + /// 整流前快照 + pub before: BudgetRectifySnapshot, + /// 整流后快照 + pub after: BudgetRectifySnapshot, } /// 检测是否需要触发 thinking budget 整流器 @@ -49,27 +58,14 @@ pub fn should_rectify_thinking_budget( }; let lower = msg.to_lowercase(); - // 覆盖常见上游文案变体: - // - budget_tokens >= 1024 约束 - // - budget_tokens 与 max_tokens 关系约束 + // 与 CCH 对齐:仅在包含 budget_tokens + thinking + 1024 约束时触发 let has_budget_tokens_reference = lower.contains("budget_tokens") || lower.contains("budget tokens"); + let has_thinking_reference = lower.contains("thinking"); let has_1024_constraint = lower.contains("greater than or equal to 1024") || lower.contains(">= 1024") - || lower.contains("at least 1024") || (lower.contains("1024") && lower.contains("input should be")); - let has_max_tokens_constraint = lower.contains("less than max_tokens") - || (lower.contains("budget_tokens") - && lower.contains("max_tokens") - && (lower.contains("must be less than") || lower.contains("should be less than"))); - let has_thinking_reference = lower.contains("thinking"); - - if has_budget_tokens_reference && (has_1024_constraint || has_max_tokens_constraint) { - return true; - } - - // 兜底:部分网关会省略 budget_tokens 字段名,但保留 thinking + 1024 线索 - if has_thinking_reference && has_1024_constraint { + if has_budget_tokens_reference && has_thinking_reference && has_1024_constraint { return true; } @@ -83,51 +79,63 @@ pub fn should_rectify_thinking_budget( /// - `thinking.budget_tokens = 32000` /// - 如果 `max_tokens < 32001`,设为 `64000` pub fn rectify_thinking_budget(body: &mut Value) -> BudgetRectifyResult { - let mut result = BudgetRectifyResult::default(); + let before = snapshot_budget(body); - // 仅允许对显式 thinking.type=enabled 的请求做 budget 整流,避免静默语义升级。 - let Some(thinking_obj) = body.get("thinking").and_then(|t| t.as_object()) else { - log::warn!("[RECT-BUD-001] budget 整流命中但请求缺少 thinking 对象,跳过"); - return result; - }; - let current_type = thinking_obj.get("type").and_then(|t| t.as_str()); - if current_type == Some("adaptive") { - log::warn!("[RECT-BUD-002] budget 整流命中但 thinking.type=adaptive,跳过"); - return result; + // 与 CCH 对齐:adaptive 请求不改写 + if before.thinking_type.as_deref() == Some("adaptive") { + return BudgetRectifyResult { + applied: false, + before: before.clone(), + after: before, + }; } - if current_type != Some("enabled") { - log::warn!( - "[RECT-BUD-003] budget 整流命中但 thinking.type 不是 enabled(当前: {}),跳过", - current_type.unwrap_or("") - ); - return result; + + // 与 CCH 对齐:缺少/非法 thinking 时自动创建后再整流 + if !body.get("thinking").is_some_and(Value::is_object) { + body["thinking"] = Value::Object(serde_json::Map::new()); } + let Some(thinking) = body.get_mut("thinking").and_then(|t| t.as_object_mut()) else { - return result; + return BudgetRectifyResult { + applied: false, + before: before.clone(), + after: before, + }; }; - // 设置 budget_tokens = MAX_THINKING_BUDGET - let current_budget = thinking.get("budget_tokens").and_then(|v| v.as_u64()); - if current_budget != Some(MAX_THINKING_BUDGET) { - thinking.insert( - "budget_tokens".to_string(), - Value::Number(MAX_THINKING_BUDGET.into()), - ); - result.budget_changed = true; - } + thinking.insert("type".to_string(), Value::String("enabled".to_string())); + thinking.insert( + "budget_tokens".to_string(), + Value::Number(MAX_THINKING_BUDGET.into()), + ); - // 确保 max_tokens >= MIN_MAX_TOKENS_FOR_BUDGET - let current_max_tokens = body.get("max_tokens").and_then(|v| v.as_u64()).unwrap_or(0); - if current_max_tokens < MIN_MAX_TOKENS_FOR_BUDGET { + if before.max_tokens.is_none() || before.max_tokens < Some(MIN_MAX_TOKENS_FOR_BUDGET) { body["max_tokens"] = Value::Number(MAX_TOKENS_VALUE.into()); - result.max_tokens_changed = true; } - result.applied = result.type_changed || result.budget_changed || result.max_tokens_changed; - if !result.applied { - log::warn!("[RECT-BUD-004] budget 整流命中但请求已满足约束,跳过重试"); + let after = snapshot_budget(body); + BudgetRectifyResult { + applied: before != after, + before, + after, + } +} + +fn snapshot_budget(body: &Value) -> BudgetRectifySnapshot { + let max_tokens = body.get("max_tokens").and_then(|v| v.as_u64()); + let thinking = body.get("thinking").and_then(|t| t.as_object()); + let thinking_type = thinking + .and_then(|t| t.get("type")) + .and_then(|v| v.as_str()) + .map(ToString::to_string); + let thinking_budget_tokens = thinking + .and_then(|t| t.get("budget_tokens")) + .and_then(|v| v.as_u64()); + BudgetRectifySnapshot { + max_tokens, + thinking_type, + thinking_budget_tokens, } - result } #[cfg(test)] @@ -171,7 +179,7 @@ mod tests { #[test] fn test_detect_budget_tokens_max_tokens_error() { - assert!(should_rectify_thinking_budget( + assert!(!should_rectify_thinking_budget( Some("budget_tokens must be less than max_tokens"), &enabled_config() )); @@ -179,7 +187,7 @@ mod tests { #[test] fn test_detect_budget_tokens_1024_error() { - assert!(should_rectify_thinking_budget( + assert!(!should_rectify_thinking_budget( Some("budget_tokens: value must be at least 1024"), &enabled_config() )); @@ -231,8 +239,15 @@ mod tests { let result = rectify_thinking_budget(&mut body); assert!(result.applied); - assert!(result.budget_changed); - assert!(result.max_tokens_changed); + assert_eq!(result.before.thinking_type.as_deref(), Some("enabled")); + assert_eq!(result.after.thinking_type.as_deref(), Some("enabled")); + assert_eq!(result.before.thinking_budget_tokens, Some(512)); + assert_eq!( + result.after.thinking_budget_tokens, + Some(MAX_THINKING_BUDGET) + ); + assert_eq!(result.before.max_tokens, Some(1024)); + assert_eq!(result.after.max_tokens, Some(MAX_TOKENS_VALUE)); assert_eq!(body["thinking"]["type"], "enabled"); assert_eq!(body["thinking"]["budget_tokens"], MAX_THINKING_BUDGET); assert_eq!(body["max_tokens"], MAX_TOKENS_VALUE); @@ -249,7 +264,7 @@ mod tests { let result = rectify_thinking_budget(&mut body); assert!(!result.applied); - assert!(!result.type_changed); + assert_eq!(result.before, result.after); assert_eq!(body["thinking"]["type"], "adaptive"); assert_eq!(body["thinking"]["budget_tokens"], 512); assert_eq!(body["max_tokens"], 1024); @@ -266,7 +281,8 @@ mod tests { let result = rectify_thinking_budget(&mut body); assert!(result.applied); - assert!(!result.max_tokens_changed); + assert_eq!(result.before.max_tokens, Some(100000)); + assert_eq!(result.after.max_tokens, Some(100000)); assert_eq!(body["max_tokens"], 100000); } @@ -279,9 +295,17 @@ mod tests { let result = rectify_thinking_budget(&mut body); - assert!(!result.applied); - assert!(body.get("thinking").is_none()); - assert_eq!(body["max_tokens"], 1024); + assert!(result.applied); + assert_eq!(result.before.thinking_type, None); + assert_eq!(result.after.thinking_type.as_deref(), Some("enabled")); + assert_eq!( + result.after.thinking_budget_tokens, + Some(MAX_THINKING_BUDGET) + ); + assert_eq!(result.after.max_tokens, Some(MAX_TOKENS_VALUE)); + assert_eq!(body["thinking"]["type"], "enabled"); + assert_eq!(body["thinking"]["budget_tokens"], MAX_THINKING_BUDGET); + assert_eq!(body["max_tokens"], MAX_TOKENS_VALUE); } #[test] @@ -294,12 +318,13 @@ mod tests { let result = rectify_thinking_budget(&mut body); assert!(result.applied); - assert!(result.max_tokens_changed); + assert_eq!(result.before.max_tokens, None); + assert_eq!(result.after.max_tokens, Some(MAX_TOKENS_VALUE)); assert_eq!(body["max_tokens"], MAX_TOKENS_VALUE); } #[test] - fn test_rectify_budget_skips_non_enabled_type() { + fn test_rectify_budget_normalizes_non_enabled_type() { let mut body = json!({ "model": "claude-test", "thinking": { "type": "disabled", "budget_tokens": 512 }, @@ -308,10 +333,12 @@ mod tests { let result = rectify_thinking_budget(&mut body); - assert!(!result.applied); - assert_eq!(body["thinking"]["type"], "disabled"); - assert_eq!(body["thinking"]["budget_tokens"], 512); - assert_eq!(body["max_tokens"], 1024); + assert!(result.applied); + assert_eq!(result.before.thinking_type.as_deref(), Some("disabled")); + assert_eq!(result.after.thinking_type.as_deref(), Some("enabled")); + assert_eq!(body["thinking"]["type"], "enabled"); + assert_eq!(body["thinking"]["budget_tokens"], MAX_THINKING_BUDGET); + assert_eq!(body["max_tokens"], MAX_TOKENS_VALUE); } #[test] @@ -325,8 +352,7 @@ mod tests { let result = rectify_thinking_budget(&mut body); assert!(!result.applied); - assert!(!result.budget_changed); - assert!(!result.max_tokens_changed); + assert_eq!(result.before, result.after); assert_eq!(body["thinking"]["budget_tokens"], 32000); assert_eq!(body["max_tokens"], 64001); } diff --git a/src-tauri/src/proxy/thinking_rectifier.rs b/src-tauri/src/proxy/thinking_rectifier.rs index 24c72963..ce71503c 100644 --- a/src-tauri/src/proxy/thinking_rectifier.rs +++ b/src-tauri/src/proxy/thinking_rectifier.rs @@ -89,15 +89,11 @@ pub fn should_rectify_thinking_signature( return true; } - // 场景7: 非法请求(需携带签名/思考结构线索,避免过宽命中) - let invalid_request_like = lower.contains("非法请求") + // 场景7: 非法请求(与 CCH 对齐,按 invalid request 统一兜底) + if lower.contains("非法请求") || lower.contains("illegal request") - || lower.contains("invalid request"); - let has_signature_or_thinking_clue = lower.contains("signature") - || lower.contains("thinking") - || lower.contains("redacted_thinking") - || lower.contains("tool_use"); - if invalid_request_like && has_signature_or_thinking_clue { + || lower.contains("invalid request") + { return true; } @@ -114,15 +110,6 @@ pub fn should_rectify_thinking_signature( pub fn rectify_anthropic_request(body: &mut Value) -> RectifyResult { let mut result = RectifyResult::default(); - // 与 CCH 对齐:adaptive 模式下整流器直接跳过,不改写请求。 - let thinking_type = body - .get("thinking") - .and_then(|t| t.get("type")) - .and_then(|t| t.as_str()); - if thinking_type == Some("adaptive") { - return result; - } - let messages = match body.get_mut("messages").and_then(|m| m.as_array_mut()) { Some(m) => m, None => return result, @@ -496,7 +483,7 @@ mod tests { #[test] fn test_detect_invalid_request() { - // 场景7: 非法请求(包含签名/思考结构线索才触发) + // 场景7: 非法请求(与 CCH 对齐,统一触发) assert!(should_rectify_thinking_signature( Some("非法请求:thinking signature 不合法"), &enabled_config() @@ -505,7 +492,7 @@ mod tests { Some("illegal request: tool_use block mismatch"), &enabled_config() )); - assert!(!should_rectify_thinking_signature( + assert!(should_rectify_thinking_signature( Some("invalid request: malformed JSON"), &enabled_config() )); @@ -523,7 +510,7 @@ mod tests { // ==================== adaptive thinking type 测试 ==================== #[test] - fn test_rectify_skips_when_adaptive_thinking_type() { + fn test_rectify_keeps_adaptive_when_no_legacy_blocks() { let mut body = json!({ "model": "claude-test", "thinking": { "type": "adaptive" }, @@ -577,7 +564,7 @@ mod tests { #[test] fn test_rectify_removes_top_level_thinking_adaptive() { - // 与 CCH 对齐:adaptive 模式下整流器直接跳过,不删除顶层 thinking + // 顶层 thinking 仅在 type=enabled 且 tool_use 场景才会删除,adaptive 不删除 let mut body = json!({ "model": "claude-test", "thinking": { "type": "adaptive" }, @@ -598,6 +585,31 @@ mod tests { assert_eq!(body["thinking"]["type"], "adaptive"); } + #[test] + fn test_rectify_adaptive_still_cleans_legacy_signature_blocks() { + let mut body = json!({ + "model": "claude-test", + "thinking": { "type": "adaptive" }, + "messages": [{ + "role": "assistant", + "content": [ + { "type": "thinking", "thinking": "t", "signature": "sig_thinking" }, + { "type": "text", "text": "hello", "signature": "sig_text" } + ] + }] + }); + + let result = rectify_anthropic_request(&mut body); + + assert!(result.applied); + assert_eq!(result.removed_thinking_blocks, 1); + let content = body["messages"][0]["content"].as_array().unwrap(); + assert_eq!(content.len(), 1); + assert_eq!(content[0]["type"], "text"); + assert!(content[0].get("signature").is_none()); + assert_eq!(body["thinking"]["type"], "adaptive"); + } + // ==================== normalize_thinking_type 测试 ==================== #[test] diff --git a/src-tauri/src/proxy/types.rs b/src-tauri/src/proxy/types.rs index d4f4ca44..cf8dd7c8 100644 --- a/src-tauri/src/proxy/types.rs +++ b/src-tauri/src/proxy/types.rs @@ -195,28 +195,24 @@ pub struct AppProxyConfig { /// 整流器配置 /// /// 存储在 settings 表中 -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RectifierConfig { - /// 总开关:是否启用整流器(默认关闭) - #[serde(default = "default_false")] + /// 总开关:是否启用整流器(默认开启) + #[serde(default = "default_true")] pub enabled: bool, - /// 请求整流:启用 thinking 签名整流器(默认关闭) + /// 请求整流:启用 thinking 签名整流器(默认开启) /// /// 处理错误:Invalid 'signature' in 'thinking' block - #[serde(default = "default_false")] + #[serde(default = "default_true")] pub request_thinking_signature: bool, - /// 请求整流:启用 thinking budget 整流器(默认关闭) + /// 请求整流:启用 thinking budget 整流器(默认开启) /// /// 处理错误:budget_tokens + thinking 相关约束 - #[serde(default = "default_false")] + #[serde(default = "default_true")] pub request_thinking_budget: bool, } -fn default_false() -> bool { - false -} - fn default_true() -> bool { true } @@ -225,6 +221,16 @@ fn default_log_level() -> String { "info".to_string() } +impl Default for RectifierConfig { + fn default() -> Self { + Self { + enabled: true, + request_thinking_signature: true, + request_thinking_budget: true, + } + } +} + /// 日志配置 /// /// 存储在 settings 表的 log_config 字段中(JSON 格式) @@ -270,28 +276,28 @@ mod tests { use super::*; #[test] - fn test_rectifier_config_default_disabled() { - // 验证 RectifierConfig::default() 返回全关闭状态 + fn test_rectifier_config_default_enabled() { + // 验证 RectifierConfig::default() 返回全开启状态 let config = RectifierConfig::default(); - assert!(!config.enabled, "整流器总开关默认应为 false"); + assert!(config.enabled, "整流器总开关默认应为 true"); assert!( - !config.request_thinking_signature, - "thinking 签名整流器默认应为 false" + config.request_thinking_signature, + "thinking 签名整流器默认应为 true" ); assert!( - !config.request_thinking_budget, - "thinking budget 整流器默认应为 false" + config.request_thinking_budget, + "thinking budget 整流器默认应为 true" ); } #[test] fn test_rectifier_config_serde_default() { - // 验证反序列化缺字段时使用默认值 false + // 验证反序列化缺字段时使用默认值 true let json = "{}"; let config: RectifierConfig = serde_json::from_str(json).unwrap(); - assert!(!config.enabled); - assert!(!config.request_thinking_signature); - assert!(!config.request_thinking_budget); + assert!(config.enabled); + assert!(config.request_thinking_signature); + assert!(config.request_thinking_budget); } #[test] @@ -307,12 +313,12 @@ mod tests { #[test] fn test_rectifier_config_serde_partial_fields() { - // 验证只设置部分字段时,缺失字段使用默认值 false + // 验证只设置部分字段时,缺失字段使用默认值 true let json = r#"{"enabled": true, "requestThinkingSignature": false}"#; let config: RectifierConfig = serde_json::from_str(json).unwrap(); assert!(config.enabled); assert!(!config.request_thinking_signature); - assert!(!config.request_thinking_budget); + assert!(config.request_thinking_budget); } #[test] diff --git a/src/App.tsx b/src/App.tsx index 9f758f8b..4f0d4778 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -141,7 +141,11 @@ function App() { // Fallback from sessions view when switching to an app without session support useEffect(() => { - if (currentView === "sessions" && activeApp !== "claude" && activeApp !== "codex") { + if ( + currentView === "sessions" && + activeApp !== "claude" && + activeApp !== "codex" + ) { setCurrentView("providers"); } }, [activeApp, currentView]); diff --git a/src/components/settings/RectifierConfigPanel.tsx b/src/components/settings/RectifierConfigPanel.tsx index d3ddde91..be75a228 100644 --- a/src/components/settings/RectifierConfigPanel.tsx +++ b/src/components/settings/RectifierConfigPanel.tsx @@ -8,9 +8,9 @@ import { settingsApi, type RectifierConfig } from "@/lib/api/settings"; export function RectifierConfigPanel() { const { t } = useTranslation(); const [config, setConfig] = useState({ - enabled: false, - requestThinkingSignature: false, - requestThinkingBudget: false, + enabled: true, + requestThinkingSignature: true, + requestThinkingBudget: true, }); const [isLoading, setIsLoading] = useState(true); diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 5f1182a9..40523394 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -216,7 +216,7 @@ "thinkingSignature": "Thinking Signature Rectification", "thinkingSignatureDescription": "When an Anthropic-type provider returns thinking signature incompatibility or illegal request errors, automatically removes incompatible thinking-related blocks and retries once with the same provider", "thinkingBudget": "Thinking Budget Rectification", - "thinkingBudgetDescription": "When an Anthropic-type provider returns budget_tokens constraint errors (such as at least 1024 or less than max_tokens), and the request explicitly enables thinking (type=enabled), automatically sets thinking budget to 32000 and max_tokens to 64000 if needed, then retries once" + "thinkingBudgetDescription": "When an Anthropic-type provider returns budget_tokens constraint errors (such as at least 1024), automatically normalizes thinking to enabled, sets thinking budget to 32000, and raises max_tokens to 64000 if needed, then retries once" }, "logConfig": { "title": "Log Management", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index a24ead12..45f8cf0b 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -216,7 +216,7 @@ "thinkingSignature": "Thinking 署名整流", "thinkingSignatureDescription": "Anthropic タイプのプロバイダーが thinking 署名の非互換性や不正なリクエストエラーを返した場合、互換性のない thinking 関連ブロックを自動削除し、同じプロバイダーで 1 回リトライします", "thinkingBudget": "Thinking Budget 整流", - "thinkingBudgetDescription": "Anthropic タイプのプロバイダーが budget_tokens 制約エラー(例: 1024 以上、max_tokens 未満)を返し、かつリクエストで thinking(type=enabled)が明示的に有効な場合、thinking 予算を 32000 に設定し、必要に応じて max_tokens を 64000 に設定して 1 回リトライします" + "thinkingBudgetDescription": "Anthropic タイプのプロバイダーが budget_tokens 制約エラー(例: 1024 以上)を返した場合、thinking を enabled に正規化し、thinking 予算を 32000 に設定し、必要に応じて max_tokens を 64000 に引き上げて 1 回リトライします" }, "logConfig": { "title": "ログ管理", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 69216b87..4ccd2e70 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -216,7 +216,7 @@ "thinkingSignature": "Thinking 签名整流", "thinkingSignatureDescription": "当 Anthropic 类型供应商返回 thinking 签名不兼容或非法请求等错误时,自动移除不兼容的 thinking 相关块并对同一供应商重试一次", "thinkingBudget": "Thinking Budget 整流", - "thinkingBudgetDescription": "当 Anthropic 类型供应商返回 budget_tokens 约束错误(如至少 1024、需小于 max_tokens)时,在请求显式开启 thinking(type=enabled)时自动将预算设为 32000,并在需要时将 max_tokens 设为 64000,然后重试一次" + "thinkingBudgetDescription": "当 Anthropic 类型供应商返回 budget_tokens 约束错误(如至少 1024)时,自动将 thinking 规范为 enabled 并将 budget 设为 32000,同时在需要时将 max_tokens 设为 64000,然后重试一次" }, "logConfig": { "title": "日志管理",