Unverified 提交 2ea9500f authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

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

上级 52aebae9
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
"stream": true, "stream": true,
"thinking": { "thinking": {
"type": "enabled", "type": "enabled",
"include_thoughts": true "include_thoughts": true,
"budget_tokens": 4000
}, },
"dyad_options": { "dyad_options": {
"files": [ "files": [
......
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
"stream": true, "stream": true,
"thinking": { "thinking": {
"type": "enabled", "type": "enabled",
"include_thoughts": true "include_thoughts": true,
"budget_tokens": 4000
}, },
"dyad_options": { "dyad_options": {
"files": [ "files": [
......
...@@ -24,7 +24,8 @@ ...@@ -24,7 +24,8 @@
"stream": true, "stream": true,
"thinking": { "thinking": {
"type": "enabled", "type": "enabled",
"include_thoughts": true "include_thoughts": true,
"budget_tokens": 4000
}, },
"dyad_options": { "dyad_options": {
"files": [ "files": [
......
...@@ -32,7 +32,8 @@ ...@@ -32,7 +32,8 @@
"stream": true, "stream": true,
"thinking": { "thinking": {
"type": "enabled", "type": "enabled",
"include_thoughts": true "include_thoughts": true,
"budget_tokens": 4000
}, },
"dyad_options": { "dyad_options": {
"files": [ "files": [
......
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
"stream": true, "stream": true,
"thinking": { "thinking": {
"type": "enabled", "type": "enabled",
"include_thoughts": true "include_thoughts": true,
"budget_tokens": 4000
}, },
"dyad_options": { "dyad_options": {
"files": [ "files": [
......
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
"stream": true, "stream": true,
"thinking": { "thinking": {
"type": "enabled", "type": "enabled",
"include_thoughts": true "include_thoughts": true,
"budget_tokens": 4000
}, },
"dyad_options": { "dyad_options": {
"files": [ "files": [
......
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
"stream": true, "stream": true,
"thinking": { "thinking": {
"type": "enabled", "type": "enabled",
"include_thoughts": true "include_thoughts": true,
"budget_tokens": 4000
}, },
"dyad_options": { "dyad_options": {
"files": [ "files": [
......
{
"selectedModel": {
"name": "gemini-2.5-pro",
"provider": "google"
},
"providerSettings": {
"auto": {
"apiKey": {
"value": "testdyadkey",
"encryptionType": "plaintext"
}
}
},
"telemetryConsent": "unset",
"telemetryUserId": "[UUID]",
"hasRunBefore": true,
"enableDyadPro": true,
"experiments": {},
"lastShownReleaseNotesVersion": "[scrubbed]",
"thinkingBudget": "low",
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"isTestMode": true
}
\ No newline at end of file
{
"selectedModel": {
"name": "gemini-2.5-pro",
"provider": "google"
},
"providerSettings": {
"auto": {
"apiKey": {
"value": "testdyadkey",
"encryptionType": "plaintext"
}
}
},
"telemetryConsent": "unset",
"telemetryUserId": "[UUID]",
"hasRunBefore": true,
"enableDyadPro": true,
"experiments": {},
"lastShownReleaseNotesVersion": "[scrubbed]",
"thinkingBudget": "medium",
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"isTestMode": true
}
\ No newline at end of file
{
"selectedModel": {
"name": "gemini-2.5-pro",
"provider": "google"
},
"providerSettings": {
"auto": {
"apiKey": {
"value": "testdyadkey",
"encryptionType": "plaintext"
}
}
},
"telemetryConsent": "unset",
"telemetryUserId": "[UUID]",
"hasRunBefore": true,
"enableDyadPro": true,
"experiments": {},
"lastShownReleaseNotesVersion": "[scrubbed]",
"thinkingBudget": "high",
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"isTestMode": true
}
\ No newline at end of file
import { testSkipIfWindows } from "./helpers/test_helper";
testSkipIfWindows("thinking budget", async ({ po }) => {
await po.setUpDyadPro();
await po.selectModel({ provider: "Google", model: "Gemini 2.5 Pro" });
await po.sendPrompt("tc=1");
// Low
await po.goToSettingsTab();
await po.page.getByRole("combobox", { name: "Thinking Budget" }).click();
await po.page.getByRole("option", { name: "Low" }).click();
await po.snapshotSettings();
await po.page.getByText("Go Back").click();
await po.sendPrompt("[dump] hi");
await po.snapshotServerDump("request");
// Medium
await po.goToSettingsTab();
await po.page.getByRole("combobox", { name: "Thinking Budget" }).click();
await po.page.getByRole("option", { name: "Medium (default)" }).click();
await po.snapshotSettings();
await po.page.getByText("Go Back").click();
await po.sendPrompt("[dump] hi");
await po.snapshotServerDump("request");
// High
await po.goToSettingsTab();
await po.page.getByRole("combobox", { name: "Thinking Budget" }).click();
await po.page.getByRole("option", { name: "High" }).click();
await po.snapshotSettings();
await po.page.getByText("Go Back").click();
await po.sendPrompt("[dump] hi");
await po.snapshotServerDump("request");
});
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>
);
};
...@@ -464,6 +464,7 @@ This conversation includes one or more image attachments. When the user uploads ...@@ -464,6 +464,7 @@ This conversation includes one or more image attachments. When the user uploads
providerOptions: { providerOptions: {
"dyad-gateway": getExtraProviderOptions( "dyad-gateway": getExtraProviderOptions(
modelClient.builtinProviderId, modelClient.builtinProviderId,
settings,
), ),
google: { google: {
thinkingConfig: { thinkingConfig: {
......
...@@ -84,6 +84,7 @@ export async function getModelClient( ...@@ -84,6 +84,7 @@ export async function getModelClient(
: settings.enableProLazyEditsMode, : settings.enableProLazyEditsMode,
enableSmartFilesContext: settings.enableProSmartFilesContextMode, enableSmartFilesContext: settings.enableProSmartFilesContextMode,
}, },
settings,
}) })
: createOpenAICompatible({ : createOpenAICompatible({
name: "dyad-gateway", name: "dyad-gateway",
......
...@@ -12,6 +12,7 @@ import { ...@@ -12,6 +12,7 @@ import {
import { OpenAICompatibleChatSettings } from "@ai-sdk/openai-compatible"; import { OpenAICompatibleChatSettings } from "@ai-sdk/openai-compatible";
import log from "electron-log"; import log from "electron-log";
import { getExtraProviderOptions } from "./thinking_utils"; import { getExtraProviderOptions } from "./thinking_utils";
import type { UserSettings } from "../../lib/schemas";
const logger = log.scope("llm_engine_provider"); const logger = log.scope("llm_engine_provider");
...@@ -48,6 +49,7 @@ or to provide a custom fetch implementation for e.g. testing. ...@@ -48,6 +49,7 @@ or to provide a custom fetch implementation for e.g. testing.
enableLazyEdits?: boolean; enableLazyEdits?: boolean;
enableSmartFilesContext?: boolean; enableSmartFilesContext?: boolean;
}; };
settings: UserSettings;
} }
export interface DyadEngineProvider { export interface DyadEngineProvider {
...@@ -125,7 +127,10 @@ export function createDyadEngine( ...@@ -125,7 +127,10 @@ export function createDyadEngine(
// Parse the request body to manipulate it // Parse the request body to manipulate it
const parsedBody = { const parsedBody = {
...JSON.parse(init.body), ...JSON.parse(init.body),
...getExtraProviderOptions(options.originalProviderId), ...getExtraProviderOptions(
options.originalProviderId,
options.settings,
),
}; };
// Add files to the request if they exist // Add files to the request if they exist
......
import { PROVIDERS_THAT_SUPPORT_THINKING } from "../shared/language_model_helpers"; 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( export function getExtraProviderOptions(
providerId: string | undefined, providerId: string | undefined,
settings: UserSettings,
): Record<string, any> { ): Record<string, any> {
if (!providerId) { if (!providerId) {
return {}; return {};
} }
if (PROVIDERS_THAT_SUPPORT_THINKING.includes(providerId)) { if (PROVIDERS_THAT_SUPPORT_THINKING.includes(providerId)) {
const budgetTokens = getThinkingBudgetTokens(settings?.thinkingBudget);
return { return {
thinking: { thinking: {
type: "enabled", type: "enabled",
include_thoughts: true, include_thoughts: true,
// -1 means dynamic thinking where model determines.
// budget_tokens: 128, // minimum for Gemini Pro is 128
budget_tokens: budgetTokens,
}, },
}; };
} }
......
...@@ -142,6 +142,7 @@ export const UserSettingsSchema = z.object({ ...@@ -142,6 +142,7 @@ export const UserSettingsSchema = z.object({
experiments: ExperimentsSchema.optional(), experiments: ExperimentsSchema.optional(),
lastShownReleaseNotesVersion: z.string().optional(), lastShownReleaseNotesVersion: z.string().optional(),
maxChatTurnsInContext: z.number().optional(), maxChatTurnsInContext: z.number().optional(),
thinkingBudget: z.enum(["low", "medium", "high"]).optional(),
enableProLazyEditsMode: z.boolean().optional(), enableProLazyEditsMode: z.boolean().optional(),
enableProSmartFilesContextMode: z.boolean().optional(), enableProSmartFilesContextMode: z.boolean().optional(),
selectedTemplateId: z.string().optional(), selectedTemplateId: z.string().optional(),
......
...@@ -7,6 +7,7 @@ import { showSuccess, showError } from "@/lib/toast"; ...@@ -7,6 +7,7 @@ import { showSuccess, showError } from "@/lib/toast";
import { AutoApproveSwitch } from "@/components/AutoApproveSwitch"; import { AutoApproveSwitch } from "@/components/AutoApproveSwitch";
import { TelemetrySwitch } from "@/components/TelemetrySwitch"; import { TelemetrySwitch } from "@/components/TelemetrySwitch";
import { MaxChatTurnsSelector } from "@/components/MaxChatTurnsSelector"; import { MaxChatTurnsSelector } from "@/components/MaxChatTurnsSelector";
import { ThinkingBudgetSelector } from "@/components/ThinkingBudgetSelector";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { useAppVersion } from "@/hooks/useAppVersion"; import { useAppVersion } from "@/hooks/useAppVersion";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
...@@ -138,6 +139,10 @@ export default function SettingsPage() { ...@@ -138,6 +139,10 @@ export default function SettingsPage() {
</div> </div>
</div> </div>
<div className="mt-4">
<ThinkingBudgetSelector />
</div>
<div className="mt-4"> <div className="mt-4">
<MaxChatTurnsSelector /> <MaxChatTurnsSelector />
</div> </div>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论