Files
prompt-optimizer/packages/core/tests/unit/services/custom-params-migration.test.ts
linshen 006565c599 refactor(core): 实现统一参数架构并修复运行时参数丢失问题
## 核心变更

### 🔧 关键Bug修复
- 修复LLM/Image服务运行时自定义参数丢失问题
- prepareRuntimeConfig()现在支持customParamOverrides向后兼容
- 确保用户配置的自定义API头、私有开关等参数不会在运行时丢失

### 🏗️ 统一参数架构
- 新增parameter-schema.ts: 统一参数定义与安全验证
- 新增parameter-utils.ts: 参数合并、验证、拆分工具
- 实现优先级参数合并: requestOverrides > customOverrides > builtInOverrides > defaults
- 安全过滤危险参数键(__proto__, apiKey, secret等)

### 📦 服务层改进
- llm/service.ts: 添加prepareRuntimeConfig()方法支持参数迁移
- image/service.ts: 同步实现参数迁移逻辑
- model/manager.ts: 添加读时迁移和弃用警告
- 所有Adapter统一使用UnifiedParameterDefinition

### 🎨 UI组件更新
- 新增ModelParameterEditor.vue: 统一参数编辑器
- 新增ModelAdvancedSection.vue: 高级参数折叠面板
- 简化TextModelEditModal和ImageModelEditModal逻辑
- 支持类型安全的参数编辑(number, boolean, string, select等)

### ✅ 测试覆盖
- 新增parameter-schema.test.ts: 参数定义验证测试
- 新增parameter-utils.test.ts: 参数工具函数测试
- 新增custom-params-migration.test.ts: 参数迁移全流程测试
- 修复anthropic-adapter和gemini-adapter测试以匹配新架构
- 所有582个测试通过(100%通过率)

## 向后兼容性
- 完全兼容旧格式配置(customParamOverrides独立字段)
- 读时自动迁移,写时清理已弃用字段
- 保留customParamOverrides以防版本回退

## 破坏性变更
- 无(完全向后兼容)

## 文档更新
- advancedParameterDefinitions.ts添加弃用说明
- 标注v3.0弃用,v4.0移除计划
2025-10-12 22:26:58 +08:00

179 lines
5.7 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest'
import { mergeOverrides } from '../../../src/services/model/parameter-utils'
import type { UnifiedParameterDefinition } from '../../../src/services/model/parameter-schema'
describe('自定义参数迁移测试', () => {
const schema: UnifiedParameterDefinition[] = [
{
name: 'temperature',
type: 'number',
minValue: 0,
maxValue: 2,
defaultValue: 1
},
{
name: 'max_tokens',
type: 'integer',
minValue: 1,
maxValue: 40000
}
]
describe('向后兼容旧格式的 customParamOverrides', () => {
it('应该合并 customParamOverrides 和 paramOverrides', () => {
// 模拟旧数据格式:内置参数在 paramOverrides,自定义参数在 customParamOverrides
const paramOverrides = { temperature: 0.7 }
const customParamOverrides = { custom_flag: 'test_value', api_version: '2024-01' }
const merged = mergeOverrides({
schema,
includeDefaults: false,
customOverrides: customParamOverrides,
requestOverrides: paramOverrides
})
// 验证内置参数被正确处理
expect(merged.temperature).toBe(0.7)
// 验证自定义参数没有丢失
expect(merged.custom_flag).toBe('test_value')
expect(merged.api_version).toBe('2024-01')
})
it('requestOverrides 应该覆盖 customOverrides', () => {
const customParamOverrides = { custom_flag: 'old_value' }
const paramOverrides = { custom_flag: 'new_value', temperature: 0.8 }
const merged = mergeOverrides({
schema,
includeDefaults: false,
customOverrides: customParamOverrides,
requestOverrides: paramOverrides
})
// requestOverrides 优先级更高
expect(merged.custom_flag).toBe('new_value')
expect(merged.temperature).toBe(0.8)
})
it('应该过滤掉空值的自定义参数', () => {
const customParamOverrides = {
valid_param: 'value',
empty_string: '',
null_value: null,
undefined_value: undefined
}
const merged = mergeOverrides({
schema,
includeDefaults: false,
customOverrides: customParamOverrides as any
})
// 只有非空值应该被保留
expect(merged.valid_param).toBe('value')
expect(merged.empty_string).toBeUndefined()
expect(merged.null_value).toBeUndefined()
expect(merged.undefined_value).toBeUndefined()
})
it('应该拒绝危险的自定义参数键名', () => {
const customParamOverrides = {
'__proto__': 'dangerous',
'apiKey': 'should_reject',
'safe_param': 'ok'
}
const merged = mergeOverrides({
schema,
includeDefaults: false,
customOverrides: customParamOverrides
})
// 危险参数应该被过滤(不会作为自己的属性存在)
expect(Object.hasOwn(merged, '__proto__')).toBe(false)
expect(Object.hasOwn(merged, 'apiKey')).toBe(false)
// 安全参数应该保留
expect(merged.safe_param).toBe('ok')
})
})
describe('LLM Service 运行时配置准备', () => {
it('应该模拟 prepareRuntimeConfig 的行为', () => {
// 模拟一个旧格式的 TextModelConfig
const modelConfig = {
id: 'test',
name: 'Test Model',
enabled: true,
providerMeta: { id: 'test', name: 'Test' } as any,
modelMeta: {
id: 'test-model',
name: 'Test Model',
providerId: 'test',
capabilities: {},
parameterDefinitions: schema
} as any,
connectionConfig: {},
paramOverrides: { temperature: 0.7, max_tokens: 1000 },
customParamOverrides: {
custom_header: 'X-Custom-Value',
extra_param: 'important_value'
}
}
// 模拟 prepareRuntimeConfig 逻辑
const mergedOverrides = mergeOverrides({
schema: modelConfig.modelMeta.parameterDefinitions,
includeDefaults: false,
customOverrides: modelConfig.customParamOverrides,
requestOverrides: modelConfig.paramOverrides
})
// 验证运行时配置包含所有参数
expect(mergedOverrides.temperature).toBe(0.7)
expect(mergedOverrides.max_tokens).toBe(1000)
expect(mergedOverrides.custom_header).toBe('X-Custom-Value')
expect(mergedOverrides.extra_param).toBe('important_value')
})
it('应该处理已经迁移的新格式配置', () => {
// 模拟已经迁移的配置:所有参数都在 paramOverrides
const modelConfig = {
id: 'test',
name: 'Test Model',
enabled: true,
providerMeta: { id: 'test', name: 'Test' } as any,
modelMeta: {
id: 'test-model',
name: 'Test Model',
providerId: 'test',
capabilities: {},
parameterDefinitions: schema
} as any,
connectionConfig: {},
paramOverrides: {
temperature: 0.7,
max_tokens: 1000,
custom_header: 'X-Custom-Value',
extra_param: 'important_value'
},
customParamOverrides: undefined // 已迁移
}
const mergedOverrides = mergeOverrides({
schema: modelConfig.modelMeta.parameterDefinitions,
includeDefaults: false,
customOverrides: modelConfig.customParamOverrides,
requestOverrides: modelConfig.paramOverrides
})
// 新格式也应该正常工作
expect(mergedOverrides.temperature).toBe(0.7)
expect(mergedOverrides.max_tokens).toBe(1000)
expect(mergedOverrides.custom_header).toBe('X-Custom-Value')
expect(mergedOverrides.extra_param).toBe('important_value')
})
})
})