feat: Add new model type for reasoning models & reasoning_effort setting (#992)

This commit is contained in:
Gutsy Yuan 2025-02-05 16:15:31 +08:00 committed by GitHub
parent 9024d48938
commit 9acae0a728
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 93 additions and 19 deletions

View File

@ -1,4 +1,4 @@
import { isEmbeddingModel, isVisionModel, isWebSearchModel } from '@renderer/config/models'
import { isEmbeddingModel, isReasoningModel, isVisionModel, isWebSearchModel } from '@renderer/config/models'
import { Model } from '@renderer/types'
import { isFreeModel } from '@renderer/utils'
import { Tag } from 'antd'
@ -29,6 +29,11 @@ const ModelTags: FC<ModelTagsProps> = ({ model, showFree = true }) => {
{t('models.embedding')}
</Tag>
)}
{isReasoningModel(model) && (
<Tag color="blue" style={{ marginLeft: 10 }}>
{t('models.reasoning')}
</Tag>
)}
</>
)
}

View File

@ -156,6 +156,8 @@ export const VISION_REGEX = new RegExp(
)
export const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview|janus/i
export const REASONING_REGEX = /^(o\d+(?:-[\w-]+)?|.*\breasoner\b.*|.*-[rR]\d+.*)$/i
export const EMBEDDING_REGEX = /(?:^text-|embed|rerank|davinci|babbage|bge-|e5-|LLM2Vec|retrieval|uae-|gte-|jina)/i
export const NOT_SUPPORTED_REGEX = /(?:^tts|rerank|whisper|speech)/i
@ -1127,6 +1129,14 @@ export function isVisionModel(model: Model): boolean {
return VISION_REGEX.test(model.id) || model.type?.includes('vision') || false
}
export function isReasoningModel(model: Model): boolean {
if (!model) {
return false
}
return REASONING_REGEX.test(model.id) || model.type?.includes('reasoning') || false
}
export function isSupportedModel(model: OpenAI.Models.Model): boolean {
if (!model) {
return false

View File

@ -40,6 +40,11 @@
"save.success": "Saved successfully",
"save.title": "Save to agent",
"search": "Search assistants...",
"settings.reasoning_effort": "Reasoning effort",
"settings.reasoning_effort.tip": "Only supports reasoning models",
"settings.reasoning_effort.low": "low",
"settings.reasoning_effort.medium": "medium",
"settings.reasoning_effort.high": "high",
"settings.auto_reset_model": "Auto Reset Model",
"settings.auto_reset_model.tip": "Automatically reset the model when a new topic is created.",
"settings.default_model": "Default Model",
@ -640,12 +645,14 @@
"select": "Select Model Types",
"text": "Text",
"vision": "Vision",
"embedding": "Embedding"
"embedding": "Embedding",
"reasoning": "Reasoning"
},
"all": "All",
"vision": "Vision",
"websearch": "WebSearch",
"free": "Free",
"reasoning": "Reasoning",
"embedding": "Embedding",
"embedding_model": "Embedding Model",
"embedding_model_tooltip": "Add in Settings->Model Provider->Manage",

View File

@ -625,12 +625,14 @@
"select": "モデルタイプを選択",
"text": "テキスト",
"vision": "画像",
"embedding": "埋め込み"
"embedding": "埋め込み",
"reasoning": "推論"
},
"all": "すべて",
"vision": "画像モデル",
"websearch": "ウェブ検索モデル",
"free": "無料モデル",
"reasoning": "推論モデル",
"embedding": "埋め込みモデル",
"embedding_model": "埋め込みモデル",
"embedding_model_tooltip": "設定->モデルサービス->管理で追加",

View File

@ -637,12 +637,14 @@
"select": "Выберите тип модели",
"text": "Текст",
"vision": "Изображение",
"embedding": "Встраиваемые"
"embedding": "Встраиваемые",
"reasoning": "Рассуждение"
},
"all": "Все",
"vision": "Визуальные модели",
"websearch": "Веб-поисковые модели",
"free": "Бесплатные модели",
"reasoning": "Модели рассуждения",
"embedding": "Встраиваемые модели",
"embedding_model": "Встраиваемые модели",
"embedding_model_tooltip": "Добавьте в настройки->модель сервиса->управление",

