Cherry Studio Provider Model 注册表系统:预设数据加载、规范化、种子写入与用户数据合并全解析
2026/9/13 6:03:39 网站建设 项目流程

Cherry Studio Provider & Model 注册表系统:预设数据加载、规范化、种子写入与用户数据合并全解析

【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio

导读

Cherry Studio 将数百家 AI 提供方(Provider)与数千个模型的定义沉淀为三份随包发布的 JSON 注册表数据(位于 packages/provider-registry/data),并在运行时将其与 SQLite 中的用户配置按"分层 Delta"契约合并,从而让上游目录更新能够零数据迁移地触达既有安装。本文基于 docs/references/provider-model/README.md 与其核心正文 docs/references/provider-model/provider-registry.md,结合 packages/provider-registry 与主进程数据服务源码,完整讲解:三类注册表 JSON 的职责划分、RegistryLoader的缓存与 O(1) 索引机制、模型 ID 规范化(normalizeModelId)规则、预设 Provider 的 insert-only 种子写入,以及模型/Provider 配置在"注册表 → 用户行"上的三层合并优先级。读完你既能理解 Cherry Studio 的 Provider/Model 数据架构,也能掌握"哪些字段该持久化、哪些字段该读时解析、何时需要数据回填"的扩展设计方法。

一、整体架构:三份 JSON + 主进程服务 + 两张数据库表

从源码结构看,整套 Provider/Model 体系由三层组成:

@cherrystudio/provider-registry (包) ├── data/ │ ├── models.json 预设模型(能力、定价、模态、推理支持……) │ ├── providers.json 预设提供方(端点、apiFeatures、元数据) │ └── provider-models.json 提供方专属的模型级覆盖(按提供方微调) ├── src/ │ ├── registry-loader.ts RegistryLoader:加载、校验、缓存、索引、空闲 TTL │ ├── registry-utils.ts 纯函数:lookupRegistryModel、buildPersistedEndpointConfigs │ ├── utils/normalize.ts normalizeModelId(聚合前缀、变体后缀……) │ └── schemas/ Zod 校验 Schema │ src/main/data/ ├── db/seeding/ │ └── seeders/ │ └── presetProviderSeeder.ts ISeeder:仅插入的 Provider 身份/认证脚手架 ├── services/ │ ├── ProviderRegistryService.ts 注册表查找与 Provider/Model 基线解析 │ ├── ModelService.ts Model CRUD 与用户 Delta 覆盖 │ └── ProviderService.ts Provider CRUD 与读取时 Provider 合并 └── api/handlers/ ├── models.ts Model CRUD、对账与注册表解析路由 └── providers.ts Provider CRUD 与预设投影路由

三个 JSON 文件是目录事实的唯一来源(source of truth),架构文档刻意不重复目录条数,因为其体积随上游数据独立变化。加载时,三份文件分别经过ModelListSchemaProviderListSchemaProviderModelListSchema的 Zod 校验(packages/provider-registry/src/schemas);校验通过后才进入内存索引。

1.1 三类数据的职责划分

文件内容典型字段
models.json全局模型目录(跨 Provider 复用)idnamecapabilitiescontextWindowmaxOutputTokensinputModalities/outputModalitiespricingreasoningparameterSupportimageGenerationownedByopenWeights
providers.json预设提供方定义idnamedefaultChatEndpointendpointConfigsbaseUrladapterFamilymodelsApiUrlsreasoningFormat)、apiFeaturesauthMethodsauthOptionalmodelListSourceserverToolsmetadata.websiteavailableInEditions
provider-models.json提供方对特定模型的覆盖providerIdmodelId(引用models.json)、apiModelId(调用 API 时真实使用的 ID)、capabilities(add/remove/force)、limitspricingreasoningContractsendpointTypesinputModalities/outputModalitiesdisabledreplaceWithname等独立字段

以 providers.json 中的真实条目为例,cherryin声明了四个端点(anthropic-messagesgoogle-generate-contentopenai-chat-completionsopenai-responses),其中openai-chat-completions端点额外声明reasoningFormat: { type: "openai-chat" },并携带serverToolsweb-searchurl-context)与官网元数据。而provider-models.json302aiclaude-opus-4-5等模型以apiModelId映射"带日期快照的真实调用 ID"(如claude-opus-4-1-20250805),同时覆盖其定价——这就是"目录规范 ID"与"厂商实际 ID"解耦的典型用法。

