ProviderManager 내부 구조
ProviderManager는 LLM 프로바이더의 라이프사이클을 담당하며, 생성·조회·수정·삭제(CRUD) 작업과 프리셋 템플릿 시스템 및 영속 저장소를 관리합니다.
파일 위치
| 파일 | 경로 |
|---|---|
| ProviderManager | packages/desktop/app/main/services/capabilities/llm/config-service/ProviderManager.ts |
| LLMConfigService | packages/desktop/app/main/services/capabilities/llm/config-service/LLMConfigService.ts |
| 프로바이더 템플릿 | packages/desktop/app/shared/llm-config.ts (PROVIDER_TEMPLATES) |
| 프로바이더 프리셋 | packages/desktop/app/shared/provider-presets.ts |
| 스키마 | packages/desktop/app/main/services/capabilities/llm/config-service/schemas.ts |
| IPC 라우터 | packages/desktop/app/main/services/routers/llm/ProviderRouter.ts |
아키텍처 컨텍스트
델리게이트 패턴
LLMConfigService는 델리게이트 패턴을 사용하여 책임을 네 개의 전문 매니저에 분산합니다. ProviderManager는 순환 의존성을 피하기 위해 ProviderManagerDelegate 인터페이스를 통해 호스트와 통신합니다.
graph TB
subgraph LLMConfigService
direction TB
PM[ProviderManager]
MDM[ModelDiscoveryManager]
CIOM[ConfigIOManager]
AMM[AgentModelsManager]
end
PM -->|Delegate| LLMConfigService
MDM -->|Delegate| LLMConfigService
CIOM -->|Delegate| LLMConfigService
AMM -->|Delegate| LLMConfigService
subgraph Storage ["저장소"]
SQLite[(SQLite 기본 저장소)]
JSON[(JSON 폴백)]
end
PM --> SQLite
PM --> JSON
subgraph Cache ["캐시"]
PI[Provider Index<br/>Map 인덱스]
end
PM --> PI
델리게이트 인터페이스
interface ProviderManagerDelegate {
loadConfig(): Promise<LLMsConfig>; // JSON 설정 불러오기
saveConfig(): Promise<void>; // JSON 설정 저장
getDb(): DbClient | null; // 데이터베이스 인스턴스 가져오기
waitForMigration(): Promise<void>; // 데이터 마이그레이션 완료 대기
useSQLite(): boolean; // SQLite 사용 여부
isMigrated(): Promise<boolean>; // 마이그레이션 완료 여부
invalidateProviderIndex(): void; // Provider 인덱스 무효화
invalidateConfig(): void; // 설정 캐시 무효화
buildProviderIndex(providers: LLMProvider[]): void; // 인덱스 빌드
getProviderFromIndex(id: string): LLMProvider | undefined; // 인덱스 조회
hasProviderIndex(): boolean; // 인덱스 존재 여부
}
데이터 구조
LLMProvider 전체 정의
interface LLMProvider {
id: string; // UUID 또는 프리셋 ID (예: 'openai', 'anthropic')
name: string; // 표시 이름
apiFormat?: ApiFormat; // 기본 API 포맷 필드
chatApiFormat?: ApiFormat; // 레거시 포맷 필드 (하위 호환성)
apiType?: string; // 레거시 타입 필드
api_base_url?: string; // API 베이스 URL
api_key: string; // 단일 키 값 (레거시; 신규 시스템은 ApiKeyPool 사용)
models: string[]; // 지원 모델 ID 목록
modelConfigs?: ModelConfig[]; // 모델별 세부 설정
modelGroups?: ModelGroup[]; // 모델 그룹 (카테고리 정보 포함)
modelsEndpoint?: string; // 모델 검색 API 엔드포인트
enabled: boolean; // 활성화 여부
transformer?: TransformerConfig; // 요청/응답 트랜스포머 체인
icon?: string; // 아이콘 경로 또는 이모지
website?: string; // 공식 웹사이트 링크
docsUrl?: string; // API 문서 링크
defaultSettings?: CompletionSettings; // 기본 완성 설정
codingPlan?: CodingPlanConfig; // Coding Plan 전용 설정
isSystem?: boolean; // 시스템 내장 플래그 (삭제 불가)
presetId?: string; // 소스 프리셋 템플릿 ID
headers?: Record<string, string>; // 커스텀 HTTP 헤더
createdAt: string; // ISO 타임스탬프
updatedAt: string; // ISO 타임스탬프
}
interface ModelConfig {
id: string;
name: string;
enabled: boolean;
category?: 'chat' | 'reasoning' | 'image' | 'video' | 'embedding' | 'code';
group?: string;
contextLength?: number;
maxTokens?: number;
completionSettings?: CompletionSettings;
vision?: boolean; // 비전 기능
functionCall?: boolean; // 함수 호출 기능
reasoning?: boolean; // 추론 기능
webSearch?: boolean; // 웹 검색 기능
}
알고리즘 및 로직
프로바이더 CRUD 작업 흐름
프로바이더 추가 (SQLite 경로)
1. LLMProviderInput 수신
2. UUID를 provider.id로 생성
3. 기본값 채우기 (enabled=false, defaultSettings=DEFAULT_COMPLETION_SETTINGS)
4. Zod 스키마로 유효성 검사 (LLMProviderSchema.parse)
5. 고유성 확인: SQLite 조회로 ID 미존재 확인
6. SQLite에 쓰기: db.llmProvidersInsert(provider)
7. 모델 목록을 llm_models 테이블에 동기화
8. 모델 그룹을 llm_model_groups 테이블에 동기화
9. Provider Index 무효화: invalidateProviderIndex()
10. { success: true, provider } 반환
프로바이더 수정
1. { id, ...updates } 수신
2. SQLite에서 기존 프로바이더 읽기
3. 수정된 필드 병합 (얕은 병합)
4. updatedAt 타임스탬프 업데이트
5. SQLite에 쓰기: db.llmProvidersUpdate(id, updates)
6. models 또는 modelConfigs 변경 시 → 모델 테이블 동기화
7. Provider Index 무효화
8. { success: true, provider } 반환
프로바이더 삭제
1. isSystem 플래그 확인 → 시스템 내장 프로바이더는 삭제 불가
2. SQLite에서 삭제: db.llmProvidersDelete(id)
3. 연관된 모델 및 그룹 계단식 삭제
4. Provider Index 무효화
5. { success: true } 반환
Provider Index (O(1) 조회)
graph LR
subgraph FirstQuery ["첫 번째 조회"]
Load[loadConfig 또는 SQLite 쿼리] --> Build[Map 빌드]
Build --> Cache[providerIndex: Map<id, Provider>]
end
subgraph SubsequentQuery ["이후 조회"]
Cache --> Lookup[O(1) Map.get]
end
subgraph Mutations ["변경 작업"]
CRUD[add/update/delete] --> Invalidate[providerIndex = null]
Invalidate --> Load
end
동작 규칙:
| 작업 | 인덱스에 미치는 영향 |
|---|---|
getProvider(id) | 인덱스 히트 → 반환; 미스 → 인덱스 재빌드 후 조회 |
getProviders() | 전체 목록 반환 및 인덱스 재빌드 |
addProvider | 인덱스 무효화 |
updateProvider | 인덱스 무효화 |
deleteProvider | 인덱스 무효화 |
toggleProvider | 인덱스 무효화 |
이중 저장소 메커니즘 (SQLite + JSON 폴백)
flowchart TD
Start[시작] --> CheckDB{DbClient 사용 가능?}
CheckDB -->|Yes| Migrate[ensureMigrated]
CheckDB -->|No| JSONMode[JSON 모드]
Migrate --> CheckMigrated{이미 마이그레이션됨?}
CheckMigrated -->|Yes| SQLiteMode[SQLite 모드]
CheckMigrated -->|No| DoMigrate[JSON → SQLite 마이그레이션]
DoMigrate --> SQLiteMode
subgraph SQLiteMode ["SQLite 모드"]
SQLiteMode --> SQLiteRead[db.llmProvidersGetAll]
SQLiteMode --> SQLiteWrite[db.llmProvidersInsert/Update/Delete]
end
subgraph JSONMode ["JSON 모드"]
JSONMode --> JSONRead[fs.readFile + JSON.parse]
JSONMode --> JSONWrite[JSON.stringify + fs.writeFile]
end
마이그레이션 절차:
- 시작 시 SQLite의
llm_config_migrated플래그 확인 - 아직 마이그레이션되지 않았으면
llms-config.json읽기 - 각 프로바이더 레코드를 SQLite에 하나씩 기록
- 모델 및 modelGroups를 해당 테이블에 동기화
- 마이그레이션 완료 플래그 설정
- JSON 파일은 백업으로 유지 (더 이상 기본 저장소가 아님)
프리셋 / 템플릿 시스템
기본 프로바이더 목록
시스템에는 다음 내장 프로바이더 템플릿이 포함됩니다 (getDefaultProviders()에 정의된 순서):
| # | ID | 이름 | API 포맷 |
|---|---|---|---|
| 1 | deepseek | DeepSeek | openai |
| 2 | openrouter | OpenRouter | openai |
| 3 | silicon | SiliconFlow | openai |
| 4 | openai | OpenAI | openai |
| 5 | anthropic | Anthropic | anthropic |
| 6 | gemini | Google Gemini | |
| 7 | zhipu | Zhipu AI | openai |
| 8 | moonshot | Moonshot | openai |
| 9 | dashscope | Tongyi Qianwen | openai |
| 10 | ollama | Ollama | openai |
| 11 | groq | Groq | openai |
| 12 | claude-code | Claude Code | anthropic |
템플릿으로 프로바이더 생성
createProviderFromTemplate(template):
1. 템플릿의 모든 설정 필드 복사
2. api_key = '' 설정 (사용자가 직접 입력해야 함)
3. enabled = false 설정
4. defaultSettings 채우기 (template.defaultSettings 또는 DEFAULT_COMPLETION_SETTINGS 사용)
5. createdAt / updatedAt 타임스탬프 생성
6. LLMProvider 인스턴스 반환
프로바이더 프리셋
프리셋 템플릿은 provider-presets.ts에서 제공되며, 내장 템플릿보다 더 풍부한 설정을 제공합니다. 주로 중국 클라우드 벤더를 대상으로 합니다. 프리셋 템플릿의 구조는 다음과 같습니다:
interface PresetProviderTemplate {
id: string; // 프리셋 ID
name: string; // 표시 이름
apiFormat: ApiFormat; // API 포맷
baseUrl: string; // 베이스 URL
models: string[]; // 모델 목록
modelConfigs: ModelConfig[]; // 모델별 세부 설정
features: string[]; // 기능 태그
// ... 추가 필드
}
프리셋으로 프로바이더 추가:
addFromPreset(presetId, apiKey?):
1. getPresetById(presetId)로 프리셋 탐색
2. 프리셋을 ProviderTemplate으로 변환
3. createProviderFromTemplate(template) 호출
4. apiKey가 제공된 경우 → 키 설정
5. addProvider()를 호출하여 영속화
6. 새 프로바이더 반환
IPC 통합 테이블
| IPC 채널 | 방향 | 파라미터 | 반환값 | Zod 유효성 검사 |
|---|---|---|---|---|
llmConfig:getProviders | R → M | 없음 | LLMProvider[] | 없음 |
llmConfig:getProvider | R → M | { id: string } | LLMProvider | null | llmProviderIdSchema |
llmConfig:addProvider | R → M | LLMProviderInput | LLMProviderResult | llmProviderCreateSchema |
llmConfig:updateProvider | R → M | { id, ...fields } | LLMProviderResult | llmProviderUpdateSchema |
llmConfig:deleteProvider | R → M | { id: string } | { success, message? } | llmProviderIdSchema |
llmConfig:toggleProvider | R → M | { id, enabled } | LLMProviderResult | id + boolean |
llmConfig:discoverModels | R → M | { id, options? } | ProviderModelDiscoveryResult | modelDiscoverOptionsSchema |
llmConfig:getProviderPresets | R → M | 없음 | PresetProviderTemplate[] | 없음 |
llmConfig:addFromPreset | R → M | { presetId, apiKey? } | LLMProviderResult | string + string? |
llmConfig:exportConfig | R → M | 없음 | LLMsConfig | 없음 |
llmConfig:importConfig | R → M | 설정 객체 | { success, message? } | Zod object |
확장 포인트
새 내장 프로바이더 추가
packages/desktop/app/shared/llm-config.ts의PROVIDER_TEMPLATES배열에ProviderTemplate추가ProviderManager.getDefaultProviders()의defaultTemplateIds배열에 새 ID 추가- 특수 URL 빌드 로직이 필요한 경우
url-builder.ts에 처리 추가 - 검색 지원이 필요한 경우
provider-presets.ts의PROVIDER_SEARCH_CONFIGS에 항목 추가 - Coding Plan 지원이 필요한 경우
CODING_PLAN_URL_PRESETS에 항목 추가 - Follow-Provider 지원이 필요한 경우
PROVIDER_MODEL_MAPPINGS에 항목 추가
새 프리셋 템플릿 추가
provider-presets.ts의PROVIDER_PRESETS배열에PresetProviderTemplate추가- 완전한 모델 설정 작성 (modelConfigs, contextLength, maxTokens, capabilities)
- 프론트엔드가 자동으로 프리셋 목록에 새 템플릿을 표시
관련 파일
| 파일 | 관계 |
|---|---|
capabilities/llm/config-service/LLMConfigService.ts | ProviderManager를 초기화하고 Delegate를 제공하는 호스트 서비스 |
capabilities/llm/config-service/schemas.ts | Zod 유효성 검사 스키마 (LLMProviderSchema 등) |
routers/llm/ProviderRouter.ts | IPC 레이어: 프론트엔드 요청 수신 후 LLMConfigService 호출 |
routers/llm/schemas.ts | IPC 파라미터 유효성 검사 스키마 |
shared/llm-config.ts | 공유 타입 정의 및 내장 템플릿 |
shared/provider-presets.ts | 프리셋 템플릿, 검색 설정, Coding Plan URL |
workers/DbClient.ts | SQLite 데이터베이스 작업 |
workers/db/apiKeys.ts | API 키 테이블 CRUD |
capabilities/llm/completion/CompletionService.ts | API 호출을 위해 프로바이더 설정을 소비 |