본문으로 건너뛰기

LLM 프로바이더 확장 방법

이 문서는 세 가지 일반적인 확장 시나리오에 대한 단계별 지침을 제공합니다: 새로운 프리셋 템플릿 추가, 새로운 API 형식 추가, 그리고 프로바이더의 검색 기능 구성.


파일 위치

파일경로
프로바이더 템플릿packages/desktop/app/shared/llm-config.ts
프로바이더 프리셋packages/desktop/app/shared/provider-presets.ts
ProviderManagerpackages/desktop/app/main/services/capabilities/llm/config-service/ProviderManager.ts
URL Builderpackages/desktop/app/main/services/capabilities/llm/completion/url-builder.ts
Header Builderpackages/desktop/app/main/services/capabilities/llm/completion/header-builder.ts
Message Converterpackages/desktop/app/main/services/capabilities/llm/completion/message-converter.ts
DirectApiHandlerpackages/desktop/app/main/services/capabilities/llm/completion/DirectApiHandler.ts
StreamHandlerpackages/desktop/app/main/services/capabilities/llm/completion/StreamHandler.ts
CompletionServicepackages/desktop/app/main/services/capabilities/llm/completion/CompletionService.ts
Typespackages/desktop/app/main/services/capabilities/llm/completion/types.ts
ProviderSearchInjectorpackages/desktop/app/main/services/capabilities/llm/completion/ProviderSearchInjector.ts
NativeSearchInjectorpackages/desktop/app/main/services/capabilities/llm/completion/NativeSearchInjector.ts
IPC Router Schemaspackages/desktop/app/main/services/routers/llm/schemas.ts
프론트엔드 i18npackages/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.tsPROVIDER_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.tsPROVIDER_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.tsApiFormat에 새로운 값을 추가합니다:

// 변경 전
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.tsApiFormatSchema도 업데이트합니다:

const ApiFormatSchema = z.enum([
'openai', 'anthropic', 'google', 'azure-openai', 'openai-response', 'newformat'
]);

그리고 packages/desktop/app/main/services/routers/llm/schemas.tsllmProviderCreateSchema도 업데이트합니다.

2단계: URL 빌더

url-builder.ts에 빌더 함수를 추가합니다:

// 의사 코드
function buildNewFormatApiUrl(baseUrl: string): string {
// 프로바이더의 API 문서에 따라 올바른 엔드포인트 URL 빌드
// 다양한 baseUrl 입력 형식 처리
}

새로운 형식에 대한 인식 로직을 resolveApiFormat()에 추가합니다(apiType에서 추론해야 하는 경우).

buildProviderApiUrl()에 새로운 형식에 대한 분기를 추가합니다.

3단계: 헤더 빌더

header-builder.tsgetProviderHeaders()에서 새로운 형식의 인증 헤더를 처리합니다:

// 의사 코드 — 프로바이더마다 다른 인증 방식 사용
// 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.tsApiFormatSchema
  • routers/llm/schemas.tsllmProviderCreateSchema.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-paramAPI가 요청 파라미터로 검색 활성화 (예: enable_search: true)DashScope, Baidu
builtin-toolAPI가 tools 배열에 특정 도구 정의 주입 필요Kimi, Volcengine
mcp외부 MCP 서버가 검색 제공커스텀 배포
sdk-nativeSDK가 네이티브로 검색 처리 (예: Anthropic 서버 사이드 도구)Anthropic
none검색 미지원Ollama

2단계: 검색 구성 추가

packages/desktop/app/shared/provider-presets.tsPROVIDER_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단계: 주입 확인

ProviderSearchInjectorgetSearchConfig()를 통해 프로바이더의 검색 구성을 자동으로 감지하고 buildSearchAugmentation()에서 주입 콘텐츠를 빌드합니다. 추가 코드 변경은 필요하지 않습니다.


파일 수정 체크리스트

시나리오 1: 새로운 프리셋 템플릿