值得注意的边界:apiModelId的作用是保留提供方原始的 ID 格式(OpenRouter 的anthropic/claude-3-5-sonnet、Vertex AI 的global.anthropic.claude-3-5-sonnet-v1:0等);未设置时直接用modelId作为 API 调用 ID。若某模型在models.json没有对应条目provider-models.jsonnamedescriptionfamilyownedByimageGeneration等字段可以让它"完全独立地"存活在覆盖文件里——解析器会用synthesizePresetFromOverride(ProviderRegistryService.ts)从覆盖合成一个预设,无需污染全局模型目录。

二、数据流:三条关键路径

2.1 启动时:预设 Provider 种子写入

DbService.onInit() → SeedRunner.runAll(seeders) → PresetProviderSeeder.run(db) → RegistryLoader.loadProviders() // 读取 providers.json → SELECT 已有 provider ID(user_provider) → 仅 INSERT 新增的 Provider 身份/认证行 → 绝不物化注册表拥有的连接配置

SeedRunner会在providers.json版本变化时重跑该 Seeder,但 Seeder 始终是insert-only:已存在的 Provider 行被跳过。这一设计的正确性建立在"注册表拥有的连接配置从不写入行"之上——每次读取时都从当前注册表实时解析。行内只保存:身份(providerIdpresetProviderId)、用户拥有的显示名(name)以及必要的认证壳(authConfig)。

从 presetProviderSeeder.ts 源码可见,认证壳仅对三个复用他人端点协议的厂商生成:vertexai生成{ type: 'iam-gcp', project: '', location: '' }azure-openai生成{ type: 'iam-azure', apiVersion: '' }aws-bedrock生成{ type: 'iam-aws', region: '' };其余返回null(按 v2 约定,这类厂商的 URL 路由完全由authType驱动,见ProviderSettings/utils/provider.ts的说明)。toDbRowpresetProviderIdp.presetProviderId ?? p.id,即"无显式分组归属时,预设就是自己"。

规范预设不可删除。多数预设满足providerId === presetProviderId;少数别名/分组预设(如zaizhipuminimax-globalminimax)通过注册表查找同样受到保护——isRegistryProvider只要发现providers.json中存在该 ID 即判定为预设行。而用户自建、从预设继承的 Provider 可以正常删除。

2.2 按需触发:模型创建

POST /models [{ providerId: 'openai', modelId: 'gpt-4o' }] → handler: 对每个条目调用 providerRegistryService.lookupModel(providerId, modelId) → RegistryLoader.findModel('gpt-4o') // O(1) 索引,规范化兜底 → RegistryLoader.findOverride('openai', 'gpt-4o') // O(1) 索引 → 从注册表数据解析端点 profile // 仅主进程,不持久化 → 返回 { presetModel, registryOverride, reasoningProfile } → handler: modelService.create(items) → mergePresetModel(preset, override, ...) → 将 DTO 显式字段与注册表基线比较 → 仅 INSERT 与基线不同的可空列到 user_model → list/get/mutation 响应 → 重建当前注册表基线 → 覆盖每个非空稀疏列

lookupModel(ProviderRegistryService.ts)是"注册表 + 数据库感知"的单模型查找:先取 Provider 上下文(含presetProviderId与默认端点),再用loader.findOverride(presetProvider.id, modelId)找覆盖,loader.findModel(...)找预设;若命中覆盖但模型目录无条目,则走synthesizePresetFromOverride合成。该服务不拥有任何数据库表、不直接访问数据库,用户数据一律经ProviderService获取。

2.3 解析 SDK 模型列表

GET /providers/:providerId/models:resolve?ids=gpt-4o&ids=o3 → providerRegistryService.resolveModels(providerId, modelIds) → 对每个 modelId: → RegistryLoader.findModel(modelId) // O(1),规范化兜底 → RegistryLoader.findOverride(providerId, modelId) // O(1) → mergePresetModel(preset, override, ...) 或 createCustomModel(...) → 返回合并后的 Model[]

