模型发现与缓存
ModelDiscoveryManager 负责从提供商 API 动态发现可用模型列表,管理发现结果的缓存,并与 AgentModelsManager 配合实现模型路由和 Follow-Provider 特性。
文件位置
| 文件 | 路径 |
|---|---|
| ModelDiscoveryManager | packages/desktop/app/main/services/capabilities/llm/config-service/ModelDiscoveryManager.ts |
| AgentModelsManager | packages/desktop/app/main/services/capabilities/llm/config-service/AgentModelsManager.ts |
| LLMConfigService | packages/desktop/app/main/services/capabilities/llm/config-service/LLMConfigService.ts |
| Provider Presets | packages/desktop/app/shared/provider-presets.ts |
| 工具函数 | packages/desktop/app/main/services/capabilities/llm/config-service/utils.ts |
| 配置合并 | packages/desktop/app/main/services/capabilities/llm/config/ (mergeModelLists) |
架构上下文
graph TB
subgraph LLMConfigService
MDM[ModelDiscoveryManager]
AMM[AgentModelsManager]
end
subgraph 缓存层
SQLiteCache[(SQLite<br/>llm_model_cache)]
FileCache[(文件缓存<br/>model-cache/*.json)]
ChainCache["Transformer Chain Cache<br/>Map (10min TTL)"]
end
subgraph Provider API
OpenAIModels["/v1/models"]
AnthropicModels["/v1/models"]
GeminiModels["/v1beta/models"]
end
subgraph 数据源
ProviderConfig[Provider 配置<br/>modelConfigs / models]
Templates[PROVIDER_TEMPLATES<br/>内置模板]
Presets[Provider Presets<br/>预设配置]
Mappings[PROVIDER_MODEL_MAPPINGS<br/>模型映射]
end
MDM -->|读写| SQLiteCache
MDM -->|回退| FileCache
MDM -->|API 调用| Provider API
MDM -->|回退模型| ProviderConfig
MDM -->|回退模型| Templates
AMM --> ChainCache
AMM --> Mappings
数据结构
模型发现结果
// 单个模型发现条目
interface ProviderModelDiscoveryEntry {
id: string; // 模型 ID(如 'gpt-4o')
name: string; // 显示名称
description?: string; // 描述
contextLength?: number; // 上下文窗口长度
maxTokens?: number; // 最大输出 Token
category?: string; // 分类: 'chat' | 'reasoning' | 'image' | ...
capabilities?: string[]; // 能力标签: 'vision' | 'function_call' | 'reasoning'
}
// 发现结果
interface ProviderModelDiscoveryResult {
success: boolean;
source: string; // 'api' | 'cache' | 'fallback'
endpoint: string; // 实际调用的 API 端点
models: ProviderModelDiscoveryEntry[];
raw?: unknown; // API 原始响应
fetchedAt?: string; // ISO 时间戳
error?: string;
}
// 可用模型列表(合并后)
interface AvailableModelsResult {
models: ProviderModelDiscoveryEntry[];
source: string;
lastUpdated?: string;
}
Transformer Chain 缓存
// LLMConfigService 中的 chain 缓存
interface ChainCacheEntry {
chain: ResolvedTransformerChain;
cachedAt: number; // 缓存时间戳
}
// 缓存 TTL
const CHAIN_CACHE_TTL_MS = 10 * 60 * 1000; // 10 分钟
模型路由配置
// 路由器配置
interface RouterConfig {
default?: string; // 默认模型 ("providerId,modelId")
background?: string; // 后台模型
think?: string; // 推理模型
longContext?: string; // 长上下文模型
longContextThreshold?: number; // 长上下文触发阈值
webSearch?: string; // 搜索模型
image?: string; // 图片模型
vision?: string; // 视觉模型
followProviderBackground?: boolean; // 后台模型跟随主模型提供商
followProviderVision?: boolean; // 视觉模型跟随主模型提供商
}
// Code ↔ Chat 路由
interface CodeToChatRouterConfig {
providerId: string;
modelId: string;
actualProviderId: string;
actualModelId: string;
}
interface ChatToCodeRouterConfig {
providerId: string;
modelId: string;
actualProviderId: string;
actualModelId: string;
}
算法/逻辑说明
模型发现流程
flowchart TD
Start[discoverModels] --> GetProvider[获取 Provider 配置]
GetProvider --> LoadPreset[加载预设模型<br/>getFallbackModels]
LoadPreset --> DeriveEndpoint[推导 API 端点<br/>deriveModelsEndpoint]
DeriveEndpoint --> HasEndpoint{端点有效?}
HasEndpoint -->|否| CheckCache[检查缓存]
HasEndpoint -->|是| CheckForce{强制刷新?}
CheckForce -->|是| CallAPI[调用 API]
CheckForce -->|否| CheckCacheAge[检查缓存新鲜度]
CheckCacheAge --> HasFreshCache{缓存有效?}
HasFreshCache -->|是| ReturnCache[返回缓存]
HasFreshCache -->|否| CallAPI
CallAPI --> APISuccess{API 成功?}
APISuccess -->|是| ParseModels[解析模型列表]
APISuccess -->|否| FallbackCache[使用缓存/预设回退]
ParseModels --> MergeModels[合并 API 结果 + 预设模型]
MergeModels --> WriteCache[写入缓存]
WriteCache --> ReturnResult[返回结果]
CheckCache --> HasCacheAtAll{有缓存?}
HasCacheAtAll -->|是| ReturnCache
HasCacheAtAll -->|否| ReturnFallback[返回预设模型]
FallbackCache --> HasCacheAtAll
步骤详解:
discoverModels(providerId, options?):
1. provider = delegate.getProvider(providerId)
如果不存在 → 返回 { success: false, error: "not found" }
2. presetModels = getFallbackModels(provider)
// 从 modelConfigs → models → PROVIDER_TEMPLATES 逐级查找
3. endpoint = deriveModelsEndpoint(provider)
// 从 modelsEndpoint 或 api_base_url 推导
4. 如果没有有效端点:
尝试从缓存返回,否则返回预设模型(source: 'fallback')
5. 如果不是强制刷新:
检查缓存 → 如果有效 → 返回缓存结果
6. 调用提供商 API:
- OpenAI 兼容: GET /v1/models
- Anthropic: GET /v1/models
- Gemini: GET /v1beta/models
使用 Electron net 模块发起请求
7. 解析响应,提取模型列表
处理不同提供商的响应格式差异
8. 合并: API 发现的模型 + 预设模型(去重)
9. 写入缓存(SQLite 或文件)
10. 返回 { success: true, source: 'api', models, ... }
回退模型解析(getFallbackModels)
当 API 发现不可用时,按以下优先级回退:
getFallbackModels(provider):
fallbackModels = []
// 优先级 1: Provider 自身的 modelConfigs
if provider.modelConfigs?.length:
convertModelConfigs(provider.modelConfigs)
// 提取 id, name, contextLength, maxTokens, capabilities
// 优先级 2: Provider 自身的 models 数组
else if provider.models?.length:
convertModelsArray(provider.models)
// 只有 id,无详细信息
// 优先级 3: 匹配的内置模板
if fallbackModels.length === 0:
template = PROVIDER_TEMPLATES.find(匹配 provider.id 或 provider.name)
if template:
// 使用模板的 modelConfigs 或 models
模型分类映射:
| 分类 | 说明 |
|---|---|
chat | 通用对话模型(默认) |
reasoning | 推理模型(o1, Claude thinking) |
image | 图片生成模型 |
video | 视频生成模型 |
embedding | 嵌入模型 |
code | 代码生成模型 |
缓存策略
双层缓存(SQLite + 文件)
flowchart LR
subgraph 读取路径
Read[readModelCache] --> CheckSQLite{useSQLite?}
CheckSQLite -->|是| SQLiteRead[db.llmModelCacheGet]
CheckSQLite -->|否| FileRead[fs.readFile]
SQLiteRead -->|失败| FileRead
end
subgraph 写入路径
Write[writeModelCache] --> CheckSQLite2{useSQLite?}
CheckSQLite2 -->|是| SQLiteWrite[db.llmModelCacheSet]
CheckSQLite2 -->|否| FileWrite[fs.writeFile]
SQLiteWrite -->|失败| FileWrite
end
SQLite 缓存表:
| 字段 | 类型 | 说明 |
|---|---|---|
| providerId | TEXT | 主键 |
| endpoint | TEXT | API 端点 |
| source | TEXT | 来源('api' / 'cache') |
| models | JSON | 序列化的模型列表 |
| raw | JSON | 原始 API 响应 |
| fetchedAt | INTEGER | 获取时间戳 |
| expiresAt | INTEGER | 过期时间戳(24 小时) |
文件缓存:
- 目录:
userData/model-cache/ - 文件名:
{providerId}.json(特殊字符替换为下划线) - 无过期机制(依赖强制刷新)
Transformer Chain 缓存
LLMConfigService 维护 Transformer Chain 的内存缓存,避免重复解析:
chainCache: Map<string, { chain: ResolvedTransformerChain, cachedAt: number }>
CHAIN_CACHE_TTL_MS = 10 * 60 * 1000 // 10 分钟
getTransformerChain(key):
cached = chainCache.get(key)
if cached && (Date.now() - cached.cachedAt < TTL):
return cached.chain
// 否则重新解析
chain = transformerService.resolve(...)
chainCache.set(key, { chain, cachedAt: Date.now() })
return chain
模型路由
Chat → Code / Code → Chat 路由
resolveRoutedModel(providerId, modelId):
config = loadConfig()
// 检查 Code → Chat 路由
for route in config.routers.codeToChat:
if route.providerId === providerId && route.modelId === modelId:
return { actualProviderId: route.actualProviderId, actualModelId: route.actualModelId }
// 检查 Chat → Code 路由
for route in config.routers.chatToCode:
if route.providerId === providerId && route.modelId === modelId:
return { actualProviderId: route.actualProviderId, actualModelId: route.actualModelId }
// 无匹配路由
return null
Follow-Provider 模型映射
resolveEffectiveModels():
router = config.router
agentDefaults = config.agentDefaultModels
background = agentDefaults.background
vision = router.vision
if (followProviderBackground || followProviderVision) && router.default:
defaultProviderId = router.default.split(',')[0]
provider = getProvider(defaultProviderId)
if provider:
mapping = resolveFollowProviderModel(provider)
// 使用 PROVIDER_MODEL_MAPPINGS 查找对应模型
if followProviderBackground: background = mapping.background
if followProviderVision: vision = mapping.vision
return { background, vision }
Provider 搜索配置(PROVIDER_SEARCH_CONFIGS)
定义每个提供商如何实现网页搜索:
| 搜索类型 | 说明 | 示例提供商 |
|---|---|---|
model-param | 请求参数中启用搜索 | DashScope, Baidu |
builtin-tool | 注入内置工具定义 | Kimi, Volcengine |
mcp | 通过 MCP 服务器提供搜索工具 | 自定义 MCP |
sdk-native | SDK 原生支持(服务端工具) | Anthropic |
none | 不支持搜索 | Ollama |
Provider 模型映射(PROVIDER_MODEL_MAPPINGS)
用于 Follow-Provider 特性,自动选择同一提供商的 background/vision 模型:
| 提供商 | 主力模型 (primary) | 后台模型 (background) | 视觉模型 (vision) |
|---|---|---|---|
zhipu | glm-5 | glm-4.5-air | glm-4.6v |
volcengine | ark-code-latest | doubao-seed-2.0-lite | doubao-seed-2.0-code |
kimi | kimi-k2.5 | kimi-k2-0905-preview | kimi-k2.5 |
| ... | ... | ... | ... |
IPC 集成表
| IPC 通道 | 方向 | 说明 |
|---|---|---|
llmConfig:discoverModels | R → M | 触发模型发现(支持 forceRefresh 参数) |
llmConfig:getProviders | R → M | 返回 Provider 包含的模型列表 |
扩展点
添加新提供商的模型发现支持
- 确保提供商模板中设置了
modelsEndpoint(如/v1/models) - 如果提供商有特殊的 models API 格式,在
ModelDiscoveryManager.discoverModels()中添加解析逻辑 - 在
utils.ts的deriveModelsEndpoint()中添加 URL 推导规则
添加 Follow-Provider 模型映射
在 packages/desktop/app/shared/provider-presets.ts 的 PROVIDER_MODEL_MAPPINGS 中添加:
// 伪代码
PROVIDER_MODEL_MAPPINGS['newProvider'] = {
primary: 'main-model-id',
background: 'lightweight-model-id',
vision: 'vision-model-id', // null 表示无视觉模型
};
自定义缓存 TTL
- 模型发现缓存:SQLite 中通过
expiresAt字段控制(当前 24 小时) - Transformer Chain 缓存:修改
LLMConfigService.CHAIN_CACHE_TTL_MS(当前 10 分钟)
关联文件表
| 文件 | 关联方式 |
|---|---|
capabilities/llm/config-service/LLMConfigService.ts | 宿主服务,初始化 ModelDiscoveryManager |
capabilities/llm/config-service/utils.ts | 工具函数:deriveModelsEndpoint, resolveApiKey 等 |
capabilities/llm/config-service/AgentModelsManager.ts | 模型路由、Follow-Provider 解析 |
shared/llm-config.ts | PROVIDER_TEMPLATES、类型定义 |
shared/provider-presets.ts | PROVIDER_MODEL_MAPPINGS、PROVIDER_SEARCH_CONFIGS |
capabilities/llm/config/model-discovery.ts | 模型列表合并去重工具(mergeModelLists) |
workers/DbClient.ts | SQLite 缓存读写 |
routers/llm/ProviderRouter.ts | IPC 层 discoverModels |
capabilities/llm/completion/ThinkingResolver.ts | 消费 discoveredModelMaxTokens |