feat(provider): 支持在提供商设置中配置 service_tier 参数

将 service_tier 配置从全局设置迁移到提供商设置中,并添加相关 UI 和逻辑支持
This commit is contained in:
icarus 2025-08-06 17:45:53 +08:00
parent 02ea056cdd
commit 1b231aed1e
7 changed files with 109 additions and 65 deletions

View File

@ -6,10 +6,9 @@ import {
isSupportFlexServiceTierModel
} from '@renderer/config/models'
import { REFERENCE_PROMPT } from '@renderer/config/prompts'
import { isSupportServiceTierProviders } from '@renderer/config/providers'
import { getLMStudioKeepAliveTime } from '@renderer/hooks/useLMStudio'
import { getStoreSetting } from '@renderer/hooks/useSettings'
import { getAssistantSettings } from '@renderer/services/AssistantService'
import { SettingsState } from '@renderer/store/settings'
import {
Assistant,
FileTypes,
@ -21,6 +20,7 @@ import {
MemoryItem,
Model,
OpenAIServiceTier,
OpenAIServiceTiers,
Provider,
ToolCallResponse,
WebSearchProviderResponse,
@ -201,22 +201,30 @@ export abstract class BaseApiClient<
return assistantSettings?.enableTopP ? assistantSettings?.topP : undefined
}
// NOTE: 这个也许可以迁移到OpenAIBaseClient
protected getServiceTier(model: Model) {
if (!isOpenAIModel(model) || model.provider === 'github' || model.provider === 'copilot') {
if (
!isSupportServiceTierProviders(this.provider) ||
!isOpenAIModel(model) ||
model.provider === 'github' ||
model.provider === 'copilot'
) {
return undefined
}
const openAI = getStoreSetting('openAI') as SettingsState['openAI']
let serviceTier = 'auto' as OpenAIServiceTier
let serviceTier: OpenAIServiceTier = OpenAIServiceTiers.AUTO
const serviceTierSetting = this.provider.serviceTier
if (openAI && openAI?.serviceTier === 'flex') {
if (serviceTierSetting === OpenAIServiceTiers.FLEX) {
if (isSupportFlexServiceTierModel(model)) {
serviceTier = 'flex'
serviceTier = OpenAIServiceTiers.FLEX
} else {
serviceTier = 'auto'
serviceTier = OpenAIServiceTiers.AUTO
}
} else if (serviceTierSetting) {
serviceTier = serviceTierSetting
} else {
serviceTier = openAI.serviceTier
// undefined 时使用默认值 auto
}
return serviceTier

View File

@ -258,7 +258,6 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
models: SYSTEM_MODELS.openai,
isSystem: true,
enabled: false,
isSupportServiceTier: true,
serviceTier: OpenAIServiceTiers.AUTO
},
'azure-openai': {
@ -415,9 +414,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
apiHost: 'https://api.groq.com/openai',
models: SYSTEM_MODELS.groq,
isSystem: true,
enabled: false,
isSupportServiceTier: true,
serviceTier: OpenAIServiceTiers.DEFAULT
enabled: false
},
together: {
id: 'together',
@ -1259,38 +1256,63 @@ export const PROVIDER_CONFIG = {
}
}
const NOT_SUPPORT_ARRAY_CONTENT_PROVIDERS = ['deepseek', 'baichuan', 'minimax', 'xirang']
const NOT_SUPPORT_ARRAY_CONTENT_PROVIDERS = [
'deepseek',
'baichuan',
'minimax',
'xirang'
] as const satisfies SystemProviderId[]
/**
* message content Only for OpenAI Chat Completions API.
*/
export const isSupportArrayContentProvider = (provider: Provider) => {
return provider.isNotSupportArrayContent !== true || !NOT_SUPPORT_ARRAY_CONTENT_PROVIDERS.includes(provider.id)
return (
provider.isNotSupportArrayContent !== true ||
!NOT_SUPPORT_ARRAY_CONTENT_PROVIDERS.some((pid) => pid === provider.id)
)
}
const NOT_SUPPORT_DEVELOPER_ROLE_PROVIDERS = ['poe']
const NOT_SUPPORT_DEVELOPER_ROLE_PROVIDERS = ['poe'] as const satisfies SystemProviderId[]
/**
* developer message role Only for OpenAI API.
*/
export const isSupportDeveloperRoleProvider = (provider: Provider) => {
return provider.isNotSupportDeveloperRole !== true || !NOT_SUPPORT_DEVELOPER_ROLE_PROVIDERS.includes(provider.id)
return (
provider.isNotSupportDeveloperRole !== true ||
!NOT_SUPPORT_DEVELOPER_ROLE_PROVIDERS.some((pid) => pid === provider.id)
)
}
const NOT_SUPPORT_STREAM_OPTIONS_PROVIDERS = ['mistral']
const NOT_SUPPORT_STREAM_OPTIONS_PROVIDERS = ['mistral'] as const satisfies SystemProviderId[]
/**
* stream_options Only for OpenAI API.
*/
export const isSupportStreamOptionsProvider = (provider: Provider) => {
return provider.isNotSupportStreamOptions !== true || !NOT_SUPPORT_STREAM_OPTIONS_PROVIDERS.includes(provider.id)
return (
provider.isNotSupportStreamOptions !== true ||
!NOT_SUPPORT_STREAM_OPTIONS_PROVIDERS.some((pid) => pid === provider.id)
)
}
const SUPPORT_QWEN3_ENABLE_THINKING_PROVIDER = ['dashscope', 'modelscope']
const SUPPORT_QWEN3_ENABLE_THINKING_PROVIDER = ['dashscope', 'modelscope'] as const satisfies SystemProviderId[]
/**
* 使enable_thinking参数来控制Qwen3系列模型的思考 Only for OpenAI Chat Completions API.
*/
export const isSupportQwen3EnableThinkingProvider = (provider: Provider) => {
return SUPPORT_QWEN3_ENABLE_THINKING_PROVIDER.includes(provider.id)
return SUPPORT_QWEN3_ENABLE_THINKING_PROVIDER.some((pid) => pid === provider.id)
}
const NOT_SUPPORT_SERVICE_TIER_PROVIDERS = ['github', 'copilot'] as const satisfies SystemProviderId[]
/**
* service_tier Only for OpenAI API.
*/
export const isSupportServiceTierProviders = (provider: Provider) => {
return (
provider.isNotSupportServiceTier !== true || !NOT_SUPPORT_SERVICE_TIER_PROVIDERS.some((pid) => pid === provider.id)
)
}

View File

@ -3054,6 +3054,7 @@
"auto": "自动",
"default": "默认",
"flex": "灵活",
"priority": "优先",
"tip": "指定用于处理请求的延迟层级",
"title": "服务层级"
},
@ -3107,6 +3108,10 @@
"label": "支持 Developer Message"
},
"label": "API 设置",
"service_tier": {
"help": "该提供商是否支持配置 service_tier 参数。开启后可在对话页面的服务层级设置中调整该参数。仅限OpenAI模型",
"label": "支持 service_tier"
},
"stream_options": {
"help": "该提供商是否支持 stream_options 参数",
"label": "支持 stream_options"

View File

@ -3,11 +3,7 @@ import { HStack } from '@renderer/components/Layout'
import Scrollbar from '@renderer/components/Scrollbar'
import Selector from '@renderer/components/Selector'
import { DEFAULT_CONTEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
import {
isOpenAIModel,
isSupportedReasoningEffortOpenAIModel,
isSupportFlexServiceTierModel
} from '@renderer/config/models'
import { isOpenAIModel } from '@renderer/config/models'
import { translateLanguageOptions } from '@renderer/config/translate'
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
import { useTheme } from '@renderer/context/ThemeProvider'
@ -168,11 +164,6 @@ const SettingsTab: FC<Props> = (props) => {
const model = assistant.model || getDefaultModel()
const isOpenAI = isOpenAIModel(model)
const isOpenAIReasoning =
isSupportedReasoningEffortOpenAIModel(model) &&
!model.id.includes('o1-pro') &&
(provider.type === 'openai-response' || provider.id === 'aihubmix')
const isOpenAIFlexServiceTier = isSupportFlexServiceTierModel(model)
return (
<Container className="settings-tab">
@ -300,8 +291,8 @@ const SettingsTab: FC<Props> = (props) => {
</CollapsibleSettingGroup>
{isOpenAI && (
<OpenAISettingsGroup
isOpenAIReasoning={isOpenAIReasoning}
isSupportedFlexServiceTier={isOpenAIFlexServiceTier}
model={model}
providerId={provider.id}
SettingGroup={SettingGroup}
SettingRowTitleSmall={SettingRowTitleSmall}
/>

View File

@ -1,9 +1,11 @@
import Selector from '@renderer/components/Selector'
import { isSupportedReasoningEffortOpenAIModel, isSupportFlexServiceTierModel } from '@renderer/config/models'
import { useProvider } from '@renderer/hooks/useProvider'
import { SettingDivider, SettingRow } from '@renderer/pages/settings'
import { CollapsibleSettingGroup } from '@renderer/pages/settings/SettingGroup'
import { RootState, useAppDispatch } from '@renderer/store'
import { setOpenAIServiceTier, setOpenAISummaryText } from '@renderer/store/settings'
import { OpenAIServiceTier, OpenAISummaryText } from '@renderer/types'
import { setOpenAISummaryText } from '@renderer/store/settings'
import { Model, OpenAIServiceTier, OpenAISummaryText } from '@renderer/types'
import { Tooltip } from 'antd'
import { CircleHelp } from 'lucide-react'
import { FC, useCallback, useEffect, useMemo } from 'react'
@ -11,8 +13,8 @@ import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
interface Props {
isOpenAIReasoning: boolean
isSupportedFlexServiceTier: boolean
model: Model
providerId: string
SettingGroup: FC<{ children: React.ReactNode }>
SettingRowTitleSmall: FC<{ children: React.ReactNode }>
}
@ -24,17 +26,20 @@ const FALL_BACK_SERVICE_TIER: Record<OpenAIServiceTier, OpenAIServiceTier> = {
priority: 'priority'
}
const OpenAISettingsGroup: FC<Props> = ({
isOpenAIReasoning,
isSupportedFlexServiceTier,
SettingGroup,
SettingRowTitleSmall
}) => {
const OpenAISettingsGroup: FC<Props> = ({ model, providerId, SettingGroup, SettingRowTitleSmall }) => {
const { t } = useTranslation()
const { provider, updateProvider } = useProvider(providerId)
const summaryText = useSelector((state: RootState) => state.settings.openAI.summaryText)
const serviceTierMode = useSelector((state: RootState) => state.settings.openAI.serviceTier)
const serviceTierMode = provider.serviceTier
const dispatch = useAppDispatch()
const isOpenAIReasoning =
isSupportedReasoningEffortOpenAIModel(model) &&
!model.id.includes('o1-pro') &&
(provider.type === 'openai-response' || provider.id === 'aihubmix')
const isSupportServiceTier = !provider.isNotSupportServiceTier
const isSupportedFlexServiceTier = isSupportFlexServiceTierModel(model)
const setSummaryText = useCallback(
(value: OpenAISummaryText) => {
dispatch(setOpenAISummaryText(value))
@ -44,9 +49,9 @@ const OpenAISettingsGroup: FC<Props> = ({
const setServiceTierMode = useCallback(
(value: OpenAIServiceTier) => {
dispatch(setOpenAIServiceTier(value))
updateProvider({ serviceTier: value })
},
[dispatch]
[updateProvider]
)
const summaryTextOptions = [
@ -97,24 +102,31 @@ const OpenAISettingsGroup: FC<Props> = ({
}
}, [serviceTierMode, serviceTierOptions, setServiceTierMode])
if (!isOpenAIReasoning && !isSupportServiceTier) {
return null
}
return (
<CollapsibleSettingGroup title={t('settings.openai.title')} defaultExpanded={true}>
<SettingGroup>
<SettingRow>
<SettingRowTitleSmall>
{t('settings.openai.service_tier.title')}{' '}
<Tooltip title={t('settings.openai.service_tier.tip')}>
<CircleHelp size={14} style={{ marginLeft: 4 }} color="var(--color-text-2)" />
</Tooltip>
</SettingRowTitleSmall>
<Selector
value={serviceTierMode}
onChange={(value) => {
setServiceTierMode(value as OpenAIServiceTier)
}}
options={serviceTierOptions}
/>
</SettingRow>
{isSupportServiceTier && (
<SettingRow>
<SettingRowTitleSmall>
{t('settings.openai.service_tier.title')}{' '}
<Tooltip title={t('settings.openai.service_tier.tip')}>
<CircleHelp size={14} style={{ marginLeft: 4 }} color="var(--color-text-2)" />
</Tooltip>
</SettingRowTitleSmall>
<Selector
value={serviceTierMode}
onChange={(value) => {
setServiceTierMode(value as OpenAIServiceTier)
}}
options={serviceTierOptions}
placeholder={t('settings.openai.service_tier.auto')}
/>
</SettingRow>
)}
{isOpenAIReasoning && (
<>
<SettingDivider />

View File

@ -59,6 +59,15 @@ const ApiOptionsSettings = ({ providerId }: Props) => {
updateProviderTransition({ ...provider, isNotSupportArrayContent: !checked })
},
checked: !provider.isNotSupportArrayContent
},
{
key: 'openai_service_tier',
label: t('settings.provider.api.options.service_tier.label'),
tip: t('settings.provider.api.options.service_tier.help'),
onChange: (checked: boolean) => {
updateProviderTransition({ ...provider, isNotSupportArrayContent: !checked })
},
checked: !provider.isNotSupportServiceTier
}
],
[t, provider, updateProviderTransition]

View File

@ -186,6 +186,7 @@ export interface SettingsState {
// OpenAI
openAI: {
summaryText: OpenAISummaryText
/** @deprecated 现在该设置迁移到Provider对象中 */
serviceTier: OpenAIServiceTier
}
// Notification
@ -759,9 +760,6 @@ const settingsSlice = createSlice({
setOpenAISummaryText: (state, action: PayloadAction<OpenAISummaryText>) => {
state.openAI.summaryText = action.payload
},
setOpenAIServiceTier: (state, action: PayloadAction<OpenAIServiceTier>) => {
state.openAI.serviceTier = action.payload
},
setNotificationSettings: (state, action: PayloadAction<SettingsState['notification']>) => {
state.notification = action.payload
},
@ -925,7 +923,6 @@ export const {
setEnableBackspaceDeleteModel,
setDisableHardwareAcceleration,
setOpenAISummaryText,
setOpenAIServiceTier,
setNotificationSettings,
// Local backup settings
setLocalBackupDir,