From bc9773ff9aab32c06220204757766aa2150d302a Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 7 Feb 2025 23:52:30 -0500 Subject: [PATCH 01/12] Configure per-configuration temperature --- src/api/providers/anthropic.ts | 6 +- src/api/providers/bedrock.ts | 4 +- src/api/providers/gemini.ts | 4 +- src/api/providers/glama.ts | 4 +- src/api/providers/lmstudio.ts | 4 +- src/api/providers/mistral.ts | 2 +- src/api/providers/ollama.ts | 12 +-- src/api/providers/openai-native.ts | 4 +- src/api/providers/openai.ts | 2 +- src/api/providers/openrouter.ts | 11 ++- src/api/providers/unbound.ts | 4 +- src/api/providers/vertex.ts | 4 +- src/core/webview/ClineProvider.ts | 6 ++ src/shared/api.ts | 1 + .../src/components/settings/ApiOptions.tsx | 13 ++++ .../settings/TemperatureControl.tsx | 73 +++++++++++++++++++ 16 files changed, 124 insertions(+), 30 deletions(-) create mode 100644 webview-ui/src/components/settings/TemperatureControl.tsx diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index e65b82ddef..2059804a54 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -44,7 +44,7 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { { model: modelId, max_tokens: this.getModel().info.maxTokens || 8192, - temperature: 0, + temperature: this.options.modelTemperature ?? 0, system: [{ text: systemPrompt, type: "text", cache_control: { type: "ephemeral" } }], // setting cache breakpoint for system prompt so new tasks can reuse it messages: messages.map((message, index) => { if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) { @@ -96,7 +96,7 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { stream = (await this.client.messages.create({ model: modelId, max_tokens: this.getModel().info.maxTokens || 8192, - temperature: 0, + temperature: this.options.modelTemperature ?? 0, system: [{ text: systemPrompt, type: "text" }], messages, // tools, @@ -179,7 +179,7 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { const response = await this.client.messages.create({ model: this.getModel().id, max_tokens: this.getModel().info.maxTokens || 8192, - temperature: 0, + temperature: this.options.modelTemperature ?? 0, messages: [{ role: "user", content: prompt }], stream: false, }) diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts index 0e90c2bcc4..17362e1f05 100644 --- a/src/api/providers/bedrock.ts +++ b/src/api/providers/bedrock.ts @@ -104,7 +104,7 @@ export class AwsBedrockHandler implements ApiHandler, SingleCompletionHandler { system: [{ text: systemPrompt }], inferenceConfig: { maxTokens: modelConfig.info.maxTokens || 5000, - temperature: 0.3, + temperature: this.options.modelTemperature ?? 0.3, topP: 0.1, ...(this.options.awsUsePromptCache ? { @@ -262,7 +262,7 @@ export class AwsBedrockHandler implements ApiHandler, SingleCompletionHandler { ]), inferenceConfig: { maxTokens: modelConfig.info.maxTokens || 5000, - temperature: 0.3, + temperature: this.options.modelTemperature ?? 0.3, topP: 0.1, }, } diff --git a/src/api/providers/gemini.ts b/src/api/providers/gemini.ts index 0577a021e6..e9a0015224 100644 --- a/src/api/providers/gemini.ts +++ b/src/api/providers/gemini.ts @@ -23,7 +23,7 @@ export class GeminiHandler implements ApiHandler, SingleCompletionHandler { contents: messages.map(convertAnthropicMessageToGemini), generationConfig: { // maxOutputTokens: this.getModel().info.maxTokens, - temperature: 0, + temperature: this.options.modelTemperature ?? 0, }, }) @@ -60,7 +60,7 @@ export class GeminiHandler implements ApiHandler, SingleCompletionHandler { const result = await model.generateContent({ contents: [{ role: "user", parts: [{ text: prompt }] }], generationConfig: { - temperature: 0, + temperature: this.options.modelTemperature ?? 0, }, }) diff --git a/src/api/providers/glama.ts b/src/api/providers/glama.ts index 95b806f27c..226891b16a 100644 --- a/src/api/providers/glama.ts +++ b/src/api/providers/glama.ts @@ -79,7 +79,7 @@ export class GlamaHandler implements ApiHandler, SingleCompletionHandler { } if (this.supportsTemperature()) { - requestOptions.temperature = 0 + requestOptions.temperature = this.options.modelTemperature ?? 0 } const { data: completion, response } = await this.client.chat.completions @@ -172,7 +172,7 @@ export class GlamaHandler implements ApiHandler, SingleCompletionHandler { } if (this.supportsTemperature()) { - requestOptions.temperature = 0 + requestOptions.temperature = this.options.modelTemperature ?? 0 } if (this.getModel().id.startsWith("anthropic/")) { diff --git a/src/api/providers/lmstudio.ts b/src/api/providers/lmstudio.ts index 81cec81b4d..cc164d240e 100644 --- a/src/api/providers/lmstudio.ts +++ b/src/api/providers/lmstudio.ts @@ -27,7 +27,7 @@ export class LmStudioHandler implements ApiHandler, SingleCompletionHandler { const stream = await this.client.chat.completions.create({ model: this.getModel().id, messages: openAiMessages, - temperature: 0, + temperature: this.options.modelTemperature ?? 0, stream: true, }) for await (const chunk of stream) { @@ -59,7 +59,7 @@ export class LmStudioHandler implements ApiHandler, SingleCompletionHandler { const response = await this.client.chat.completions.create({ model: this.getModel().id, messages: [{ role: "user", content: prompt }], - temperature: 0, + temperature: this.options.modelTemperature ?? 0, stream: false, }) return response.choices[0]?.message.content || "" diff --git a/src/api/providers/mistral.ts b/src/api/providers/mistral.ts index c4377f0003..4bcf1a191c 100644 --- a/src/api/providers/mistral.ts +++ b/src/api/providers/mistral.ts @@ -30,7 +30,7 @@ export class MistralHandler implements ApiHandler { const stream = await this.client.chat.stream({ model: this.getModel().id, // max_completion_tokens: this.getModel().info.maxTokens, - temperature: 0, + temperature: this.options.modelTemperature ?? 0, messages: [{ role: "system", content: systemPrompt }, ...convertToMistralMessages(messages)], stream: true, }) diff --git a/src/api/providers/ollama.ts b/src/api/providers/ollama.ts index 4175b78fa5..44e0d3f4af 100644 --- a/src/api/providers/ollama.ts +++ b/src/api/providers/ollama.ts @@ -20,7 +20,7 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler { async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { const modelId = this.getModel().id - const useR1Format = modelId.toLowerCase().includes('deepseek-r1') + const useR1Format = modelId.toLowerCase().includes("deepseek-r1") const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ { role: "system", content: systemPrompt }, ...(useR1Format ? convertToR1Format(messages) : convertToOpenAiMessages(messages)), @@ -29,7 +29,7 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler { const stream = await this.client.chat.completions.create({ model: this.getModel().id, messages: openAiMessages, - temperature: 0, + temperature: this.options.modelTemperature ?? 0, stream: true, }) for await (const chunk of stream) { @@ -53,11 +53,13 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler { async completePrompt(prompt: string): Promise { try { const modelId = this.getModel().id - const useR1Format = modelId.toLowerCase().includes('deepseek-r1') + const useR1Format = modelId.toLowerCase().includes("deepseek-r1") const response = await this.client.chat.completions.create({ model: this.getModel().id, - messages: useR1Format ? convertToR1Format([{ role: "user", content: prompt }]) : [{ role: "user", content: prompt }], - temperature: 0, + messages: useR1Format + ? convertToR1Format([{ role: "user", content: prompt }]) + : [{ role: "user", content: prompt }], + temperature: this.options.modelTemperature ?? 0, stream: false, }) return response.choices[0]?.message.content || "" diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts index e4883b7a98..a40e002ce1 100644 --- a/src/api/providers/openai-native.ts +++ b/src/api/providers/openai-native.ts @@ -88,7 +88,7 @@ export class OpenAiNativeHandler implements ApiHandler, SingleCompletionHandler ): ApiStream { const stream = await this.client.chat.completions.create({ model: modelId, - temperature: 0, + temperature: this.options.modelTemperature ?? 0, messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], stream: true, stream_options: { include_usage: true }, @@ -189,7 +189,7 @@ export class OpenAiNativeHandler implements ApiHandler, SingleCompletionHandler return { model: modelId, messages: [{ role: "user", content: prompt }], - temperature: 0, + temperature: this.options.modelTemperature ?? 0, } } } diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 408f4e5cc3..da3cf1b9e7 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -57,7 +57,7 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { } const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelId, - temperature: 0, + temperature: this.options.modelTemperature ?? (deepseekReasoner ? 0.6 : 0), messages: deepseekReasoner ? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) : [systemMessage, ...convertToOpenAiMessages(messages)], diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 0e23c5d35d..fa1c65d126 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -115,7 +115,7 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler { break } - let temperature = 0 + let defaultTemperature = 0 let topP: number | undefined = undefined // Handle models based on deepseek-r1 @@ -124,9 +124,8 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler { this.getModel().id === "perplexity/sonar-reasoning" ) { // Recommended temperature for DeepSeek reasoning models - temperature = 0.6 - // DeepSeek highly recommends using user instead of system - // role + defaultTemperature = 0.6 + // DeepSeek highly recommends using user instead of system role openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) // Some provider support topP and 0.95 is value that Deepseek used in their benchmarks topP = 0.95 @@ -137,7 +136,7 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler { const stream = await this.client.chat.completions.create({ model: this.getModel().id, max_tokens: maxTokens, - temperature: temperature, + temperature: this.options.modelTemperature ?? defaultTemperature, top_p: topP, messages: openAiMessages, stream: true, @@ -224,7 +223,7 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler { const response = await this.client.chat.completions.create({ model: this.getModel().id, messages: [{ role: "user", content: prompt }], - temperature: 0, + temperature: this.options.modelTemperature ?? 0, stream: false, }) diff --git a/src/api/providers/unbound.ts b/src/api/providers/unbound.ts index 305bd282ad..809ae21889 100644 --- a/src/api/providers/unbound.ts +++ b/src/api/providers/unbound.ts @@ -79,7 +79,7 @@ export class UnboundHandler implements ApiHandler, SingleCompletionHandler { { model: this.getModel().id.split("/")[1], max_tokens: maxTokens, - temperature: 0, + temperature: this.options.modelTemperature ?? 0, messages: openAiMessages, stream: true, }, @@ -146,7 +146,7 @@ export class UnboundHandler implements ApiHandler, SingleCompletionHandler { const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { model: this.getModel().id.split("/")[1], messages: [{ role: "user", content: prompt }], - temperature: 0, + temperature: this.options.modelTemperature ?? 0, } if (this.getModel().id.startsWith("anthropic/")) { diff --git a/src/api/providers/vertex.ts b/src/api/providers/vertex.ts index 1ea68eaa4e..0ee22e5893 100644 --- a/src/api/providers/vertex.ts +++ b/src/api/providers/vertex.ts @@ -22,7 +22,7 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler { const stream = await this.client.messages.create({ model: this.getModel().id, max_tokens: this.getModel().info.maxTokens || 8192, - temperature: 0, + temperature: this.options.modelTemperature ?? 0, system: systemPrompt, messages, stream: true, @@ -89,7 +89,7 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler { const response = await this.client.messages.create({ model: this.getModel().id, max_tokens: this.getModel().info.maxTokens || 8192, - temperature: 0, + temperature: this.options.modelTemperature ?? 0, messages: [{ role: "user", content: prompt }], stream: false, }) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 23346d945c..221c89a9ad 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -119,6 +119,7 @@ type GlobalStateKey = | "autoApprovalEnabled" | "customModes" // Array of custom modes | "unboundModelId" + | "modelTemperature" export const GlobalFileNames = { apiConversationHistory: "api_conversation_history.json", @@ -1538,6 +1539,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { mistralApiKey, unboundApiKey, unboundModelId, + modelTemperature, } = apiConfiguration await this.updateGlobalState("apiProvider", apiProvider) await this.updateGlobalState("apiModelId", apiModelId) @@ -1578,6 +1580,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.storeSecret("mistralApiKey", mistralApiKey) await this.storeSecret("unboundApiKey", unboundApiKey) await this.updateGlobalState("unboundModelId", unboundModelId) + await this.updateGlobalState("modelTemperature", modelTemperature) if (this.cline) { this.cline.api = buildApiHandler(apiConfiguration) } @@ -2254,6 +2257,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { experiments, unboundApiKey, unboundModelId, + modelTemperature, ] = await Promise.all([ this.getGlobalState("apiProvider") as Promise, this.getGlobalState("apiModelId") as Promise, @@ -2328,6 +2332,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getGlobalState("experiments") as Promise | undefined>, this.getSecret("unboundApiKey") as Promise, this.getGlobalState("unboundModelId") as Promise, + this.getGlobalState("modelTemperature") as Promise, ]) let apiProvider: ApiProvider @@ -2385,6 +2390,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { vsCodeLmModelSelector, unboundApiKey, unboundModelId, + modelTemperature, }, lastShownAnnouncementId, customInstructions, diff --git a/src/shared/api.ts b/src/shared/api.ts index 39bc2a69ca..c4f62259a7 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -60,6 +60,7 @@ export interface ApiHandlerOptions { includeMaxTokens?: boolean unboundApiKey?: string unboundModelId?: string + modelTemperature?: number } export type ApiConfiguration = ApiHandlerOptions & { diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 4f9c8e1b78..7e51347c0b 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -2,6 +2,7 @@ import { memo, useCallback, useEffect, useMemo, useState } from "react" import { useEvent, useInterval } from "react-use" import { Checkbox, Dropdown, Pane, type DropdownOption } from "vscrui" import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" +import { TemperatureControl } from "./TemperatureControl" import * as vscodemodels from "vscode" import { @@ -1361,6 +1362,18 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = )} +
+ { + handleInputChange("modelTemperature")({ + target: { value }, + }) + }} + maxValue={2} + /> +
+ {modelIdErrorMessage && (

void + maxValue?: number // Some providers like OpenAI use 0-2 range +} + +export const TemperatureControl = ({ value, onChange, maxValue = 1 }: TemperatureControlProps) => { + const [isCustomTemperature, setIsCustomTemperature] = useState(value !== undefined) + + // Sync internal state with prop changes when switching profiles + useEffect(() => { + setIsCustomTemperature(value !== undefined) + }, [value]) + + return ( +

+ { + setIsCustomTemperature(checked) + if (!checked) { + onChange(undefined) // Reset to provider default + } else { + onChange(0) // Set initial value when enabling + } + }}> + Use custom temperature + + + {isCustomTemperature && ( + <> + + { + const newValue = parseFloat(e.target.value) + onChange(isNaN(newValue) ? undefined : newValue) + }} + style={{ + flexGrow: 1, + accentColor: "var(--vscode-button-background)", + height: "2px", + }} + /> + + {value?.toFixed(2)} + + + )} +
+ ) +} From ce858aa34e221a8d41dbb504d21d6aa655bdc926 Mon Sep 17 00:00:00 2001 From: Joe Manley Date: Fri, 7 Feb 2025 21:22:18 -0800 Subject: [PATCH 02/12] Update src/api/providers/ollama.ts Suggestion from Matt Co-authored-by: Matt Rubens --- src/api/providers/ollama.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/providers/ollama.ts b/src/api/providers/ollama.ts index 44e0d3f4af..b6b11ccc07 100644 --- a/src/api/providers/ollama.ts +++ b/src/api/providers/ollama.ts @@ -59,7 +59,7 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler { messages: useR1Format ? convertToR1Format([{ role: "user", content: prompt }]) : [{ role: "user", content: prompt }], - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? (useR1Format ? 0.6 : 0), stream: false, }) return response.choices[0]?.message.content || "" From ce8cdd8daccec7c5235ca81709eb649faa0bf0f6 Mon Sep 17 00:00:00 2001 From: Joe Manley Date: Fri, 7 Feb 2025 21:49:27 -0800 Subject: [PATCH 03/12] Better UX similar to our experimental diffs --- .../settings/TemperatureControl.tsx | 84 +++++++++---------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/webview-ui/src/components/settings/TemperatureControl.tsx b/webview-ui/src/components/settings/TemperatureControl.tsx index ba590ec70e..21b4f5d669 100644 --- a/webview-ui/src/components/settings/TemperatureControl.tsx +++ b/webview-ui/src/components/settings/TemperatureControl.tsx @@ -1,4 +1,4 @@ -import { Checkbox } from "vscrui" +import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" import { useEffect, useState } from "react" interface TemperatureControlProps { @@ -16,57 +16,53 @@ export const TemperatureControl = ({ value, onChange, maxValue = 1 }: Temperatur }, [value]) return ( -
- + { - setIsCustomTemperature(checked) - if (!checked) { + onChange={(e: any) => { + setIsCustomTemperature(e.target.checked) + if (!e.target.checked) { onChange(undefined) // Reset to provider default } else { onChange(0) // Set initial value when enabling } }}> - Use custom temperature - + Use custom temperature + + +

+ Controls randomness in the model's responses. Higher values make output more random, lower values make + it more deterministic. +

{isCustomTemperature && ( - <> - - { - const newValue = parseFloat(e.target.value) - onChange(isNaN(newValue) ? undefined : newValue) - }} - style={{ - flexGrow: 1, - accentColor: "var(--vscode-button-background)", - height: "2px", - }} - /> - - {value?.toFixed(2)} - - +
+
+ { + const newValue = parseFloat(e.target.value) + onChange(isNaN(newValue) ? undefined : newValue) + }} + style={{ + flexGrow: 1, + accentColor: "var(--vscode-button-background)", + height: "2px", + }} + /> + {value?.toFixed(2)} +
+
)}
) From dd8214897b9c88878c8768f547de8e70d5cfa898 Mon Sep 17 00:00:00 2001 From: Joe Manley Date: Fri, 7 Feb 2025 22:49:57 -0800 Subject: [PATCH 04/12] Fix bug --- .../src/components/settings/TemperatureControl.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/webview-ui/src/components/settings/TemperatureControl.tsx b/webview-ui/src/components/settings/TemperatureControl.tsx index 21b4f5d669..321f25f1b7 100644 --- a/webview-ui/src/components/settings/TemperatureControl.tsx +++ b/webview-ui/src/components/settings/TemperatureControl.tsx @@ -12,7 +12,8 @@ export const TemperatureControl = ({ value, onChange, maxValue = 1 }: Temperatur // Sync internal state with prop changes when switching profiles useEffect(() => { - setIsCustomTemperature(value !== undefined) + const hasCustomTemperature = value !== undefined + setIsCustomTemperature(hasCustomTemperature) }, [value]) return ( @@ -26,11 +27,12 @@ export const TemperatureControl = ({ value, onChange, maxValue = 1 }: Temperatur { - setIsCustomTemperature(e.target.checked) - if (!e.target.checked) { - onChange(undefined) // Reset to provider default - } else { - onChange(0) // Set initial value when enabling + const isChecked = e.target.checked + setIsCustomTemperature(isChecked) + if (!isChecked) { + onChange(undefined) // Unset the temperature + } else if (value !== undefined) { + onChange(value) // Use the value from apiConfiguration, if set } }}> Use custom temperature From e3fbfb7186947375280142a1fcda59165f0b325f Mon Sep 17 00:00:00 2001 From: Joe Manley Date: Fri, 7 Feb 2025 22:54:18 -0800 Subject: [PATCH 05/12] Add tests --- .../settings/TemperatureControl.tsx | 2 +- .../__tests__/TemperatureControl.test.tsx | 75 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 webview-ui/src/components/settings/__tests__/TemperatureControl.test.tsx diff --git a/webview-ui/src/components/settings/TemperatureControl.tsx b/webview-ui/src/components/settings/TemperatureControl.tsx index 321f25f1b7..ed5bc1b098 100644 --- a/webview-ui/src/components/settings/TemperatureControl.tsx +++ b/webview-ui/src/components/settings/TemperatureControl.tsx @@ -51,7 +51,7 @@ export const TemperatureControl = ({ value, onChange, maxValue = 1 }: Temperatur min="0" max={maxValue} step="0.05" - value={value} + value={value ?? 0} onChange={(e) => { const newValue = parseFloat(e.target.value) onChange(isNaN(newValue) ? undefined : newValue) diff --git a/webview-ui/src/components/settings/__tests__/TemperatureControl.test.tsx b/webview-ui/src/components/settings/__tests__/TemperatureControl.test.tsx new file mode 100644 index 0000000000..8401f3d5fb --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/TemperatureControl.test.tsx @@ -0,0 +1,75 @@ +import { render, screen, fireEvent } from "@testing-library/react" +import { TemperatureControl } from "../TemperatureControl" + +describe("TemperatureControl", () => { + it("renders with default temperature disabled", () => { + const onChange = jest.fn() + render() + + const checkbox = screen.getByRole("checkbox") + expect(checkbox).not.toBeChecked() + expect(screen.queryByRole("slider")).not.toBeInTheDocument() + }) + + it("renders with custom temperature enabled", () => { + const onChange = jest.fn() + render() + + const checkbox = screen.getByRole("checkbox") + expect(checkbox).toBeChecked() + + const slider = screen.getByRole("slider") + expect(slider).toBeInTheDocument() + expect(slider).toHaveValue("0.7") + }) + + it("updates when checkbox is toggled", () => { + const onChange = jest.fn() + render() + + const checkbox = screen.getByRole("checkbox") + + // Uncheck - should clear temperature + fireEvent.click(checkbox) + expect(onChange).toHaveBeenCalledWith(undefined) + + // Check - should restore previous temperature + fireEvent.click(checkbox) + expect(onChange).toHaveBeenCalledWith(0.7) + }) + + it("updates temperature when slider changes", () => { + const onChange = jest.fn() + render() + + const slider = screen.getByRole("slider") + fireEvent.change(slider, { target: { value: "0.8" } }) + + expect(onChange).toHaveBeenCalledWith(0.8) + }) + + it("respects maxValue prop", () => { + const onChange = jest.fn() + render() + + const slider = screen.getByRole("slider") + expect(slider).toHaveAttribute("max", "2") + }) + + it("syncs checkbox state when value prop changes", () => { + const onChange = jest.fn() + const { rerender } = render() + + // Initially checked + const checkbox = screen.getByRole("checkbox") + expect(checkbox).toBeChecked() + + // Update to undefined + rerender() + expect(checkbox).not.toBeChecked() + + // Update back to a value + rerender() + expect(checkbox).toBeChecked() + }) +}) From adedc1c2c6d9912071672f62f87a717c9a2b9a98 Mon Sep 17 00:00:00 2001 From: Joe Manley Date: Fri, 7 Feb 2025 23:08:26 -0800 Subject: [PATCH 06/12] Remove magic numbers --- src/api/providers/anthropic.ts | 8 +++++--- src/api/providers/bedrock.ts | 6 ++++-- src/api/providers/deepseek.ts | 2 ++ src/api/providers/gemini.ts | 6 ++++-- src/api/providers/glama.ts | 7 ++++--- src/api/providers/lmstudio.ts | 6 ++++-- src/api/providers/mistral.ts | 4 +++- src/api/providers/ollama.ts | 9 +++++++-- src/api/providers/openai-native.ts | 6 ++++-- src/api/providers/openai.ts | 6 +++++- src/api/providers/openrouter.ts | 9 ++++++--- 11 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index 2059804a54..9a14756f5d 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -10,6 +10,8 @@ import { import { ApiHandler, SingleCompletionHandler } from "../index" import { ApiStream } from "../transform/stream" +const ANTHROPIC_DEFAULT_TEMPERATURE = 0 + export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { private options: ApiHandlerOptions private client: Anthropic @@ -44,7 +46,7 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { { model: modelId, max_tokens: this.getModel().info.maxTokens || 8192, - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? ANTHROPIC_DEFAULT_TEMPERATURE, system: [{ text: systemPrompt, type: "text", cache_control: { type: "ephemeral" } }], // setting cache breakpoint for system prompt so new tasks can reuse it messages: messages.map((message, index) => { if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) { @@ -96,7 +98,7 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { stream = (await this.client.messages.create({ model: modelId, max_tokens: this.getModel().info.maxTokens || 8192, - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? ANTHROPIC_DEFAULT_TEMPERATURE, system: [{ text: systemPrompt, type: "text" }], messages, // tools, @@ -179,7 +181,7 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { const response = await this.client.messages.create({ model: this.getModel().id, max_tokens: this.getModel().info.maxTokens || 8192, - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? ANTHROPIC_DEFAULT_TEMPERATURE, messages: [{ role: "user", content: prompt }], stream: false, }) diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts index 17362e1f05..8f897fda2a 100644 --- a/src/api/providers/bedrock.ts +++ b/src/api/providers/bedrock.ts @@ -11,6 +11,8 @@ import { ApiHandlerOptions, BedrockModelId, ModelInfo, bedrockDefaultModelId, be import { ApiStream } from "../transform/stream" import { convertToBedrockConverseMessages, convertToAnthropicMessage } from "../transform/bedrock-converse-format" +const BEDROCK_DEFAULT_TEMPERATURE = 0.3 + // Define types for stream events based on AWS SDK export interface StreamEvent { messageStart?: { @@ -104,7 +106,7 @@ export class AwsBedrockHandler implements ApiHandler, SingleCompletionHandler { system: [{ text: systemPrompt }], inferenceConfig: { maxTokens: modelConfig.info.maxTokens || 5000, - temperature: this.options.modelTemperature ?? 0.3, + temperature: this.options.modelTemperature ?? BEDROCK_DEFAULT_TEMPERATURE, topP: 0.1, ...(this.options.awsUsePromptCache ? { @@ -262,7 +264,7 @@ export class AwsBedrockHandler implements ApiHandler, SingleCompletionHandler { ]), inferenceConfig: { maxTokens: modelConfig.info.maxTokens || 5000, - temperature: this.options.modelTemperature ?? 0.3, + temperature: this.options.modelTemperature ?? BEDROCK_DEFAULT_TEMPERATURE, topP: 0.1, }, } diff --git a/src/api/providers/deepseek.ts b/src/api/providers/deepseek.ts index 1c7186d48c..308fea871b 100644 --- a/src/api/providers/deepseek.ts +++ b/src/api/providers/deepseek.ts @@ -2,6 +2,8 @@ import { OpenAiHandler } from "./openai" import { ApiHandlerOptions, ModelInfo } from "../../shared/api" import { deepSeekModels, deepSeekDefaultModelId } from "../../shared/api" +export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6 + export class DeepSeekHandler extends OpenAiHandler { constructor(options: ApiHandlerOptions) { super({ diff --git a/src/api/providers/gemini.ts b/src/api/providers/gemini.ts index e9a0015224..0d7179320c 100644 --- a/src/api/providers/gemini.ts +++ b/src/api/providers/gemini.ts @@ -5,6 +5,8 @@ import { ApiHandlerOptions, geminiDefaultModelId, GeminiModelId, geminiModels, M import { convertAnthropicMessageToGemini } from "../transform/gemini-format" import { ApiStream } from "../transform/stream" +const GEMINI_DEFAULT_TEMPERATURE = 0 + export class GeminiHandler implements ApiHandler, SingleCompletionHandler { private options: ApiHandlerOptions private client: GoogleGenerativeAI @@ -23,7 +25,7 @@ export class GeminiHandler implements ApiHandler, SingleCompletionHandler { contents: messages.map(convertAnthropicMessageToGemini), generationConfig: { // maxOutputTokens: this.getModel().info.maxTokens, - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? GEMINI_DEFAULT_TEMPERATURE, }, }) @@ -60,7 +62,7 @@ export class GeminiHandler implements ApiHandler, SingleCompletionHandler { const result = await model.generateContent({ contents: [{ role: "user", parts: [{ text: prompt }] }], generationConfig: { - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? GEMINI_DEFAULT_TEMPERATURE, }, }) diff --git a/src/api/providers/glama.ts b/src/api/providers/glama.ts index 226891b16a..72b41e5f58 100644 --- a/src/api/providers/glama.ts +++ b/src/api/providers/glama.ts @@ -5,7 +5,8 @@ import { ApiHandler, SingleCompletionHandler } from "../" import { ApiHandlerOptions, ModelInfo, glamaDefaultModelId, glamaDefaultModelInfo } from "../../shared/api" import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream } from "../transform/stream" -import delay from "delay" + +const GLAMA_DEFAULT_TEMPERATURE = 0 export class GlamaHandler implements ApiHandler, SingleCompletionHandler { private options: ApiHandlerOptions @@ -79,7 +80,7 @@ export class GlamaHandler implements ApiHandler, SingleCompletionHandler { } if (this.supportsTemperature()) { - requestOptions.temperature = this.options.modelTemperature ?? 0 + requestOptions.temperature = this.options.modelTemperature ?? GLAMA_DEFAULT_TEMPERATURE } const { data: completion, response } = await this.client.chat.completions @@ -172,7 +173,7 @@ export class GlamaHandler implements ApiHandler, SingleCompletionHandler { } if (this.supportsTemperature()) { - requestOptions.temperature = this.options.modelTemperature ?? 0 + requestOptions.temperature = this.options.modelTemperature ?? GLAMA_DEFAULT_TEMPERATURE } if (this.getModel().id.startsWith("anthropic/")) { diff --git a/src/api/providers/lmstudio.ts b/src/api/providers/lmstudio.ts index cc164d240e..7efa037f46 100644 --- a/src/api/providers/lmstudio.ts +++ b/src/api/providers/lmstudio.ts @@ -5,6 +5,8 @@ import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../.. import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream } from "../transform/stream" +const LMSTUDIO_DEFAULT_TEMPERATURE = 0 + export class LmStudioHandler implements ApiHandler, SingleCompletionHandler { private options: ApiHandlerOptions private client: OpenAI @@ -27,7 +29,7 @@ export class LmStudioHandler implements ApiHandler, SingleCompletionHandler { const stream = await this.client.chat.completions.create({ model: this.getModel().id, messages: openAiMessages, - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? LMSTUDIO_DEFAULT_TEMPERATURE, stream: true, }) for await (const chunk of stream) { @@ -59,7 +61,7 @@ export class LmStudioHandler implements ApiHandler, SingleCompletionHandler { const response = await this.client.chat.completions.create({ model: this.getModel().id, messages: [{ role: "user", content: prompt }], - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? LMSTUDIO_DEFAULT_TEMPERATURE, stream: false, }) return response.choices[0]?.message.content || "" diff --git a/src/api/providers/mistral.ts b/src/api/providers/mistral.ts index 4bcf1a191c..9ce70a297c 100644 --- a/src/api/providers/mistral.ts +++ b/src/api/providers/mistral.ts @@ -14,6 +14,8 @@ import { import { convertToMistralMessages } from "../transform/mistral-format" import { ApiStream } from "../transform/stream" +const MISTRAL_DEFAULT_TEMPERATURE = 0 + export class MistralHandler implements ApiHandler { private options: ApiHandlerOptions private client: Mistral @@ -30,7 +32,7 @@ export class MistralHandler implements ApiHandler { const stream = await this.client.chat.stream({ model: this.getModel().id, // max_completion_tokens: this.getModel().info.maxTokens, - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? MISTRAL_DEFAULT_TEMPERATURE, messages: [{ role: "system", content: systemPrompt }, ...convertToMistralMessages(messages)], stream: true, }) diff --git a/src/api/providers/ollama.ts b/src/api/providers/ollama.ts index b6b11ccc07..b8c2dfaeee 100644 --- a/src/api/providers/ollama.ts +++ b/src/api/providers/ollama.ts @@ -5,6 +5,9 @@ import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../.. import { convertToOpenAiMessages } from "../transform/openai-format" import { convertToR1Format } from "../transform/r1-format" import { ApiStream } from "../transform/stream" +import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./deepseek" + +const OLLAMA_DEFAULT_TEMPERATURE = 0 export class OllamaHandler implements ApiHandler, SingleCompletionHandler { private options: ApiHandlerOptions @@ -29,7 +32,7 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler { const stream = await this.client.chat.completions.create({ model: this.getModel().id, messages: openAiMessages, - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? DEFAULT_TEMPERATURE, stream: true, }) for await (const chunk of stream) { @@ -59,7 +62,9 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler { messages: useR1Format ? convertToR1Format([{ role: "user", content: prompt }]) : [{ role: "user", content: prompt }], - temperature: this.options.modelTemperature ?? (useR1Format ? 0.6 : 0), + temperature: + this.options.modelTemperature ?? + (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : OLLAMA_DEFAULT_TEMPERATURE), stream: false, }) return response.choices[0]?.message.content || "" diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts index a40e002ce1..1a4f9e613a 100644 --- a/src/api/providers/openai-native.ts +++ b/src/api/providers/openai-native.ts @@ -11,6 +11,8 @@ import { import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream } from "../transform/stream" +const OPENAI_NATIVE_DEFAULT_TEMPERATURE = 0 + export class OpenAiNativeHandler implements ApiHandler, SingleCompletionHandler { private options: ApiHandlerOptions private client: OpenAI @@ -88,7 +90,7 @@ export class OpenAiNativeHandler implements ApiHandler, SingleCompletionHandler ): ApiStream { const stream = await this.client.chat.completions.create({ model: modelId, - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? OPENAI_NATIVE_DEFAULT_TEMPERATURE, messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], stream: true, stream_options: { include_usage: true }, @@ -189,7 +191,7 @@ export class OpenAiNativeHandler implements ApiHandler, SingleCompletionHandler return { model: modelId, messages: [{ role: "user", content: prompt }], - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? OPENAI_NATIVE_DEFAULT_TEMPERATURE, } } } diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index da3cf1b9e7..07bfbf7c28 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -11,6 +11,8 @@ import { ApiHandler, SingleCompletionHandler } from "../index" import { convertToOpenAiMessages } from "../transform/openai-format" import { convertToR1Format } from "../transform/r1-format" import { ApiStream } from "../transform/stream" +import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./deepseek" +const OPENAI_DEFAULT_TEMPERATURE = 0 export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { protected options: ApiHandlerOptions @@ -57,7 +59,9 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { } const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelId, - temperature: this.options.modelTemperature ?? (deepseekReasoner ? 0.6 : 0), + temperature: + this.options.modelTemperature ?? + (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : OPENAI_DEFAULT_TEMPERATURE), messages: deepseekReasoner ? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) : [systemMessage, ...convertToOpenAiMessages(messages)], diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index fa1c65d126..4334e6d48a 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -6,6 +6,9 @@ import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefau import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStreamChunk, ApiStreamUsageChunk } from "../transform/stream" import delay from "delay" +import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./deepseek" + +const OPENROUTER_DEFAULT_TEMPERATURE = 0 // Add custom interface for OpenRouter params type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & { @@ -115,7 +118,7 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler { break } - let defaultTemperature = 0 + let defaultTemperature = OPENROUTER_DEFAULT_TEMPERATURE let topP: number | undefined = undefined // Handle models based on deepseek-r1 @@ -124,7 +127,7 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler { this.getModel().id === "perplexity/sonar-reasoning" ) { // Recommended temperature for DeepSeek reasoning models - defaultTemperature = 0.6 + defaultTemperature = DEEP_SEEK_DEFAULT_TEMPERATURE // DeepSeek highly recommends using user instead of system role openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) // Some provider support topP and 0.95 is value that Deepseek used in their benchmarks @@ -223,7 +226,7 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler { const response = await this.client.chat.completions.create({ model: this.getModel().id, messages: [{ role: "user", content: prompt }], - temperature: this.options.modelTemperature ?? 0, + temperature: this.options.modelTemperature ?? OPENROUTER_DEFAULT_TEMPERATURE, stream: false, }) From 88fb6ec15a1d3de438affc7ad27f6316b74db8fd Mon Sep 17 00:00:00 2001 From: Joe Manley Date: Fri, 7 Feb 2025 23:09:06 -0800 Subject: [PATCH 07/12] Remove magic numbers --- src/api/providers/ollama.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/providers/ollama.ts b/src/api/providers/ollama.ts index b8c2dfaeee..1a204b5d6c 100644 --- a/src/api/providers/ollama.ts +++ b/src/api/providers/ollama.ts @@ -32,7 +32,7 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler { const stream = await this.client.chat.completions.create({ model: this.getModel().id, messages: openAiMessages, - temperature: this.options.modelTemperature ?? DEFAULT_TEMPERATURE, + temperature: this.options.modelTemperature ?? OLLAMA_DEFAULT_TEMPERATURE, stream: true, }) for await (const chunk of stream) { From 71d344d0a85eee7ed552088967d3b57e24b601a2 Mon Sep 17 00:00:00 2001 From: Joe Manley Date: Fri, 7 Feb 2025 23:17:02 -0800 Subject: [PATCH 08/12] Fix tests --- src/api/providers/__tests__/openai.test.ts | 2 -- src/api/providers/deepseek.ts | 2 -- src/api/providers/ollama.ts | 2 +- src/api/providers/openai.ts | 3 ++- src/api/providers/openrouter.ts | 2 +- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/api/providers/__tests__/openai.test.ts b/src/api/providers/__tests__/openai.test.ts index 52d0c5c2bb..5b5da20f51 100644 --- a/src/api/providers/__tests__/openai.test.ts +++ b/src/api/providers/__tests__/openai.test.ts @@ -1,7 +1,5 @@ import { OpenAiHandler } from "../openai" import { ApiHandlerOptions } from "../../../shared/api" -import { ApiStream } from "../../transform/stream" -import OpenAI from "openai" import { Anthropic } from "@anthropic-ai/sdk" // Mock OpenAI client diff --git a/src/api/providers/deepseek.ts b/src/api/providers/deepseek.ts index 308fea871b..1c7186d48c 100644 --- a/src/api/providers/deepseek.ts +++ b/src/api/providers/deepseek.ts @@ -2,8 +2,6 @@ import { OpenAiHandler } from "./openai" import { ApiHandlerOptions, ModelInfo } from "../../shared/api" import { deepSeekModels, deepSeekDefaultModelId } from "../../shared/api" -export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6 - export class DeepSeekHandler extends OpenAiHandler { constructor(options: ApiHandlerOptions) { super({ diff --git a/src/api/providers/ollama.ts b/src/api/providers/ollama.ts index 1a204b5d6c..8c8e9ed8da 100644 --- a/src/api/providers/ollama.ts +++ b/src/api/providers/ollama.ts @@ -5,7 +5,7 @@ import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../.. import { convertToOpenAiMessages } from "../transform/openai-format" import { convertToR1Format } from "../transform/r1-format" import { ApiStream } from "../transform/stream" -import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./deepseek" +import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./openai" const OLLAMA_DEFAULT_TEMPERATURE = 0 diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 07bfbf7c28..98fdbc410b 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -11,7 +11,8 @@ import { ApiHandler, SingleCompletionHandler } from "../index" import { convertToOpenAiMessages } from "../transform/openai-format" import { convertToR1Format } from "../transform/r1-format" import { ApiStream } from "../transform/stream" -import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./deepseek" + +export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6 const OPENAI_DEFAULT_TEMPERATURE = 0 export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 4334e6d48a..6a33d713ac 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -6,7 +6,7 @@ import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefau import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStreamChunk, ApiStreamUsageChunk } from "../transform/stream" import delay from "delay" -import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./deepseek" +import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./openai" const OPENROUTER_DEFAULT_TEMPERATURE = 0 From f8f571414a7a42a4852260a02f4e65892cc40ec6 Mon Sep 17 00:00:00 2001 From: Joe Manley Date: Fri, 7 Feb 2025 23:32:27 -0800 Subject: [PATCH 09/12] Add aria-lable for temperature control range input Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- webview-ui/src/components/settings/TemperatureControl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/settings/TemperatureControl.tsx b/webview-ui/src/components/settings/TemperatureControl.tsx index ed5bc1b098..b8908de536 100644 --- a/webview-ui/src/components/settings/TemperatureControl.tsx +++ b/webview-ui/src/components/settings/TemperatureControl.tsx @@ -46,7 +46,7 @@ export const TemperatureControl = ({ value, onChange, maxValue = 1 }: Temperatur {isCustomTemperature && (
- Date: Sun, 9 Feb 2025 20:09:21 -0800 Subject: [PATCH 10/12] Back to main --- src/api/providers/openai.ts | 7 +------ src/core/webview/ClineProvider.ts | 6 ------ src/shared/api.ts | 1 - 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 8551d812a3..203eb44040 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -13,9 +13,6 @@ import { convertToR1Format } from "../transform/r1-format" import { convertToSimpleMessages } from "../transform/simple-format" import { ApiStream } from "../transform/stream" -export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6 -const OPENAI_DEFAULT_TEMPERATURE = 0 - export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { protected options: ApiHandlerOptions private client: OpenAI @@ -73,9 +70,7 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelId, - temperature: - this.options.modelTemperature ?? - (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : OPENAI_DEFAULT_TEMPERATURE), + temperature: 0, messages: convertedMessages, stream: true as const, stream_options: { include_usage: true }, diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 1051267c43..74e797e4a1 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -123,7 +123,6 @@ type GlobalStateKey = | "customModes" // Array of custom modes | "unboundModelId" | "unboundModelInfo" - | "modelTemperature" export const GlobalFileNames = { apiConversationHistory: "api_conversation_history.json", @@ -1588,7 +1587,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { unboundApiKey, unboundModelId, unboundModelInfo, - modelTemperature, } = apiConfiguration await this.updateGlobalState("apiProvider", apiProvider) await this.updateGlobalState("apiModelId", apiModelId) @@ -1630,7 +1628,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.storeSecret("unboundApiKey", unboundApiKey) await this.updateGlobalState("unboundModelId", unboundModelId) await this.updateGlobalState("unboundModelInfo", unboundModelInfo) - await this.updateGlobalState("modelTemperature", modelTemperature) if (this.cline) { this.cline.api = buildApiHandler(apiConfiguration) } @@ -2391,7 +2388,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { unboundApiKey, unboundModelId, unboundModelInfo, - modelTemperature, ] = await Promise.all([ this.getGlobalState("apiProvider") as Promise, this.getGlobalState("apiModelId") as Promise, @@ -2468,7 +2464,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getSecret("unboundApiKey") as Promise, this.getGlobalState("unboundModelId") as Promise, this.getGlobalState("unboundModelInfo") as Promise, - this.getGlobalState("modelTemperature") as Promise, ]) let apiProvider: ApiProvider @@ -2527,7 +2522,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { unboundApiKey, unboundModelId, unboundModelInfo, - modelTemperature, }, lastShownAnnouncementId, customInstructions, diff --git a/src/shared/api.ts b/src/shared/api.ts index 5f41130990..77c73a8c36 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -61,7 +61,6 @@ export interface ApiHandlerOptions { unboundApiKey?: string unboundModelId?: string unboundModelInfo?: ModelInfo - modelTemperature?: number } export type ApiConfiguration = ApiHandlerOptions & { From d4b7a60138759922e98126370278dd858aab0ebe Mon Sep 17 00:00:00 2001 From: Joe Manley Date: Sun, 9 Feb 2025 20:09:37 -0800 Subject: [PATCH 11/12] Bring back changes --- src/api/providers/openai.ts | 7 ++++++- src/core/webview/ClineProvider.ts | 6 ++++++ src/shared/api.ts | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 203eb44040..8551d812a3 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -13,6 +13,9 @@ import { convertToR1Format } from "../transform/r1-format" import { convertToSimpleMessages } from "../transform/simple-format" import { ApiStream } from "../transform/stream" +export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6 +const OPENAI_DEFAULT_TEMPERATURE = 0 + export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { protected options: ApiHandlerOptions private client: OpenAI @@ -70,7 +73,9 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelId, - temperature: 0, + temperature: + this.options.modelTemperature ?? + (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : OPENAI_DEFAULT_TEMPERATURE), messages: convertedMessages, stream: true as const, stream_options: { include_usage: true }, diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 74e797e4a1..1051267c43 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -123,6 +123,7 @@ type GlobalStateKey = | "customModes" // Array of custom modes | "unboundModelId" | "unboundModelInfo" + | "modelTemperature" export const GlobalFileNames = { apiConversationHistory: "api_conversation_history.json", @@ -1587,6 +1588,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { unboundApiKey, unboundModelId, unboundModelInfo, + modelTemperature, } = apiConfiguration await this.updateGlobalState("apiProvider", apiProvider) await this.updateGlobalState("apiModelId", apiModelId) @@ -1628,6 +1630,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.storeSecret("unboundApiKey", unboundApiKey) await this.updateGlobalState("unboundModelId", unboundModelId) await this.updateGlobalState("unboundModelInfo", unboundModelInfo) + await this.updateGlobalState("modelTemperature", modelTemperature) if (this.cline) { this.cline.api = buildApiHandler(apiConfiguration) } @@ -2388,6 +2391,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { unboundApiKey, unboundModelId, unboundModelInfo, + modelTemperature, ] = await Promise.all([ this.getGlobalState("apiProvider") as Promise, this.getGlobalState("apiModelId") as Promise, @@ -2464,6 +2468,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getSecret("unboundApiKey") as Promise, this.getGlobalState("unboundModelId") as Promise, this.getGlobalState("unboundModelInfo") as Promise, + this.getGlobalState("modelTemperature") as Promise, ]) let apiProvider: ApiProvider @@ -2522,6 +2527,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { unboundApiKey, unboundModelId, unboundModelInfo, + modelTemperature, }, lastShownAnnouncementId, customInstructions, diff --git a/src/shared/api.ts b/src/shared/api.ts index 77c73a8c36..5f41130990 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -61,6 +61,7 @@ export interface ApiHandlerOptions { unboundApiKey?: string unboundModelId?: string unboundModelInfo?: ModelInfo + modelTemperature?: number } export type ApiConfiguration = ApiHandlerOptions & { From be9a90489a00c239c8623a4d975a420d3bec9db5 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 9 Feb 2025 23:06:45 -0500 Subject: [PATCH 12/12] Change input from a slider to a text input --- .../settings/TemperatureControl.tsx | 53 +++++++++++-------- .../__tests__/TemperatureControl.test.tsx | 29 ++++++---- 2 files changed, 51 insertions(+), 31 deletions(-) diff --git a/webview-ui/src/components/settings/TemperatureControl.tsx b/webview-ui/src/components/settings/TemperatureControl.tsx index b8908de536..422356bf69 100644 --- a/webview-ui/src/components/settings/TemperatureControl.tsx +++ b/webview-ui/src/components/settings/TemperatureControl.tsx @@ -9,21 +9,17 @@ interface TemperatureControlProps { export const TemperatureControl = ({ value, onChange, maxValue = 1 }: TemperatureControlProps) => { const [isCustomTemperature, setIsCustomTemperature] = useState(value !== undefined) + const [inputValue, setInputValue] = useState(value?.toString() ?? "0") // Sync internal state with prop changes when switching profiles useEffect(() => { const hasCustomTemperature = value !== undefined setIsCustomTemperature(hasCustomTemperature) + setInputValue(value?.toString() ?? "0") }, [value]) return ( -
+
{ @@ -39,31 +35,44 @@ export const TemperatureControl = ({ value, onChange, maxValue = 1 }: Temperatur

- Controls randomness in the model's responses. Higher values make output more random, lower values make - it more deterministic. + Controls randomness in the model's responses.

{isCustomTemperature && ( -
+
- { + setInputValue(e.target.value)} + onBlur={(e) => { const newValue = parseFloat(e.target.value) - onChange(isNaN(newValue) ? undefined : newValue) + if (!isNaN(newValue) && newValue >= 0 && newValue <= maxValue) { + onChange(newValue) + setInputValue(newValue.toString()) + } else { + setInputValue(value?.toString() ?? "0") // Reset to last valid value + } }} style={{ - flexGrow: 1, - accentColor: "var(--vscode-button-background)", - height: "2px", + width: "60px", + padding: "4px 8px", + border: "1px solid var(--vscode-input-border)", + background: "var(--vscode-input-background)", + color: "var(--vscode-input-foreground)", }} /> - {value?.toFixed(2)}
+

+ Higher values make output more random, lower values make it more deterministic. +

)}
diff --git a/webview-ui/src/components/settings/__tests__/TemperatureControl.test.tsx b/webview-ui/src/components/settings/__tests__/TemperatureControl.test.tsx index 8401f3d5fb..d178cfafbc 100644 --- a/webview-ui/src/components/settings/__tests__/TemperatureControl.test.tsx +++ b/webview-ui/src/components/settings/__tests__/TemperatureControl.test.tsx @@ -8,7 +8,7 @@ describe("TemperatureControl", () => { const checkbox = screen.getByRole("checkbox") expect(checkbox).not.toBeChecked() - expect(screen.queryByRole("slider")).not.toBeInTheDocument() + expect(screen.queryByRole("textbox")).not.toBeInTheDocument() }) it("renders with custom temperature enabled", () => { @@ -18,9 +18,9 @@ describe("TemperatureControl", () => { const checkbox = screen.getByRole("checkbox") expect(checkbox).toBeChecked() - const slider = screen.getByRole("slider") - expect(slider).toBeInTheDocument() - expect(slider).toHaveValue("0.7") + const input = screen.getByRole("textbox") + expect(input).toBeInTheDocument() + expect(input).toHaveValue("0.7") }) it("updates when checkbox is toggled", () => { @@ -38,12 +38,13 @@ describe("TemperatureControl", () => { expect(onChange).toHaveBeenCalledWith(0.7) }) - it("updates temperature when slider changes", () => { + it("updates temperature when input loses focus", () => { const onChange = jest.fn() render() - const slider = screen.getByRole("slider") - fireEvent.change(slider, { target: { value: "0.8" } }) + const input = screen.getByRole("textbox") + fireEvent.change(input, { target: { value: "0.8" } }) + fireEvent.blur(input) expect(onChange).toHaveBeenCalledWith(0.8) }) @@ -52,8 +53,18 @@ describe("TemperatureControl", () => { const onChange = jest.fn() render() - const slider = screen.getByRole("slider") - expect(slider).toHaveAttribute("max", "2") + const input = screen.getByRole("textbox") + + // Valid value within max + fireEvent.change(input, { target: { value: "1.8" } }) + fireEvent.blur(input) + expect(onChange).toHaveBeenCalledWith(1.8) + + // Invalid value above max + fireEvent.change(input, { target: { value: "2.5" } }) + fireEvent.blur(input) + expect(input).toHaveValue("1.5") // Should revert to original value + expect(onChange).toHaveBeenCalledTimes(1) // Should not call onChange for invalid value }) it("syncs checkbox state when value prop changes", () => {