Как расширять LLM Providers
Этот документ даёт пошаговые инструкции для трёх распространённых сценариев расширения: добавление нового preset template, добавление нового API format и настройка search capabilities для провайдера.
Расположение файлов
| Файл | Путь |
|---|---|
| Provider templates | packages/desktop/app/shared/llm-config.ts |
| Provider presets | 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 |
| Frontend i18n | packages/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.ts→ApiFormatSchemarouters/llm/schemas.ts→llmProviderCreateSchema.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-param | API включает поиск через параметр запроса (например enable_search: true) | DashScope, Baidu |
builtin-tool | API требует внедрить конкретное определение tool в массив tools | Kimi, Volcengine |
mcp | Поиск предоставляется внешним MCP server | Пользовательский deployment |
sdk-native | SDK обрабатывает поиск нативно (например 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.ts | CODING_PLAN_URL_PRESETS (если нужно) | Опционально |
shared/provider-presets.ts | PROVIDER_MODEL_MAPPINGS (если нужно) | Опционально |
shared/provider-presets.ts | PROVIDER_SEARCH_CONFIGS (если нужен поиск) | Опционально |
| i18n JSON files (en/zh/ja) | Переводы имени провайдера | Рекомендуется |
Сценарий 2: новый API format
| Файл | Изменение | Обязательно |
|---|---|---|
capabilities/llm/completion/types.ts | Тип ApiFormat | Да |
capabilities/llm/completion/url-builder.ts | URL builder + resolveApiFormat + buildProviderApiUrl | Да |
capabilities/llm/completion/header-builder.ts | Auth headers | Да |
capabilities/llm/completion/message-converter.ts | Преобразование формата сообщений | Да |
capabilities/llm/completion/DirectApiHandler.ts | Non-streaming handler | Да |
capabilities/llm/completion/StreamHandler.ts | Streaming handler | Да |
capabilities/llm/completion/CompletionService.ts | Регистрация switch branch | Да |
capabilities/llm/config-service/schemas.ts | ApiFormatSchema | Да |
routers/llm/schemas.ts | llmProviderCreateSchema | Да |
Сценарий 3: добавление search config
| Файл | Изменение | Обязательно |
|---|---|---|
shared/provider-presets.ts | Запись PROVIDER_SEARCH_CONFIGS | Да |
Руководство по тестированию
Тесты preset template
-
Unit tests: проверьте целостность данных template
- Все обязательные поля присутствуют и валидны
- Каждый model ID из modelConfigs присутствует в массиве models
- Значение apiFormat входит в enum ApiFormat
-
Integration tests:
- Создать провайдера из preset → проверить корректность данных провайдера
- Включить провайдера → настроить валидный API Key → отправить тестовое сообщение
- Model discovery → проверить возвращённый список моделей
-
UI tests:
- Новый preset появляется в списке preset на странице настроек
- Нажатие "Add" корректно создаёт провайдера
- Страница конфигурации провайдера показывает правильные поля
Тесты API format
-
URL builder tests:
- Разные форматы baseUrl → корректный API endpoint
- URL с path prefixes → prefix не теряется
-
Message conversion tests:
- Plain text message → корректный format
- Message with images → корректная обработка
- Message with tool calls → корректный format
-
Handler tests:
- Non-streaming: отправить запрос → разобрать ответ → CompletionResult
- Streaming: SSE events → callbacks вызываются корректно
- Error handling: сетевые ошибки, API errors, format errors
-
End-to-end tests:
- Использовать
testModel()для проверки полного pipeline - Streaming conversation → проверить callbacks onDelta/onDone
- Использовать
Тесты search config
-
Injection tests:
getSearchConfig()корректно распознаёт нового провайдераbuildSearchAugmentation()создаёт корректное injection-содержимое- Тип model-param: request body содержит корректные параметры
- Тип builtin-tool: tools array содержит корректное tool definition
-
Conflict tests:
- Если
conflictsWithFCtrue: search tool и function calling не сосуществуют - Ограничение
applicableModels: поиск не inject для неприменимых моделей
- Если
Связанные файлы
| Файл | Связь |
|---|---|
shared/llm-config.ts | PROVIDER_TEMPLATES, ApiFormat, core types |
shared/provider-presets.ts | Preset templates, search configs, Coding Plan URLs, model mappings |
capabilities/llm/config-service/ProviderManager.ts | Default provider list, template creation logic |
capabilities/llm/config-service/schemas.ts | ApiFormatSchema, validation schemas |
capabilities/llm/completion/types.ts | Определение типа ApiFormat |
capabilities/llm/completion/url-builder.ts | resolveApiFormat, URL builders |
capabilities/llm/completion/header-builder.ts | Auth headers |
capabilities/llm/completion/message-converter.ts | Преобразование формата сообщений |
capabilities/llm/completion/DirectApiHandler.ts | Non-streaming API handler |
capabilities/llm/completion/StreamHandler.ts | Streaming API handler |
capabilities/llm/completion/CompletionService.ts | Регистрация handler dispatch |
capabilities/llm/completion/ProviderSearchInjector.ts | Search injection logic |
routers/llm/schemas.ts | IPC parameter validation schemas |
| i18n JSON files | Frontend translations |