メインコンテンツまでスキップ

モデル検出とキャッシュ

ModelDiscoveryManager は、provider 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 ["Cache Layer"]
SQLiteCache[(SQLite<br/>llm_model_cache)]
FileCache[(File cache<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 ["Data Sources"]
ProviderConfig[Provider config<br/>modelConfigs / models]
Templates[PROVIDER_TEMPLATES<br/>built-in templates]
Presets[Provider Presets<br/>preset configs]
Mappings[PROVIDER_MODEL_MAPPINGS<br/>model mappings]
end

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

AMM --> ChainCache
AMM --> Mappings

データ構造

モデル検出結果

// Single model discovery entry
interface ProviderModelDiscoveryEntry {
id: string; // Model ID (e.g. 'gpt-4o')
name: string; // Display name
description?: string; // Description
contextLength?: number; // Context window length
maxTokens?: number; // Max output tokens
category?: string; // Category: 'chat' | 'reasoning' | 'image' | ...
capabilities?: string[]; // Capability tags: 'vision' | 'function_call' | 'reasoning'
}

// Discovery result
interface ProviderModelDiscoveryResult {
success: boolean;
source: string; // 'api' | 'cache' | 'fallback'
endpoint: string; // Actual API endpoint called
models: ProviderModelDiscoveryEntry[];
raw?: unknown; // Raw API response
fetchedAt?: string; // ISO timestamp
error?: string;
}

// Available model list (merged)
interface AvailableModelsResult {
models: ProviderModelDiscoveryEntry[];
source: string;
lastUpdated?: string;
}

Transformer チェーンキャッシュ

// Chain cache entry in LLMConfigService
interface ChainCacheEntry {
chain: ResolvedTransformerChain;
cachedAt: number; // Cache timestamp
}

// Cache TTL
const CHAIN_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes

モデルルーティング設定

// Router configuration
interface RouterConfig {
default?: string; // Default model ("providerId,modelId")
background?: string; // Background model
think?: string; // Reasoning model
longContext?: string; // Long-context model
longContextThreshold?: number; // Long-context trigger threshold
webSearch?: string; // Search model
image?: string; // Image model
vision?: string; // Vision model
followProviderBackground?: boolean; // Background model follows primary model's provider
followProviderVision?: boolean; // Vision model follows primary model's provider
}

// Code ↔ Chat routing
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[Get provider config]
GetProvider --> LoadPreset[Load preset models<br/>getFallbackModels]

LoadPreset --> DeriveEndpoint[Derive API endpoint<br/>deriveModelsEndpoint]
DeriveEndpoint --> HasEndpoint{Valid endpoint?}

HasEndpoint -->|No| CheckCache[Check cache]
HasEndpoint -->|Yes| CheckForce{Force refresh?}

CheckForce -->|Yes| CallAPI[Call API]
CheckForce -->|No| CheckCacheAge[Check cache freshness]
CheckCacheAge --> HasFreshCache{Cache valid?}
HasFreshCache -->|Yes| ReturnCache[Return cached result]
HasFreshCache -->|No| CallAPI

CallAPI --> APISuccess{API succeeded?}
APISuccess -->|Yes| ParseModels[Parse model list]
APISuccess -->|No| FallbackCache[Use cache/preset fallback]

ParseModels --> MergeModels[Merge API results + preset models]
MergeModels --> WriteCache[Write to cache]
WriteCache --> ReturnResult[Return result]

CheckCache --> HasCacheAtAll{Cache exists?}
HasCacheAtAll -->|Yes| ReturnCache
HasCacheAtAll -->|No| ReturnFallback[Return preset models]

FallbackCache --> HasCacheAtAll

詳細な手順:

discoverModels(providerId, options?):
1. provider = delegate.getProvider(providerId)
If not found → return { success: false, error: "not found" }

2. presetModels = getFallbackModels(provider)
// Look up in order: modelConfigs → models → PROVIDER_TEMPLATES

3. endpoint = deriveModelsEndpoint(provider)
// Derive from modelsEndpoint or api_base_url

4. If no valid endpoint:
Try to return from cache; otherwise return preset models (source: 'fallback')

5. If not a forced refresh:
Check cache → if valid → return cached result

6. Call provider API:
- OpenAI compatible: GET /v1/models
- Anthropic: GET /v1/models
- Gemini: GET /v1beta/models
Request is made via Electron's net module

7. Parse response, extract model list
Handle response format differences across providers

8. Merge: API-discovered models + preset models (deduplicated)

9. Write to cache (SQLite or file)

10. Return { success: true, source: 'api', models, ... }

フォールバックモデル解決 (getFallbackModels)

API 検出を利用できない場合は、次の優先順位でフォールバックします。

getFallbackModels(provider):
fallbackModels = []

// Priority 1: Provider's own modelConfigs
if provider.modelConfigs?.length:
convertModelConfigs(provider.modelConfigs)
// Extract id, name, contextLength, maxTokens, capabilities

// Priority 2: Provider's own models array
else if provider.models?.length:
convertModelsArray(provider.models)
// Only id, no detailed info

// Priority 3: Matching built-in template
if fallbackModels.length === 0:
template = PROVIDER_TEMPLATES.find(matching provider.id or provider.name)
if template:
// Use template's modelConfigs or models

モデルカテゴリのマッピング:

カテゴリ説明
chat一般的な会話モデル (デフォルト)
reasoning推論モデル (o1, Claude thinking)
image画像生成モデル
video動画生成モデル
embedding埋め込みモデル
codeコード生成モデル

キャッシュ戦略

二層キャッシュ (SQLite + ファイル)

flowchart LR
subgraph ReadPath ["Read Path"]
Read[readModelCache] --> CheckSQLite{useSQLite?}
CheckSQLite -->|Yes| SQLiteRead[db.llmModelCacheGet]
CheckSQLite -->|No| FileRead[fs.readFile]
SQLiteRead -->|failure| FileRead
end

subgraph WritePath ["Write Path"]
Write[writeModelCache] --> CheckSQLite2{useSQLite?}
CheckSQLite2 -->|Yes| SQLiteWrite[db.llmModelCacheSet]
CheckSQLite2 -->|No| FileWrite[fs.writeFile]
SQLiteWrite -->|failure| 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 minutes

getTransformerChain(key):
cached = chainCache.get(key)
if cached && (Date.now() - cached.cachedAt < TTL):
return cached.chain
// Otherwise re-parse
chain = transformerService.resolve(...)
chainCache.set(key, { chain, cachedAt: Date.now() })
return chain

モデルルーティング

Chat → Code / Code → Chat ルーティング

resolveRoutedModel(providerId, modelId):
config = loadConfig()

// Check Code → Chat routing
for route in config.routers.codeToChat:
if route.providerId === providerId && route.modelId === modelId:
return { actualProviderId: route.actualProviderId, actualModelId: route.actualModelId }

// Check Chat → Code routing
for route in config.routers.chatToCode:
if route.providerId === providerId && route.modelId === modelId:
return { actualProviderId: route.actualProviderId, actualModelId: route.actualModelId }

// No matching route
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)
// Use PROVIDER_MODEL_MAPPINGS to find the corresponding model
if followProviderBackground: background = mapping.background
if followProviderVision: vision = mapping.vision

return { background, vision }

Provider 検索設定 (PROVIDER_SEARCH_CONFIGS)

各 provider が Web 検索をどのように実装するかを定義します。

検索タイプ説明例の provider
model-paramリクエストパラメータで検索を有効化DashScope, Baidu
builtin-tool組み込みツール定義を注入Kimi, Volcengine
mcpMCP サーバー経由で検索ツールを提供Custom MCP
sdk-nativeSDK ネイティブ対応 (サーバーサイドツール)Anthropic
none検索非対応Ollama

Provider モデルマッピング (PROVIDER_MODEL_MAPPINGS)

Follow-Provider 機能で、同じ provider の background/vision モデルを自動選択するために使用されます。

ProviderPrimary modelBackground modelVision model
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各 provider に含まれるモデルリストを返す

拡張ポイント

新しい provider にモデル検出対応を追加する

  1. provider テンプレートに modelsEndpoint が設定されていることを確認します (例: /v1/models)
  2. provider が非標準の models API 形式を使用する場合は、ModelDiscoveryManager.discoverModels() に解析ロジックを追加します
  3. utils.tsderiveModelsEndpoint() に URL 導出ルールを追加します

Follow-Provider モデルマッピングを追加する

packages/desktop/app/shared/provider-presets.tsPROVIDER_MODEL_MAPPINGS に追加します。

// Pseudocode
PROVIDER_MODEL_MAPPINGS['newProvider'] = {
primary: 'main-model-id',
background: 'lightweight-model-id',
vision: 'vision-model-id', // null if no vision model
};

キャッシュ TTL をカスタマイズする

  • モデル検出キャッシュ: SQLite の expiresAt フィールドで制御されます (現在は 24 時間)
  • Transformer チェーンキャッシュ: LLMConfigService.CHAIN_CACHE_TTL_MS を変更します (現在は 10 分)

関連ファイル

ファイル関係
capabilities/llm/config-service/LLMConfigService.tsModelDiscoveryManager を初期化するホストサービス
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.tsSQLite キャッシュの読み書き
routers/llm/ProviderRouter.tsIPC レイヤーの discoverModels
capabilities/llm/completion/ThinkingResolver.tsdiscoveredModelMaxTokens を利用