mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
refactor: improve temperature and top_p parameter handling (#11663)
* refactor(model-params): split temperature and top_p support checks into separate functions Replace deprecated isNotSupportTemperatureAndTopP with isSupportTemperatureModel and isSupportTopPModel Add comprehensive tests for new model parameter support functions * refactor(model-parameters): improve temperature and topP parameter handling - Add fallback to DEFAULT_ASSISTANT_SETTINGS when enableTemperature/enableTopP is undefined - Simplify conditional logic in parameter validation - Update documentation to better explain parameter selection rules * refactor(models): remove deprecated isNotSupportTemperatureAndTopP function The function was marked as deprecated and its usage has been replaced by isSupportTemperatureModel and isSupportTopPModel. Also removed corresponding test cases. * feat(models): add mutual exclusivity check for temperature and top_p Add new utility function to enforce mutual exclusivity between temperature and top_p parameters for Claude 4.5 reasoning models. Update model parameter preparation logic to use this new check and add corresponding tests.
This commit is contained in:
parent
1a737f5137
commit
9f7e47304d
@ -2,9 +2,10 @@ import { loggerService } from '@logger'
|
||||
import {
|
||||
getModelSupportedVerbosity,
|
||||
isFunctionCallingModel,
|
||||
isNotSupportTemperatureAndTopP,
|
||||
isOpenAIModel,
|
||||
isSupportFlexServiceTierModel
|
||||
isSupportFlexServiceTierModel,
|
||||
isSupportTemperatureModel,
|
||||
isSupportTopPModel
|
||||
} from '@renderer/config/models'
|
||||
import { REFERENCE_PROMPT } from '@renderer/config/prompts'
|
||||
import { getLMStudioKeepAliveTime } from '@renderer/hooks/useLMStudio'
|
||||
@ -200,7 +201,7 @@ export abstract class BaseApiClient<
|
||||
}
|
||||
|
||||
public getTemperature(assistant: Assistant, model: Model): number | undefined {
|
||||
if (isNotSupportTemperatureAndTopP(model)) {
|
||||
if (!isSupportTemperatureModel(model)) {
|
||||
return undefined
|
||||
}
|
||||
const assistantSettings = getAssistantSettings(assistant)
|
||||
@ -208,7 +209,7 @@ export abstract class BaseApiClient<
|
||||
}
|
||||
|
||||
public getTopP(assistant: Assistant, model: Model): number | undefined {
|
||||
if (isNotSupportTemperatureAndTopP(model)) {
|
||||
if (!isSupportTopPModel(model)) {
|
||||
return undefined
|
||||
}
|
||||
const assistantSettings = getAssistantSettings(assistant)
|
||||
|
||||
@ -4,60 +4,81 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
isClaude45ReasoningModel,
|
||||
isClaudeReasoningModel,
|
||||
isMaxTemperatureOneModel,
|
||||
isNotSupportTemperatureAndTopP,
|
||||
isSupportedFlexServiceTier,
|
||||
isSupportedThinkingTokenClaudeModel
|
||||
isSupportedThinkingTokenClaudeModel,
|
||||
isSupportTemperatureModel,
|
||||
isSupportTopPModel,
|
||||
isTemperatureTopPMutuallyExclusiveModel
|
||||
} from '@renderer/config/models'
|
||||
import { getAssistantSettings, getProviderByModel } from '@renderer/services/AssistantService'
|
||||
import {
|
||||
DEFAULT_ASSISTANT_SETTINGS,
|
||||
getAssistantSettings,
|
||||
getProviderByModel
|
||||
} from '@renderer/services/AssistantService'
|
||||
import type { Assistant, Model } from '@renderer/types'
|
||||
import { defaultTimeout } from '@shared/config/constant'
|
||||
|
||||
import { getAnthropicThinkingBudget } from '../utils/reasoning'
|
||||
|
||||
/**
|
||||
* Claude 4.5 推理模型:
|
||||
* - 只启用 temperature → 使用 temperature
|
||||
* - 只启用 top_p → 使用 top_p
|
||||
* - 同时启用 → temperature 生效,top_p 被忽略
|
||||
* - 都不启用 → 都不使用
|
||||
* 获取温度参数
|
||||
* Retrieves the temperature parameter, adapting it based on assistant.settings and model capabilities.
|
||||
* - Disabled for Claude reasoning models when reasoning effort is set.
|
||||
* - Disabled for models that do not support temperature.
|
||||
* - Disabled for Claude 4.5 reasoning models when TopP is enabled and temperature is disabled.
|
||||
* Otherwise, returns the temperature value if the assistant has temperature enabled.
|
||||
*/
|
||||
export function getTemperature(assistant: Assistant, model: Model): number | undefined {
|
||||
if (assistant.settings?.reasoning_effort && isClaudeReasoningModel(model)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (!isSupportTemperatureModel(model)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (
|
||||
isNotSupportTemperatureAndTopP(model) ||
|
||||
(isClaude45ReasoningModel(model) && assistant.settings?.enableTopP && !assistant.settings?.enableTemperature)
|
||||
isTemperatureTopPMutuallyExclusiveModel(model) &&
|
||||
assistant.settings?.enableTopP &&
|
||||
!assistant.settings?.enableTemperature
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const assistantSettings = getAssistantSettings(assistant)
|
||||
let temperature = assistantSettings?.temperature
|
||||
if (temperature && isMaxTemperatureOneModel(model)) {
|
||||
temperature = Math.min(1, temperature)
|
||||
}
|
||||
return assistantSettings?.enableTemperature ? temperature : undefined
|
||||
|
||||
// FIXME: assistant.settings.enableTemperature should be always a boolean value.
|
||||
const enableTemperature = assistantSettings?.enableTemperature ?? DEFAULT_ASSISTANT_SETTINGS.enableTemperature
|
||||
return enableTemperature ? temperature : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 TopP 参数
|
||||
* Retrieves the TopP parameter, adapting it based on assistant.settings and model capabilities.
|
||||
* - Disabled for Claude reasoning models when reasoning effort is set.
|
||||
* - Disabled for models that do not support TopP.
|
||||
* - Disabled for Claude 4.5 reasoning models when temperature is explicitly enabled.
|
||||
* Otherwise, returns the TopP value if the assistant has TopP enabled.
|
||||
*/
|
||||
export function getTopP(assistant: Assistant, model: Model): number | undefined {
|
||||
if (assistant.settings?.reasoning_effort && isClaudeReasoningModel(model)) {
|
||||
return undefined
|
||||
}
|
||||
if (
|
||||
isNotSupportTemperatureAndTopP(model) ||
|
||||
(isClaude45ReasoningModel(model) && assistant.settings?.enableTemperature)
|
||||
) {
|
||||
if (!isSupportTopPModel(model)) {
|
||||
return undefined
|
||||
}
|
||||
if (isTemperatureTopPMutuallyExclusiveModel(model) && assistant.settings?.enableTemperature) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const assistantSettings = getAssistantSettings(assistant)
|
||||
return assistantSettings?.enableTopP ? assistantSettings?.topP : undefined
|
||||
// FIXME: assistant.settings.enableTopP should be always a boolean value.
|
||||
const enableTopP = assistantSettings.enableTopP ?? DEFAULT_ASSISTANT_SETTINGS.enableTopP
|
||||
return enableTopP ? assistantSettings?.topP : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -25,11 +25,13 @@ import {
|
||||
isGenerateImageModels,
|
||||
isMaxTemperatureOneModel,
|
||||
isNotSupportSystemMessageModel,
|
||||
isNotSupportTemperatureAndTopP,
|
||||
isNotSupportTextDeltaModel,
|
||||
isSupportedFlexServiceTier,
|
||||
isSupportedModel,
|
||||
isSupportFlexServiceTierModel,
|
||||
isSupportTemperatureModel,
|
||||
isSupportTopPModel,
|
||||
isTemperatureTopPMutuallyExclusiveModel,
|
||||
isVisionModels,
|
||||
isZhipuModel
|
||||
} from '../utils'
|
||||
@ -273,27 +275,104 @@ describe('model utils', () => {
|
||||
})
|
||||
|
||||
describe('Temperature and top-p support', () => {
|
||||
describe('isNotSupportTemperatureAndTopP', () => {
|
||||
it('returns true for reasoning models', () => {
|
||||
describe('isSupportTemperatureModel', () => {
|
||||
it('returns false for reasoning models (non-open weight)', () => {
|
||||
const model = createModel({ id: 'o1' })
|
||||
reasoningMock.mockReturnValue(true)
|
||||
expect(isNotSupportTemperatureAndTopP(model)).toBe(true)
|
||||
expect(isSupportTemperatureModel(model)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for open weight models', () => {
|
||||
it('returns true for open weight models', () => {
|
||||
const openWeight = createModel({ id: 'gpt-oss-debug' })
|
||||
expect(isNotSupportTemperatureAndTopP(openWeight)).toBe(false)
|
||||
expect(isSupportTemperatureModel(openWeight)).toBe(true)
|
||||
})
|
||||
|
||||
it('returns true for chat-only models without reasoning', () => {
|
||||
it('returns false for chat-only models', () => {
|
||||
const chatOnly = createModel({ id: 'o1-preview' })
|
||||
reasoningMock.mockReturnValue(false)
|
||||
expect(isNotSupportTemperatureAndTopP(chatOnly)).toBe(true)
|
||||
expect(isSupportTemperatureModel(chatOnly)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns true for Qwen MT models', () => {
|
||||
it('returns false for Qwen MT models', () => {
|
||||
const qwenMt = createModel({ id: 'qwen-mt-large', provider: 'aliyun' })
|
||||
expect(isNotSupportTemperatureAndTopP(qwenMt)).toBe(true)
|
||||
expect(isSupportTemperatureModel(qwenMt)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for null/undefined models', () => {
|
||||
expect(isSupportTemperatureModel(null)).toBe(false)
|
||||
expect(isSupportTemperatureModel(undefined)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns true for regular GPT models', () => {
|
||||
const model = createModel({ id: 'gpt-4' })
|
||||
expect(isSupportTemperatureModel(model)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isSupportTopPModel', () => {
|
||||
it('returns false for reasoning models (non-open weight)', () => {
|
||||
const model = createModel({ id: 'o1' })
|
||||
reasoningMock.mockReturnValue(true)
|
||||
expect(isSupportTopPModel(model)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns true for open weight models', () => {
|
||||
const openWeight = createModel({ id: 'gpt-oss-debug' })
|
||||
expect(isSupportTopPModel(openWeight)).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false for chat-only models', () => {
|
||||
const chatOnly = createModel({ id: 'o1-preview' })
|
||||
expect(isSupportTopPModel(chatOnly)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for Qwen MT models', () => {
|
||||
const qwenMt = createModel({ id: 'qwen-mt-large', provider: 'aliyun' })
|
||||
expect(isSupportTopPModel(qwenMt)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for null/undefined models', () => {
|
||||
expect(isSupportTopPModel(null)).toBe(false)
|
||||
expect(isSupportTopPModel(undefined)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns true for regular GPT models', () => {
|
||||
const model = createModel({ id: 'gpt-4' })
|
||||
expect(isSupportTopPModel(model)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isTemperatureTopPMutuallyExclusiveModel', () => {
|
||||
it('returns true for Claude 4.5 reasoning models', () => {
|
||||
const claude45Sonnet = createModel({ id: 'claude-sonnet-4.5-20250514' })
|
||||
expect(isTemperatureTopPMutuallyExclusiveModel(claude45Sonnet)).toBe(true)
|
||||
|
||||
const claude45Opus = createModel({ id: 'claude-opus-4.5-20250514' })
|
||||
expect(isTemperatureTopPMutuallyExclusiveModel(claude45Opus)).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false for Claude 4 models', () => {
|
||||
const claude4Sonnet = createModel({ id: 'claude-sonnet-4-20250514' })
|
||||
expect(isTemperatureTopPMutuallyExclusiveModel(claude4Sonnet)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for Claude 3.x models', () => {
|
||||
const claude35Sonnet = createModel({ id: 'claude-3-5-sonnet-20241022' })
|
||||
expect(isTemperatureTopPMutuallyExclusiveModel(claude35Sonnet)).toBe(false)
|
||||
|
||||
const claude3Opus = createModel({ id: 'claude-3-opus-20240229' })
|
||||
expect(isTemperatureTopPMutuallyExclusiveModel(claude3Opus)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for other AI models', () => {
|
||||
expect(isTemperatureTopPMutuallyExclusiveModel(createModel({ id: 'gpt-4o' }))).toBe(false)
|
||||
expect(isTemperatureTopPMutuallyExclusiveModel(createModel({ id: 'o1' }))).toBe(false)
|
||||
expect(isTemperatureTopPMutuallyExclusiveModel(createModel({ id: 'gemini-2.0-flash' }))).toBe(false)
|
||||
expect(isTemperatureTopPMutuallyExclusiveModel(createModel({ id: 'qwen-max' }))).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for null/undefined models', () => {
|
||||
expect(isTemperatureTopPMutuallyExclusiveModel(null)).toBe(false)
|
||||
expect(isTemperatureTopPMutuallyExclusiveModel(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
isSupportVerbosityModel
|
||||
} from './openai'
|
||||
import { isQwenMTModel } from './qwen'
|
||||
import { isClaude45ReasoningModel } from './reasoning'
|
||||
import { isGenerateImageModel, isTextToImageModel, isVisionModel } from './vision'
|
||||
export const NOT_SUPPORTED_REGEX = /(?:^tts|whisper|speech)/i
|
||||
export const GEMINI_FLASH_MODEL_REGEX = new RegExp('gemini.*-flash.*$', 'i')
|
||||
@ -42,20 +43,71 @@ export function isSupportedModel(model: OpenAI.Models.Model): boolean {
|
||||
return !NOT_SUPPORTED_REGEX.test(modelId)
|
||||
}
|
||||
|
||||
export function isNotSupportTemperatureAndTopP(model: Model): boolean {
|
||||
/**
|
||||
* Check if the model supports temperature parameter
|
||||
* @param model - The model to check
|
||||
* @returns true if the model supports temperature parameter
|
||||
*/
|
||||
export function isSupportTemperatureModel(model: Model | undefined | null): boolean {
|
||||
if (!model) {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
(isOpenAIReasoningModel(model) && !isOpenAIOpenWeightModel(model)) ||
|
||||
isOpenAIChatCompletionOnlyModel(model) ||
|
||||
isQwenMTModel(model)
|
||||
) {
|
||||
return true
|
||||
// OpenAI reasoning models (except open weight) don't support temperature
|
||||
if (isOpenAIReasoningModel(model) && !isOpenAIOpenWeightModel(model)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
// OpenAI chat completion only models don't support temperature
|
||||
if (isOpenAIChatCompletionOnlyModel(model)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Qwen MT models don't support temperature
|
||||
if (isQwenMTModel(model)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model supports top_p parameter
|
||||
* @param model - The model to check
|
||||
* @returns true if the model supports top_p parameter
|
||||
*/
|
||||
export function isSupportTopPModel(model: Model | undefined | null): boolean {
|
||||
if (!model) {
|
||||
return false
|
||||
}
|
||||
|
||||
// OpenAI reasoning models (except open weight) don't support top_p
|
||||
if (isOpenAIReasoningModel(model) && !isOpenAIOpenWeightModel(model)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// OpenAI chat completion only models don't support top_p
|
||||
if (isOpenAIChatCompletionOnlyModel(model)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Qwen MT models don't support top_p
|
||||
if (isQwenMTModel(model)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model enforces mutual exclusivity between temperature and top_p parameters.
|
||||
* Currently only Claude 4.5 reasoning models require this constraint.
|
||||
* @param model - The model to check
|
||||
* @returns true if temperature and top_p are mutually exclusive for this model
|
||||
*/
|
||||
export function isTemperatureTopPMutuallyExclusiveModel(model: Model | undefined | null): boolean {
|
||||
if (!model) return false
|
||||
return isClaude45ReasoningModel(model)
|
||||
}
|
||||
|
||||
export function isGemmaModel(model?: Model): boolean {
|
||||
|
||||
@ -27,7 +27,7 @@ import { uuid } from '@renderer/utils'
|
||||
|
||||
const logger = loggerService.withContext('AssistantService')
|
||||
|
||||
export const DEFAULT_ASSISTANT_SETTINGS: AssistantSettings = {
|
||||
export const DEFAULT_ASSISTANT_SETTINGS = {
|
||||
temperature: DEFAULT_TEMPERATURE,
|
||||
enableTemperature: true,
|
||||
contextCount: DEFAULT_CONTEXTCOUNT,
|
||||
@ -39,7 +39,7 @@ export const DEFAULT_ASSISTANT_SETTINGS: AssistantSettings = {
|
||||
// It would gracefully fallback to prompt if not supported by model.
|
||||
toolUseMode: 'function',
|
||||
customParameters: []
|
||||
} as const
|
||||
} as const satisfies AssistantSettings
|
||||
|
||||
export function getDefaultAssistant(): Assistant {
|
||||
return {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user