mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 06:30:10 +08:00
fix(translate): default to first supported reasoning effort when translating (#11869)
* feat(translate): add reasoning effort option to translate service Add support for configuring reasoning effort level in translation requests. This allows better control over the translation quality and processing time based on model capabilities. * test: add comprehensive tests for getModelSupportedReasoningEffort * test(reasoning): update model test cases and comments - Remove test case for 'gpt-4o-deep-research' as it needs to be an actual OpenAI model - Add provider requirement comment for Grok 4 Fast recognition - Simplify array assertions in test cases - Add comment about Qwen models working well for name-based fallback * docs(reasoning): add detailed jsdoc for getModelSupportedReasoningEffort * refactor(openai): replace getThinkModelType with getModelSupportedReasoningEffort Simplify reasoning effort validation by using getModelSupportedReasoningEffort * refactor(models): rename getModelSupportedReasoningEffort to getModelSupportedReasoningEffortOptions Update function name and all related references to better reflect its purpose of returning reasoning effort options
This commit is contained in:
parent
4d3d5ae4ce
commit
71df9d61fd
@ -10,7 +10,7 @@ import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
|
||||
import {
|
||||
findTokenLimit,
|
||||
GEMINI_FLASH_MODEL_REGEX,
|
||||
getThinkModelType,
|
||||
getModelSupportedReasoningEffortOptions,
|
||||
isDeepSeekHybridInferenceModel,
|
||||
isDoubaoThinkingAutoModel,
|
||||
isGPT5SeriesModel,
|
||||
@ -33,7 +33,6 @@ import {
|
||||
isSupportedThinkingTokenQwenModel,
|
||||
isSupportedThinkingTokenZhipuModel,
|
||||
isVisionModel,
|
||||
MODEL_SUPPORTED_REASONING_EFFORT,
|
||||
ZHIPU_RESULT_TOKENS
|
||||
} from '@renderer/config/models'
|
||||
import { mapLanguageToQwenMTModel } from '@renderer/config/translate'
|
||||
@ -304,16 +303,15 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
|
||||
// Grok models/Perplexity models/OpenAI models
|
||||
if (isSupportedReasoningEffortModel(model)) {
|
||||
// 检查模型是否支持所选选项
|
||||
const modelType = getThinkModelType(model)
|
||||
const supportedOptions = MODEL_SUPPORTED_REASONING_EFFORT[modelType]
|
||||
if (supportedOptions.includes(reasoningEffort)) {
|
||||
const supportedOptions = getModelSupportedReasoningEffortOptions(model)
|
||||
if (supportedOptions?.includes(reasoningEffort)) {
|
||||
return {
|
||||
reasoning_effort: reasoningEffort
|
||||
}
|
||||
} else {
|
||||
// 如果不支持,fallback到第一个支持的值
|
||||
return {
|
||||
reasoning_effort: supportedOptions[0]
|
||||
reasoning_effort: supportedOptions?.[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
|
||||
import {
|
||||
findTokenLimit,
|
||||
GEMINI_FLASH_MODEL_REGEX,
|
||||
getThinkModelType,
|
||||
getModelSupportedReasoningEffortOptions,
|
||||
isDeepSeekHybridInferenceModel,
|
||||
isDoubaoSeedAfter251015,
|
||||
isDoubaoThinkingAutoModel,
|
||||
@ -30,8 +30,7 @@ import {
|
||||
isSupportedThinkingTokenHunyuanModel,
|
||||
isSupportedThinkingTokenModel,
|
||||
isSupportedThinkingTokenQwenModel,
|
||||
isSupportedThinkingTokenZhipuModel,
|
||||
MODEL_SUPPORTED_REASONING_EFFORT
|
||||
isSupportedThinkingTokenZhipuModel
|
||||
} from '@renderer/config/models'
|
||||
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
||||
import { getAssistantSettings, getProviderByModel } from '@renderer/services/AssistantService'
|
||||
@ -330,16 +329,15 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
|
||||
// Grok models/Perplexity models/OpenAI models, use reasoning_effort
|
||||
if (isSupportedReasoningEffortModel(model)) {
|
||||
// 检查模型是否支持所选选项
|
||||
const modelType = getThinkModelType(model)
|
||||
const supportedOptions = MODEL_SUPPORTED_REASONING_EFFORT[modelType]
|
||||
if (supportedOptions.includes(reasoningEffort)) {
|
||||
const supportedOptions = getModelSupportedReasoningEffortOptions(model)
|
||||
if (supportedOptions?.includes(reasoningEffort)) {
|
||||
return {
|
||||
reasoningEffort
|
||||
}
|
||||
} else {
|
||||
// 如果不支持,fallback到第一个支持的值
|
||||
return {
|
||||
reasoningEffort: supportedOptions[0]
|
||||
reasoningEffort: supportedOptions?.[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import { isEmbeddingModel, isRerankModel } from '../embedding'
|
||||
import { isOpenAIReasoningModel, isSupportedReasoningEffortOpenAIModel } from '../openai'
|
||||
import {
|
||||
findTokenLimit,
|
||||
getModelSupportedReasoningEffortOptions,
|
||||
getThinkModelType,
|
||||
isClaude4SeriesModel,
|
||||
isClaude45ReasoningModel,
|
||||
@ -1651,3 +1652,355 @@ describe('isGemini3ThinkingTokenModel', () => {
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getModelSupportedReasoningEffortOptions', () => {
|
||||
describe('Edge cases', () => {
|
||||
it('should return undefined for undefined model', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for null model', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(null)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for non-reasoning models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-4o' }))).toBeUndefined()
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'claude-3-opus' }))).toBeUndefined()
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'random-model' }))).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('OpenAI models', () => {
|
||||
it('should return correct options for o-series models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'o3' }))).toEqual(['low', 'medium', 'high'])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'o3-mini' }))).toEqual(['low', 'medium', 'high'])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'o4' }))).toEqual(['low', 'medium', 'high'])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-oss-reasoning' }))).toEqual([
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
})
|
||||
|
||||
it('should return correct options for deep research models', () => {
|
||||
// Note: Deep research models need to be actual OpenAI reasoning models to be detected
|
||||
// 'sonar-deep-research' from Perplexity is the primary deep research model
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'sonar-deep-research' }))).toEqual(['medium'])
|
||||
})
|
||||
|
||||
it('should return correct options for GPT-5 models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5' }))).toEqual([
|
||||
'minimal',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5-preview' }))).toEqual([
|
||||
'minimal',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
})
|
||||
|
||||
it('should return correct options for GPT-5 Pro models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5-pro' }))).toEqual(['high'])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5-pro-preview' }))).toEqual(['high'])
|
||||
})
|
||||
|
||||
it('should return correct options for GPT-5 Codex models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5-codex' }))).toEqual([
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5-codex-mini' }))).toEqual([
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
})
|
||||
|
||||
it('should return correct options for GPT-5.1 models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5.1' }))).toEqual([
|
||||
'none',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5.1-preview' }))).toEqual([
|
||||
'none',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5.1-mini' }))).toEqual([
|
||||
'none',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
})
|
||||
|
||||
it('should return correct options for GPT-5.1 Codex models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5.1-codex' }))).toEqual([
|
||||
'none',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5.1-codex-mini' }))).toEqual([
|
||||
'none',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Grok models', () => {
|
||||
it('should return correct options for Grok 3 mini', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'grok-3-mini' }))).toEqual(['low', 'high'])
|
||||
})
|
||||
|
||||
it('should return correct options for Grok 4 Fast', () => {
|
||||
expect(
|
||||
getModelSupportedReasoningEffortOptions(createModel({ id: 'grok-4-fast', provider: 'openrouter' }))
|
||||
).toEqual(['none', 'auto'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Gemini models', () => {
|
||||
it('should return correct options for Gemini Flash models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-2.5-flash-latest' }))).toEqual([
|
||||
'none',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'auto'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-flash-latest' }))).toEqual([
|
||||
'none',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'auto'
|
||||
])
|
||||
})
|
||||
|
||||
it('should return correct options for Gemini Pro models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-2.5-pro-latest' }))).toEqual([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'auto'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-pro-latest' }))).toEqual([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'auto'
|
||||
])
|
||||
})
|
||||
|
||||
it('should return correct options for Gemini 3 models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-3-flash' }))).toEqual([
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-3-pro-preview' }))).toEqual([
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Qwen models', () => {
|
||||
it('should return correct options for controllable Qwen models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'qwen-plus' }))).toEqual([
|
||||
'none',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'qwen-turbo' }))).toEqual([
|
||||
'none',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'qwen-flash' }))).toEqual([
|
||||
'none',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'qwen3-8b' }))).toEqual([
|
||||
'none',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
})
|
||||
|
||||
it('should return undefined for always-thinking Qwen models', () => {
|
||||
// These models always think and don't support thinking token control
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'qwen3-thinking' }))).toBeUndefined()
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'qwen3-vl-235b-thinking' }))).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Doubao models', () => {
|
||||
it('should return correct options for auto-thinking Doubao models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'doubao-seed-1.6' }))).toEqual([
|
||||
'none',
|
||||
'auto',
|
||||
'high'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'doubao-1-5-thinking-pro-m' }))).toEqual([
|
||||
'none',
|
||||
'auto',
|
||||
'high'
|
||||
])
|
||||
})
|
||||
|
||||
it('should return correct options for Doubao models after 251015', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'doubao-seed-1-6-251015' }))).toEqual([
|
||||
'minimal',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'doubao-seed-1-6-lite-251015' }))).toEqual([
|
||||
'minimal',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
})
|
||||
|
||||
it('should return correct options for other Doubao thinking models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'doubao-1.5-thinking-vision-pro' }))).toEqual([
|
||||
'none',
|
||||
'high'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Other providers', () => {
|
||||
it('should return correct options for Hunyuan models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'hunyuan-a13b' }))).toEqual(['none', 'auto'])
|
||||
})
|
||||
|
||||
it('should return correct options for Zhipu models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'glm-4.5' }))).toEqual(['none', 'auto'])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'glm-4.6' }))).toEqual(['none', 'auto'])
|
||||
})
|
||||
|
||||
it('should return correct options for Perplexity models', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'sonar-deep-research' }))).toEqual(['medium'])
|
||||
})
|
||||
|
||||
it('should return correct options for DeepSeek hybrid models', () => {
|
||||
expect(
|
||||
getModelSupportedReasoningEffortOptions(createModel({ id: 'deepseek-v3.1', provider: 'deepseek' }))
|
||||
).toEqual(['none', 'auto'])
|
||||
expect(
|
||||
getModelSupportedReasoningEffortOptions(createModel({ id: 'deepseek-v3.2', provider: 'openrouter' }))
|
||||
).toEqual(['none', 'auto'])
|
||||
expect(
|
||||
getModelSupportedReasoningEffortOptions(createModel({ id: 'deepseek-chat', provider: 'deepseek' }))
|
||||
).toEqual(['none', 'auto'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Name-based fallback', () => {
|
||||
it('should fall back to name when id does not match', () => {
|
||||
// Grok 4 Fast requires openrouter provider to be recognized
|
||||
expect(
|
||||
getModelSupportedReasoningEffortOptions(
|
||||
createModel({
|
||||
id: 'custom-id',
|
||||
name: 'grok-4-fast',
|
||||
provider: 'openrouter'
|
||||
})
|
||||
)
|
||||
).toEqual(['none', 'auto'])
|
||||
|
||||
expect(
|
||||
getModelSupportedReasoningEffortOptions(
|
||||
createModel({
|
||||
id: 'custom-id',
|
||||
name: 'gpt-5.1'
|
||||
})
|
||||
)
|
||||
).toEqual(['none', 'low', 'medium', 'high'])
|
||||
|
||||
// Qwen models work well for name-based fallback
|
||||
expect(
|
||||
getModelSupportedReasoningEffortOptions(
|
||||
createModel({
|
||||
id: 'custom-id',
|
||||
name: 'qwen-plus'
|
||||
})
|
||||
)
|
||||
).toEqual(['none', 'low', 'medium', 'high'])
|
||||
})
|
||||
|
||||
it('should use id result when id matches', () => {
|
||||
expect(
|
||||
getModelSupportedReasoningEffortOptions(
|
||||
createModel({
|
||||
id: 'gpt-5.1',
|
||||
name: 'Different Name'
|
||||
})
|
||||
)
|
||||
).toEqual(['none', 'low', 'medium', 'high'])
|
||||
|
||||
expect(
|
||||
getModelSupportedReasoningEffortOptions(
|
||||
createModel({
|
||||
id: 'o3-mini',
|
||||
name: 'Some other name'
|
||||
})
|
||||
)
|
||||
).toEqual(['low', 'medium', 'high'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Case sensitivity', () => {
|
||||
it('should handle case insensitive model IDs', () => {
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'GPT-5.1' }))).toEqual([
|
||||
'none',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'O3-MINI' }))).toEqual(['low', 'medium', 'high'])
|
||||
expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'Gemini-2.5-Flash-Latest' }))).toEqual([
|
||||
'none',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'auto'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Integration with MODEL_SUPPORTED_OPTIONS', () => {
|
||||
it('should return values that match MODEL_SUPPORTED_OPTIONS configuration', () => {
|
||||
// Verify that returned values match the configuration
|
||||
const model = createModel({ id: 'o3' })
|
||||
const result = getModelSupportedReasoningEffortOptions(model)
|
||||
expect(result).toEqual(MODEL_SUPPORTED_OPTIONS.o)
|
||||
|
||||
const gpt5Model = createModel({ id: 'gpt-5' })
|
||||
const gpt5Result = getModelSupportedReasoningEffortOptions(gpt5Model)
|
||||
expect(gpt5Result).toEqual(MODEL_SUPPORTED_OPTIONS.gpt5)
|
||||
|
||||
const geminiModel = createModel({ id: 'gemini-2.5-flash-latest' })
|
||||
const geminiResult = getModelSupportedReasoningEffortOptions(geminiModel)
|
||||
expect(geminiResult).toEqual(MODEL_SUPPORTED_OPTIONS.gemini)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type {
|
||||
Model,
|
||||
ReasoningEffortConfig,
|
||||
ReasoningEffortOption,
|
||||
SystemProviderId,
|
||||
ThinkingModelType,
|
||||
ThinkingOptionConfig
|
||||
@ -28,7 +29,7 @@ export const REASONING_REGEX =
|
||||
|
||||
// 模型类型到支持的reasoning_effort的映射表
|
||||
// TODO: refactor this. too many identical options
|
||||
export const MODEL_SUPPORTED_REASONING_EFFORT: ReasoningEffortConfig = {
|
||||
export const MODEL_SUPPORTED_REASONING_EFFORT = {
|
||||
default: ['low', 'medium', 'high'] as const,
|
||||
o: ['low', 'medium', 'high'] as const,
|
||||
openai_deep_research: ['medium'] as const,
|
||||
@ -54,7 +55,7 @@ export const MODEL_SUPPORTED_REASONING_EFFORT: ReasoningEffortConfig = {
|
||||
zhipu: ['auto'] as const,
|
||||
perplexity: ['low', 'medium', 'high'] as const,
|
||||
deepseek_hybrid: ['auto'] as const
|
||||
} as const
|
||||
} as const satisfies ReasoningEffortConfig
|
||||
|
||||
// 模型类型到支持选项的映射表
|
||||
export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = {
|
||||
@ -166,6 +167,64 @@ export const getThinkModelType = (model: Model): ThinkingModelType => {
|
||||
}
|
||||
}
|
||||
|
||||
const _getModelSupportedReasoningEffortOptions = (model: Model): ReasoningEffortOption[] | undefined => {
|
||||
if (!isSupportedReasoningEffortModel(model) && !isSupportedThinkingTokenModel(model)) {
|
||||
return undefined
|
||||
}
|
||||
// use private function to avoid redundant function calling
|
||||
const thinkingType = _getThinkModelType(model)
|
||||
return MODEL_SUPPORTED_OPTIONS[thinkingType]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the supported reasoning effort options for a given model.
|
||||
*
|
||||
* This function determines which reasoning effort levels a model supports based on its type.
|
||||
* It works with models that support either `reasoning_effort` parameter (like OpenAI o-series)
|
||||
* or thinking token control (like Claude, Gemini, Qwen, etc.).
|
||||
*
|
||||
* The function implements a fallback mechanism: it first checks the model's `id`, and if that
|
||||
* doesn't match any known patterns, it falls back to checking the model's `name`.
|
||||
*
|
||||
* @param model - The model to check for reasoning effort support. Can be undefined or null.
|
||||
* @returns An array of supported reasoning effort options, or undefined if:
|
||||
* - The model is null/undefined
|
||||
* - The model doesn't support reasoning effort or thinking tokens
|
||||
*
|
||||
* @example
|
||||
* // OpenAI o-series models support low, medium, high
|
||||
* getModelSupportedReasoningEffortOptions({ id: 'o3-mini', ... })
|
||||
* // Returns: ['low', 'medium', 'high']
|
||||
*
|
||||
* @example
|
||||
* // GPT-5.1 models support none, low, medium, high
|
||||
* getModelSupportedReasoningEffortOptions({ id: 'gpt-5.1', ... })
|
||||
* // Returns: ['none', 'low', 'medium', 'high']
|
||||
*
|
||||
* @example
|
||||
* // Gemini Flash models support none, low, medium, high, auto
|
||||
* getModelSupportedReasoningEffortOptions({ id: 'gemini-2.5-flash-latest', ... })
|
||||
* // Returns: ['none', 'low', 'medium', 'high', 'auto']
|
||||
*
|
||||
* @example
|
||||
* // Non-reasoning models return undefined
|
||||
* getModelSupportedReasoningEffortOptions({ id: 'gpt-4o', ... })
|
||||
* // Returns: undefined
|
||||
*
|
||||
* @example
|
||||
* // Name fallback when id doesn't match
|
||||
* getModelSupportedReasoningEffortOptions({ id: 'custom-id', name: 'gpt-5.1', ... })
|
||||
* // Returns: ['none', 'low', 'medium', 'high']
|
||||
*/
|
||||
export const getModelSupportedReasoningEffortOptions = (
|
||||
model: Model | undefined | null
|
||||
): ReasoningEffortOption[] | undefined => {
|
||||
if (!model) return undefined
|
||||
|
||||
const { idResult, nameResult } = withModelIdAndNameAsId(model, _getModelSupportedReasoningEffortOptions)
|
||||
return idResult ?? nameResult
|
||||
}
|
||||
|
||||
function _isSupportedThinkingTokenModel(model: Model): boolean {
|
||||
// Specifically for DeepSeek V3.1. White list for now
|
||||
if (isDeepSeekHybridInferenceModel(model)) {
|
||||
@ -201,12 +260,14 @@ function _isSupportedThinkingTokenModel(model: Model): boolean {
|
||||
}
|
||||
|
||||
/** 用于判断是否支持控制思考,但不一定以reasoning_effort的方式 */
|
||||
// TODO: rename it
|
||||
export function isSupportedThinkingTokenModel(model?: Model): boolean {
|
||||
if (!model) return false
|
||||
const { idResult, nameResult } = withModelIdAndNameAsId(model, _isSupportedThinkingTokenModel)
|
||||
return idResult || nameResult
|
||||
}
|
||||
|
||||
// TODO: it should be merged in isSupportedThinkingTokenModel
|
||||
export function isSupportedReasoningEffortModel(model?: Model): boolean {
|
||||
if (!model) {
|
||||
return false
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
MAX_CONTEXT_COUNT,
|
||||
UNLIMITED_CONTEXT_COUNT
|
||||
} from '@renderer/config/constant'
|
||||
import { getModelSupportedReasoningEffortOptions } from '@renderer/config/models'
|
||||
import { isQwenMTModel } from '@renderer/config/models/qwen'
|
||||
import { UNKNOWN } from '@renderer/config/translate'
|
||||
import { getStoreProviders } from '@renderer/hooks/useStore'
|
||||
@ -54,7 +55,11 @@ export function getDefaultAssistant(): Assistant {
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultTranslateAssistant(targetLanguage: TranslateLanguage, text: string): TranslateAssistant {
|
||||
export function getDefaultTranslateAssistant(
|
||||
targetLanguage: TranslateLanguage,
|
||||
text: string,
|
||||
_settings?: Partial<AssistantSettings>
|
||||
): TranslateAssistant {
|
||||
const model = getTranslateModel()
|
||||
const assistant: Assistant = getDefaultAssistant()
|
||||
|
||||
@ -68,9 +73,12 @@ export function getDefaultTranslateAssistant(targetLanguage: TranslateLanguage,
|
||||
throw new Error('Unknown target language')
|
||||
}
|
||||
|
||||
const reasoningEffort = getModelSupportedReasoningEffortOptions(model)?.[0]
|
||||
const settings = {
|
||||
temperature: 0.7
|
||||
}
|
||||
temperature: 0.7,
|
||||
reasoning_effort: reasoningEffort,
|
||||
..._settings
|
||||
} satisfies Partial<AssistantSettings>
|
||||
|
||||
const getTranslateContent = (model: Model, text: string, targetLanguage: TranslateLanguage): string => {
|
||||
if (isQwenMTModel(model)) {
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { db } from '@renderer/databases'
|
||||
import type {
|
||||
AssistantSettings,
|
||||
CustomTranslateLanguage,
|
||||
FetchChatCompletionRequestOptions,
|
||||
ReasoningEffortOption,
|
||||
TranslateHistory,
|
||||
TranslateLanguage,
|
||||
TranslateLanguageCode
|
||||
@ -20,6 +22,10 @@ import { getDefaultTranslateAssistant } from './AssistantService'
|
||||
|
||||
const logger = loggerService.withContext('TranslateService')
|
||||
|
||||
type TranslateOptions = {
|
||||
reasoningEffort: ReasoningEffortOption
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译文本到目标语言
|
||||
* @param text - 需要翻译的文本内容
|
||||
@ -33,10 +39,14 @@ export const translateText = async (
|
||||
text: string,
|
||||
targetLanguage: TranslateLanguage,
|
||||
onResponse?: (text: string, isComplete: boolean) => void,
|
||||
abortKey?: string
|
||||
abortKey?: string,
|
||||
options?: TranslateOptions
|
||||
) => {
|
||||
let abortError
|
||||
const assistant = getDefaultTranslateAssistant(targetLanguage, text)
|
||||
const assistantSettings: Partial<AssistantSettings> | undefined = options
|
||||
? { reasoning_effort: options?.reasoningEffort }
|
||||
: undefined
|
||||
const assistant = getDefaultTranslateAssistant(targetLanguage, text, assistantSettings)
|
||||
|
||||
const signal = abortKey ? readyToAbort(abortKey) : undefined
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user