Configurable thinking budget (default to medium) (#494)

This commit is contained in:
Will Chen
2025-06-25 15:36:05 -07:00
committed by GitHub
parent 52aebae903
commit 2ea9500f73
21 changed files with 1417 additions and 8 deletions

View File

@@ -0,0 +1,80 @@
import React from "react";
import { useSettings } from "@/hooks/useSettings";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
interface OptionInfo {
value: string;
label: string;
description: string;
}
const defaultValue = "medium";
const options: OptionInfo[] = [
{
value: "low",
label: "Low",
description:
"Minimal thinking tokens for faster responses and lower costs.",
},
{
value: defaultValue,
label: "Medium (default)",
description: "Balanced thinking for most conversations.",
},
{
value: "high",
label: "High",
description:
"Extended thinking for complex problems requiring deep analysis.",
},
];
export const ThinkingBudgetSelector: React.FC = () => {
const { settings, updateSettings } = useSettings();
const handleValueChange = (value: string) => {
updateSettings({ thinkingBudget: value as "low" | "medium" | "high" });
};
// Determine the current value
const currentValue = settings?.thinkingBudget || defaultValue;
// Find the current option to display its description
const currentOption =
options.find((opt) => opt.value === currentValue) || options[1];
return (
<div className="space-y-1">
<div className="flex items-center gap-4">
<label
htmlFor="thinking-budget"
className="text-sm font-medium text-gray-700 dark:text-gray-300"
>
Thinking Budget
</label>
<Select value={currentValue} onValueChange={handleValueChange}>
<SelectTrigger className="w-[180px]" id="thinking-budget">
<SelectValue placeholder="Select budget" />
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{currentOption.description}
</div>
</div>
);
};

View File

@@ -464,6 +464,7 @@ This conversation includes one or more image attachments. When the user uploads
providerOptions: {
"dyad-gateway": getExtraProviderOptions(
modelClient.builtinProviderId,
settings,
),
google: {
thinkingConfig: {

View File

@@ -84,6 +84,7 @@ export async function getModelClient(
: settings.enableProLazyEditsMode,
enableSmartFilesContext: settings.enableProSmartFilesContextMode,
},
settings,
})
: createOpenAICompatible({
name: "dyad-gateway",

View File

@@ -12,6 +12,7 @@ import {
import { OpenAICompatibleChatSettings } from "@ai-sdk/openai-compatible";
import log from "electron-log";
import { getExtraProviderOptions } from "./thinking_utils";
import type { UserSettings } from "../../lib/schemas";
const logger = log.scope("llm_engine_provider");
@@ -48,6 +49,7 @@ or to provide a custom fetch implementation for e.g. testing.
enableLazyEdits?: boolean;
enableSmartFilesContext?: boolean;
};
settings: UserSettings;
}
export interface DyadEngineProvider {
@@ -125,7 +127,10 @@ export function createDyadEngine(
// Parse the request body to manipulate it
const parsedBody = {
...JSON.parse(init.body),
...getExtraProviderOptions(options.originalProviderId),
...getExtraProviderOptions(
options.originalProviderId,
options.settings,
),
};
// Add files to the request if they exist

View File

@@ -1,16 +1,37 @@
import { PROVIDERS_THAT_SUPPORT_THINKING } from "../shared/language_model_helpers";
import type { UserSettings } from "../../lib/schemas";
function getThinkingBudgetTokens(
thinkingBudget?: "low" | "medium" | "high",
): number {
switch (thinkingBudget) {
case "low":
return 1_000;
case "medium":
return 4_000;
case "high":
return -1;
default:
return 4_000; // Default to medium
}
}
export function getExtraProviderOptions(
providerId: string | undefined,
settings: UserSettings,
): Record<string, any> {
if (!providerId) {
return {};
}
if (PROVIDERS_THAT_SUPPORT_THINKING.includes(providerId)) {
const budgetTokens = getThinkingBudgetTokens(settings?.thinkingBudget);
return {
thinking: {
type: "enabled",
include_thoughts: true,
// -1 means dynamic thinking where model determines.
// budget_tokens: 128, // minimum for Gemini Pro is 128
budget_tokens: budgetTokens,
},
};
}

View File

@@ -142,6 +142,7 @@ export const UserSettingsSchema = z.object({
experiments: ExperimentsSchema.optional(),
lastShownReleaseNotesVersion: z.string().optional(),
maxChatTurnsInContext: z.number().optional(),
thinkingBudget: z.enum(["low", "medium", "high"]).optional(),
enableProLazyEditsMode: z.boolean().optional(),
enableProSmartFilesContextMode: z.boolean().optional(),
selectedTemplateId: z.string().optional(),

View File

@@ -7,6 +7,7 @@ import { showSuccess, showError } from "@/lib/toast";
import { AutoApproveSwitch } from "@/components/AutoApproveSwitch";
import { TelemetrySwitch } from "@/components/TelemetrySwitch";
import { MaxChatTurnsSelector } from "@/components/MaxChatTurnsSelector";
import { ThinkingBudgetSelector } from "@/components/ThinkingBudgetSelector";
import { useSettings } from "@/hooks/useSettings";
import { useAppVersion } from "@/hooks/useAppVersion";
import { Button } from "@/components/ui/button";
@@ -138,6 +139,10 @@ export default function SettingsPage() {
</div>
</div>
<div className="mt-4">
<ThinkingBudgetSelector />
</div>
<div className="mt-4">
<MaxChatTurnsSelector />
</div>