View File

@ -40,6 +40,11 @@
"save.success": "保存成功",
"save.title": "保存到智能体",
"search": "搜索助手",
"settings.reasoning_effort": "思维链长度",
"settings.reasoning_effort.tip": "该设置仅支持推理模型",
"settings.reasoning_effort.low": "短",
"settings.reasoning_effort.medium": "中",
"settings.reasoning_effort.high": "长",
"settings.auto_reset_model": "自动重置模型",
"settings.auto_reset_model.tip": "创建新话题时自动重置模型",
"settings.default_model": "默认模型",
@ -627,12 +632,14 @@
"select": "选择模型类型",
"text": "文本",
"vision": "图像",
"embedding": "嵌入"
"embedding": "嵌入",
"reasoning": "推理"
},
"all": "全部",
"vision": "视觉模型",
"websearch": "联网模型",
"free": "免费模型",
"reasoning": "推理模型",
"embedding": "嵌入模型",
"embedding_model": "嵌入模型",
"embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加",

View File

@ -40,6 +40,11 @@
"save.success": "儲存成功",
"save.title": "儲存到智能體",
"search": "搜尋助手...",
"settings.reasoning_effort": "思維鏈長度",
"settings.reasoning_effort.tip": "該設置僅支持推理模型",
"settings.reasoning_effort.low": "短",
"settings.reasoning_effort.medium": "中",
"settings.reasoning_effort.high": "長",
"settings.auto_reset_model": "自動重置模型",
"settings.auto_reset_model.tip": "每次新的話題時自動重置模型",
"settings.default_model": "預設模型",
@ -626,12 +631,14 @@
"select": "選擇模型類型",
"text": "文字",
"vision": "圖像",
"embedding": "嵌入"
"embedding": "嵌入",
"reasoning": "推理"
},
"all": "全部",
"vision": "視覺模型",
"websearch": "網路搜索模型",
"free": "免費模型",
"reasoning": "推理模型",
"embedding": "嵌入模型",
"embedding_model": "嵌入模型",
"embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加",

View File

