diff --git a/web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/local-llm-configs.ts b/web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/local-llm-configs.ts index 5f175c3874..8ccbe84833 100644 --- a/web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/local-llm-configs.ts +++ b/web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/local-llm-configs.ts @@ -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 = { - [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 diff --git a/web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/provider-config-map.ts b/web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/provider-config-map.ts index ce18a048f5..77a0170c09 100644 --- a/web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/provider-config-map.ts +++ b/web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/provider-config-map.ts @@ -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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { }, { name: 'opendataloader_apiserver', - label: 'opendataloaderApiserver', + label: 'baseUrl', type: FormFieldType.Text, required: true, placeholder: 'opendataloaderApiserverPlaceholder', @@ -649,7 +656,7 @@ export const ProviderConfigMap: Record = { placeholder: 'apiKeyPlaceholder', }, ], - verifyTransform: (values, modelInfo) => { + verifyTransform: (values) => { const cfg: Record = {}; if (values.opendataloader_apiserver) { cfg.opendataloader_apiserver = values.opendataloader_apiserver; @@ -657,13 +664,20 @@ export const ProviderConfigMap: Record = { 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 = {}; if (values.opendataloader_apiserver) { cfg.opendataloader_apiserver = values.opendataloader_apiserver; @@ -671,12 +685,19 @@ export const ProviderConfigMap: Record = { 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 = { ], }, ], - verifyTransform: (values, modelInfo) => { + verifyTransform: (values) => { const cfg: Record = {}; if (values.paddleocr_api_url) cfg.paddleocr_api_url = values.paddleocr_api_url; @@ -743,12 +764,12 @@ export const ProviderConfigMap: Record = { 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 = {}; if (values.paddleocr_api_url) cfg.paddleocr_api_url = values.paddleocr_api_url; @@ -759,9 +780,9 @@ export const ProviderConfigMap: Record = { 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 = { defaultValue: true, }, ], - verifyTransform: (values, modelInfo) => { + verifyTransform: (values) => { const cfg: Record = { ...values }; delete cfg.instance_name; delete cfg.model_name; @@ -848,12 +869,12 @@ export const ProviderConfigMap: Record = { 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 = { ...values }; delete cfg.instance_name; delete cfg.model_name; @@ -864,9 +885,9 @@ export const ProviderConfigMap: Record = { 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), }; }, }, diff --git a/web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/utils.ts b/web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/utils.ts index 53c3482c54..4c19db0e6f 100644 --- a/web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/utils.ts +++ b/web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/utils.ts @@ -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, +): 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]; +}; diff --git a/web/src/pages/user-setting/setting-model/modal/provider-modal/hooks/use-list-models-picker.ts b/web/src/pages/user-setting/setting-model/modal/provider-modal/hooks/use-list-models-picker.ts index 618b204833..a4480f2a76 100644 --- a/web/src/pages/user-setting/setting-model/modal/provider-modal/hooks/use-list-models-picker.ts +++ b/web/src/pages/user-setting/setting-model/modal/provider-modal/hooks/use-list-models-picker.ts @@ -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, diff --git a/web/src/pages/user-setting/setting-model/modal/provider-modal/hooks/use-provider-modal-actions.ts b/web/src/pages/user-setting/setting-model/modal/provider-modal/hooks/use-provider-modal-actions.ts index 47c08a37b2..875339478c 100644 --- a/web/src/pages/user-setting/setting-model/modal/provider-modal/hooks/use-provider-modal-actions.ts +++ b/web/src/pages/user-setting/setting-model/modal/provider-modal/hooks/use-provider-modal-actions.ts @@ -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, modelInfoList) + ? config.submitTransform({ + ...(values as Record), + model_info: modelInfoList, + }) : values ) as Record; const region = resolveRegionFromValues( diff --git a/web/src/pages/user-setting/setting-model/modal/provider-modal/types.ts b/web/src/pages/user-setting/setting-model/modal/provider-modal/types.ts index 4ebd845104..32095a238f 100644 --- a/web/src/pages/user-setting/setting-model/modal/provider-modal/types.ts +++ b/web/src/pages/user-setting/setting-model/modal/provider-modal/types.ts @@ -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, - modelInfo: IModelInfo[], - ) => { - apiKey: string; + verifyTransform?: (values: Record) => { + apiKey: string | object | Record; 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, - modelInfo: IModelInfo[], - ) => Record; + submitTransform?: (values: Record) => Record; /** * Optional link at the bottom of the modal * e.g. the official documentation link for Ollama-family providers diff --git a/web/src/pages/user-setting/setting-model/payload-utils.ts b/web/src/pages/user-setting/setting-model/payload-utils.ts index 812e4aeab1..052aef007d 100644 --- a/web/src/pages/user-setting/setting-model/payload-utils.ts +++ b/web/src/pages/user-setting/setting-model/payload-utils.ts @@ -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);