fix: resolve issue where some models do not use modelInfo parameter (#15830)

### What problem does this PR solve?

fix: resolve issue where some models do not use modelInfo parameter

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2026-06-09 13:18:01 +08:00
committed by GitHub
parent 9c0cc77e35
commit 298a23f74c
7 changed files with 266 additions and 136 deletions

View File

@@ -1,96 +1,148 @@
import { FormFieldType } from '@/components/dynamic-form';
import { LLMFactory } from '@/constants/llm';
import type { FieldConfig, ProviderConfig } from '../types';
import { capitalize } from './utils';
import { buildModelInfoFromValues, capitalize } from './utils';
/**
* Factory configuration for local/compatible factories
* Used for scenarios after OllamaModal merge
*/
export const LocalLlmConfigs: Record<string, ProviderConfig> = {
[LLMFactory.Ollama]: buildLocalConfig(LLMFactory.Ollama, 'Ollama', [
'chat',
'embedding',
'rerank',
'image2text',
]),
[LLMFactory.Ollama]: buildLocalConfig(
LLMFactory.Ollama,
'Ollama',
['chat', 'embedding', 'rerank', 'image2text'],
undefined,
false,
undefined,
'https://github.com/infiniflow/ragflow/blob/main/docs/guides/models/deploy_local_llm.mdx',
),
[LLMFactory.Xinference]: buildLocalConfig(
LLMFactory.Xinference,
'Xinference',
['chat', 'embedding', 'rerank', 'image2text', 'speech2text', 'tts'],
'modelUid',
false,
undefined,
'https://inference.readthedocs.io/en/latest/user_guide',
),
[LLMFactory.ModelScope]: buildLocalConfig(
LLMFactory.ModelScope,
'ModelScope',
['chat'],
undefined,
false,
undefined,
'https://www.modelscope.cn/docs/model-service/API-Inference/intro',
),
[LLMFactory.LocalAI]: buildLocalConfig(
LLMFactory.LocalAI,
'LocalAI',
['chat', 'embedding', 'rerank', 'image2text'],
undefined,
false,
undefined,
'https://localai.io/docs/getting-started/models/',
),
[LLMFactory.LMStudio]: buildLocalConfig(
LLMFactory.LMStudio,
'LMStudio',
['chat', 'embedding', 'image2text'],
undefined,
false,
undefined,
'https://lmstudio.ai/docs/basics',
),
[LLMFactory.LocalAI]: buildLocalConfig(LLMFactory.LocalAI, 'LocalAI', [
'chat',
'embedding',
'rerank',
'image2text',
]),
[LLMFactory.LMStudio]: buildLocalConfig(LLMFactory.LMStudio, 'LMStudio', [
'chat',
'embedding',
'image2text',
]),
[LLMFactory.OpenAiAPICompatible]: buildLocalConfig(
LLMFactory.OpenAiAPICompatible,
'OpenAiAPICompatible',
['chat', 'embedding', 'rerank', 'image2text'],
undefined,
false,
undefined,
'https://platform.openai.com/docs/models/gpt-4',
),
[LLMFactory.RAGcon]: buildLocalConfig(
LLMFactory.RAGcon,
'RAGcon',
['chat', 'embedding', 'rerank', 'image2text', 'speech2text', 'tts'],
undefined,
false,
undefined,
'https://www.ragcon.ai/erste-schritte-mit-ragflow/',
),
[LLMFactory.RAGcon]: buildLocalConfig(LLMFactory.RAGcon, 'RAGcon', [
'chat',
'embedding',
'rerank',
'image2text',
'speech2text',
'tts',
]),
[LLMFactory.TogetherAI]: buildLocalConfig(
LLMFactory.TogetherAI,
'TogetherAI',
['chat', 'embedding', 'rerank', 'image2text'],
undefined,
false,
undefined,
'https://docs.together.ai/docs/deployment-options',
),
[LLMFactory.Replicate]: buildLocalConfig(
LLMFactory.Replicate,
'Replicate',
['chat', 'embedding', 'rerank', 'image2text'],
undefined,
false,
undefined,
'https://replicate.com/docs/topics/deployments',
),
[LLMFactory.Replicate]: buildLocalConfig(LLMFactory.Replicate, 'Replicate', [
'chat',
'embedding',
'rerank',
'image2text',
]),
[LLMFactory.OpenRouter]: buildLocalConfig(
LLMFactory.OpenRouter,
'OpenRouter',
['chat', 'image2text'],
undefined,
true,
[
{
name: 'base_url',
label: 'addLlmBaseUrl',
type: 'inputSelect',
required: false,
placeholder: 'baseUrlNameMessage',
shouldRender: 'hideWhenInstanceExists',
},
],
'https://openrouter.ai/docs',
),
[LLMFactory.HuggingFace]: buildLocalConfig(
LLMFactory.HuggingFace,
'HuggingFace',
['embedding', 'chat', 'rerank'],
undefined,
false,
undefined,
'https://huggingface.co/docs/text-embeddings-inference/quick_tour',
),
[LLMFactory.GPUStack]: buildLocalConfig(LLMFactory.GPUStack, 'GPUStack', [
'chat',
'embedding',
'rerank',
'speech2text',
'tts',
]),
[LLMFactory.VLLM]: buildLocalConfig(LLMFactory.VLLM, 'VLLM', [
'chat',
'embedding',
'rerank',
'image2text',
]),
[LLMFactory.TokenPony]: buildLocalConfig(LLMFactory.TokenPony, 'TokenPony', [
'chat',
'embedding',
'rerank',
'image2text',
]),
[LLMFactory.GPUStack]: buildLocalConfig(
LLMFactory.GPUStack,
'GPUStack',
['chat', 'embedding', 'rerank', 'speech2text', 'tts'],
undefined,
false,
undefined,
'https://docs.gpustack.ai/latest/quickstart',
),
[LLMFactory.VLLM]: buildLocalConfig(
LLMFactory.VLLM,
'VLLM',
['chat', 'embedding', 'rerank', 'image2text'],
undefined,
false,
undefined,
'https://docs.vllm.ai/en/latest/',
),
// [LLMFactory.TokenPony]: buildLocalConfig(
// LLMFactory.TokenPony,
// 'TokenPony',
// ['chat', 'embedding', 'rerank', 'image2text'],
// undefined,
// false,
// undefined,
// 'https://docs.tokenpony.cn/#/',
// ),
};
/**
@@ -102,6 +154,8 @@ function buildLocalConfig(
modelTypes: string[],
modelNameLabel?: string,
addProviderOrder = false,
customFields?: FieldConfig[],
docLink?: string,
): ProviderConfig {
const fields: FieldConfig[] = [
{
@@ -179,19 +233,28 @@ function buildLocalConfig(
shouldRender: 'modelTypeIncludesChat',
});
const customFieldMap = new Map((customFields ?? []).map((f) => [f.name, f]));
const mergedFields = fields
.map((f) => customFieldMap.get(f.name) ?? f)
.concat(
(customFields ?? []).filter(
(f) => !fields.some((ef) => ef.name === f.name),
),
);
return {
llmFactory,
title,
fields,
verifyTransform: (values, modelInfo) => ({
fields: mergedFields,
...(docLink ? { docLink, docLinkI18nKey: 'ollamaLink' } : {}),
verifyTransform: (values) => ({
apiKey: values.api_key || '',
baseUrl: values.base_url,
modelInfo,
modelInfo: buildModelInfoFromValues(values),
}),
submitTransform: (values, modelInfo) => ({
submitTransform: (values) => ({
instance_name: values.instance_name,
llm_factory: llmFactory,
model_info: modelInfo,
model_info: buildModelInfoFromValues(values),
api_base: values.base_url,
api_key: values.api_key,
...(values.provider_order

View File

@@ -1,6 +1,7 @@
import { FormFieldType } from '@/components/dynamic-form';
import { LLMFactory } from '@/constants/llm';
import type { ProviderConfig } from '../types';
import { buildModelInfoFromValues } from './utils';
/**
* Factory configuration mapping table
@@ -86,18 +87,18 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
shouldRender: 'modelTypeIncludesChat',
},
],
verifyTransform: (values, modelInfo) => ({
verifyTransform: (values) => ({
apiKey: values.api_key,
baseUrl: values.api_base,
modelInfo,
modelInfo: buildModelInfoFromValues(values),
}),
submitTransform: (values, modelInfo) => ({
submitTransform: (values) => ({
instance_name: values.instance_name,
llm_factory: LLMFactory.AzureOpenAI,
api_base: values.api_base,
api_key: values.api_key,
api_version: values.api_version,
model_info: modelInfo,
model_info: buildModelInfoFromValues(values),
}),
},
@@ -165,19 +166,19 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
validation: { min: 0 },
},
],
verifyTransform: (values, modelInfo) => ({
apiKey: JSON.stringify({
verifyTransform: (values) => ({
apiKey: {
ark_api_key: values.api_key,
endpoint_id: values.endpoint_id,
}),
modelInfo,
},
modelInfo: buildModelInfoFromValues(values),
}),
submitTransform: (values, modelInfo) => ({
submitTransform: (values) => ({
instance_name: values.instance_name,
llm_factory: LLMFactory.VolcEngine,
endpoint_id: values.endpoint_id,
ark_api_key: values.api_key,
model_info: modelInfo,
model_info: buildModelInfoFromValues(values),
}),
},
@@ -251,21 +252,21 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
validation: { min: 0, message: 'maxTokensMinMessage' },
},
],
verifyTransform: (values, modelInfo) => ({
apiKey: JSON.stringify({
verifyTransform: (values) => ({
apiKey: {
google_project_id: values.google_project_id,
google_region: values.google_region,
google_service_account_key: values.google_service_account_key,
}),
modelInfo,
},
modelInfo: buildModelInfoFromValues(values),
}),
submitTransform: (values, modelInfo) => ({
submitTransform: (values) => ({
instance_name: values.instance_name,
llm_factory: LLMFactory.GoogleCloud,
google_project_id: values.google_project_id,
google_region: values.google_region,
google_service_account_key: values.google_service_account_key,
model_info: modelInfo,
model_info: buildModelInfoFromValues(values),
}),
},
@@ -344,19 +345,19 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
validation: { message: 'TencentCloudSKMessage' },
},
],
verifyTransform: (values, modelInfo) => ({
apiKey: JSON.stringify({
verifyTransform: (values) => ({
apiKey: {
TencentCloud_sid: values.TencentCloud_sid,
TencentCloud_sk: values.TencentCloud_sk,
}),
modelInfo,
},
modelInfo: buildModelInfoFromValues(values),
}),
submitTransform: (values, modelInfo) => ({
submitTransform: (values) => ({
instance_name: values.instance_name,
llm_factory: LLMFactory.TencentCloud,
TencentCloud_sid: values.TencentCloud_sid,
TencentCloud_sk: values.TencentCloud_sk,
model_info: modelInfo,
model_info: buildModelInfoFromValues(values),
}),
},
@@ -439,19 +440,25 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
validation: { min: 0, message: 'maxTokensInvalidMessage' },
},
],
verifyTransform: (values, modelInfo) => ({
apiKey: JSON.stringify({
verifyTransform: (values) => ({
apiKey: {
spark_api_password: values.spark_api_password,
spark_app_id: values.spark_app_id,
spark_api_secret: values.spark_api_secret,
spark_api_key: values.spark_api_key,
}),
modelInfo,
},
modelInfo: buildModelInfoFromValues(values),
}),
submitTransform: (values, modelInfo) => ({
submitTransform: (values) => ({
instance_name: values.instance_name,
llm_factory: LLMFactory.XunFeiSpark,
model_info: modelInfo,
api_key: {
spark_api_password: values.spark_api_password,
spark_app_id: values.spark_app_id,
spark_api_secret: values.spark_api_secret,
spark_api_key: values.spark_api_key,
},
model_info: buildModelInfoFromValues(values),
}),
},
@@ -517,21 +524,21 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
validation: { min: 0 },
},
],
verifyTransform: (values, modelInfo) => ({
apiKey: JSON.stringify({
verifyTransform: (values) => ({
apiKey: {
yiyan_ak: values.yiyan_ak,
yiyan_sk: values.yiyan_sk,
}),
modelInfo,
},
modelInfo: buildModelInfoFromValues(values),
}),
submitTransform: (values, modelInfo) => ({
submitTransform: (values) => ({
instance_name: values.instance_name,
llm_factory: LLMFactory.BaiduYiYan,
api_key: {
yiyan_ak: values.yiyan_ak,
yiyan_sk: values.yiyan_sk,
},
model_info: modelInfo,
model_info: buildModelInfoFromValues(values),
}),
},
@@ -595,19 +602,19 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
validation: { min: 0, message: 'maxTokensInvalidMessage' },
},
],
verifyTransform: (values, modelInfo) => ({
apiKey: JSON.stringify({
verifyTransform: (values) => ({
apiKey: {
fish_audio_ak: values.fish_audio_ak,
fish_audio_refid: values.fish_audio_refid,
}),
modelInfo,
},
modelInfo: buildModelInfoFromValues(values),
}),
submitTransform: (values, modelInfo) => ({
submitTransform: (values) => ({
instance_name: values.instance_name,
llm_factory: LLMFactory.FishAudio,
fish_audio_ak: values.fish_audio_ak,
fish_audio_refid: values.fish_audio_refid,
model_info: modelInfo,
model_info: buildModelInfoFromValues(values),
}),
},
@@ -635,7 +642,7 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
},
{
name: 'opendataloader_apiserver',
label: 'opendataloaderApiserver',
label: 'baseUrl',
type: FormFieldType.Text,
required: true,
placeholder: 'opendataloaderApiserverPlaceholder',
@@ -649,7 +656,7 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
placeholder: 'apiKeyPlaceholder',
},
],
verifyTransform: (values, modelInfo) => {
verifyTransform: (values) => {
const cfg: Record<string, any> = {};
if (values.opendataloader_apiserver) {
cfg.opendataloader_apiserver = values.opendataloader_apiserver;
@@ -657,13 +664,20 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
if (values.opendataloader_api_key) {
cfg.opendataloader_api_key = values.opendataloader_api_key;
}
cfg.llm_name = values.model_name;
return {
apiKey: JSON.stringify(cfg),
apiKey: cfg,
baseUrl: values.opendataloader_apiserver,
modelInfo,
modelInfo: [
{
model_name: values.model_name,
model_type: 'ocr',
max_tokens: 0,
},
],
};
},
submitTransform: (values, modelInfo) => {
submitTransform: (values) => {
const cfg: Record<string, any> = {};
if (values.opendataloader_apiserver) {
cfg.opendataloader_apiserver = values.opendataloader_apiserver;
@@ -671,12 +685,19 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
if (values.opendataloader_api_key) {
cfg.opendataloader_api_key = values.opendataloader_api_key;
}
cfg.llm_name = values.model_name;
return {
instance_name: values.instance_name,
llm_factory: LLMFactory.OpenDataLoader,
api_key: JSON.stringify(cfg),
api_key: cfg,
api_base: '',
model_info: modelInfo,
model_info: [
{
model_name: values.model_name,
model_type: 'ocr',
max_tokens: 0,
},
],
};
},
},
@@ -734,7 +755,7 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
],
},
],
verifyTransform: (values, modelInfo) => {
verifyTransform: (values) => {
const cfg: Record<string, any> = {};
if (values.paddleocr_api_url)
cfg.paddleocr_api_url = values.paddleocr_api_url;
@@ -743,12 +764,12 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
if (values.paddleocr_algorithm)
cfg.paddleocr_algorithm = values.paddleocr_algorithm;
return {
apiKey: JSON.stringify(cfg),
apiKey: cfg,
baseUrl: values.paddleocr_api_url,
modelInfo,
modelInfo: buildModelInfoFromValues(values),
};
},
submitTransform: (values, modelInfo) => {
submitTransform: (values) => {
const cfg: Record<string, any> = {};
if (values.paddleocr_api_url)
cfg.paddleocr_api_url = values.paddleocr_api_url;
@@ -759,9 +780,9 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
return {
instance_name: values.instance_name,
llm_factory: LLMFactory.PaddleOCR,
api_key: JSON.stringify(cfg),
api_key: cfg,
api_base: '',
model_info: modelInfo,
model_info: buildModelInfoFromValues(values),
};
},
},
@@ -839,7 +860,7 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
defaultValue: true,
},
],
verifyTransform: (values, modelInfo) => {
verifyTransform: (values) => {
const cfg: Record<string, any> = { ...values };
delete cfg.instance_name;
delete cfg.model_name;
@@ -848,12 +869,12 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
delete cfg.mineru_server_url;
}
return {
apiKey: JSON.stringify(cfg),
apiKey: cfg,
baseUrl: values.mineru_apiserver,
modelInfo,
modelInfo: buildModelInfoFromValues(values),
};
},
submitTransform: (values, modelInfo) => {
submitTransform: (values) => {
const cfg: Record<string, any> = { ...values };
delete cfg.instance_name;
delete cfg.model_name;
@@ -864,9 +885,9 @@ export const ProviderConfigMap: Record<string, ProviderConfig> = {
return {
instance_name: values.instance_name,
llm_factory: LLMFactory.MinerU,
api_key: JSON.stringify(cfg),
api_key: cfg,
api_base: '',
model_info: modelInfo,
model_info: buildModelInfoFromValues(values),
};
},
},

View File

@@ -1,3 +1,5 @@
import { IModelInfo } from '@/interfaces/request/llm';
/**
* Capitalize the first letter of a string
*/
@@ -22,3 +24,34 @@ export function applyChatToImage2Text(
}
return arr;
}
/**
* Build the IModelInfo[] payload for verify/submit from the form values.
*
* Resolution order:
* 1. If `values.model_info` is a non-empty array (the picker-merged case,
* populated by the call site before invoking the transform), use it as-is.
* 2. Otherwise, assemble a single-entry array from the individual form
* fields (`model_name`, `model_type`, `max_tokens`, plus `is_tools` /
* `vision` placed under `extra.is_tools` when present).
* 3. If `model_name` is missing, return an empty array — the caller can
* decide whether to short-circuit (most providers require a model name).
*/
export const buildModelInfoFromValues = (
values: Record<string, any>,
): IModelInfo[] => {
if (Array.isArray(values.model_info) && values.model_info.length > 0) {
return values.model_info;
}
if (!values.model_name) return [];
const is_tools = values.is_tools ?? values.vision;
const entry: IModelInfo = {
model_name: values.model_name,
model_type: values.model_type ?? [],
max_tokens: values.max_tokens ?? 0,
};
if (is_tools !== undefined) {
entry.extra = { is_tools };
}
return [entry];
};

