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:
Phantom 2025-12-15 15:43:00 +08:00 committed by GitHub
parent 4d3d5ae4ce
commit 71df9d61fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 448 additions and 20 deletions

View File

@ -10,7 +10,7 @@ import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
import { import {
findTokenLimit, findTokenLimit,
GEMINI_FLASH_MODEL_REGEX, GEMINI_FLASH_MODEL_REGEX,
getThinkModelType, getModelSupportedReasoningEffortOptions,
isDeepSeekHybridInferenceModel, isDeepSeekHybridInferenceModel,
isDoubaoThinkingAutoModel, isDoubaoThinkingAutoModel,
isGPT5SeriesModel, isGPT5SeriesModel,
@ -33,7 +33,6 @@ import {
isSupportedThinkingTokenQwenModel, isSupportedThinkingTokenQwenModel,
isSupportedThinkingTokenZhipuModel, isSupportedThinkingTokenZhipuModel,
isVisionModel, isVisionModel,
MODEL_SUPPORTED_REASONING_EFFORT,
ZHIPU_RESULT_TOKENS ZHIPU_RESULT_TOKENS
} from '@renderer/config/models' } from '@renderer/config/models'
import { mapLanguageToQwenMTModel } from '@renderer/config/translate' import { mapLanguageToQwenMTModel } from '@renderer/config/translate'
@ -304,16 +303,15 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
// Grok models/Perplexity models/OpenAI models // Grok models/Perplexity models/OpenAI models
if (isSupportedReasoningEffortModel(model)) { if (isSupportedReasoningEffortModel(model)) {
// 检查模型是否支持所选选项 // 检查模型是否支持所选选项
const modelType = getThinkModelType(model) const supportedOptions = getModelSupportedReasoningEffortOptions(model)
const supportedOptions = MODEL_SUPPORTED_REASONING_EFFORT[modelType] if (supportedOptions?.includes(reasoningEffort)) {
if (supportedOptions.includes(reasoningEffort)) {
return { return {
reasoning_effort: reasoningEffort reasoning_effort: reasoningEffort
} }
} else { } else {
// 如果不支持fallback到第一个支持的值 // 如果不支持fallback到第一个支持的值
return { return {
reasoning_effort: supportedOptions[0] reasoning_effort: supportedOptions?.[0]
} }
} }
} }

View File

@ -8,7 +8,7 @@ import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
import { import {
findTokenLimit, findTokenLimit,
GEMINI_FLASH_MODEL_REGEX, GEMINI_FLASH_MODEL_REGEX,
getThinkModelType, getModelSupportedReasoningEffortOptions,
isDeepSeekHybridInferenceModel, isDeepSeekHybridInferenceModel,
isDoubaoSeedAfter251015, isDoubaoSeedAfter251015,
isDoubaoThinkingAutoModel, isDoubaoThinkingAutoModel,
@ -30,8 +30,7 @@ import {
isSupportedThinkingTokenHunyuanModel, isSupportedThinkingTokenHunyuanModel,
isSupportedThinkingTokenModel, isSupportedThinkingTokenModel,
isSupportedThinkingTokenQwenModel, isSupportedThinkingTokenQwenModel,
isSupportedThinkingTokenZhipuModel, isSupportedThinkingTokenZhipuModel
MODEL_SUPPORTED_REASONING_EFFORT
} from '@renderer/config/models' } from '@renderer/config/models'
import { getStoreSetting } from '@renderer/hooks/useSettings' import { getStoreSetting } from '@renderer/hooks/useSettings'
import { getAssistantSettings, getProviderByModel } from '@renderer/services/AssistantService' 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 // Grok models/Perplexity models/OpenAI models, use reasoning_effort
if (isSupportedReasoningEffortModel(model)) { if (isSupportedReasoningEffortModel(model)) {
// 检查模型是否支持所选选项 // 检查模型是否支持所选选项
const modelType = getThinkModelType(model) const supportedOptions = getModelSupportedReasoningEffortOptions(model)
const supportedOptions = MODEL_SUPPORTED_REASONING_EFFORT[modelType] if (supportedOptions?.includes(reasoningEffort)) {
if (supportedOptions.includes(reasoningEffort)) {
return { return {
reasoningEffort reasoningEffort
} }
} else { } else {
// 如果不支持fallback到第一个支持的值 // 如果不支持fallback到第一个支持的值
return { return {
reasoningEffort: supportedOptions[0] reasoningEffort: supportedOptions?.[0]
} }
} }
} }

View File

@ -5,6 +5,7 @@ import { isEmbeddingModel, isRerankModel } from '../embedding'
import { isOpenAIReasoningModel, isSupportedReasoningEffortOpenAIModel } from '../openai' import { isOpenAIReasoningModel, isSupportedReasoningEffortOpenAIModel } from '../openai'
import { import {
findTokenLimit, findTokenLimit,
getModelSupportedReasoningEffortOptions,
getThinkModelType, getThinkModelType,
isClaude4SeriesModel, isClaude4SeriesModel,
isClaude45ReasoningModel, isClaude45ReasoningModel,
@ -1651,3 +1652,355 @@ describe('isGemini3ThinkingTokenModel', () => {
).toBe(false) ).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)
})
})
})

View File

