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

Как расширять LLM Providers

Этот документ даёт пошаговые инструкции для трёх распространённых сценариев расширения: добавление нового preset template, добавление нового API format и настройка search capabilities для провайдера.


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

ФайлПуть
Provider templatespackages/desktop/app/shared/llm-config.ts
Provider presetspackages/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
Frontend i18npackages/renderer/src/locales/{en,zh,ja}/providers/llm.json

Архитектурный контекст

graph TB
subgraph ExtensionPoints ["Extension Points"]
direction TB
A["(1) New preset template<br/>provider-presets.ts"]
B["(2) New API format<br/>types + handlers"]
C["(3) Search config<br/>PROVIDER_SEARCH_CONFIGS"]
end

subgraph AffectedLayers ["Affected Layers"]
direction TB
Shared[shared layer<br/>types + presets]
Completion[completion layer<br/>Handlers + URL]
Config[config-service layer<br/>ProviderManager]
Router[router layer<br/>IPC Schemas]
Frontend[renderer layer<br/>UI + i18n]
end

A --> Shared
A --> Config
A --> Frontend

B --> Shared
B --> Completion
B --> Router

C --> Shared
C --> Completion

Сценарий 1: добавление нового preset template

Когда нужно поддержать нового LLM provider (например, нового облачного вендора или международного провайдера), создайте preset template, чтобы пользователи могли добавить его из UI одним нажатием.

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

// Full interface for a preset template
interface PresetProviderTemplate {
id: string; // Unique ID (lowercase, e.g. 'minimax')
name: string; // Display name
apiFormat: ApiFormat; // API format
baseUrl: string; // Base URL
modelsEndpoint?: string; // Model discovery endpoint
models: string[]; // List of supported model IDs
modelConfigs: ModelConfig[]; // Per-model detailed config
features: string[]; // Feature tags
icon?: string; // Icon
website?: string; // Official website
docsUrl?: string; // API documentation
defaultSettings?: CompletionSettings; // Default completion parameters
}

Шаги

Шаг 1: определите preset template

Добавьте запись в массив PROVIDER_PRESETS в packages/desktop/app/shared/provider-presets.ts:

// Pseudocode — adding a new preset
{
id: 'newprovider',
name: 'NewProvider AI',
apiFormat: 'openai', // Most Chinese vendors are OpenAI-compatible
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 (опционально): сделайте его провайдером по умолчанию

Если новый провайдер должен появляться в начальных списках у всех пользователей:

Добавьте ID в массив defaultTemplateIds в ProviderManager.getDefaultProviders().

Также добавьте соответствующий ProviderTemplate в PROVIDER_TEMPLATES в packages/desktop/app/shared/llm-config.ts.

Шаг 3 (опционально): добавьте поддержку Coding Plan

Если у провайдера есть отдельный Coding Plan API:

// Add to CODING_PLAN_URL_PRESETS
CODING_PLAN_URL_PRESETS['newprovider'] = {
baseUrl: 'https://api.newprovider.com/coding/v1',
separateApiKey: false, // Whether a separate API Key is needed
};

Шаг 4 (опционально): добавьте mapping для Follow-Provider

Если нужно автоматически выбирать background/vision модели того же провайдера:

// Add to PROVIDER_MODEL_MAPPINGS
PROVIDER_MODEL_MAPPINGS['newprovider'] = {
primary: 'np-large',
background: 'np-lite',
vision: 'np-vision', // Set to null if no vision model exists
};

Шаг 5: добавьте frontend i18n

Добавьте перевод имени провайдера в providers/llm.json под packages/renderer/src/locales/ для en/zh/ja.


Сценарий 2: добавление нового API format

Если вы столкнулись с API провайдера, несовместимым с существующими форматами (openai/anthropic/google/azure-openai/openai-response), нужно добавить новый API format.

Обзор шагов

flowchart TD
S1["(1) Define format identifier<br/>types.ts"] --> S2["(2) URL builder<br/>url-builder.ts"]
S2 --> S3["(3) Header builder<br/>header-builder.ts"]
S3 --> S4["(4) Message conversion<br/>message-converter.ts"]
S4 --> S5["(5) Non-streaming handler<br/>DirectApiHandler.ts"]
S5 --> S6["(6) Streaming handler<br/>StreamHandler.ts"]
S6 --> S7["(7) Register dispatch<br/>CompletionService.ts"]
S7 --> S8["(8) Schema update<br/>schemas.ts"]

Шаг 1: определите идентификатор формата

Добавьте новое значение в ApiFormat в packages/desktop/app/main/services/capabilities/llm/completion/types.ts:

// Before
type ApiFormat = 'openai' | 'anthropic' | 'google' | 'azure-openai' | 'openai-response';

// After
type ApiFormat = 'openai' | 'anthropic' | 'google' | 'azure-openai' | 'openai-response' | 'newformat';

Также обновите ApiFormatSchema в packages/desktop/app/main/services/capabilities/llm/config-service/schemas.ts:

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

И обновите llmProviderCreateSchema в packages/desktop/app/main/services/routers/llm/schemas.ts.

Шаг 2: URL Builder

Добавьте builder-функцию в url-builder.ts:

// Pseudocode
function buildNewFormatApiUrl(baseUrl: string): string {
// Build the correct endpoint URL according to the provider's API docs
// Handle various baseUrl input formats
}

Добавьте распознавание нового формата в resolveApiFormat() (если его нужно выводить из apiType).

Добавьте ветку для нового формата в buildProviderApiUrl().

Шаг 3: Header Builder

Обработайте authentication headers для нового формата в getProviderHeaders() внутри header-builder.ts:

// Pseudocode — different providers use different auth schemes
// OpenAI: Authorization: Bearer <key>
// Anthropic: x-api-key: <key>
// New format may use different headers

Шаг 4: преобразование формата сообщений

Добавьте функцию преобразования в message-converter.ts:

// Pseudocode
function convertMessageToNewFormat(message: SimpleChatMessage): NewFormatMessage {
// Convert the generic message format to the provider-specific format
// Handle role mapping, content structure, images, tool calls, etc.
}

Шаг 5: non-streaming handler

Добавьте в DirectApiHandler.ts:

// Pseudocode
async function callNewFormatCompletion(
provider: LLMProvider,
apiKey: string,
options: CompletionOptions,
logger: LoggerService
): Promise<CompletionResult> {
// Build request body
// Send request
// Parse response into CompletionResult
}

Шаг 6: streaming handler

Добавьте в StreamHandler.ts:

// Pseudocode
async function streamNewFormatCompletion(
provider: LLMProvider,
apiKey: string,
options: CompletionOptions,
messageId: string,
callbacks: StreamCallbacks,
logger: LoggerService
): Promise<void> {
// Build streaming request body
// Use streamSSEResponse() or custom stream parsing
// Call callbacks to deliver incremental content
}

Шаг 7: зарегистрируйте dispatch

Зарегистрируйте новый формат в CompletionService:

// In the switch inside callDirectHandler
case 'newformat':
return callNewFormatCompletion(provider, apiKey, options, this.logger);

// In the switch inside callStreamHandler
case 'newformat':
await streamNewFormatCompletion(provider, apiKey, options, messageId, callbacks, this.logger);
return;

Шаг 8: обновите schema

Убедитесь, что все Zod schemas, которые ссылаются на enum ApiFormat, обновлены:

  • capabilities/llm/config-service/schemas.tsApiFormatSchema
  • routers/llm/schemas.tsllmProviderCreateSchema.apiFormat

Сценарий 3: добавление search config для провайдера

Если провайдер поддерживает web search, нужно настроить способ injection поиска.

Выбор типа поиска

flowchart TD
Start[Provider supports search?] --> Type{Search implementation}
Type -->|Request parameter| ModelParam["model-param<br/>modify request body"]
Type -->|Built-in tool definition| BuiltinTool["builtin-tool<br/>inject into tools array"]
Type -->|MCP server| MCP["mcp<br/>external handling"]
Type -->|SDK native| SDKNative["sdk-native<br/>NativeSearchInjector"]
Type -->|Not supported| None["none<br/>no config needed"]

Шаги

Шаг 1: определите тип поиска

Реализация поискаКритерийПримеры
model-paramAPI включает поиск через параметр запроса (например enable_search: true)DashScope, Baidu
builtin-toolAPI требует внедрить конкретное определение tool в массив toolsKimi, Volcengine
mcpПоиск предоставляется внешним MCP serverПользовательский deployment
sdk-nativeSDK обрабатывает поиск нативно (например Anthropic server-side tools)Anthropic
noneПоиск не поддерживаетсяOllama

Шаг 2: добавьте search config

Добавьте запись в PROVIDER_SEARCH_CONFIGS в packages/desktop/app/shared/provider-presets.ts:

Тип model-param (параметр запроса):

// Pseudocode
PROVIDER_SEARCH_CONFIGS['newprovider'] = {
type: 'model-param',
paramName: 'enable_search', // parameter name
paramValue: true, // parameter value
extraParams: { // extra parameters (optional)
search_mode: 'auto',
},
applicableModels: null, // null means all models
};

Тип builtin-tool (встроенный tool):

// Pseudocode
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, // Whether it conflicts with function calling
applicableModels: ['np-large'], // Only certain models support it (null = all)
};

Шаг 3: проверьте injection