View File

@@ -164,7 +164,10 @@ export const useListModelsPicker = ({
// (empty on first load, populated on re-opens) so the backend
// sees an array shape consistent with the verify/submit payloads.
const verifyArgs = config.verifyTransform
? config.verifyTransform(values, modelInfoList)
? config.verifyTransform({
...values,
model_info: modelInfoList,
})
: { apiKey: values.api_key ?? '', baseUrl: values.base_url };
const res = await listProviderModels({
provider_name: llmFactory,

View File

@@ -87,7 +87,10 @@ export const useProviderModalActions = ({
if (!config.verifyTransform) {
return { isValid: null, logs: '' } as VerifyResult;
}
const verifyArgs = config.verifyTransform(values, modelInfoList);
const verifyArgs = config.verifyTransform({
...values,
model_info: modelInfoList,
});
const region = resolveRegionFromValues(values, baseUrlRegionMaps);
if (region !== undefined) {
verifyArgs.region = region;
@@ -133,7 +136,10 @@ export const useProviderModalActions = ({
const transformed = (
config.submitTransform
? config.submitTransform(values as Record<string, any>, modelInfoList)
? config.submitTransform({
...(values as Record<string, any>),
model_info: modelInfoList,
})
: values
) as Record<string, any>;
const region = resolveRegionFromValues(

View File

@@ -100,14 +100,12 @@ export interface ProviderConfig {
/**
* Transform form values into verify API parameters
* Used to construct api_key / base_url / region / model_info when the Verify button is clicked.
* `modelInfo` is the array of currently selected models from the list-models picker
* (one entry per checked list item). Providers without a list-models picker can ignore it.
* `modelInfo` is assembled from `values` by the transform itself: if `values.model_info`
* is already an array (the picker-merged case), it is used as-is; otherwise the transform
* falls back to assembling from individual form fields (model_name / model_type / max_tokens / is_tools).
*/
verifyTransform?: (
values: Record<string, any>,
modelInfo: IModelInfo[],
) => {
apiKey: string;
verifyTransform?: (values: Record<string, any>) => {
apiKey: string | object | Record<string, any>;
baseUrl?: string;
region?: string;
modelInfo?: IModelInfo[];
@@ -115,13 +113,9 @@ export interface ProviderConfig {
/**
* Transform form values into submit API parameters.
* Used to handle special field name mapping (e.g. volcengine's endpoint_id -> ark_api_key).
* `modelInfo` is the array of currently selected models from the list-models picker
* (one entry per checked list item). Providers without a list-models picker can ignore it.
* `modelInfo` is assembled from `values` by the transform itself (same rules as verifyTransform).
*/
submitTransform?: (
values: Record<string, any>,
modelInfo: IModelInfo[],
) => Record<string, any>;
submitTransform?: (values: Record<string, any>) => Record<string, any>;
/**
* Optional link at the bottom of the modal
* e.g. the official documentation link for Ollama-family providers

View File

@@ -86,13 +86,23 @@ const collectModelExtras = (payload: FlatPayload) => {
};
export const splitProviderPayload = (payload: FlatPayload): SplitResult => {
const {
instance_name,
llm_factory,
base_url,
api_base,
region,
model_info,
...other
} = payload;
const instancePayload = {
instance_name: payload.instance_name as string,
llm_factory: payload.llm_factory as string,
instance_name: instance_name as string,
llm_factory: llm_factory as string,
api_key: collectApiKeyExtras(payload),
base_url: (payload.base_url ?? payload.api_base) as string | undefined,
region: (payload.region as string | undefined) || 'default',
model_info: payload.model_info,
base_url: (base_url ?? api_base) as string | undefined,
region: (region as string | undefined) || 'default',
model_info: model_info,
...other,
};
const modelExtra = collectModelExtras(payload);