파일변경 내용필수 여부
shared/provider-presets.tsPROVIDER_PRESETS 항목 추가필수
shared/llm-config.tsPROVIDER_TEMPLATES 항목 추가 (기본값으로 필요한 경우)선택
capabilities/llm/config-service/ProviderManager.tsdefaultTemplateIds에 추가 (기본값으로 필요한 경우)선택
shared/provider-presets.tsCODING_PLAN_URL_PRESETS (필요한 경우)선택
shared/provider-presets.tsPROVIDER_MODEL_MAPPINGS (필요한 경우)선택
shared/provider-presets.tsPROVIDER_SEARCH_CONFIGS (검색이 필요한 경우)선택
i18n JSON 파일 (en/zh/ja)프로바이더 이름 번역권장

시나리오 2: 새로운 API 형식

파일변경 내용필수 여부
capabilities/llm/completion/types.tsApiFormat 타입필수
capabilities/llm/completion/url-builder.tsURL 빌더 + 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.tsswitch 분기 등록필수
capabilities/llm/config-service/schemas.tsApiFormatSchema필수
routers/llm/schemas.tsllmProviderCreateSchema필수

시나리오 3: 검색 구성 추가

파일변경 내용필수 여부
shared/provider-presets.tsPROVIDER_SEARCH_CONFIGS 항목필수

테스트 가이드

프리셋 템플릿 테스트

  1. 단위 테스트: 템플릿 데이터 무결성 검증

    • 모든 필수 필드가 존재하고 유효한지 확인
    • modelConfigs의 모든 모델 ID가 models 배열에 존재하는지 확인
    • apiFormat 값이 ApiFormat 열거형 내에 있는지 확인
  2. 통합 테스트:

    • 프리셋으로 프로바이더 생성 → 프로바이더 데이터가 올바른지 확인
    • 프로바이더 활성화 → 유효한 API Key 구성 → 테스트 메시지 전송
    • 모델 탐색 → 반환된 모델 목록 확인
  3. UI 테스트:

    • 설정 페이지의 프리셋 목록에 새로운 프리셋이 표시되는지 확인
    • "추가" 클릭 시 프로바이더가 올바르게 생성되는지 확인
    • 프로바이더 구성 페이지에 올바른 필드가 표시되는지 확인

API 형식 테스트

  1. URL 빌더 테스트:

    • 다양한 baseUrl 입력 형식 → 올바른 API 엔드포인트
    • 경로 프리픽스가 있는 URL → 프리픽스가 유실되지 않음
  2. 메시지 변환 테스트:

    • 일반 텍스트 메시지 → 올바른 형식
    • 이미지가 있는 메시지 → 올바르게 처리됨
    • 도구 호출이 있는 메시지 → 올바른 형식
  3. 핸들러 테스트:

    • 비스트리밍: 요청 전송 → 응답 파싱 → CompletionResult
    • 스트리밍: SSE 이벤트 → 콜백이 올바르게 호출됨
    • 오류 처리: 네트워크 오류, API 오류, 형식 오류
  4. 엔드투엔드 테스트:

    • testModel()을 사용하여 전체 파이프라인 검증
    • 스트리밍 대화 → onDelta/onDone 콜백 확인

검색 구성 테스트

  1. 주입 테스트:

    • getSearchConfig()가 새로운 프로바이더를 올바르게 인식하는지 확인
    • buildSearchAugmentation()이 올바른 주입 콘텐츠를 생성하는지 확인
    • model-param 유형: 요청 본문에 올바른 파라미터가 포함되는지 확인
    • builtin-tool 유형: tools 배열에 올바른 도구 정의가 포함되는지 확인
  2. 충돌 테스트:

    • conflictsWithFC가 true일 때: 검색 도구와 함수 호출이 공존하지 않는지 확인
    • applicableModels 제한: 적용 불가 모델에는 검색이 주입되지 않는지 확인

관련 파일

파일관계
shared/llm-config.tsPROVIDER_TEMPLATES, ApiFormat, 핵심 타입
shared/provider-presets.ts프리셋 템플릿, 검색 구성, Coding Plan URL, 모델 매핑
capabilities/llm/config-service/ProviderManager.ts기본 프로바이더 목록, 템플릿 생성 로직
capabilities/llm/config-service/schemas.tsApiFormatSchema, 검증 스키마
capabilities/llm/completion/types.tsApiFormat 타입 정의
capabilities/llm/completion/url-builder.tsresolveApiFormat, 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.tsIPC 파라미터 검증 스키마
i18n JSON 파일프론트엔드 번역