Configure per-configuration temperature

This commit is contained in:
Roo Code 2025-02-07 23:52:30 -05:00
parent e29ce276c5
commit bc9773ff9a
16 changed files with 124 additions and 30 deletions

View file

@ -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,
})

View file

@ -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,
},
}

View file

@ -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,
},
})

View file

@ -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/")) {

View file

@ -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 || ""

View file

@ -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,
})

View file

@ -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<string> {
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 || ""

View file

@ -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,
}
}
}

View file

@ -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)],

View file

@ -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,
})

View file

@ -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/")) {

View file

@ -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,
})

View file

@ -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<ApiProvider | undefined>,
this.getGlobalState("apiModelId") as Promise<string | undefined>,
@ -2328,6 +2332,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.getGlobalState("experiments") as Promise<Record<ExperimentId, boolean> | undefined>,
this.getSecret("unboundApiKey") as Promise<string | undefined>,
this.getGlobalState("unboundModelId") as Promise<string | undefined>,
this.getGlobalState("modelTemperature") as Promise<number | undefined>,
])
let apiProvider: ApiProvider
@ -2385,6 +2390,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
vsCodeLmModelSelector,
unboundApiKey,
unboundModelId,
modelTemperature,
},
lastShownAnnouncementId,
customInstructions,

View file

@ -60,6 +60,7 @@ export interface ApiHandlerOptions {
includeMaxTokens?: boolean
unboundApiKey?: string
unboundModelId?: string
modelTemperature?: number
}
export type ApiConfiguration = ApiHandlerOptions & {

View file

@ -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) =
</>
)}
<div style={{ marginTop: "10px" }}>
<TemperatureControl
value={apiConfiguration?.modelTemperature}
onChange={(value) => {
handleInputChange("modelTemperature")({
target: { value },
})
}}
maxValue={2}
/>
</div>
{modelIdErrorMessage && (
<p
style={{

View file

@ -0,0 +1,73 @@
import { Checkbox } from "vscrui"
import { useEffect, useState } from "react"
interface TemperatureControlProps {
value: number | undefined
onChange: (value: number | undefined) => 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 (
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<Checkbox
checked={isCustomTemperature}
onChange={(checked: boolean) => {
setIsCustomTemperature(checked)
if (!checked) {
onChange(undefined) // Reset to provider default
} else {
onChange(0) // Set initial value when enabling
}
}}>
<span style={{ fontWeight: 500 }}>Use custom temperature</span>
</Checkbox>
{isCustomTemperature && (
<>
<i
className="codicon codicon-info"
title={`Controls randomness in the model's responses. Higher values (e.g. 0.8) make output more random, lower values (e.g. 0.2) make it more deterministic. Range: 0-${maxValue}`}
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
cursor: "help",
}}
/>
<input
type="range"
min="0"
max={maxValue}
step="0.05"
value={value}
onChange={(e) => {
const newValue = parseFloat(e.target.value)
onChange(isNaN(newValue) ? undefined : newValue)
}}
style={{
flexGrow: 1,
accentColor: "var(--vscode-button-background)",
height: "2px",
}}
/>
<span
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
minWidth: "36px",
textAlign: "right",
}}>
{value?.toFixed(2)}
</span>
</>
)}
</div>
)
}