ProviderSearchInjector автоматически определяет search config провайдера через getSearchConfig() и строит содержимое injection в buildSearchAugmentation(). Дополнительные изменения кода не требуются.


Checklist изменений файлов

Сценарий 1: новый preset template

ФайлИзменениеОбязательно
shared/provider-presets.tsДобавить запись PROVIDER_PRESETSДа
shared/llm-config.tsДобавить запись PROVIDER_TEMPLATES (если нужно по умолчанию)Опционально
capabilities/llm/config-service/ProviderManager.tsДобавить в defaultTemplateIds (если нужно по умолчанию)Опционально
shared/provider-presets.tsCODING_PLAN_URL_PRESETS (если нужно)Опционально
shared/provider-presets.tsPROVIDER_MODEL_MAPPINGS (если нужно)Опционально
shared/provider-presets.tsPROVIDER_SEARCH_CONFIGS (если нужен поиск)Опционально
i18n JSON files (en/zh/ja)Переводы имени провайдераРекомендуется

Сценарий 2: новый API format

ФайлИзменениеОбязательно
capabilities/llm/completion/types.tsТип ApiFormatДа
capabilities/llm/completion/url-builder.tsURL builder + resolveApiFormat + buildProviderApiUrlДа
capabilities/llm/completion/header-builder.tsAuth headersДа
capabilities/llm/completion/message-converter.tsПреобразование формата сообщенийДа
capabilities/llm/completion/DirectApiHandler.tsNon-streaming handlerДа
capabilities/llm/completion/StreamHandler.tsStreaming handlerДа
capabilities/llm/completion/CompletionService.tsРегистрация switch branchДа
capabilities/llm/config-service/schemas.tsApiFormatSchemaДа
routers/llm/schemas.tsllmProviderCreateSchemaДа

Сценарий 3: добавление search config

ФайлИзменениеОбязательно
shared/provider-presets.tsЗапись PROVIDER_SEARCH_CONFIGSДа

Руководство по тестированию

Тесты preset template

  1. Unit tests: проверьте целостность данных template

    • Все обязательные поля присутствуют и валидны
    • Каждый model ID из modelConfigs присутствует в массиве models
    • Значение apiFormat входит в enum ApiFormat
  2. Integration tests:

    • Создать провайдера из preset → проверить корректность данных провайдера
    • Включить провайдера → настроить валидный API Key → отправить тестовое сообщение
    • Model discovery → проверить возвращённый список моделей
  3. UI tests:

    • Новый preset появляется в списке preset на странице настроек
    • Нажатие "Add" корректно создаёт провайдера
    • Страница конфигурации провайдера показывает правильные поля

Тесты API format

  1. URL builder tests:

    • Разные форматы baseUrl → корректный API endpoint
    • URL с path prefixes → prefix не теряется
  2. Message conversion tests:

    • Plain text message → корректный format
    • Message with images → корректная обработка
    • Message with tool calls → корректный format
  3. Handler tests:

    • Non-streaming: отправить запрос → разобрать ответ → CompletionResult
    • Streaming: SSE events → callbacks вызываются корректно
    • Error handling: сетевые ошибки, API errors, format errors
  4. End-to-end tests:

    • Использовать testModel() для проверки полного pipeline
    • Streaming conversation → проверить callbacks onDelta/onDone

Тесты search config

  1. Injection tests:

    • getSearchConfig() корректно распознаёт нового провайдера
    • buildSearchAugmentation() создаёт корректное injection-содержимое
    • Тип model-param: request body содержит корректные параметры
    • Тип builtin-tool: tools array содержит корректное tool definition
  2. Conflict tests:

    • Если conflictsWithFC true: search tool и function calling не сосуществуют
    • Ограничение applicableModels: поиск не inject для неприменимых моделей

Связанные файлы

ФайлСвязь
shared/llm-config.tsPROVIDER_TEMPLATES, ApiFormat, core types
shared/provider-presets.tsPreset templates, search configs, Coding Plan URLs, model mappings
capabilities/llm/config-service/ProviderManager.tsDefault provider list, template creation logic
capabilities/llm/config-service/schemas.tsApiFormatSchema, validation schemas
capabilities/llm/completion/types.tsОпределение типа ApiFormat
capabilities/llm/completion/url-builder.tsresolveApiFormat, URL builders
capabilities/llm/completion/header-builder.tsAuth headers
capabilities/llm/completion/message-converter.tsПреобразование формата сообщений
capabilities/llm/completion/DirectApiHandler.tsNon-streaming API handler
capabilities/llm/completion/StreamHandler.tsStreaming API handler
capabilities/llm/completion/CompletionService.tsРегистрация handler dispatch
capabilities/llm/completion/ProviderSearchInjector.tsSearch injection logic
routers/llm/schemas.tsIPC parameter validation schemas
i18n JSON filesFrontend translations