Перейти к основному содержимому

Обнаружение и кэширование моделей

ModelDiscoveryManager динамически обнаруживает доступные списки моделей из API провайдеров, управляет кэшем результатов обнаружения и работает совместно с AgentModelsManager для реализации маршрутизации моделей и функции Follow-Provider.


Расположение файлов

ФайлПуть
ModelDiscoveryManagerpackages/desktop/app/main/services/capabilities/llm/config-service/ModelDiscoveryManager.ts
AgentModelsManagerpackages/desktop/app/main/services/capabilities/llm/config-service/AgentModelsManager.ts
LLMConfigServicepackages/desktop/app/main/services/capabilities/llm/config-service/LLMConfigService.ts
Provider Presetspackages/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 CacheLayer ["Слой кэша"]
SQLiteCache[(SQLite<br/>llm_model_cache)]
FileCache[(Файловый кэш<br/>model-cache/*.json)]
ChainCache["Transformer Chain Cache<br/>Map (10min TTL)"]
end

subgraph ProviderAPI ["Provider API"]
OpenAIModels["/v1/models"]
AnthropicModels["/v1/models"]
GeminiModels["/v1beta/models"]
end

subgraph DataSources ["Источники данных"]
ProviderConfig[Конфигурация провайдера<br/>modelConfigs / models]
Templates[PROVIDER_TEMPLATES<br/>встроенные шаблоны]
Presets[Provider Presets<br/>предустановленные конфиги]
Mappings[PROVIDER_MODEL_MAPPINGS<br/>маппинг моделей]
end

MDM -->|read/write| SQLiteCache
MDM -->|fallback| FileCache
MDM -->|API calls| ProviderAPI
MDM -->|fallback models| ProviderConfig
MDM -->|fallback models| Templates

AMM --> ChainCache
AMM --> Mappings

Структуры данных

Результаты обнаружения моделей

// Одна запись обнаружения модели
interface ProviderModelDiscoveryEntry {
id: string; // ID модели (например, 'gpt-4o')
name: string; // Отображаемое название
description?: string; // Описание
contextLength?: number; // Длина контекстного окна
maxTokens?: number; // Максимум выходных токенов
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

// Запись кэша цепочки в LLMConfigService
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[Получить конфигурацию провайдера]
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[Использовать кэш/предустановленный fallback]

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
Запрос выполняется через модуль net в Electron

7. Разобрать ответ, извлечь список моделей
Обработать различия в формате ответа разных провайдеров

8. Объединить: модели, обнаруженные через API + предустановленные модели (с дедупликацией)

9. Записать в кэш (SQLite или файл)

10. Вернуть { success: true, source: 'api', models, ... }

Разрешение резервных моделей (getFallbackModels)

Когда обнаружение через API недоступно, используется следующий порядок приоритетов:

getFallbackModels(provider):
fallbackModels = []

// Приоритет 1: Собственный modelConfigs провайдера
if provider.modelConfigs?.length:
convertModelConfigs(provider.modelConfigs)
// Извлечь id, name, contextLength, maxTokens, capabilities

// Приоритет 2: Собственный массив 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 ReadPath ["Путь чтения"]
Read[readModelCache] --> CheckSQLite{useSQLite?}
CheckSQLite -->|Да| SQLiteRead[db.llmModelCacheGet]
CheckSQLite -->|Нет| FileRead[fs.readFile]
SQLiteRead -->|сбой| FileRead
end

subgraph WritePath ["Путь записи"]
Write[writeModelCache] --> CheckSQLite2{useSQLite?}
CheckSQLite2 -->|Да| SQLiteWrite[db.llmModelCacheSet]
CheckSQLite2 -->|Нет| FileWrite[fs.writeFile]
SQLiteWrite -->|сбой| FileWrite
end

Таблица кэша SQLite:

ПолеТипОписание
providerIdTEXTПервичный ключ
endpointTEXTAPI-эндпоинт
sourceTEXTИсточник ('api' / 'cache')
modelsJSONСериализованный список моделей
rawJSONНеобработанный ответ API
fetchedAtINTEGERМетка времени получения
expiresAtINTEGERМетка времени истечения (24 часа)

Файловый кэш:

  • Каталог: userData/model-cache/
  • Имя файла: {providerId}.json (специальные символы заменяются подчёркиванием)
  • Нет механизма истечения (зависит от принудительного обновления)

Кэш цепочки Transformer

LLMConfigService поддерживает кэш в памяти для цепочек Transformer, чтобы избежать повторного разбора:

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_SEARCH_CONFIGS)

Определяет, как каждый провайдер реализует веб-поиск:

Тип поискаОписаниеПримеры провайдеров
model-paramВключить поиск через параметр запросаDashScope, Baidu
builtin-toolВнедрить определение встроенного инструментаKimi, Volcengine
mcpПредоставить инструменты поиска через MCP-серверКастомный MCP
sdk-nativeНативная поддержка SDK (серверные инструменты)Anthropic
noneПоиск не поддерживаетсяOllama

Маппинг моделей провайдера (PROVIDER_MODEL_MAPPINGS)

Используется функцией Follow-Provider для автоматического выбора фоновой/зрительной модели того же провайдера:

ПровайдерОсновная модельФоновая модельЗрительная модель
zhipuglm-5glm-4.5-airglm-4.6v
volcengineark-code-latestdoubao-seed-2.0-litedoubao-seed-2.0-code
kimikimi-k2.5kimi-k2-0905-previewkimi-k2.5
............

Таблица интеграции IPC

IPC-каналНаправлениеОписание
llmConfig:discoverModelsR → MЗапустить обнаружение моделей (поддерживает параметр forceRefresh)
llmConfig:getProvidersR → MВернуть список моделей, включённых в каждый провайдер

Точки расширения

Добавление поддержки обнаружения моделей для нового провайдера

  1. Убедиться, что в шаблоне провайдера задан modelsEndpoint (например, /v1/models)
  2. Если провайдер использует нестандартный формат API моделей, добавить логику разбора в ModelDiscoveryManager.discoverModels()
  3. Добавить правила определения URL в deriveModelsEndpoint() файла utils.ts

Добавление маппинга моделей Follow-Provider

Добавить в PROVIDER_MODEL_MAPPINGS в файле packages/desktop/app/shared/provider-presets.ts:

// Псевдокод
PROVIDER_MODEL_MAPPINGS['newProvider'] = {
primary: 'main-model-id',
background: 'lightweight-model-id',
vision: 'vision-model-id', // null если модель зрения отсутствует
};

Настройка TTL кэша

  • Кэш обнаружения моделей: управляется полем expiresAt в SQLite (в настоящее время 24 часа)
  • Кэш цепочки Transformer: изменить 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.tsPROVIDER_TEMPLATES, определения типов
shared/provider-presets.tsPROVIDER_MODEL_MAPPINGS, PROVIDER_SEARCH_CONFIGS
capabilities/llm/config/model-discovery.tsУтилита слияния/дедупликации списков моделей (mergeModelLists)
workers/DbClient.tsЧтение/запись кэша SQLite
routers/llm/ProviderRouter.tsIPC-слой discoverModels
capabilities/llm/completion/ThinkingResolver.tsИспользует discoveredModelMaxTokens