@ -1,6 +1,7 @@
import type { import type {
Model, Model,
ReasoningEffortConfig, ReasoningEffortConfig,
ReasoningEffortOption,
SystemProviderId, SystemProviderId,
ThinkingModelType, ThinkingModelType,
ThinkingOptionConfig ThinkingOptionConfig
@ -28,7 +29,7 @@ export const REASONING_REGEX =
// 模型类型到支持的reasoning_effort的映射表 // 模型类型到支持的reasoning_effort的映射表
// TODO: refactor this. too many identical options // 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, default: ['low', 'medium', 'high'] as const,
o: ['low', 'medium', 'high'] as const, o: ['low', 'medium', 'high'] as const,
openai_deep_research: ['medium'] as const, openai_deep_research: ['medium'] as const,
@ -54,7 +55,7 @@ export const MODEL_SUPPORTED_REASONING_EFFORT: ReasoningEffortConfig = {
zhipu: ['auto'] as const, zhipu: ['auto'] as const,
perplexity: ['low', 'medium', 'high'] as const, perplexity: ['low', 'medium', 'high'] as const,
deepseek_hybrid: ['auto'] as const deepseek_hybrid: ['auto'] as const
} as const } as const satisfies ReasoningEffortConfig
// 模型类型到支持选项的映射表 // 模型类型到支持选项的映射表
export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = { 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 { function _isSupportedThinkingTokenModel(model: Model): boolean {
// Specifically for DeepSeek V3.1. White list for now // Specifically for DeepSeek V3.1. White list for now
if (isDeepSeekHybridInferenceModel(model)) { if (isDeepSeekHybridInferenceModel(model)) {
@ -201,12 +260,14 @@ function _isSupportedThinkingTokenModel(model: Model): boolean {
} }
/** 用于判断是否支持控制思考但不一定以reasoning_effort的方式 */ /** 用于判断是否支持控制思考但不一定以reasoning_effort的方式 */
// TODO: rename it
export function isSupportedThinkingTokenModel(model?: Model): boolean { export function isSupportedThinkingTokenModel(model?: Model): boolean {
if (!model) return false if (!model) return false
const { idResult, nameResult } = withModelIdAndNameAsId(model, _isSupportedThinkingTokenModel) const { idResult, nameResult } = withModelIdAndNameAsId(model, _isSupportedThinkingTokenModel)
return idResult || nameResult return idResult || nameResult
} }
// TODO: it should be merged in isSupportedThinkingTokenModel
export function isSupportedReasoningEffortModel(model?: Model): boolean { export function isSupportedReasoningEffortModel(model?: Model): boolean {
if (!model) { if (!model) {
return false return false

View File

@ -6,6 +6,7 @@ import {
MAX_CONTEXT_COUNT, MAX_CONTEXT_COUNT,
UNLIMITED_CONTEXT_COUNT UNLIMITED_CONTEXT_COUNT
} from '@renderer/config/constant' } from '@renderer/config/constant'
import { getModelSupportedReasoningEffortOptions } from '@renderer/config/models'
import { isQwenMTModel } from '@renderer/config/models/qwen' import { isQwenMTModel } from '@renderer/config/models/qwen'
import { UNKNOWN } from '@renderer/config/translate' import { UNKNOWN } from '@renderer/config/translate'
import { getStoreProviders } from '@renderer/hooks/useStore' 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 model = getTranslateModel()
const assistant: Assistant = getDefaultAssistant() const assistant: Assistant = getDefaultAssistant()
@ -68,9 +73,12 @@ export function getDefaultTranslateAssistant(targetLanguage: TranslateLanguage,
throw new Error('Unknown target language') throw new Error('Unknown target language')
} }
const reasoningEffort = getModelSupportedReasoningEffortOptions(model)?.[0]
const settings = { const settings = {
temperature: 0.7 temperature: 0.7,
} reasoning_effort: reasoningEffort,
..._settings
} satisfies Partial<AssistantSettings>
const getTranslateContent = (model: Model, text: string, targetLanguage: TranslateLanguage): string => { const getTranslateContent = (model: Model, text: string, targetLanguage: TranslateLanguage): string => {
if (isQwenMTModel(model)) { if (isQwenMTModel(model)) {

View File

@ -1,8 +1,10 @@
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { db } from '@renderer/databases' import { db } from '@renderer/databases'
import type { import type {
AssistantSettings,
CustomTranslateLanguage, CustomTranslateLanguage,
FetchChatCompletionRequestOptions, FetchChatCompletionRequestOptions,
ReasoningEffortOption,
TranslateHistory, TranslateHistory,
TranslateLanguage, TranslateLanguage,
TranslateLanguageCode TranslateLanguageCode
@ -20,6 +22,10 @@ import { getDefaultTranslateAssistant } from './AssistantService'
const logger = loggerService.withContext('TranslateService') const logger = loggerService.withContext('TranslateService')
type TranslateOptions = {
reasoningEffort: ReasoningEffortOption
}
/** /**
* *
* @param text - * @param text -
@ -33,10 +39,14 @@ export const translateText = async (
text: string, text: string,
targetLanguage: TranslateLanguage, targetLanguage: TranslateLanguage,
onResponse?: (text: string, isComplete: boolean) => void, onResponse?: (text: string, isComplete: boolean) => void,
abortKey?: string abortKey?: string,
options?: TranslateOptions
) => { ) => {
let abortError 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 const signal = abortKey ? readyToAbort(abortKey) : undefined