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

View File

@ -258,7 +258,6 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
models: SYSTEM_MODELS.openai, models: SYSTEM_MODELS.openai,
isSystem: true, isSystem: true,
enabled: false, enabled: false,
isSupportServiceTier: true,
serviceTier: OpenAIServiceTiers.AUTO serviceTier: OpenAIServiceTiers.AUTO
}, },
'azure-openai': { 'azure-openai': {
@ -415,9 +414,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
apiHost: 'https://api.groq.com/openai', apiHost: 'https://api.groq.com/openai',
models: SYSTEM_MODELS.groq, models: SYSTEM_MODELS.groq,
isSystem: true, isSystem: true,
enabled: false, enabled: false
isSupportServiceTier: true,
serviceTier: OpenAIServiceTiers.DEFAULT
}, },
together: { together: {
id: '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. * message content Only for OpenAI Chat Completions API.
*/ */
export const isSupportArrayContentProvider = (provider: Provider) => { 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. * developer message role Only for OpenAI API.
*/ */
export const isSupportDeveloperRoleProvider = (provider: Provider) => { 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. * stream_options Only for OpenAI API.
*/ */
export const isSupportStreamOptionsProvider = (provider: Provider) => { 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. * 使enable_thinking参数来控制Qwen3系列模型的思考 Only for OpenAI Chat Completions API.
*/ */
export const isSupportQwen3EnableThinkingProvider = (provider: Provider) => { 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": "自动", "auto": "自动",
"default": "默认", "default": "默认",
"flex": "灵活", "flex": "灵活",
"priority": "优先",
"tip": "指定用于处理请求的延迟层级", "tip": "指定用于处理请求的延迟层级",
"title": "服务层级" "title": "服务层级"
}, },
@ -3107,6 +3108,10 @@
"label": "支持 Developer Message" "label": "支持 Developer Message"
}, },
"label": "API 设置", "label": "API 设置",
"service_tier": {
"help": "该提供商是否支持配置 service_tier 参数。开启后可在对话页面的服务层级设置中调整该参数。仅限OpenAI模型",
"label": "支持 service_tier"
},
"stream_options": { "stream_options": {
"help": "该提供商是否支持 stream_options 参数", "help": "该提供商是否支持 stream_options 参数",
"label": "支持 stream_options" "label": "支持 stream_options"

View File

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

View File

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

View File

@ -59,6 +59,15 @@ const ApiOptionsSettings = ({ providerId }: Props) => {
updateProviderTransition({ ...provider, isNotSupportArrayContent: !checked }) updateProviderTransition({ ...provider, isNotSupportArrayContent: !checked })
}, },
checked: !provider.isNotSupportArrayContent 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] [t, provider, updateProviderTransition]

View File

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