@ -5,7 +5,7 @@ import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
import { SettingRow } from '@renderer/pages/settings'
import { Assistant, AssistantSettingCustomParameters, AssistantSettings } from '@renderer/types'
import { Button, Col, Divider, Input, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
import { Button, Col, Divider, Input, InputNumber, Radio, Row, Select, Slider, Switch, Tooltip } from 'antd'
import { isNull } from 'lodash'
import { FC, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -23,6 +23,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
const [enableMaxTokens, setEnableMaxTokens] = useState(assistant?.settings?.enableMaxTokens ?? false)
const [maxTokens, setMaxTokens] = useState(assistant?.settings?.maxTokens ?? 0)
const [autoResetModel, setAutoResetModel] = useState(assistant?.settings?.autoResetModel ?? false)
const [reasoningEffort, setReasoningEffort] = useState(assistant?.settings?.reasoning_effort ?? 'medium')
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
const [defaultModel, setDefaultModel] = useState(assistant?.defaultModel)
const [topP, setTopP] = useState(assistant?.settings?.topP ?? 1)
@ -43,6 +44,10 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
}
}
const onReasoningEffortChange = (value) => {
updateAssistantSettings({ reasoning_effort: value })
}
const onContextCountChange = (value) => {
if (!isNaN(value as number)) {
updateAssistantSettings({ contextCount: value })
@ -384,6 +389,26 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
/>
</SettingRow>
<Divider style={{ margin: '10px 0' }} />
<SettingRow style={{ minHeight: 30 }}>
<Label>
{t('assistants.settings.reasoning_effort')}{' '}
<Tooltip title={t('assistants.settings.reasoning_effort.tip')}>
<QuestionIcon />
</Tooltip>
</Label>
<Radio.Group
value={reasoningEffort}
buttonStyle="solid"
onChange={(e) => {
setReasoningEffort(e.target.value)
onReasoningEffortChange(e.target.value)
}}>
<Radio.Button value="low">{t('assistants.settings.reasoning_effort.low')}</Radio.Button>
<Radio.Button value="medium">{t('assistants.settings.reasoning_effort.medium')}</Radio.Button>
<Radio.Button value="high">{t('assistants.settings.reasoning_effort.high')}</Radio.Button>
</Radio.Group>
</SettingRow>
<Divider style={{ margin: '10px 0' }} />
<SettingRow style={{ minHeight: 30 }}>
<Label>{t('models.custom_parameters')}</Label>
<Button icon={<PlusOutlined />} onClick={onAddCustomParameter}>

View File

@ -9,7 +9,7 @@ import {
} from '@ant-design/icons'
import ModelTags from '@renderer/components/ModelTags'
import OAuthButton from '@renderer/components/OAuth/OAuthButton'
import { EMBEDDING_REGEX, getModelLogo, VISION_REGEX } from '@renderer/config/models'
import { EMBEDDING_REGEX, getModelLogo, REASONING_REGEX, VISION_REGEX } from '@renderer/config/models'
import { PROVIDER_CONFIG } from '@renderer/config/providers'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useAssistants, useDefaultModel } from '@renderer/hooks/useAssistant'
@ -187,7 +187,8 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
onChange={(types) => onUpdateModelTypes(model, types as ModelType[])}
options={[
{ label: t('models.type.vision'), value: 'vision', disabled: VISION_REGEX.test(model.id) },
{ label: t('models.type.embedding'), value: 'embedding', disabled: EMBEDDING_REGEX.test(model.id) }
{ label: t('models.type.embedding'), value: 'embedding', disabled: EMBEDDING_REGEX.test(model.id) },
{ label: t('models.type.reasoning'), value: 'reasoning', disabled: REASONING_REGEX.test(model.id) }
]}
/>
</div>

View File

@ -1,4 +1,4 @@
import { getOpenAIWebSearchParams, isSupportedModel, isVisionModel } from '@renderer/config/models'
import { getOpenAIWebSearchParams, isReasoningModel, isSupportedModel, isVisionModel } from '@renderer/config/models'
import { getStoreSetting } from '@renderer/hooks/useSettings'
import i18n from '@renderer/i18n'
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService'
@ -118,13 +118,7 @@ export default class OpenAIProvider extends BaseProvider {
}
private getTemperature(assistant: Assistant, model: Model) {
if (model.id.startsWith('o1') || model.id.startsWith('o3')) {
return undefined
}
if (model.provider === 'deepseek' && model.id === 'deepseek-reasoner') {
return undefined
}
if (isReasoningModel(model)) return undefined
return assistant?.settings?.temperature
}
@ -141,6 +135,18 @@ export default class OpenAIProvider extends BaseProvider {
return {}
}
private getTopP(assistant: Assistant, model: Model) {
if (isReasoningModel(model)) return undefined
return assistant?.settings?.topP
}
private getReasoningEffort(assistant: Assistant, model: Model) {
if (isReasoningModel(model)) return assistant?.settings?.reasoning_effort
return undefined
}
async completions({ messages, assistant, onChunk, onFilterMessages }: CompletionsParams): Promise<void> {
const defaultModel = getDefaultModel()
const model = assistant.model || defaultModel
@ -182,10 +188,11 @@ export default class OpenAIProvider extends BaseProvider {
Boolean
) as ChatCompletionMessageParam[],
temperature: this.getTemperature(assistant, model),
top_p: assistant?.settings?.topP,
top_p: this.getTopP(assistant, model),
max_tokens: maxTokens,
keep_alive: this.keepAliveTime,
stream: isSupportStreamOutput(),
reasoning_effort: this.getReasoningEffort(assistant, model),
...(assistant.enableWebSearch ? getOpenAIWebSearchParams(model) : {}),
...this.getProviderSpecificParameters(model),
...this.getCustomParameters(assistant)

View File

@ -38,6 +38,7 @@ export type AssistantSettings = {
defaultModel?: Model
autoResetModel: boolean
customParameters?: AssistantSettingCustomParameters[]
reasoning_effort?: 'low' | 'medium' | 'high'
}
export type Agent = Omit<Assistant, 'model'>
@ -107,7 +108,7 @@ export type Provider = {
export type ProviderType = 'openai' | 'anthropic' | 'gemini' | 'qwenlm' | 'azure-openai'
export type ModelType = 'text' | 'vision' | 'embedding'
export type ModelType = 'text' | 'vision' | 'embedding' | 'reasoning'
export type Model = {
id: string