resolveModels(ProviderRegistryService.ts)是"SDK 只提供 ID、其余全部来自注册表"的关键路径:SDK 数据不会覆盖精心维护的注册表数据。对注册表中未命中的 ID,createCustomModel生成最小自定义模型(能力为空数组、supportsStreaming: true、默认启用);注册表合并失败被视为致命错误,避免把不完整结果当成功同步持久化。解析时还会用deriveResolvedModelName生成可区分显示名——当多个 SKU 共享同一规范条目时(如MiniMax/MiniMax-M2.1与裸MiniMax-M2.1),规范化命中的条目会附加"命名空间前缀/日期快照/变体"等装饰后缀,防止界面混淆。

三、合并函数与优先级

3.1 三个函数、三种场景

函数适用场景合并层
mergePresetModel注册表查询、resolveModelspreset → override
applyUserOverlay带显式用户 Delta 的 Model 读取合并后的注册表基线 → user
createCustomModel注册表无匹配仅 modelId

共享逻辑被抽取为applyPresetAndOverride(preset + override 合并,见 ProviderRegistryService.ts,处理能力、模态、限制、定价、参数支持的逐字段合并)与resolveReasoning(推理配置解析)。

3.2 合并优先级

非空稀疏列(用户 Delta) > provider-models.json > models.json 最高优先级 中间 最低

对预设支撑的行(preset-backed row),每个可空的模型配置列都是独立的"所有权标记"null表示继承注册表,任何非空值都是用户 Delta。自定义行则存储完整配置。这一约定让目录变更无需数据迁移即可触达既有行——空字符串与空数组作为显式覆盖依旧有效。

applyUserOverlay(ModelService.ts)的实现非常直白:对namedescriptioncapabilitiescontextWindowmaxInputTokens/maxOutputTokenspricingparameterSupportreasoningsupportsStreamingendpointTypes、模态等字段逐一判断"!= null才覆盖",undefined/null即"未设置"。配合createModelsSqliteHandlers中"仅插入与基线不同的可空列",共同保证 user_model 里永远只存真正的用户偏差。

3.3 用户覆盖保护

当用户修改某个可被注册表增强的字段(例如name),该值直接存入对应可空列。读取时从当前注册表出发、逐个应用非空列;创建/PATCH 时把入参值与当前注册表基线比较——因此渲染层回显(renderer echo)不会冻结目录值;把值恢复回基线时该列被清空(重新变回null继承)。

3.4 未来新增字段的分支策略

  • 注册表拥有(用户不可编辑):加入注册表 Schema、运行时ModelmergePresetModel不要新增user_model列或持久化 Delta。既有预设行在下一次读取时自动获得该字段,零 Schema 迁移、零数据回填。
  • 用户可编辑的预设字段:为其增加一个可空 Delta 列,纳入 create/PATCH 覆盖映射与预设 Delta 字段集。Schema 迁移仅新增列,既有行无需回填——null即继承当前注册表值。
  • 自定义模型字段:自定义行拥有完整配置,新必需字段要么给运行时默认值、要么做自定义行回填;这是预设继承规则的有意例外
  • 一个列可同时被自定义行与预设行共享,对自定义行必填、对预设行保持null(典型如capabilitiesreasoning)。

四、RegistryLoader:缓存、索引与空闲 TTL

registry-loader.ts 是注册表 JSON 的读取与查询引擎,其生命周期契约:

  • 懒加载:首次访问时才读盘(启动时不加载);
  • 预计算索引:首次加载后一次性构建,换取 O(1) 查找;
  • 空闲 TTL:默认 30 秒无访问即自动失效(DEFAULT_IDLE_TTL_MS = 30_000);
  • 访问即触碰:每次findModel/findOverride/loadModels都会重置计时器;
  • 服务级缓存ProviderRegistryService共享一个 loader;Provider Seeder 自建独立 loader。

RegistryLoader的构造函数接收三个文件路径(RegistryPaths)并允许自定义 TTL;touch()内部用setTimeout实现失效,invalidate()清空全部数据与索引,下次访问重新加载。

4.1 索引一览

