feat: Retain LLM Configuration Settings When Changing Model (#21247)

This commit is contained in:
Kalo Chin
2025-07-01 12:32:46 +09:00
committed by GitHub
parent b8b9c3a783
commit 1b99e44e99
3 changed files with 128 additions and 5 deletions

View File

@@ -80,6 +80,8 @@ import {
import PluginDependency from '@/app/components/workflow/plugin-dependency' import PluginDependency from '@/app/components/workflow/plugin-dependency'
import { supportFunctionCall } from '@/utils/tool-call' import { supportFunctionCall } from '@/utils/tool-call'
import { MittProvider } from '@/context/mitt-context' import { MittProvider } from '@/context/mitt-context'
import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params'
import Toast from '@/app/components/base/toast'
type PublishConfig = { type PublishConfig = {
modelConfig: ModelConfig modelConfig: ModelConfig
@@ -453,7 +455,21 @@ const Configuration: FC = () => {
...visionConfig, ...visionConfig,
enabled: supportVision, enabled: supportVision,
}, true) }, true)
setCompletionParams({})
try {
const { params: filtered, removedDetails } = await fetchAndMergeValidCompletionParams(
provider,
modelId,
completionParams,
)
if (Object.keys(removedDetails).length)
Toast.notify({ type: 'warning', message: `${t('common.modelProvider.parametersInvalidRemoved')}: ${Object.entries(removedDetails).map(([k, reason]) => `${k} (${reason})`).join(', ')}` })
setCompletionParams(filtered)
}
catch (e) {
Toast.notify({ type: 'error', message: t('common.error') })
setCompletionParams({})
}
} }
const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision) const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision)

View File

@@ -19,6 +19,8 @@ import Editor from '@/app/components/workflow/nodes/_base/components/prompt/edit
import StructureOutput from './components/structure-output' import StructureOutput from './components/structure-output'
import Switch from '@/app/components/base/switch' import Switch from '@/app/components/base/switch'
import { RiAlertFill, RiQuestionLine } from '@remixicon/react' import { RiAlertFill, RiQuestionLine } from '@remixicon/react'
import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params'
import Toast from '@/app/components/base/toast'
const i18nPrefix = 'workflow.nodes.llm' const i18nPrefix = 'workflow.nodes.llm'
@@ -68,10 +70,27 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
modelId: string modelId: string
mode?: string mode?: string
}) => { }) => {
handleCompletionParamsChange({}) (async () => {
handleModelChanged(model) try {
// eslint-disable-next-line react-hooks/exhaustive-deps const { params: filtered, removedDetails } = await fetchAndMergeValidCompletionParams(
}, []) model.provider,
model.modelId,
inputs.model.completion_params,
)
const keys = Object.keys(removedDetails)
if (keys.length)
Toast.notify({ type: 'warning', message: `${t('common.modelProvider.parametersInvalidRemoved')}: ${keys.map(k => `${k} (${removedDetails[k]})`).join(', ')}` })
handleCompletionParamsChange(filtered)
}
catch (e) {
Toast.notify({ type: 'error', message: t('common.error') })
handleCompletionParamsChange({})
}
finally {
handleModelChanged(model)
}
})()
}, [inputs.model.completion_params])
return ( return (
<div className='mt-2'> <div className='mt-2'>

View File

@@ -0,0 +1,88 @@
import type { FormValue, ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations'
export const mergeValidCompletionParams = (
oldParams: FormValue | undefined,
rules: ModelParameterRule[],
): { params: FormValue; removedDetails: Record<string, string> } => {
if (!oldParams || Object.keys(oldParams).length === 0)
return { params: {}, removedDetails: {} }
const acceptedKeys = new Set(rules.map(r => r.name))
const ruleMap: Record<string, ModelParameterRule> = {}
rules.forEach((r) => {
ruleMap[r.name] = r
})
const nextParams: FormValue = {}
const removedDetails: Record<string, string> = {}
Object.entries(oldParams).forEach(([key, value]) => {
if (!acceptedKeys.has(key)) {
removedDetails[key] = 'unsupported'
return
}
const rule = ruleMap[key]
if (!rule) {
removedDetails[key] = 'unsupported'
return
}
switch (rule.type) {
case 'int':
case 'float': {
if (typeof value !== 'number') {
removedDetails[key] = 'invalid type'
return
}
const min = rule.min ?? Number.NEGATIVE_INFINITY
const max = rule.max ?? Number.POSITIVE_INFINITY
if (value < min || value > max) {
removedDetails[key] = `out of range (${min}-${max})`
return
}
nextParams[key] = value
return
}
case 'boolean': {
if (typeof value !== 'boolean') {
removedDetails[key] = 'invalid type'
return
}
nextParams[key] = value
return
}
case 'string':
case 'text': {
if (typeof value !== 'string') {
removedDetails[key] = 'invalid type'
return
}
if (Array.isArray(rule.options) && rule.options.length) {
if (!(rule.options as string[]).includes(value)) {
removedDetails[key] = 'unsupported option'
return
}
}
nextParams[key] = value
return
}
default: {
removedDetails[key] = `unsupported rule type: ${(rule as any)?.type ?? 'unknown'}`
}
}
})
return { params: nextParams, removedDetails }
}
export const fetchAndMergeValidCompletionParams = async (
provider: string,
modelId: string,
oldParams: FormValue | undefined,
): Promise<{ params: FormValue; removedDetails: Record<string, string> }> => {
const { fetchModelParameterRules } = await import('@/service/common')
const url = `/workspaces/current/model-providers/${provider}/models/parameter-rules?model=${modelId}`
const { data: parameterRules } = await fetchModelParameterRules(url)
return mergeValidCompletionParams(oldParams, parameterRules ?? [])
}