fix: bad model field ref

This commit is contained in:
Junyan Qin
2026-03-13 11:47:31 +08:00
parent 6e77351eda
commit 10c716be0c
5 changed files with 74 additions and 15 deletions

View File

@@ -105,11 +105,16 @@ class LLMModelsService:
) )
) )
pipeline = result.first() pipeline = result.first()
if pipeline is not None and pipeline.config['ai']['local-agent']['model'] == '': if pipeline is not None:
pipeline_config = pipeline.config model_config = pipeline.config.get('ai', {}).get('local-agent', {}).get('model', {})
pipeline_config['ai']['local-agent']['model'] = model_data['uuid'] if not model_config.get('primary', ''):
pipeline_data = {'config': pipeline_config} pipeline_config = pipeline.config
await self.ap.pipeline_service.update_pipeline(pipeline.uuid, pipeline_data) pipeline_config['ai']['local-agent']['model'] = {
'primary': model_data['uuid'],
'fallbacks': [],
}
pipeline_data = {'config': pipeline_config}
await self.ap.pipeline_service.update_pipeline(pipeline.uuid, pipeline_data)
return model_data['uuid'] return model_data['uuid']

View File

@@ -41,7 +41,10 @@
"runner": "local-agent" "runner": "local-agent"
}, },
"local-agent": { "local-agent": {
"model": "", "model": {
"primary": "",
"fallbacks": []
},
"max-round": 10, "max-round": 10,
"prompt": [ "prompt": [
{ {

View File

@@ -194,7 +194,7 @@ def sample_query(sample_message_chain, sample_message_event, mock_adapter):
pipeline_config={ pipeline_config={
'ai': { 'ai': {
'runner': {'runner': 'local-agent'}, 'runner': {'runner': 'local-agent'},
'local-agent': {'model': 'test-model-uuid', 'prompt': 'test-prompt'}, 'local-agent': {'model': {'primary': 'test-model-uuid', 'fallbacks': []}, 'prompt': 'test-prompt'},
}, },
'output': {'misc': {'at-sender': False, 'quote-origin': False}}, 'output': {'misc': {'at-sender': False, 'quote-origin': False}},
'trigger': {'misc': {'combine-quote-message': False}}, 'trigger': {'misc': {'combine-quote-message': False}},
@@ -219,7 +219,7 @@ def sample_pipeline_config():
return { return {
'ai': { 'ai': {
'runner': {'runner': 'local-agent'}, 'runner': {'runner': 'local-agent'},
'local-agent': {'model': 'test-model-uuid', 'prompt': 'test-prompt'}, 'local-agent': {'model': {'primary': 'test-model-uuid', 'fallbacks': []}, 'prompt': 'test-prompt'},
}, },
'output': {'misc': {'at-sender': False, 'quote-origin': False}}, 'output': {'misc': {'at-sender': False, 'quote-origin': False}},
'trigger': {'misc': {'combine-quote-message': False}}, 'trigger': {'misc': {'combine-quote-message': False}},

View File

@@ -34,6 +34,35 @@ export default function DynamicFormComponent({
const previousInitialValues = useRef(initialValues); const previousInitialValues = useRef(initialValues);
const { t } = useTranslation(); const { t } = useTranslation();
// Normalize a form value according to its field type.
// This ensures legacy/malformed data (e.g. a plain string for
// model-fallback-selector) is coerced to the expected shape
// so that downstream components never crash.
const normalizeFieldValue = (
item: IDynamicFormItemSchema,
value: unknown,
): unknown => {
if (item.type === 'model-fallback-selector') {
if (value != null && typeof value === 'object' && !Array.isArray(value)) {
const obj = value as Record<string, unknown>;
return {
primary: typeof obj.primary === 'string' ? obj.primary : '',
fallbacks: Array.isArray(obj.fallbacks)
? (obj.fallbacks as unknown[]).filter(
(v): v is string => typeof v === 'string',
)
: [],
};
}
// Legacy string format or any other unexpected type
return {
primary: typeof value === 'string' ? value : '',
fallbacks: [],
};
}
return value;
};
// 根据 itemConfigList 动态生成 zod schema // 根据 itemConfigList 动态生成 zod schema
const formSchema = z.object( const formSchema = z.object(
itemConfigList.reduce( itemConfigList.reduce(
@@ -116,10 +145,10 @@ export default function DynamicFormComponent({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: itemConfigList.reduce((acc, item) => { defaultValues: itemConfigList.reduce((acc, item) => {
// 优先使用 initialValues如果没有则使用默认值 // 优先使用 initialValues如果没有则使用默认值
const value = initialValues?.[item.name] ?? item.default; const rawValue = initialValues?.[item.name] ?? item.default;
return { return {
...acc, ...acc,
[item.name]: value, [item.name]: normalizeFieldValue(item, rawValue),
}; };
}, {} as FormValues), }, {} as FormValues),
}); });
@@ -144,7 +173,8 @@ export default function DynamicFormComponent({
// 合并默认值和初始值 // 合并默认值和初始值
const mergedValues = itemConfigList.reduce( const mergedValues = itemConfigList.reduce(
(acc, item) => { (acc, item) => {
acc[item.name] = initialValues[item.name] ?? item.default; const rawValue = initialValues[item.name] ?? item.default;
acc[item.name] = normalizeFieldValue(item, rawValue) as object;
return acc; return acc;
}, },
{} as Record<string, object>, {} as Record<string, object>,

View File

@@ -348,10 +348,31 @@ export default function DynamicFormItemComponent({
{} as Record<string, LLMModel[]>, {} as Record<string, LLMModel[]>,
); );
const modelValue = field.value as { const rawModelValue = field.value;
primary: string; const modelValue: { primary: string; fallbacks: string[] } =
fallbacks: string[]; rawModelValue != null &&
}; typeof rawModelValue === 'object' &&
!Array.isArray(rawModelValue)
? {
primary:
typeof (rawModelValue as Record<string, unknown>).primary ===
'string'
? ((rawModelValue as Record<string, unknown>)
.primary as string)
: '',
fallbacks: Array.isArray(
(rawModelValue as Record<string, unknown>).fallbacks,
)
? (
(rawModelValue as Record<string, unknown>)
.fallbacks as unknown[]
).filter((v): v is string => typeof v === 'string')
: [],
}
: {
primary: typeof rawModelValue === 'string' ? rawModelValue : '',
fallbacks: [],
};
const renderModelSelect = ( const renderModelSelect = (
value: string, value: string,