索引用途
modelByIdmodel.id精确模型查找
modelByNormIdnormalizeModelId(id)规范化兜底
modelBySizedNorm保留参数规模的规范化 ID解析带参数规模标签的变体(gpt-oss:20bgpt-oss-20b
overrideByKeyproviderId::modelId精确覆盖查找
overrideByNormKeyproviderId::normalizeModelId(id)规范化兜底
overrideByApiKeyproviderId::apiModelId按提供方侧调用 ID 精确查找
overrideByNormApiKeyproviderId::normalizeModelId(apiModelId)规范化后的提供方 ID 兜底
overridesByProviderproviderId某提供方的全部覆盖

索引构建有几个值得注意的细节(均有源码注释佐证):modelBySizedNormgpt-oss-20bgpt-oss-120b保持区分(它们在大小写无关键上会坍缩为同一个gpt-oss);overrideByKey采用"自变体优先"规则——同一提供方可能用多个apiModelId服务同一个规范模型(如 tokenhub 带日期的原厂直供变体共享deepseek-v4-flash),规范键必须解析到无日期的自变体(apiModelId === modelId),日期变体只能经apiModelId索引触达。

4.2 查询 API

loader.findModel(modelId) // O(1):精确 → 规范化兜底 loader.findOverride(providerId, modelId) // O(1):精确 → 规范化兜底 loader.getOverridesForProvider(providerId) // O(1):按提供方分组 loader.invalidate() // 释放全部数据,下次访问重载

findModel对带冒号规模标签的 ID(gpt-oss:20b)会先用colonVariantTagToHyphen对齐为连字符拼写再走modelBySizedNorm,宁可返回null也不做"错误兄弟型号"的规模无关猜测。findOverride同样严格:精确的规范modelId与提供方apiModelId两种精确查找必须先于两种规范化兜底,否则google.gemma-3-27b-it这类 ID 会被同族的gemma-3-12b-it抢先占用规范化键。

另外 registry-loader.ts 还定义了REGISTRY_SCHEMA_VERSION = 2REGISTRY_MIN_APP_VERSION = '2.0.13':远程注册表更新器按v{版本}/路径拉取数据,只有 Schema 结构变化才升版本;新模态/能力/effort 等枚举词条的增长不再升级(v2 客户端会丢弃不认识的字段),但旧运行时无法执行的语义值(新 adapter family、端点类型等)由REGISTRY_MIN_APP_VERSION兜底门控。

五、模型 ID 规范化(normalizeModelId)

用户侧看到的模型 ID 往往与注册表规范 ID 不同,规范化让两者对齐:

用户看到注册表有规范化动作
aihubmix-gpt-4ogpt-4o剥掉聚合前缀
gpt-4o:freegpt-4o剥掉变体后缀
claude-3.5-sonnetclaude-3-5-sonnet规范化版本分隔符
aihubmix-gpt-4o:freegpt-4o组合处理

normalizeModelId()实现在 packages/provider-registry/src/utils/normalize.ts,执行顺序为:

1. 剥离 Provider 前缀("anthropic/claude-3" → "claude-3",取最后一个 "/" 段) 2. 小写化 3. 剥离聚合前缀(aihubmix-、zai-、siliconflow-、nvidia-、groq-……) 4. 展开已知缩写(mm- → minimax-) 5. 剥离变体后缀(:free、-thinking、(beta)……) 6. 剥离参数规模(-72b、-7b……) 7. 规范化版本分隔符(3.5 → 3-5、3p5 → 3-5) 8. 下划线折叠为连字符(HF 风格 bce-embedding-base_v1 → bce-embedding-base-v1)

实现细节相当严谨:COMMON_AGGREGATOR_PREFIXES刻意不放mm-(它是 MiniMax 缩写,交给PREFIX_EXPANSIONS展开,若先当聚合前缀剥掉会得到孤儿 IDm2-1);-medium同样被排除在变体后缀之外(它是真实型号层级mistral-medium);剥变体时还保护non/no/pre/anti/post等复合前缀开头的词(-no-think不被误剥)。变体 → 量化 → 日期快照的剥离被组织为stripVariantQuantDateSuffixes的不动点循环(一次遍历非幂等,因为尾部日期会屏蔽内层变体)。此外还有针对 Bedrock 跨厂商 ARN 的专门处理:us.anthropic.claude-sonnet-4-5-v1:0会被折叠为claude-sonnet-4-5(区域+厂商点号前缀、厂商连字符前缀、-v1:0修订全部剥离)。

查找策略:精确匹配优先、规范化兜底。这保证当gpt-4oaihubmix-gpt-4o作为独立条目同时存在时,精确匹配胜出,规范化不会造成错误折叠。

六、关键数据库表

6.1 user_provider

表定义见 src/main/data/db/schemas/userProvider.ts。核心原则是"一个 Provider 实例 = 一个 apiHost(1:1),一个 apiHost 可挂多个 API Key(1:N)":

用途
providerId主键,用户自定义唯一 ID
presetProviderId指向 providers.json 条目(null = 自定义 Provider)。双重职责:既是来源预设标识,也是侧边栏分组键——少数注册表行(zaizhipuminimax-globalminimax)指向不同预设以归入该分组
name用户拥有的显示名,首次种子写入时由预设初始化
endpointConfigsJSON Delta:用户的baseUrl覆盖;自定义 Provider 还可存adapterFamily路由提示
defaultChatEndpoint可空用户覆盖;null 继承注册表默认
apiKeysJSON 数组的 API Key 条目
apiFeaturesJSON Delta:仅存与注册表/应用默认不同的标志;null 继承全部默认
authConfig统一认证配置(如iam-gcp/iam-azure/iam-aws

6.2 user_model

用途
id确定性主键:providerId::modelId
providerId+modelIdProvider 内的唯一模型身份
presetModelId指向 models.json 条目(null = 自定义模型)
name/capabilities/supportsStreaming自定义行必填;预设行是可空 Delta
inputModalities/outputModalities自定义行完整配置或可空 Delta
contextWindow/maxOutputTokens自定义行完整配置或可空 Delta
reasoning自定义模型的内在控制/令牌上限;预设行从注册表解析
pricing自定义行完整配置或可空 Delta
parameters自定义行完整配置或可空 Delta
orderKey提供方模型列表中的分数排序键
notes用户备注

七、Provider 配置合并:行是 Delta,不是快照

Provider 连接配置遵循与模型相同的分层、读时合并。user_provider行是Delta:只存用户显式设置的值;键缺失即"使用注册表值"。合并发生在rowToRuntimeProvider(ProviderService)经由ProviderRegistryService.mergeEndpointConfigs/getProviderDisplayMetadata完成:

user_provider (DB, delta) > providers.json (registry) > app 默认值
字段所有权解析规则
endpointConfigs[ep].baseUrl用户行 > 注册表
endpointConfigs[ep].adapterFamily注册表注册表 > 行(自定义 Provider 提示)>inferAdapterFamily(ep)
endpointConfigs[ep].modelsApiUrls注册表仅注册表
端点类型键集合注册表 ∪ 用户注册表键与行键的并集
apiFeatures混合{...DEFAULT_API_FEATURES, ...registry, ...row}
defaultChatEndpoint混合行 > 注册表

mergeEndpointConfigs(ProviderRegistryService.ts)的具体实现印证了这一点:端点键取并集(新增端点无需迁移);adapterFamily优先注册表值、否则行值、最后inferAdapterFamily(ep)按端点协议推导(anthropic-messages→anthropic、openai-responses→openai、兜底openai-compatible,见 registry-utils.ts);dialect逐键浅合并,行只声明用户发现的偏差;输出按字段重建,历史遗留的仅注册表字段(如reasoningFormatType)绝不会跨入运行时状态。

为什么零数据迁移(对应 issue #17096):注册表拥有的事实从不冻结进行。端点类型新增、adapterFamily 变化、baseUrl/功能开关/默认端点调整,都会在 Delta 契约下自动到达既有行。写路径强制维持 Delta:EndpointConfigOverride是唯一可持久化的端点形状,PATCH 规范化会丢弃与注册表基线相等的值。

一个直观例子:未被用户触碰的预设baseUrl不在行中;若提供方在providers.json中修改该 URL,下一次读取就会返回新 URL。用户自定义的代理 URL 则留在行里持续生效,直到用户将其重置为当前注册表值。

name是有意例外:它是用户拥有的完整值(种子初始化),不是注册表 Delta;后续注册表改名不会覆盖它。若产品语义要改为"改名之前一直继承",name必须先转换成显式 Delta 表示。

7.1 何时需要回填

注册表内容更新在存储所有权契约不变时不需要回填

  • 仅注册表字段读时直接解析;
  • baseUrlapiFeaturesdefaultChatEndpoint等混合字段在行 Delta 缺失时继承;
  • 既有用户覆盖有意持续生效,不是陈旧数据;
  • 新注册表拥有字段应加入读时投影,而非持久化。

Schema 迁移可能仍需为新用户可编辑字段新增存储,但预设行在"null/缺失=继承"语义下无需数据回填。只有两种情形必须回填:完整自定义行新增无运行时默认值的必需字段;或既有字段的所有权从完整快照改为 Delta 且需保留旧库数据。

新增注册表字段的分支:

  • 注册表拥有:只加进读时合并输出;对端点配置字段不要加入EndpointConfigOverride——Zod 会自动从写 DTO 剥离未知键,零迁移。
  • 用户可编辑端点字段(混合所有权):加入EndpointConfigOverrideSchema(其keyof集合即权威所有权声明),在合并中加一条row.x ?? registry.x规则,写路径可选丢弃等于基线的值,零迁移。
  • 用户可编辑 Provider 字段:若属于既有 JSON Delta(如apiFeatures),扩展对应 Schema 与合并规则即零迁移;否则需选择显式持久化位置。新增独立列属 Schema 变更,但可空预设 Delta 列仍无需值回填。
  • 永远不要把注册表拥有值作为行快照持久化——这正是注册表更新变陈旧的确切成因。

八、推理(Reasoning)配置的双边界设计

推理配置被刻意拆成两个边界:

  • 模型数据声明内在控制与令牌上限(ReasoningSupportSchemacontrolseffort离散档位 /budget数字预算 /toggle开关,见 packages/provider-registry/src/schemas/model.ts)。主进程注册表增强会将其投影为渲染层控件消费的运行时selectableEffortsderiveSelectableEfforts按端点 wire profile 过滤掉无法表达的选择,如无off时剔除none);
  • Provider 注册表数据声明封闭的reasoningFormatwire profile(reasoningContractsendpointConfigs[*].reasoningFormat),仅在主进程解析与解释,从不复制进 SQLite、DataApi 或渲染层状态。

请求路径按"精确 provider-model → 端点覆盖/默认 → 穷尽格式默认"的顺序解析出一个 profile(resolveReasoningProfileFromRegistrycontract.wire ?? format.wire ?? selectFormatWire(formatDefault, wireDialect)),再与提交时的规范选择组合,最终发射为原生 AI SDK Provider 选项或通用兼容参数。wireDialecteffort/budget)解决同一协议两代参数形状互斥的问题(Gemini 3thinkingLevelvs 2.xthinkingBudget、Claude 4.6+adaptivevs ≤4.5enabled+budget_tokens),且该事实随模型而非端点。更完整的 Schema、优先级与 UI→请求数据流见 packages/provider-registry/docs/reasoning-control.md。

九、文件索引速查

内容位置
注册表 JSON 数据packages/provider-registry/data
Zod Schemapackages/provider-registry/src/schemas
RegistryLoader(加载、索引、TTL)packages/provider-registry/src/registry-loader.ts
纯查找/转换函数packages/provider-registry/src/registry-utils.ts
规范化工具packages/provider-registry/src/utils/normalize.ts
种子运行器src/main/data/db/seeding/SeedRunner.ts
预设 Provider 种子src/main/data/db/seeding/seeders/presetProviderSeeder.ts
注册表服务(合并查询)src/main/data/services/ProviderRegistryService.ts
模型服务(用户 Delta 覆盖)src/main/data/services/ModelService.ts
Provider 服务(读时合并)src/main/data/services/ProviderService.ts
DB Schemasrc/main/data/db/schemas/userModel.ts、userProvider.ts
合并行为测试src/main/data/services/tests/modelMerger.test.ts

结语

Cherry Studio 的 Provider/Model 注册表系统用"三份 JSON 做事实源、SQLite 行做 Delta、读时三层合并"的组合,同时满足了目录数据的频繁更新与用户自定义的持久化需求。理解这套设计的关键在于三句话:注册表拥有的值永不落库(读时解析,让上游更新零迁移生效);每个可空列是独立的所有权标记(null 继承、非空即用户覆盖);models.jsonprovider-models.json→ 用户 Delta 的优先级是全局唯一规则。无论是排查"为什么改了注册表 baseUrl 不生效"、为目录贡献新模型,还是设计自己的 Provider/Model 数据架构,本文所述的索引策略、规范化步骤与 Delta 契约都是可直接复用的范式。

【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询