Expose per-combo reasoning token buffer toggle (#6702)

* fix(combos): default reasoning token buffer off

* feat(combos): expose reasoning token buffer toggle

* fix(combos): keep reasoning-token buffer default enabled, opt-out toggle

#6702 shipped bundled with #6536's own commit (identical SHA
37ac38f17f), flipping the combo-wide reasoningTokenBufferEnabled
default from true to false. #6536 was subsequently closed by the
author in favor of #6714, which explicitly keeps the existing
default-enabled buffer behavior and instead clamps the buffer to the
model's known output cap. Reconciled #6702 with that resolution:
dropped the default-flip changes across comboConfig.ts, combo.ts,
comboSetup.ts, ComboDefaultsTab.tsx, and the combo-defaults settings
route (plus their test assertions), and inverted the new per-combo
ReasoningTokenBufferToggle to opt-out semantics (`!== false`) so an
existing combo's behavior is unchanged unless the operator explicitly
unchecks it.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
Xiangzhe
2026-07-10 03:49:20 +08:00
committed by GitHub
parent abfced8b28
commit edae0cf33f
6 changed files with 72 additions and 21 deletions

View File

@@ -11,6 +11,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral
### ✨ New Features
- **Provider/model param filters**: config-driven parameter denylist/allowlist per provider/model with auto-learn from upstream 400s (#6649 — thanks @ThongAccount, closes #6625)
- **Per-combo reasoning token buffer toggle**: the combo builder now exposes an explicit checkbox for the `#3587` reasoning-model `max_tokens` buffer, defaulting to the existing enabled behavior, so a combo can opt out without hand-editing raw JSON config (#6702 — thanks @xz-dev)
### 🐛 Bug Fixes

View File

@@ -3032,7 +3032,10 @@ async function handleRoundRobinCombo({
resilienceSettings.providerCooldown.enabled &&
provider &&
provider !== "unknown" &&
!(result.status === 500 && hasPerModelQuota(provider, parseModel(modelStr).model || modelStr))
!(
result.status === 500 &&
hasPerModelQuota(provider, parseModel(modelStr).model || modelStr)
)
) {
recordProviderCooldown(
provider,

View File

@@ -90,9 +90,7 @@ export function phaseComboSetup(ctx: ComboContext): ComboSetup {
const universalHandoffConfig = resolveUniversalHandoffConfig(
(combo.universal_handoff || combo.universalHandoff) as
| Record<string, unknown>
| null
| undefined,
Record<string, unknown> | null | undefined,
relayOptions?.universalHandoffConfig as Record<string, unknown> | null | undefined
);

View File

@@ -0,0 +1,52 @@
import Tooltip from "@/shared/components/Tooltip";
type TranslationFn = {
(key: string): string;
has?: (key: string) => boolean;
};
type Props = {
config: Record<string, any>;
setConfig: (config: Record<string, any>) => void;
t: TranslationFn;
};
function getI18nOrFallback(t: TranslationFn, key: string, fallback: string): string {
try {
if (typeof t.has === "function" && t.has(key)) return t(key);
} catch {}
return fallback;
}
export default function ReasoningTokenBufferToggle({ config, setConfig, t }: Props) {
return (
<div className="flex items-center gap-2 py-1">
<input
type="checkbox"
id="reasoningTokenBufferEnabled"
data-testid="combo-reasoning-token-buffer-enabled"
checked={config.reasoningTokenBufferEnabled !== false}
onChange={(e) => setConfig({ ...config, reasoningTokenBufferEnabled: e.target.checked })}
className="w-3.5 h-3.5 rounded border border-black/20 dark:border-white/20 accent-primary cursor-pointer"
/>
<label
htmlFor="reasoningTokenBufferEnabled"
className="text-xs text-text-muted cursor-pointer select-none"
>
{getI18nOrFallback(t, "reasoningTokenBuffer", "Reasoning token buffer")}
</label>
<Tooltip
position="bottom"
content={getI18nOrFallback(
t,
"advancedHelp.reasoningTokenBuffer",
"When enabled (default), OmniRoute may increase max_tokens for reasoning-capable models so they have headroom to think. Turn this off if you need this combo to preserve the client's exact max_tokens."
)}
>
<span className="material-symbols-outlined text-[12px] text-text-muted cursor-help">
help
</span>
</Tooltip>
</div>
);
}

View File

@@ -15,6 +15,7 @@ import Tooltip from "@/shared/components/Tooltip";
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
import { FieldLabelWithHelp, WeightTotalBar } from "./parts";
import { ResponseValidationEditor, type ResponseValidationValue } from "./ResponseValidationEditor";
import ReasoningTokenBufferToggle from "./ReasoningTokenBufferToggle";
import { pickDisplayValue } from "@/shared/utils/maskEmail";
import useEmailPrivacyStore from "@/store/emailPrivacyStore";
import { useNotificationStore } from "@/store/notificationStore";
@@ -217,10 +218,6 @@ function sanitizeComboRuntimeConfig(config) {
);
}
// Build the next combo config when a Fusion tuning field changes. Prunes empty /
// non-finite entries and drops the whole `fusionTuning` object when no field is
// set, so an empty `{}` is never persisted (sanitizeComboRuntimeConfig keeps any
// non-null object as-is).
function updateFusionTuning(config, field, rawValue) {
const value = rawValue === "" ? undefined : Number(rawValue);
const next = { ...(config.fusionTuning || {}), [field]: value };
@@ -522,9 +519,7 @@ function getStrategyBadgeClass(strategy) {
function getI18nOrFallback(t, key, fallback) {
try {
if (typeof t.has === "function" && t.has(key)) return t(key);
} catch {
// Some translations require ICU variables; fallback keeps optional helper text safe.
}
} catch {}
return fallback;
}
@@ -1023,7 +1018,6 @@ export default function CombosPage() {
return (
<div className="flex flex-col gap-6">
{/* Header */}
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div>
<h1 className="text-2xl font-semibold">{t("title")}</h1>
@@ -2063,7 +2057,6 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
}
}, [builderStage, comboBuilderStages]);
// DnD state
const hasPricingForModel = useCallback(
(modelValue) => {
const parsed = parseQualifiedModel(modelValue);
@@ -2752,9 +2745,7 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
saveData.description = null;
}
// Include config only if any values are set
const configToSave = sanitizeComboRuntimeConfig(config);
// Add round-robin specific fields to config
if (strategy === "round-robin") {
if (config.concurrencyPerModel !== undefined)
configToSave.concurrencyPerModel = config.concurrencyPerModel;
@@ -3740,7 +3731,6 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
/>
</div>
</div>
{/* failoverBeforeRetry + maxSetRetries + setRetryDelayMs */}
<div className="grid grid-cols-2 gap-2 pt-2 border-t border-black/5 dark:border-white/5">
<div className="col-span-2">
<div className="flex items-center gap-2 py-1">
@@ -3776,6 +3766,9 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
</Tooltip>
</div>
</div>
<div className="col-span-2">
<ReasoningTokenBufferToggle config={config} setConfig={setConfig} t={t} />
</div>
<div>
<FieldLabelWithHelp
label={t("maxSetRetries")}
@@ -4166,7 +4159,11 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
</div>
<div>
<FieldLabelWithHelp
label={getI18nOrFallback(t, "fusionStragglerGraceMs", "Straggler grace (ms)")}
label={getI18nOrFallback(
t,
"fusionStragglerGraceMs",
"Straggler grace (ms)"
)}
help={getI18nOrFallback(
t,
"fusionStragglerGraceMsHelp",
@@ -4181,7 +4178,9 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
value={config.fusionTuning?.stragglerGraceMs ?? ""}
placeholder="8000"
onChange={(e) =>
setConfig(updateFusionTuning(config, "stragglerGraceMs", e.target.value))
setConfig(
updateFusionTuning(config, "stragglerGraceMs", e.target.value)
)
}
className="w-full text-xs py-1.5 px-2 rounded border border-black/10 dark:border-white/10 bg-transparent focus:border-primary focus:outline-none"
/>
@@ -4580,7 +4579,6 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
</div>
)}
{/* Actions */}
{isExpertMode ? (
<div className="flex gap-2 pt-1">
<Button onClick={onClose} variant="ghost" fullWidth size="sm">

View File

@@ -292,8 +292,7 @@ export default function ComboDefaultsTab() {
// Filtered provider list — excludes already-added ones, filtered by search query
const filteredProviders = availableProviders.filter(
(p) =>
!providerOverrides[p.provider] && matchesSearch(p.provider, searchQuery)
(p) => !providerOverrides[p.provider] && matchesSearch(p.provider, searchQuery)
);
const handleDropdownKeyDown = (e: React.KeyboardEvent) => {