LLM 프로바이더 확장 방법
이 문서는 세 가지 일반적인 확장 시나리오에 대한 단계별 지침을 제공합니다: 새로운 프리셋 템플릿 추가, 새로운 API 형식 추가, 그리고 프로바이더의 검색 기능 구성.
파일 위치
| 파일 | 경로 |
|---|---|
| 프로바이더 템플릿 | packages/desktop/app/shared/llm-config.ts |
| 프로바이더 프리셋 | packages/desktop/app/shared/provider-presets.ts |
| ProviderManager | packages/desktop/app/main/services/capabilities/llm/config-service/ProviderManager.ts |
| URL Builder | packages/desktop/app/main/services/capabilities/llm/completion/url-builder.ts |
| Header Builder | packages/desktop/app/main/services/capabilities/llm/completion/header-builder.ts |
| Message Converter | packages/desktop/app/main/services/capabilities/llm/completion/message-converter.ts |
| DirectApiHandler | packages/desktop/app/main/services/capabilities/llm/completion/DirectApiHandler.ts |
| StreamHandler | packages/desktop/app/main/services/capabilities/llm/completion/StreamHandler.ts |
| CompletionService | packages/desktop/app/main/services/capabilities/llm/completion/CompletionService.ts |
| Types | packages/desktop/app/main/services/capabilities/llm/completion/types.ts |
| ProviderSearchInjector | packages/desktop/app/main/services/capabilities/llm/completion/ProviderSearchInjector.ts |
| NativeSearchInjector | packages/desktop/app/main/services/capabilities/llm/completion/NativeSearchInjector.ts |
| IPC Router Schemas | packages/desktop/app/main/services/routers/llm/schemas.ts |
| 프론트엔드 i18n | packages/renderer/src/locales/{en,zh,ja}/providers/llm.json |
아키텍처 컨텍스트
graph TB
subgraph ExtensionPoints ["확장 포인트"]
direction TB
A["(1) 새로운 프리셋 템플릿<br/>provider-presets.ts"]
B["(2) 새로운 API 형식<br/>types + handlers"]
C["(3) 검색 구성<br/>PROVIDER_SEARCH_CONFIGS"]
end
subgraph AffectedLayers ["영향받는 레이어"]
direction TB
Shared[shared 레이어<br/>types + presets]
Completion[completion 레이어<br/>Handlers + URL]
Config[config-service 레이어<br/>ProviderManager]
Router[router 레이어<br/>IPC Schemas]
Frontend[renderer 레이어<br/>UI + i18n]
end
A --> Shared
A --> Config
A --> Frontend
B --> Shared
B --> Completion
B --> Router
C --> Shared
C --> Completion
시나리오 1: 새로운 프리셋 템플릿 추가
새로운 LLM 프로바이더(예: 새로운 클라우드 벤더 또는 해외 프로바이더)를 지원해야 할 때, 사용자가 UI에서 클릭 한 번으로 추가할 수 있도록 프리셋 템플릿을 생성합니다.
데이터 구조
// 프리셋 템플릿의 전체 인터페이스
interface PresetProviderTemplate {
id: string; // 고유 ID (소문자, 예: 'minimax')
name: string; // 표시 이름
apiFormat: ApiFormat; // API 형식
baseUrl: string; // 기본 URL
modelsEndpoint?: string; // 모델 탐색 엔드포인트
models: string[]; // 지원 모델 ID 목록
modelConfigs: ModelConfig[]; // 모델별 상세 구성
features: string[]; // 기능 태그
icon?: string; // 아이콘
website?: string; // 공식 웹사이트
docsUrl?: string; // API 문서
defaultSettings?: CompletionSettings; // 기본 완성 파라미터
}
단계
1단계: 프리셋 템플릿 정의
packages/desktop/app/shared/provider-presets.ts의 PROVIDER_PRESETS 배열에 항목을 추가합니다:
// 의사 코드 — 새로운 프리셋 추가
{
id: 'newprovider',
name: 'NewProvider AI',
apiFormat: 'openai', // 대부분의 벤더는 OpenAI 호환
baseUrl: 'https://api.newprovider.com/v1',
modelsEndpoint: '/models',
models: ['np-large', 'np-lite', 'np-vision'],
modelConfigs: [
{
id: 'np-large',
name: 'NP Large',
enabled: true,
category: 'chat',
contextLength: 128000,
maxTokens: 8192,
vision: false,
functionCall: true,
reasoning: false,
},
{
id: 'np-lite',
name: 'NP Lite',
enabled: true,
category: 'chat',
contextLength: 32000,
maxTokens: 4096,
},
{
id: 'np-vision',
name: 'NP Vision',
enabled: true,
category: 'chat',
contextLength: 64000,
maxTokens: 4096,
vision: true,
},
],
features: ['chat', 'function_call'],
website: 'https://newprovider.com',
docsUrl: 'https://docs.newprovider.com/api',
}
2단계 (선택): 기본 프로바이더로 설정
새로운 프로바이더가 모든 사용자의 초기 목록에 나타나게 하려면:
ProviderManager.getDefaultProviders()의 defaultTemplateIds 배열에 ID를 추가합니다.
또한 packages/desktop/app/shared/llm-config.ts의 PROVIDER_TEMPLATES에 해당 ProviderTemplate을 추가합니다.
3단계 (선택): Coding Plan 지원 추가
프로바이더가 전용 Coding Plan API를 제공하는 경우:
// CODING_PLAN_URL_PRESETS에 추가
CODING_PLAN_URL_PRESETS['newprovider'] = {
baseUrl: 'https://api.newprovider.com/coding/v1',
separateApiKey: false, // 별도의 API Key 필요 여부
};
4단계 (선택): Follow-Provider 모델 매핑 추가
동일 프로바이더의 백그라운드/비전 모델을 자동 선택하려면:
// PROVIDER_MODEL_MAPPINGS에 추가
PROVIDER_MODEL_MAPPINGS['newprovider'] = {
primary: 'np-large',
background: 'np-lite',
vision: 'np-vision', // 비전 모델이 없으면 null로 설정
};
5단계: 프론트엔드 i18n 추가
packages/renderer/src/locales/의 en/zh/ja 각 providers/llm.json에 프로바이더 이름 번역을 추가합니다.
시나리오 2: 새로운 API 형식 추가
기존 형식(openai/anthropic/google/azure-openai/openai-response)과 호환되지 않는 프로바이더 API를 만났을 때, 새로운 API 형식을 추가해야 합니다.
단계 개요
flowchart TD
S1["(1) 형식 식별자 정의<br/>types.ts"] --> S2["(2) URL 빌더<br/>url-builder.ts"]
S2 --> S3["(3) 헤더 빌더<br/>header-builder.ts"]
S3 --> S4["(4) 메시지 변환<br/>message-converter.ts"]
S4 --> S5["(5) 비스트리밍 핸들러<br/>DirectApiHandler.ts"]
S5 --> S6["(6) 스트리밍 핸들러<br/>StreamHandler.ts"]
S6 --> S7["(7) 디스패치 등록<br/>CompletionService.ts"]
S7 --> S8["(8) 스키마 업데이트<br/>schemas.ts"]
1단계: 형식 식별자 정의
packages/desktop/app/main/services/capabilities/llm/completion/types.ts의 ApiFormat에 새로운 값을 추가합니다:
// 변경 전
type ApiFormat = 'openai' | 'anthropic' | 'google' | 'azure-openai' | 'openai-response';
// 변경 후
type ApiFormat = 'openai' | 'anthropic' | 'google' | 'azure-openai' | 'openai-response' | 'newformat';
packages/desktop/app/main/services/capabilities/llm/config-service/schemas.ts의 ApiFormatSchema도 업데이트합니다:
const ApiFormatSchema = z.enum([
'openai', 'anthropic', 'google', 'azure-openai', 'openai-response', 'newformat'
]);
그리고 packages/desktop/app/main/services/routers/llm/schemas.ts의 llmProviderCreateSchema도 업데이트합니다.
2단계: URL 빌더
url-builder.ts에 빌더 함수를 추가합니다:
// 의사 코드
function buildNewFormatApiUrl(baseUrl: string): string {
// 프로바이더의 API 문서에 따라 올바른 엔드포인트 URL 빌드
// 다양한 baseUrl 입력 형식 처리
}
새로운 형식에 대한 인식 로직을 resolveApiFormat()에 추가합니다(apiType에서 추론해야 하는 경우).
buildProviderApiUrl()에 새로운 형식에 대한 분기를 추가합니다.
3단계: 헤더 빌더
header-builder.ts의 getProviderHeaders()에서 새로운 형식의 인증 헤더를 처리합니다:
// 의사 코드 — 프로바이더마다 다른 인증 방식 사용
// OpenAI: Authorization: Bearer <key>
// Anthropic: x-api-key: <key>
// 새로운 형식은 다른 헤더를 사용할 수 있음
4단계: 메시지 형식 변환
message-converter.ts에 변환 함수를 추가합니다:
// 의사 코드
function convertMessageToNewFormat(message: SimpleChatMessage): NewFormatMessage {
// 범용 메시지 형식을 프로바이더별 형식으로 변환
// 역할 매핑, 콘텐츠 구조, 이미지, 도구 호출 등 처리
}
5단계: 비스트리밍 핸들러
DirectApiHandler.ts에 추가합니다:
// 의사 코드
async function callNewFormatCompletion(
provider: LLMProvider,
apiKey: string,
options: CompletionOptions,
logger: LoggerService
): Promise<CompletionResult> {
// 요청 본문 빌드
// 요청 전송
// 응답을 CompletionResult로 파싱
}
6단계: 스트리밍 핸들러
StreamHandler.ts에 추가합니다:
// 의사 코드
async function streamNewFormatCompletion(
provider: LLMProvider,
apiKey: string,
options: CompletionOptions,
messageId: string,
callbacks: StreamCallbacks,
logger: LoggerService
): Promise<void> {
// 스트리밍 요청 본문 빌드
// streamSSEResponse() 또는 커스텀 스트림 파싱 사용
// 콜백을 호출하여 증분 콘텐츠 전달
}
7단계: 디스패치 등록
CompletionService에 새로운 형식을 등록합니다:
// callDirectHandler 내부의 switch 문에서
case 'newformat':
return callNewFormatCompletion(provider, apiKey, options, this.logger);
// callStreamHandler 내부의 switch 문에서
case 'newformat':
await streamNewFormatCompletion(provider, apiKey, options, messageId, callbacks, this.logger);
return;
8단계: 스키마 업데이트
ApiFormat 열거형을 참조하는 모든 Zod 스키마가 업데이트되었는지 확인합니다:
capabilities/llm/config-service/schemas.ts→ApiFormatSchemarouters/llm/schemas.ts→llmProviderCreateSchema.apiFormat
시나리오 3: 프로바이더에 검색 구성 추가
프로바이더가 웹 검색을 지원하는 경우, 검색 주입 방식을 구성해야 합니다.
검색 유형 결정
flowchart TD
Start[프로바이더가 검색을 지원하나요?] --> Type{검색 구현 방식}
Type -->|요청 파라미터| ModelParam["model-param<br/>요청 본문 수정"]
Type -->|내장 도구 정의| BuiltinTool["builtin-tool<br/>tools 배열에 주입"]
Type -->|MCP 서버| MCP["mcp<br/>외부 처리"]
Type -->|SDK 네이티브| SDKNative["sdk-native<br/>NativeSearchInjector"]
Type -->|미지원| None["none<br/>구성 불필요"]
단계
1단계: 검색 유형 결정
| 검색 구현 방식 | 기준 | 예시 |
|---|---|---|
model-param | API가 요청 파라미터로 검색 활성화 (예: enable_search: true) | DashScope, Baidu |
builtin-tool | API가 tools 배열에 특정 도구 정의 주입 필요 | Kimi, Volcengine |
mcp | 외부 MCP 서버가 검색 제공 | 커스텀 배포 |
sdk-native | SDK가 네이티브로 검색 처리 (예: Anthropic 서버 사이드 도구) | Anthropic |
none | 검색 미지원 | Ollama |
2단계: 검색 구성 추가
packages/desktop/app/shared/provider-presets.ts의 PROVIDER_SEARCH_CONFIGS에 항목을 추가합니다:
model-param 유형 (요청 파라미터):
// 의사 코드
PROVIDER_SEARCH_CONFIGS['newprovider'] = {
type: 'model-param',
paramName: 'enable_search', // 파라미터 이름
paramValue: true, // 파라미터 값
extraParams: { // 추가 파라미터 (선택)
search_mode: 'auto',
},
applicableModels: null, // null은 모든 모델 적용
};
builtin-tool 유형 (내장 도구):
// 의사 코드
PROVIDER_SEARCH_CONFIGS['newprovider'] = {
type: 'builtin-tool',
toolDefinition: {
type: 'function',
function: {
name: 'web_search',
description: 'Search the web for information',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
},
required: ['query'],
},
},
},
conflictsWithFC: false, // 함수 호출과 충돌 여부
applicableModels: ['np-large'], // 특정 모델만 지원 (null = 전체)
};
3단계: 주입 확인
ProviderSearchInjector는 getSearchConfig()를 통해 프로바이더의 검색 구성을 자동으로 감지하고 buildSearchAugmentation()에서 주입 콘텐츠를 빌드합니다. 추가 코드 변경은 필요하지 않습니다.
파일 수정 체크리스트
시나리오 1: 새로운 프리셋 템플릿
| 파일 | 변경 내용 | 필수 여부 |
|---|---|---|
shared/provider-presets.ts | PROVIDER_PRESETS 항목 추가 | 필수 |
shared/llm-config.ts | PROVIDER_TEMPLATES 항목 추가 (기본값으로 필요한 경우) | 선택 |
capabilities/llm/config-service/ProviderManager.ts | defaultTemplateIds에 추가 (기본값으로 필요한 경우) | 선택 |
shared/provider-presets.ts | CODING_PLAN_URL_PRESETS (필요한 경우) | 선택 |
shared/provider-presets.ts | PROVIDER_MODEL_MAPPINGS (필요한 경우) | 선택 |
shared/provider-presets.ts | PROVIDER_SEARCH_CONFIGS (검색이 필요한 경우) | 선택 |
| i18n JSON 파일 (en/zh/ja) | 프로바이더 이름 번역 | 권장 |
시나리오 2: 새로운 API 형식
| 파일 | 변경 내용 | 필수 여부 |
|---|---|---|
capabilities/llm/completion/types.ts | ApiFormat 타입 | 필수 |
capabilities/llm/completion/url-builder.ts | URL 빌더 + resolveApiFormat + buildProviderApiUrl | 필수 |
capabilities/llm/completion/header-builder.ts | 인증 헤더 | 필수 |
capabilities/llm/completion/message-converter.ts | 메시지 형식 변환 | 필수 |
capabilities/llm/completion/DirectApiHandler.ts | 비스트리밍 핸들러 | 필수 |
capabilities/llm/completion/StreamHandler.ts | 스트리밍 핸들러 | 필수 |
capabilities/llm/completion/CompletionService.ts | switch 분기 등록 | 필수 |
capabilities/llm/config-service/schemas.ts | ApiFormatSchema | 필수 |
routers/llm/schemas.ts | llmProviderCreateSchema | 필수 |
시나리오 3: 검색 구성 추가
| 파일 | 변경 내용 | 필수 여부 |
|---|---|---|
shared/provider-presets.ts | PROVIDER_SEARCH_CONFIGS 항목 | 필수 |
테스트 가이드
프리셋 템플릿 테스트
-
단위 테스트: 템플릿 데이터 무결성 검증
- 모든 필수 필드가 존재하고 유효한지 확인
- modelConfigs의 모든 모델 ID가 models 배열에 존재하는지 확인
- apiFormat 값이 ApiFormat 열거형 내에 있는지 확인
-
통합 테스트:
- 프리셋으로 프로바이더 생성 → 프로바이더 데이터가 올바른지 확인
- 프로바이더 활성화 → 유효한 API Key 구성 → 테스트 메시지 전송
- 모델 탐색 → 반환된 모델 목록 확인
-
UI 테스트:
- 설정 페이지의 프리셋 목록에 새로운 프리셋이 표시되는지 확인
- "추가" 클릭 시 프로바이더가 올바르게 생성되는지 확인
- 프로바이더 구성 페이지에 올바른 필드가 표시되는지 확인
API 형식 테스트
-
URL 빌더 테스트:
- 다양한 baseUrl 입력 형식 → 올바른 API 엔드포인트
- 경로 프리픽스가 있는 URL → 프리픽스가 유실되지 않음
-
메시지 변환 테스트:
- 일반 텍스트 메시지 → 올바른 형식
- 이미지가 있는 메시지 → 올바르게 처리됨
- 도구 호출이 있는 메시지 → 올바른 형식
-
핸들러 테스트:
- 비스트리밍: 요청 전송 → 응답 파싱 → CompletionResult
- 스트리밍: SSE 이벤트 → 콜백이 올바르게 호출됨
- 오류 처리: 네트워크 오류, API 오류, 형식 오류
-
엔드투엔드 테스트:
testModel()을 사용하여 전체 파이프라인 검증- 스트리밍 대화 → onDelta/onDone 콜백 확인
검색 구성 테스트
-
주입 테스트:
getSearchConfig()가 새로운 프로바이더를 올바르게 인식하는지 확인buildSearchAugmentation()이 올바른 주입 콘텐츠를 생성하는지 확인- model-param 유형: 요청 본문에 올바른 파라미터가 포함되는지 확인
- builtin-tool 유형: tools 배열에 올바른 도구 정의가 포함되는지 확인
-
충돌 테스트:
conflictsWithFC가 true일 때: 검색 도구와 함수 호출이 공존하지 않는지 확인applicableModels제한: 적용 불가 모델에는 검색이 주입되지 않는지 확인
관련 파일
| 파일 | 관계 |
|---|---|
shared/llm-config.ts | PROVIDER_TEMPLATES, ApiFormat, 핵심 타입 |
shared/provider-presets.ts | 프리셋 템플릿, 검색 구성, Coding Plan URL, 모델 매핑 |
capabilities/llm/config-service/ProviderManager.ts | 기본 프로바이더 목록, 템플릿 생성 로직 |
capabilities/llm/config-service/schemas.ts | ApiFormatSchema, 검증 스키마 |
capabilities/llm/completion/types.ts | ApiFormat 타입 정의 |
capabilities/llm/completion/url-builder.ts | resolveApiFormat, URL 빌더 |
capabilities/llm/completion/header-builder.ts | 인증 헤더 |
capabilities/llm/completion/message-converter.ts | 메시지 형식 변환 |
capabilities/llm/completion/DirectApiHandler.ts | 비스트리밍 API 핸들러 |
capabilities/llm/completion/StreamHandler.ts | 스트리밍 API 핸들러 |
capabilities/llm/completion/CompletionService.ts | 핸들러 디스패치 등록 |
capabilities/llm/completion/ProviderSearchInjector.ts | 검색 주입 로직 |
routers/llm/schemas.ts | IPC 파라미터 검증 스키마 |
| i18n JSON 파일 | 프론트엔드 번역 |