mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 08:44:41 +08:00
feat: add Bedrock request optimizer (PRE-SEND thinking + cache injection) (#1301)
* feat: add Bedrock request optimizer (PRE-SEND thinking + cache injection) Add a PRE-SEND request optimizer that enhances Bedrock API requests before forwarding, complementing the existing POST-ERROR rectifier system. New modules: - thinking_optimizer: 3-path model detection (adaptive/legacy/skip) - Opus 4.6/Sonnet 4.6: adaptive thinking + effort max + 1M context beta - Legacy models: inject extended thinking with max budget - Haiku: skip (no modification) - cache_injector: auto-inject cache_control breakpoints (max 4) - Injects at tools/system/assistant message positions - TTL upgrade for existing breakpoints (5m → 1h) Gate: only activates for Bedrock providers (CLAUDE_CODE_USE_BEDROCK=1) Config: stored in SQLite settings table, default OFF, user opt-in UI: new Optimizer section in RectifierConfigPanel with 3 toggles + TTL 18 unit tests covering all paths. Verified against live Bedrock API. * chore: remove docs/plans directory * fix: address code review findings for Bedrock request optimizer P0 fixes: - Replace hardcoded Chinese with i18n t() calls in optimizer panel, add translation keys to zh/en/ja locale files - Fix u64 underflow: max_tokens - 1 → max_tokens.saturating_sub(1) - Move optimizer from before retry loop to per-provider with body cloning, preventing Bedrock fields leaking to non-Bedrock providers P1 fixes: - Replace .map() side-effect pattern with idiomatic if-let (clippy) - Fix module alphabetical ordering in mod.rs - Add cache_ttl whitelist validation in set_optimizer_config - Remove #[allow(unused_assignments)] and dead budget decrement --------- Co-authored-by: Keith (via OpenClaw) <keithyt06@users.noreply.github.com> Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
@@ -3,7 +3,11 @@ import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { settingsApi, type RectifierConfig } from "@/lib/api/settings";
|
||||
import {
|
||||
settingsApi,
|
||||
type RectifierConfig,
|
||||
type OptimizerConfig,
|
||||
} from "@/lib/api/settings";
|
||||
|
||||
export function RectifierConfigPanel() {
|
||||
const { t } = useTranslation();
|
||||
@@ -12,6 +16,12 @@ export function RectifierConfigPanel() {
|
||||
requestThinkingSignature: true,
|
||||
requestThinkingBudget: true,
|
||||
});
|
||||
const [optimizerConfig, setOptimizerConfig] = useState<OptimizerConfig>({
|
||||
enabled: false,
|
||||
thinkingOptimizer: true,
|
||||
cacheInjection: true,
|
||||
cacheTtl: "1h",
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -20,6 +30,10 @@ export function RectifierConfigPanel() {
|
||||
.then(setConfig)
|
||||
.catch((e) => console.error("Failed to load rectifier config:", e))
|
||||
.finally(() => setIsLoading(false));
|
||||
settingsApi
|
||||
.getOptimizerConfig()
|
||||
.then(setOptimizerConfig)
|
||||
.catch((e) => console.error("Failed to load optimizer config:", e));
|
||||
}, []);
|
||||
|
||||
const handleChange = async (updates: Partial<RectifierConfig>) => {
|
||||
@@ -34,6 +48,18 @@ export function RectifierConfigPanel() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleOptimizerChange = async (updates: Partial<OptimizerConfig>) => {
|
||||
const newConfig = { ...optimizerConfig, ...updates };
|
||||
setOptimizerConfig(newConfig);
|
||||
try {
|
||||
await settingsApi.setOptimizerConfig(newConfig);
|
||||
} catch (e) {
|
||||
console.error("Failed to save optimizer config:", e);
|
||||
toast.error(String(e));
|
||||
setOptimizerConfig(optimizerConfig);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) return null;
|
||||
|
||||
return (
|
||||
@@ -86,6 +112,94 @@ export function RectifierConfigPanel() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-6 mt-6">
|
||||
<div className="space-y-1 mb-4">
|
||||
<h3 className="text-sm font-medium">
|
||||
{t("settings.advanced.optimizer.title")}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("settings.advanced.optimizer.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label>{t("settings.advanced.optimizer.enabled")}</Label>
|
||||
</div>
|
||||
<Switch
|
||||
checked={optimizerConfig.enabled}
|
||||
onCheckedChange={(checked) =>
|
||||
handleOptimizerChange({ enabled: checked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 pl-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label>
|
||||
{t("settings.advanced.optimizer.thinkingOptimizer")}
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t(
|
||||
"settings.advanced.optimizer.thinkingOptimizerDescription",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={optimizerConfig.thinkingOptimizer}
|
||||
disabled={!optimizerConfig.enabled}
|
||||
onCheckedChange={(checked) =>
|
||||
handleOptimizerChange({ thinkingOptimizer: checked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label>{t("settings.advanced.optimizer.cacheInjection")}</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("settings.advanced.optimizer.cacheInjectionDescription")}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={optimizerConfig.cacheInjection}
|
||||
disabled={!optimizerConfig.enabled}
|
||||
onCheckedChange={(checked) =>
|
||||
handleOptimizerChange({ cacheInjection: checked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{optimizerConfig.cacheInjection && (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label>{t("settings.advanced.optimizer.cacheTtl")}</Label>
|
||||
</div>
|
||||
<select
|
||||
className="h-9 rounded-md border border-input bg-background px-3 text-sm"
|
||||
value={optimizerConfig.cacheTtl}
|
||||
disabled={
|
||||
!optimizerConfig.enabled || !optimizerConfig.cacheInjection
|
||||
}
|
||||
onChange={(e) =>
|
||||
handleOptimizerChange({ cacheTtl: e.target.value })
|
||||
}
|
||||
>
|
||||
<option value="5m">
|
||||
{t("settings.advanced.optimizer.cacheTtl5m")}
|
||||
</option>
|
||||
<option value="1h">
|
||||
{t("settings.advanced.optimizer.cacheTtl1h")}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -257,6 +257,18 @@
|
||||
"thinkingBudget": "Thinking Budget Rectification",
|
||||
"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"
|
||||
},
|
||||
"optimizer": {
|
||||
"title": "Bedrock Request Optimizer",
|
||||
"description": "Automatically optimize Thinking and Cache configuration before sending requests (only applies to Bedrock providers)",
|
||||
"enabled": "Enable Optimizer",
|
||||
"thinkingOptimizer": "Thinking Optimization",
|
||||
"thinkingOptimizerDescription": "Automatically enable Adaptive Thinking for Opus/Sonnet, and inject Extended Thinking for legacy models",
|
||||
"cacheInjection": "Cache Injection",
|
||||
"cacheInjectionDescription": "Automatically inject cache breakpoints at key positions in requests to reduce duplicate token billing",
|
||||
"cacheTtl": "Cache TTL",
|
||||
"cacheTtl5m": "5 minutes",
|
||||
"cacheTtl1h": "1 hour"
|
||||
},
|
||||
"logConfig": {
|
||||
"title": "Log Management",
|
||||
"description": "Control log output level",
|
||||
|
||||
@@ -257,6 +257,18 @@
|
||||
"thinkingBudget": "Thinking Budget 整流",
|
||||
"thinkingBudgetDescription": "Anthropic タイプのプロバイダーが budget_tokens 制約エラー(例: 1024 以上)を返した場合、thinking を enabled に正規化し、thinking 予算を 32000 に設定し、必要に応じて max_tokens を 64000 に引き上げて 1 回リトライします"
|
||||
},
|
||||
"optimizer": {
|
||||
"title": "Bedrock リクエストオプティマイザー",
|
||||
"description": "リクエスト送信前に Thinking と Cache の設定を自動最適化(Bedrock プロバイダーのみ有効)",
|
||||
"enabled": "オプティマイザーを有効化",
|
||||
"thinkingOptimizer": "Thinking 最適化",
|
||||
"thinkingOptimizerDescription": "Opus/Sonnet に Adaptive Thinking を自動的に有効化し、レガシーモデルに Extended Thinking を注入",
|
||||
"cacheInjection": "Cache 注入",
|
||||
"cacheInjectionDescription": "リクエストの重要な位置に Cache ブレークポイントを自動注入し、重複トークンの課金を削減",
|
||||
"cacheTtl": "Cache TTL",
|
||||
"cacheTtl5m": "5 分",
|
||||
"cacheTtl1h": "1 時間"
|
||||
},
|
||||
"logConfig": {
|
||||
"title": "ログ管理",
|
||||
"description": "ログ出力レベルを制御",
|
||||
|
||||
@@ -257,6 +257,18 @@
|
||||
"thinkingBudget": "Thinking Budget 整流",
|
||||
"thinkingBudgetDescription": "当 Anthropic 类型供应商返回 budget_tokens 约束错误(如至少 1024)时,自动将 thinking 规范为 enabled 并将 budget 设为 32000,同时在需要时将 max_tokens 设为 64000,然后重试一次"
|
||||
},
|
||||
"optimizer": {
|
||||
"title": "Bedrock 请求优化器",
|
||||
"description": "在请求发送前自动优化 Thinking 和 Cache 配置(仅 Bedrock 供应商生效)",
|
||||
"enabled": "启用优化器",
|
||||
"thinkingOptimizer": "Thinking 优化",
|
||||
"thinkingOptimizerDescription": "自动为 Opus/Sonnet 启用 Adaptive Thinking,为旧模型注入 Extended Thinking",
|
||||
"cacheInjection": "Cache 注入",
|
||||
"cacheInjectionDescription": "自动在请求关键位置注入 Cache 断点,减少重复 token 计费",
|
||||
"cacheTtl": "Cache TTL",
|
||||
"cacheTtl5m": "5 分钟",
|
||||
"cacheTtl1h": "1 小时"
|
||||
},
|
||||
"logConfig": {
|
||||
"title": "日志管理",
|
||||
"description": "控制日志输出级别",
|
||||
|
||||
@@ -196,6 +196,14 @@ export const settingsApi = {
|
||||
return await invoke("set_rectifier_config", { config });
|
||||
},
|
||||
|
||||
async getOptimizerConfig(): Promise<OptimizerConfig> {
|
||||
return await invoke("get_optimizer_config");
|
||||
},
|
||||
|
||||
async setOptimizerConfig(config: OptimizerConfig): Promise<boolean> {
|
||||
return await invoke("set_optimizer_config", { config });
|
||||
},
|
||||
|
||||
async getLogConfig(): Promise<LogConfig> {
|
||||
return await invoke("get_log_config");
|
||||
},
|
||||
@@ -211,6 +219,13 @@ export interface RectifierConfig {
|
||||
requestThinkingBudget: boolean;
|
||||
}
|
||||
|
||||
export interface OptimizerConfig {
|
||||
enabled: boolean;
|
||||
thinkingOptimizer: boolean;
|
||||
cacheInjection: boolean;
|
||||
cacheTtl: string;
|
||||
}
|
||||
|
||||
export interface LogConfig {
|
||||
enabled: boolean;
|
||||
level: "error" | "warn" | "info" | "debug" | "trace";
|
||||
|
||||
Reference in New Issue
Block a user