mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-28 05:11:24 +08:00
46 KiB
46 KiB
模型和供应商参数化配置实现方案
📋 项目概述
本文档详细描述了在 @packages/catalog/ 下实现模型和供应商参数化配置的完整方案,目标是将现有的硬编码逻辑重构为元数据驱动的适配器架构。
🎯 目标
主要目标
- 将硬编码的模型识别逻辑转换为 JSON 配置驱动
- 解决"同一模型在不同供应商下有差异"的问题
- 支持通过 JSON 文件在线更新新模型,无需发布代码
- 提供类型安全的配置系统(使用 JSON Schema + Zod)
痛点解决
- 当前问题:
src/renderer/src/config/models/下复杂的正则表达式和硬编码逻辑 - 期望状态:配置以 JSON 形式存在,代码中预定义 JSON Schema 解析
- 用户体验:新模型发布时用户可自动获取更新配置
🏗️ 架构设计
三层分离的元数据架构
1. Base Model Catalog (models/*.json)
├─ 模型基础信息(ID、能力、模态、限制、价格)
└─ 官方/标准配置
2. Provider Catalog (providers/*.json)
├─ 供应商特性(端点支持、内置工具、MCP支持)
└─ API 兼容性配置
3. Provider Model Overrides (overrides/*.json)
├─ 供应商对特定模型的覆盖
└─ 解决"同一模型不同供应商差异"问题
文件结构
packages/catalog/
├── src/ # 所有源代码和数据
│ ├── index.ts # 主导出文件
│ ├── schemas/ # Schema 定义
│ │ ├── index.ts # 统一导出
│ │ ├── model.schema.ts # 模型配置 Schema + Zod
│ │ ├── provider.schema.ts # 供应商配置 Schema + Zod
│ │ ├── override.schema.ts # 覆盖配置 Schema + Zod
│ │ └── common.types.ts # 通用类型定义
│ ├── data/ # 配置数据
│ │ ├── models/ # 模型配置(按供应商分组)
│ │ │ ├── anthropic.json # Anthropic 模型
│ │ │ ├── openai.json # OpenAI 模型
│ │ │ ├── google.json # Google 模型
│ │ │ ├── deepseek.json # DeepSeek 模型
│ │ │ ├── qwen.json # 通义千问模型
│ │ │ ├── doubao.json # 豆包模型
│ │ │ ├── mistral.json # Mistral 模型
│ │ │ ├── meta.json # Meta 模型
│ │ │ └── community.json # 社区模型
│ │ ├── providers/ # 供应商配置
│ │ │ ├── direct-providers.json # 直接供应商 (anthropic, openai, google)
│ │ │ ├── cloud-platforms.json # 云平台 (aws, gcp, azure)
│ │ │ ├── unified-gateways.json # 统一网关 (openrouter, litellm)
│ │ │ ├── api-proxies.json # API 代理 (new-api, one-api)
│ │ │ └── self-hosted.json # 自托管 (ollama, lmstudio)
│ │ └── overrides/ # 供应商模型覆盖
│ │ ├── openrouter.json # OpenRouter 特殊配置
│ │ ├── aws-bedrock.json # AWS Bedrock 覆盖
│ │ ├── azure-openai.json # Azure OpenAI 覆盖
│ │ └── custom.json # 用户自定义覆盖
│ ├── catalog/ # 目录服务
│ │ ├── ModelCatalog.ts # 模型目录服务
│ │ ├── ProviderCatalog.ts # 供应商目录服务
│ │ └── CatalogService.ts # 统一目录服务
│ ├── loader/ # 配置加载
│ │ ├── ConfigLoader.ts # 配置文件加载器
│ │ ├── CacheManager.ts # 缓存管理
│ │ └── UpdateManager.ts # 在线更新管理
│ ├── validator/ # 验证器
│ │ ├── SchemaValidator.ts # Schema 验证
│ │ └── ZodValidator.ts # Zod 验证器
│ ├── matcher/ # 匹配逻辑
<0A><> │ ├── ModelMatcher.ts # 模型匹配
│ │ └── PatternMatcher.ts # 模式匹配
│ ├── resolver/ # 配置解析
│ │ ├── ConfigResolver.ts # 配置解析器
│ │ └── OverrideResolver.ts # 覆盖解析器
│ ├── utils/ # 工具函数
│ │ ├── migration.ts # 从旧代码迁移
│ │ ├── migrate.ts # 迁移脚本
│ │ ├── compatibility.ts # 兼容性检查
│ │ ├── helpers.ts # 辅助函数
│ │ └── behaviors.ts # 行为特征分析工具
│ └── __tests__/ # 测试文件
│ ├── fixtures/ # 测试数据
│ ├── __snapshots__/ # 快照文件
│ ├── schemas/ # Schema 测试
│ ├── catalog/ # 目录服务测试
│ └── integration/ # 集成测试
├── docs/ # 文档
│ ├── schema-guide.md # Schema 使用指南
│ ├── migration-guide.md # 迁移指南
│ └── contribution-guide.md # 贡献指南
└── scripts/ # 构建和工具脚本
├── schema-generator.ts # Schema 生成工具
├── validator-cli.ts # 命令行验证工具
└── migration-cli.ts # 迁移命令行工具
📝 详细 Schema 定义
1. 模型配置 Schema
// packages/catalog/schemas/model.schema.ts
import { EndpointTypeSchema } from './provider.schema'
// 模态类型 - 支持的输入输出模态
export const ModalitySchema = z.enum(['TEXT', 'VISION', 'AUDIO', 'VIDEO', 'VECTOR'])
// 能力类型 - 模型支持的具体能力
export const ModelCapabilityTypeSchema = z.enum([
'FUNCTION_CALL', // 函数调用
'REASONING', // 推理
'IMAGE_RECOGNITION', // 图像识别
'IMAGE_GENERATION', // 图像生成
'AUDIO_RECOGNITION', // 音频识别
'AUDIO_GENERATION', // 音频生成
'EMBEDDING', // 嵌入向量生成
'RERANK', // 文本重排序
'AUDIO_TRANSCRIPT', // 音频转录
'VIDEO_RECOGNITION', // 视频识别
'VIDEO_GENERATION', // 视频生成
'STRUCTURED_OUTPUT', // 结构化输出
'FILE_INPUT', // 文件输入支持
'WEB_SEARCH', // 内置网络搜索
'CODE_EXECUTION', // 代码执行
'FILE_SEARCH', // 文件搜索
'COMPUTER_USE' // 计算机使用
])
// 参数支持配置 - 替代硬编码的参数检查
export const ParameterSupportSchema = z.object({
temperature: z.object({
supported: z.boolean(),
min: z.number().min(0).max(2).optional(),
max: z.number().min(0).max(2).optional(),
default: z.number().min(0).max(2).optional()
}).optional(),
topP: z.object({
supported: z.boolean(),
min: z.number().min(0).max(1).optional(),
max: z.number().min(0).max(1).optional(),
default: z.number().min(0).max(1).optional()
}).optional(),
topK: z.object({
supported: z.boolean(),
min: z.number().positive().optional(),
max: z.number().positive().optional()
}).optional(),
frequencyPenalty: z.boolean().optional(),
presencePenalty: z.boolean().optional(),
maxTokens: z.boolean().optional(),
stopSequences: z.boolean().optional(),
systemMessage: z.boolean().optional(),
developerRole: z.boolean().optional()
})
// 模型定价配置
export const ModelPricingSchema = z.object({
input: z.object({
perMillionTokens: z.number(),
currency: z.string().default('USD')
}),
output: z.object({
perMillionTokens: z.number(),
currency: z.string().default('USD')
}),
// 图像定价(可选)
perImage: z.object({
price: z.number(),
currency: z.string().default('USD'),
unit: z.enum(['image', 'pixel']).optional()
}).optional(),
// 音/视频定价(可选)
perMinute: z.object({
price: z.number(),
currency: z.string().default('USD')
}).optional()
})
// 模型配置 Schema
export const ModelConfigSchema = z.object({
// 基础信息
id: z.string(),
name: z.string().optional(),
ownedBy: z.string().optional(),
description: z.string().optional(),
// 能力(核心)
capabilities: z.array(ModelCapabilityTypeSchema),
// 模态
inputModalities: z.array(ModalitySchema),
outputModalities: z.array(ModalitySchema),
// 限制
contextWindow: z.number(),
maxOutputTokens: z.number(),
maxInputTokens: z.number().optional(),
// 价格
pricing: ModelPricingSchema.optional(),
// 推理配置
reasoning: ReasoningConfigSchema.optional(),
// 参数支持
parameters: ParameterSupportSchema.optional(),
// 端点类型(复用 Provider Schema 中的 EndpointTypeSchema)
endpointTypes: z.array(EndpointTypeSchema).optional(),
// 元数据
releaseDate: z.string().optional(),
deprecationDate: z.string().optional(),
replacedBy: z.string().optional(),
// 版本控制
version: z.string().optional(),
compatibility: z.object({
minVersion: z.string().optional(),
maxVersion: z.string().optional()
}).optional()
})
2. 供应商配置 Schema
// packages/catalog/schemas/provider.schema.ts
// 端点类型
export const EndpointTypeSchema = z.enum([
'CHAT_COMPLETIONS', // /chat/completions
'COMPLETIONS', // /completions
'EMBEDDINGS', // /embeddings
'IMAGE_GENERATION', // /images/generations
'IMAGE_EDIT', // /images/edits
'AUDIO_SPEECH', // /audio/speech (TTS)
'AUDIO_TRANSCRIPTIONS', // /audio/transcriptions (STT)
'MESSAGES', // /messages
'RESPONSES', // /responses
'GENERATE_CONTENT', // :generateContent
'STREAM_GENERATE_CONTENT', // :streamGenerateContent
'RERANK', // /rerank
'MODERATIONS', // /moderations
])
// 认证方式
export const AuthenticationSchema = z.enum([
'API_KEY', // 标准 API Key 认证
'OAUTH', // OAuth 2.0 认证
'CLOUD_CREDENTIALS', // 云服务凭证 (AWS, GCP, Azure)
])
// 定价模型 - 实际影响 UI 和行为
export const PricingModelSchema = z.enum([
'UNIFIED', // 统一定价 (如 OpenRouter)
'PER_MODEL', // 按模型独立定价 (如 OpenAI 官方)
'TRANSPARENT', // 透明定价 (如 New-API)
'USAGE_BASED', // 基于使用量的动态定价
'SUBSCRIPTION' // 订阅制定价
])
// 模型路由策略 - 影响性能和可靠性
export const ModelRoutingSchema = z.enum([
'INTELLIGENT', // 智能路由,自动选择最优实例
'DIRECT', // 直接路由到指定模型
'LOAD_BALANCED', // 负载均衡到多个实例
'GEO_ROUTED', // 地理位置路由
'COST_OPTIMIZED' // 成本优化路由
])
// 服务端 MCP 支持
export const McpSupportSchema = z.object({
supported: z.boolean().default(false),
configuration: z.object({
supportsUrlPassThrough: z.boolean().default(false),
supportedServers: z.array(z.string()).optional(),
maxConcurrentServers: z.number().optional()
}).optional()
})
// API 兼容性配置
export const ApiCompatibilitySchema = z.object({
supportsArrayContent: z.boolean().default(true),
supportsStreamOptions: z.boolean().default(true),
supportsDeveloperRole: z.boolean().default(false),
supportsServiceTier: z.boolean().default(false),
supportsThinkingControl: z.boolean().default(false),
supportsApiVersion: z.boolean().default(false),
supportsParallelTools: z.boolean().default(false),
supportsMultimodal: z.boolean().default(false),
maxFileUploadSize: z.number().optional(), // bytes
supportedFileTypes: z.array(z.string()).optional()
})
// 行为特性配置 - 替代分类,描述实际行为
export const ProviderBehaviorsSchema = z.object({
// 模型管理
supportsCustomModels: z.boolean().default(false), // 是否支持用户自定义模型
providesModelMapping: z.boolean().default(false), // 是否提供模型名称映射
supportsModelVersioning: z.boolean().default(false), // 是否支持模型版<E59E8B><E78988><EFBFBD>控制
// 可靠性和容错
providesFallbackRouting: z.boolean().default(false), // 是否提供降级路由
hasAutoRetry: z.boolean().default(false), // 是否有自动重试机制
supportsHealthCheck: z.boolean().default(false), // 是否支持健康检查
// 监控和指标
hasRealTimeMetrics: z.boolean().default(false), // 是否有实时指标
providesUsageAnalytics: z.boolean().default(false), // 是否提供使用分析
supportsWebhookEvents: z.boolean().default(false), // 是否支持 Webhook 事件
// 配置和管理
requiresApiKeyValidation: z.boolean().default(true), // 是否需要 API Key 验证
supportsRateLimiting: z.boolean().default(false), // 是否支持速率限制
providesUsageLimits: z.boolean().default(false), // 是否提供使用限制配置
// 高级功能
supportsStreaming: z.boolean().default(true), // 是否支持流式响应
supportsBatchProcessing: z.boolean().default(false), // 是否支持批量处理
supportsModelFineTuning: z.boolean().default(false) // 是否提供模型微调
})
// 供应商配置 Schema
export const ProviderConfigSchema = z.object({
// 基础信息
id: z.string(),
name: z.string(),
description: z.string().optional(),
// 行为相关配置
authentication: AuthenticationSchema,
pricingModel: PricingModelSchema,
modelRouting: ModelRoutingSchema,
behaviors: ProviderBehaviorsSchema,
// 功能支持
supportedEndpoints: z.array(EndpointTypeSchema),
mcpSupport: McpSupportSchema.optional(),
apiCompatibility: ApiCompatibilitySchema.optional(),
// 默认配置
defaultApiHost: z.string().optional(),
defaultRateLimit: z.number().optional(), // requests per minute
// 模型匹配辅助
modelIdPatterns: z.array(z.string()).optional(),
aliasModelIds: z.record(z.string()).optional(), // 模型别名映射
// 特殊配置
specialConfig: z.record(z.string(), z.unknown()).optional(),
// 元数据和链接
documentation: z.string().url().optional(),
statusPage: z.string().url().optional(),
pricingPage: z.string().url().optional(),
supportEmail: z.string().email().optional(),
// 状态管理
deprecated: z.boolean().default(false),
deprecationDate: z.string().optional(),
maintenanceMode: z.boolean().default(false),
// 版本和兼容性
minAppVersion: z.string().optional(), // 最低支持的应用版本
maxAppVersion: z.string().optional(), // 最高支持的应用版本
configVersion: z.string().default('1.0.0') // 配置文件版本
})
3. 覆盖配置 Schema
// packages/catalog/schemas/override.schema.ts
import { EndpointTypeSchema } from './provider.schema'
export const ProviderModelOverrideSchema = z.object({
providerId: z.string(),
modelId: z.string(),
// 能力覆盖
capabilities: z.object({
add: z.array(ModelCapabilityTypeSchema).optional(),
remove: z.array(ModelCapabilityTypeSchema).optional(),
force: z.array(ModelCapabilityTypeSchema).optional() // 强制设置,忽略基础配置
}).optional(),
// 限制覆盖
limits: z.object({
contextWindow: z.number().optional(),
maxOutputTokens: z.number().optional(),
maxInputTokens: z.number().optional()
}).optional(),
// 价格覆盖
pricing: ModelPricingSchema.optional(),
// 推理配置覆盖
reasoning: ReasoningConfigSchema.optional(),
// 参数支持覆盖
parameters: ParameterSupportSchema.optional(),
// 端点类型覆盖
endpointTypes: z.array(EndpointTypeSchema).optional(),
// 禁用模型
disabled: z.boolean().optional(),
// 替换为其他模型
replaceWith: z.string().optional(),
// 覆盖原因和元数据
reason: z.string().optional(),
lastUpdated: z.string().optional(),
updatedBy: z.string().optional()
})
🔧 核心 API 设计
主要接口
// packages/catalog/src/index.ts
export interface ModelCapabilities {
[key: string]: {
supported: boolean
config?: any
}
}
export interface ModelFilters {
capabilities?: ModelCapabilityType[]
inputModalities?: Modality[]
outputModalities?: Modality[]
providers?: string[]
minContextWindow?: number
maxOutputTokens?: number
}
export class ModelCatalog {
/**
* 获取模型完整配置(应用供应商覆盖)
*/
getModelConfig(modelId: string, providerId?: string): ModelConfig | null
/**
* 检查模型是否支持某个能力
*/
hasCapability(
modelId: string,
capability: ModelCapabilityType,
providerId?: string
): boolean
/**
* 获取模型的所有能力
*/
getCapabilities(modelId: string, providerId?: string): ModelCapabilities
/**
* 获取模型的推理配置
*/
getReasoningConfig(modelId: string, providerId?: string): ReasoningConfig | null
/**
* 获取模型参数范围
*/
getParameterRange(
modelId: string,
parameter: 'temperature' | 'topP' | 'topK',
providerId?: string
): { min: number, max: number, default?: number } | null
/**
* 批量匹配模型(用于列表渲染)
*/
matchModels(pattern: string, filters?: ModelFilters): ModelConfig[]
/**
* 获取模型定价
*/
getPricing(modelId: string, providerId?: string): ModelPricingSchema | null
/**
* 检查模型是否支持特定端点类型
*/
supportsEndpoint(modelId: string, endpointType: string, providerId?: string): boolean
}
export interface ProviderFilter {
// 行为特性筛选
behaviors?: Partial<ProviderBehaviorsSchema>
// 核心配置筛选
authentication?: AuthenticationSchema
pricingModel?: PricingModelSchema
modelRouting?: ModelRoutingSchema
// 功能支持筛选
supportsEndpoint?: EndpointType
// 状态筛选
notDeprecated?: boolean
notInMaintenance?: boolean
// 支持的最小应用版本
minAppVersion?: string
}
export class ProviderCatalog {
/**
* 获取供应商配置
*/
getProviderConfig(providerId: string): ProviderConfig | null
/**
* 检查供应商是否支持某个端点
*/
supportsEndpoint(providerId: string, endpoint: EndpointType): boolean
/**
* 获取 API 兼容性配置
*/
getApiCompatibility(providerId: string): ApiCompatibility
/**
* 获取供应商的行为特性
*/
getProviderBehaviors(providerId: string): ProviderBehaviorsSchema | null
/**
* 检查供应商是否具有特定行为特性
*/
hasBehavior(providerId: string, behavior: keyof ProviderBehaviorsSchema): boolean
/**
* 根据行为特性查找供应商(替代分类查询)
*/
findProviders(filter: ProviderFilter): ProviderConfig[]
/**
* 获取供应商的所有模型 ID 模式
*/
getModelIdPatterns(providerId: string): string[]
/**
* 检查供应商是否支持服务端 MCP
*/
supportsServerSideMcp(providerId: string): McpSupport
/**
* 获取按定价模型分组的供应商
*/
getProvidersByPricingModel(pricingModel: PricingModelSchema): ProviderConfig[]
/**
* 获取按认证方式分组的供应商
*/
getProvidersByAuthentication(authType: AuthenticationSchema): ProviderConfig[]
/**
* 获取支持特定端点的供应商
*/
getProvidersByEndpoint(endpoint: EndpointType): ProviderConfig[]
/**
* 获取具有特定行为组合的供应商
*/
getProvidersWithBehaviors(behaviors: Partial<ProviderBehaviorsSchema>): ProviderConfig[]
}
export class CatalogService {
modelCatalog: ModelCatalog
providerCatalog: ProviderCatalog
/**
* 根据现有 Model 类型获取增强配置
*/
getEnhancedModel(model: Model): EnhancedModel | null
/**
* 批量处理模型列表
*/
processModels(models: Model[]): EnhancedModel[]
/**
* 配置验证和修复
*/
validateAndFixConfig(): ValidationResult
/**
* 获取配置更新
*/
checkForUpdates(): Promise<UpdateInfo>
/**
* 应用配置更新
*/
applyUpdate(update: ConfigUpdate): Promise<void>
}
// 统一导出
export const catalog = new CatalogService()
// 向后兼容的辅助函数
export const isFunctionCallingModel = (model: Model): boolean =>
catalog.modelCatalog.hasCapability(model.id, 'FUNCTION_CALL', model.provider)
export const isReasoningModel = (model: Model): boolean =>
catalog.modelCatalog.hasCapability(model.id, 'REASONING', model.provider)
export const isVisionModel = (model: Model): boolean =>
catalog.modelCatalog.hasCapability(model.id, 'IMAGE_RECOGNITION', model.provider)
📊 JSON 配置示例
模型配置示例
// packages/catalog/src/data/models/anthropic.json
{
"version": "2025.11.24",
"models": [
{
"id": "claude-3-5-sonnet-20241022",
"name": "Claude 3.5 Sonnet (October 2024)",
"ownedBy": "anthropic",
"description": "Most capable Claude 3.5 model, with improved performance on coding, math, and reasoning tasks.",
"capabilities": [
"FUNCTION_CALL",
"REASONING",
"IMAGE_RECOGNITION",
"STRUCTURED_OUTPUT",
"FILE_INPUT"
],
"inputModalities": ["TEXT", "VISION"],
"outputModalities": ["TEXT"],
"contextWindow": 200000,
"maxOutputTokens": 8192,
"pricing": {
"input": { "perMillionTokens": 3.0, "currency": "USD" },
"output": { "perMillionTokens": 15.0, "currency": "USD" }
},
"reasoning": {
"supportedEfforts": ["low", "medium", "high"],
"implementation": "ANTHROPIC_CLAUDE",
"reasoningMode": "ON_DEMAND"
},
"parameters": {
"temperature": {
"supported": true,
"min": 0.0,
"max": 1.0,
"default": 1.0
},
"topP": {
"supported": false
},
"maxTokens": {
"supported": true
}
},
"endpointTypes": ["MESSAGES"],
"releaseDate": "2024-10-22"
},
{
"id": "claude-3-5-haiku-20241022",
"name": "Claude 3.5 Haiku (October 2024)",
"ownedBy": "anthropic",
"description": "Fast, lightweight Claude 3.5 model for cost-conscious applications.",
"capabilities": [
"FUNCTION_CALL",
"IMAGE_RECOGNITION",
"STRUCTURED_OUTPUT",
"FILE_INPUT"
],
"inputModalities": ["TEXT", "VISION"],
"outputModalities": ["TEXT"],
"contextWindow": 200000,
"maxOutputTokens": 8192,
"pricing": {
"input": { "perMillionTokens": 0.8, "currency": "USD" },
"output": { "perMillionTokens": 4.0, "currency": "USD" }
},
"parameters": {
"temperature": {
"supported": true,
"min": 0.0,
"max": 1.0,
"default": 1.0
}
},
"endpointTypes": ["MESSAGES"]
}
]
}
供应商配置示例
// packages/catalog/src/data/providers/direct-providers.json
{
"version": "2025.11.24",
"providers": [
{
"id": "anthropic",
"name": "Anthropic",
"description": "Direct access to Anthropic Claude models",
"authentication": "API_KEY",
"pricingModel": "PER_MODEL",
"modelRouting": "DIRECT",
"behaviors": {
"supportsCustomModels": false,
"providesModelMapping": false,
"providesFallbackRouting": false,
"hasRealTimeMetrics": true,
"supportsRateLimiting": true,
"supportsStreaming": true,
"supportsModelFineTuning": false
},
"supportedEndpoints": [
"MESSAGES"
],
"mcpSupport": {
"supported": false
},
"apiCompatibility": {
"supportsArrayContent": false,
"supportsStreamOptions": true,
"supportsDeveloperRole": false,
"supportsServiceTier": false,
"supportsThinkingControl": false,
"supportsApiVersion": false,
"supportsParallelTools": true,
"supportsMultimodal": true,
"maxFileUploadSize": 52428800,
"supportedFileTypes": ["pdf", "txt", "csv", "docx", "html", "md", "jpeg", "png", "gif", "webp"]
},
"defaultApiHost": "https://api.anthropic.com",
"defaultRateLimit": 5000,
"modelIdPatterns": [
"claude-.*",
"claude.*"
],
"documentation": "https://docs.anthropic.com/claude/reference",
"statusPage": "https://status.anthropic.com/",
"supportEmail": "support@anthropic.com"
},
{
"id": "openai",
"name": "OpenAI",
"description": "Official OpenAI API access",
"authentication": "API_KEY",
"pricingModel": "PER_MODEL",
"modelRouting": "DIRECT",
"behaviors": {
"supportsCustomModels": true,
"providesModelMapping": false,
"providesFallbackRouting": false,
"hasRealTimeMetrics": true,
"supportsRateLimiting": true,
"supportsStreaming": true,
"supportsModelFineTuning": true,
"supportsBatchProcessing": true,
"providesUsageAnalytics": true
},
"supportedEndpoints": [
"CHAT_COMPLETIONS",
"COMPLETIONS",
"EMBEDDINGS",
"IMAGE_GENERATION",
"AUDIO_SPEECH",
"AUDIO_TRANSCRIPTIONS",
"MODERATIONS"
],
"mcpSupport": {
"supported": false
},
"apiCompatibility": {
"supportsArrayContent": true,
"supportsStreamOptions": true,
"supportsDeveloperRole": true,
"supportsServiceTier": true,
"supportsThinkingControl": true,
"supportsApiVersion": false,
"supportsParallelTools": true,
"supportsMultimodal": true
},
"defaultApiHost": "https://api.openai.com",
"defaultRateLimit": 10000,
"documentation": "https://platform.openai.com/docs/api-reference",
"statusPage": "https://status.openai.com/",
"pricingPage": "https://openai.com/pricing"
}
]
}
统一网关示例
// packages/catalog/src/data/providers/unified-gateways.json
{
"version": "2025.11.24",
"providers": [
{
"id": "openrouter",
"name": "OpenRouter",
"description": "Unified access to multiple AI models with intelligent routing",
"authentication": "API_KEY",
"pricingModel": "UNIFIED",
"modelRouting": "INTELLIGENT",
"behaviors": {
"supportsCustomModels": true,
"providesModelMapping": true,
"providesFallbackRouting": true,
"hasAutoRetry": true,
"hasRealTimeMetrics": true,
"providesUsageAnalytics": true,
"supportsWebhookEvents": true,
"supportsRateLimiting": true,
"supportsStreaming": true
},
"supportedEndpoints": [
"CHAT_COMPLETIONS",
"EMBEDDINGS"
],
"mcpSupport": {
"supported": false
},
"apiCompatibility": {
"supportsArrayContent": true,
"supportsStreamOptions": true,
"supportsDeveloperRole": true,
"supportsServiceTier": true,
"supportsThinkingControl": false,
"supportsApiVersion": false,
"supportsParallelTools": true,
"supportsMultimodal": true
},
"defaultApiHost": "https://openrouter.ai/api/v1",
"defaultRateLimit": 300,
"modelIdPatterns": [
".*",
"anthropic/.*",
"openai/.*",
"google/.*",
"meta/.*"
],
"aliasModelIds": {
"claude-3-5-sonnet": "anthropic/claude-3.5-sonnet",
"gpt-4": "openai/gpt-4-turbo"
},
"documentation": "https://openrouter.ai/docs",
"statusPage": "https://status.openrouter.ai/",
"pricingPage": "https://openrouter.ai/pricing"
}
]
}
覆盖配置示例
// packages/catalog/src/data/overrides/openrouter.json
{
"version": "2025.11.24",
"overrides": [
{
"providerId": "openrouter",
"modelId": "anthropic/claude-3.5-sonnet",
"overrides": {
"pricing": {
"input": { "perMillionTokens": 4.5, "currency": "USD" },
"output": { "perMillionTokens": 22.5, "currency": "USD" }
},
"capabilities": {
"add": ["WEB_SEARCH"],
"remove": []
}
},
"reason": "OpenRouter applies markup and adds web search capability",
"lastUpdated": "2025-11-24",
"updatedBy": "catalog-maintainer"
},
{
"providerId": "openrouter",
"modelId": "openai/gpt-4-turbo",
"overrides": {
"parameters": {
"temperature": {
"supported": true,
"min": 0.0,
"max": 2.0
}
}
},
"reason": "OpenRouter extends temperature range beyond OpenAI limits",
"lastUpdated": "2025-11-24"
}
]
}
🔄 迁移策略
Phase 1: 基础架构实现 (1-2 days)
目标:建立核心架构和类型系统
任务:
-
Schema 定义
# 创建基础文件结构 mkdir -p packages/catalog/{schemas,data,src,catalog,loader,validator,matcher,resolver,utils} # 实现 Schema + Zod 验证 touch packages/catalog/schemas/{model,provider,override}.schema.ts -
配置加载器
// packages/catalog/src/loader/ConfigLoader.ts export class ConfigLoader { async loadModels(): Promise<ModelConfig[]> async loadProviders(): Promise<ProviderConfig[]> async loadOverrides(): Promise<ProviderModelOverride[]> } -
验证器
// packages/catalog/src/validator/SchemaValidator.ts export class SchemaValidator { validateModel(config: any): ModelConfig validateProvider(config: any): ProviderConfig validateOverride(config: any): ProviderModelOverride }
验收标准:
- 所有 Schema 定义完成,通过 Zod 验证
- 配置加载器可以读取 JSON 文件并返回类型安全的数据
- 单元测试覆盖率达到 90%
Phase 2: 数据迁移 (2-3 days)
目标:从现有硬编码逻辑生成 JSON 配置
任务:
-
迁移工具开发
// packages/catalog/utils/migration.ts export class MigrationTool { generateModelConfigs(): Promise<ModelConfig[]> generateProviderConfigs(): Promise<ProviderConfig[]> validateMigration(): Promise<MigrationReport> } -
自动迁移脚本
# 运行迁移脚本 yarn catalog:migrate # 生成迁移报告 yarn catalog:migration-report -
手动审核和调整
- 审核自动生成的配置文件
- 调整不准确的模型能力定义
- 补充缺失的价格和限制信息
验收标准:
- 90% 的现有模型配置能够正确迁移
- 迁移后的配置与原逻辑行为一致
- 迁移报告显示成功率和差异
Phase 3: 核心服务实现 (1-2 days)
目标:实现配置查询和解析 API
任务:
-
目录服务
// packages/catalog/src/catalog/ModelCatalog.ts export class ModelCatalog { getModelConfig(modelId: string, providerId?: string): ModelConfig | null hasCapability(modelId: string, capability: ModelCapabilityType): boolean // ... 其他方法 } -
配置解析器
// packages/catalog/src/resolver/ConfigResolver.ts export class ConfigResolver { resolveModelOverrides(model: ModelConfig, providerId: string): ModelConfig applyOverrides(base: ModelConfig, overrides: ProviderModelOverride[]): ModelConfig } -
匹配器
// packages/catalog/src/matcher/ModelMatcher.ts export class ModelMatcher { matchModels(pattern: string, filters?: ModelFilters): ModelConfig[] findCompatibleModels(capabilities: ModelCapabilityType[]): ModelConfig[] }
验收标准:
- 所有 API 方法正常工作
- 配置覆盖逻辑正确应用
- 模式匹配和过滤功能完善
Phase 4: 集成重构 (2-3 days)
目标:替换现有硬编码逻辑
任务:
-
向后兼容层
// packages/catalog/src/compatibility/BackwardCompat.ts // 保持现有函数签名,内部使用新配置系统 export const isFunctionCallingModel = (model: Model): boolean => { return catalog.modelCatalog.hasCapability(model.id, 'FUNCTION_CALL', model.provider) } -
逐步替换
- 替换
src/renderer/src/config/models/中的函数 - 更新调用点使用新的配置 API
- 保持测试通过
- 替换
-
性能优化
- 实现配置缓存
- 懒加载大型配置文件
- 优化查询性能
验收标准:
- 所有现有测试通过
- 新配置系统与旧系统行为一致
- 性能不低于原有实现
Phase 5: 在线更新机制 (1-2 days)
目标:支持配置的在线更新
任务:
-
更新管理器
// packages/catalog/src/loader/UpdateManager.ts export class UpdateManager { checkForUpdates(): Promise<UpdateInfo> downloadLatestCatalog(): Promise<void> applyPatch(patch: ConfigPatch): Promise<void> rollback(): Promise<void> } -
版本控制
{ "version": "2025.11.24", "models": { ... }, "providers": { ... }, "overrides": { ... } } -
增量更新
- 支持 JSON Patch 格式
- 验证更新完整性
- 支持回滚机制
验收标准:
- 可以检查和下载配置更新
- 增量更新正常工作
- 更新失败时可以回滚
🧪 测试策略
测试覆盖范围
-
Schema 测试
// packages/catalog/tests/schemas/model.schema.test.ts describe('ModelConfig Schema', () => { it('should validate correct model config', () => { const validConfig = { /* valid config */ } expect(() => ModelConfigSchema.parse(validConfig)).not.toThrow() }) it('should reject invalid model config', () => { const invalidConfig = { /* invalid config */ } expect(() => ModelConfigSchema.parse(invalidConfig)).toThrow() }) }) -
目录服务测试
// packages/catalog/tests/catalog/ModelCatalog.test.ts describe('ModelCatalog', () => { it('should return model config with overrides applied', () => { const config = modelCatalog.getModelConfig('claude-3-5-sonnet', 'openrouter') expect(config?.pricing).toEqual(expectedPricing) }) it('should correctly check model capabilities', () => { expect(modelCatalog.hasCapability('gpt-4', 'FUNCTION_CALL')).toBe(true) }) }) -
集成测试
// packages/catalog/tests/integration/config-loading.test.ts describe('Configuration Loading', () => { it('should load and validate all configuration files', async () => { const catalog = new CatalogService() await catalog.initialize() expect(catalog.isHealthy()).toBe(true) }) }) -
兼容性测试
// packages/catalog/tests/compatibility/backward-compat.test.ts describe('Backward Compatibility', () => { it('should produce same results as legacy functions', () => { const legacyResult = isFunctionCallingModelLegacy(testModel) const newResult = isFunctionCallingModel(testModel) expect(newResult).toBe(legacyResult) }) })
测试数据
// packages/catalog/tests/fixtures/sample-configs.json
{
"models": [
{
"id": "test-model",
"capabilities": ["FUNCTION_CALL", "REASONING"],
"contextWindow": 100000,
"pricing": {
"input": { "perMillionTokens": 1.0 },
"output": { "perMillionTokens": 2.0 }
}
}
],
"providers": [
{
"id": "test-provider",
"name": "Test Provider",
"supportedEndpoints": ["CHAT_COMPLETIONS"]
}
],
"overrides": [
{
"providerId": "test-provider",
"modelId": "test-model",
"overrides": {
"capabilities": { "add": ["WEB_SEARCH"] }
}
}
]
}
📖 使用指南
基本用法
import { catalog } from '@cherrystudio/catalog'
// 检查模型能力
const canCallFunctions = catalog.modelCatalog.hasCapability('gpt-4', 'FUNCTION_CALL')
const canReason = catalog.modelCatalog.hasCapability('o1-preview', 'REASONING')
// 获取模型配置
const modelConfig = catalog.modelCatalog.getModelConfig('claude-3-5-sonnet', 'openrouter')
// 批量匹配模型
const visionModels = catalog.modelCatalog.matchModels('', {
capabilities: ['IMAGE_RECOGNITION'],
providers: ['anthropic', 'openai']
})
// 获取供应商信息
const providerInfo = catalog.providerCatalog.getProviderConfig('openrouter')
高级用法
// 获取推理配置
const reasoningConfig = catalog.modelCatalog.getReasoningConfig('o1-preview')
console.log(reasoningConfig?.supportedEfforts) // ['low', 'medium', 'high']
// 获取参数范围
const tempRange = catalog.modelCatalog.getParameterRange('gpt-4', 'temperature')
console.log(tempRange) // { min: 0, max: 2, default: 1 }
// 获取定价信息
const pricing = catalog.modelCatalog.getPricing('claude-3-5-sonnet', 'openrouter')
// 检查端点支持
const supportsChat = catalog.modelCatalog.supportsEndpoint('gpt-4', 'OPENAI')
// 基于行为的供应商查询(替代分类查询)
const providersWithFallbackRouting = catalog.providerCatalog.findProviders({
behaviors: { providesFallbackRouting: true }
})
// 返回: [openrouter, litellm, ...]
const providersWithUnifiedPricing = catalog.providerCatalog.findProviders({
pricingModel: 'UNIFIED'
})
// 返回: [openrouter, litellm, ...]
const providersSupportingCustomModels = catalog.providerCatalog.findProviders({
behaviors: { supportsCustomModels: true }
})
// 返回: [openai, openrouter, ...]
// 复合行为查询
const reliableProviders = catalog.providerCatalog.findProviders({
behaviors: {
providesFallbackRouting: true,
hasRealTimeMetrics: true,
supportsRateLimiting: true
},
pricingModel: 'UNIFIED'
})
// 返回: 具备所有这些特性的供应商
// 获取供应商的详细行为信息
const openrouterBehaviors = catalog.providerCatalog.getProviderBehaviors('openrouter')
console.log(openrouterBehaviors.providesFallbackRouting) // true
console.log(openrouterBehaviors.hasAutoRetry) // true
配置扩展
// 添加自定义覆盖
await catalog.applyOverride({
providerId: 'custom-provider',
modelId: 'custom-model',
overrides: {
capabilities: { add: ['CUSTOM_CAPABILITY'] },
pricing: { input: { perMillionTokens: 5.0 } }
}
})
📝 维护指南
添加新模型
-
确定模型归属
# 如果是已知供应商的模型,编辑对应文件 vim packages/catalog/src/data/models/openai.json # 如果是新供应商,创建新文件 vim packages/catalog/src/data/models/newprovider.json -
添加模型配置
{ "id": "new-model-v1", "name": "New Model v1", "capabilities": ["FUNCTION_CALL", "REASONING"], "contextWindow": 200000, "maxOutputTokens": 4096, "pricing": { "input": { "perMillionTokens": 2.0 }, "output": { "perMillionTokens": 6.0 } } } -
验证配置
yarn catalog:validate yarn catalog:test -
提交 PR
git add packages/catalog/src/data/models/ git commit -m "feat: add New Model v1 to catalog" git push origin feat/add-new-model
添加新供应商
-
创建供应商配置
vim packages/catalog/src/data/providers/newprovider.json -
添加供应商信息
{ "id": "newprovider", "name": "New Provider", "supportedEndpoints": ["CHAT_COMPLETIONS"], "apiCompatibility": { "supportsArrayContent": true, "supportsStreamOptions": true } } -
添加模型覆盖(如果需要)
vim packages/catalog/src/data/overrides/newprovider.json
配置更新流程
-
本地开发
# 修改配置文件 vim packages/catalog/src/data/models/anthropic.json # 验证更改 yarn catalog:validate # 运行测试 yarn catalog:test -
发布更新
# 更新版本号 vim packages/catalog/src/data/models/anthropic.json # 更新 version 字段 # 生成变更日志 yarn catalog:changelog # 提交更改 git add packages/catalog/ git commit -m "feat: update Anthropic models to 2025.11.24" -
在线更新(用户端)
// 检查更新 const updateInfo = await catalog.checkForUpdates() if (updateInfo.hasUpdates) { // 应用更新 await catalog.applyUpdate(updateInfo.update) }
🎨 UI 分组展示示例
基于行为的动态分组
// UI 组件:供应商选择器
export const ProviderSelector = () => {
const [providers] = useState(catalog.getAllProviders())
// 基于行为特性的动态分组(替代固定分类)
const providerGroups = useMemo(() => {
return {
'🏢 官方供应商': providers.filter(p =>
p.pricingModel === 'PER_MODEL' &&
p.modelRouting === 'DIRECT'
),
'🌐 统一平台': providers.filter(p =>
p.pricingModel === 'UNIFIED' &&
p.behaviors.providesFallbackRouting
),
'☁️ <20><><EFBFBD>服务': providers.filter(p =>
p.authentication === 'CLOUD_CREDENTIALS'
),
'🔗 API 网关': providers.filter(p =>
p.behaviors.providesModelMapping &&
p.behaviors.supportsCustomModels
),
'🏠 自托管': providers.filter(p =>
p.pricingModel === 'TRANSPARENT'
),
'⚡ 高可靠性': providers.filter(p =>
p.behaviors.providesFallbackRouting &&
p.behaviors.hasAutoRetry &&
p.behaviors.hasRealTimeMetrics
),
'💰 <20><>本优化': providers.filter(p =>
p.modelRouting === 'COST_OPTIMIZED' ||
p.pricingModel === 'UNIFIED'
)
}
}, [providers])
return (
<div>
{Object.entries(providerGroups).map(([groupName, groupProviders]) => (
<ProviderGroup
key={groupName}
title={groupName}
providers={groupProviders}
/>
))}
</div>
)
}
特性标签展示
// 供应商卡片组件
export const ProviderCard = ({ provider }: { provider: ProviderConfig }) => {
const features = []
// 根据行为特性动态生成标签
if (provider.behaviors.providesFallbackRouting) {
features.push('🔄 自动降级')
}
if (provider.behaviors.hasRealTimeMetrics) {
features.push('📊 实时监控')
}
if (provider.pricingModel === 'UNIFIED') {
features.push('💵 统一定价')
}
if (provider.behaviors.supportsCustomModels) {
features.push('🎛️ 自定义模型')
}
if (provider.behaviors.providesUsageAnalytics) {
features.push('📈 使用分析')
}
return (
<Card>
<h3>{provider.name}</h3>
<div className="features">
{features.map(feature => (
<Tag key={feature}>{feature}</Tag>
))}
</div>
</Card>
)
}
智能推荐逻辑
// 基于用户需求的供应商推荐
export const getRecommendedProviders = (requirements: {
budgetConscious?: boolean
needsReliability?: boolean
requiresCustomModels?: boolean
prefersUnifiedPricing?: boolean
}) => {
const filters: ProviderFilter = {
notDeprecated: true,
notInMaintenance: true
}
if (requirements.budgetConscious) {
filters.pricingModel = 'UNIFIED'
filters.behaviors = {
...filters.behaviors,
supportsRateLimiting: true
}
}
if (requirements.needsReliability) {
filters.behaviors = {
...filters.behaviors,
providesFallbackRouting: true,
hasAutoRetry: true,
hasRealTimeMetrics: true
}
}
if (requirements.requiresCustomModels) {
filters.behaviors = {
...filters.behaviors,
supportsCustomModels: true
}
}
return catalog.providerCatalog.findProviders(filters)
}
🔧 开发工具
命令行工具
// package.json scripts
{
"scripts": {
"catalog:validate": "node utils/validate-cli.js",
"catalog:migrate": "node utils/migration-cli.js",
"catalog:test": "vitest run packages/catalog/tests",
"catalog:build": "tsdown",
"catalog:dev": "tsdown --watch",
"catalog:changelog": "node utils/changelog-cli.js",
"catalog:analyze": "node utils/behavior-analyzer.js"
}
}
VS Code 扩展推荐
-
JSON Schema 支持
// .vscode/settings.json { "json.schemas": [ { "fileMatch": ["packages/catalog/src/data/models/*.json"], "schema": "./packages/catalog/src/schemas/model.schema.json" }, { "fileMatch": ["packages/catalog/src/data/providers/*.json"], "schema": "./packages/catalog/src/schemas/provider.schema.json" } ] } -
自动验证
{ "editor.codeActionsOnSave": { "source.fixAll.eslint": true } } -
行为分析工具
# 分析供应商行为分布 yarn catalog:analyze --type behavior-distribution # 检查配置完整性 yarn catalog:analyze --type completeness-check # 生成行为报告 yarn catalog:analyze --type behavior-report --output markdown
📚 附录
迁移对照表
| 旧函数 | 新 API | 说明 |
|---|---|---|
isFunctionCallingModel(model) |
catalog.modelCatalog.hasCapability(model.id, 'FUNCTION_CALL', model.provider) |
检查函数调用能力 |
isReasoningModel(model) |
catalog.modelCatalog.hasCapability(model.id, 'REASONING', model.provider) |
检查推理能力 |
isVisionModel(model) |
catalog.modelCatalog.hasCapability(model.id, 'IMAGE_RECOGNITION', model.provider) |
检查视觉能力 |
isEmbeddingModel(model) |
catalog.modelCatalog.hasCapability(model.id, 'EMBEDDING', model.provider) |
检查嵌入能力 |
getThinkModelType(model) |
catalog.modelCatalog.getReasoningConfig(model.id, model.provider) |
获取推理配置 |
版本兼容性
| 配置版本 | 应用版本 | 说明 |
|---|---|---|
| 1.0.0 | v2.0.0 | 初始版本 |
| 1.1.0 | v2.1.0 | 添加视频模态支持 |
| 1.2.0 | v2.2.0 | 增强推理配置 |
性能指标
- 配置加载时间:< 100ms
- 模型查询时间:< 1ms
- 内存使用:< 50MB
- 缓存命中率:> 95%
这个方案提供了一个完整的、可扩展的、类型安全的模型和供应商配置系统,能够解决现有硬编码逻辑的问题,并为未来的扩展提供良好的基础。