refactor(provider-pool): 重构提供者池管理器的日志和健康检查逻辑
- 添加日志级别控制和统一的日志输出方法 - 重构健康检查逻辑,使用默认模型配置和标准化的请求构建 - 优化批量保存机制,减少文件I/O操作 - 添加参数校验和错误处理 - 重命名方法以更准确表达其功能
This commit is contained in:
parent
08881ae144
commit
1c22b8c5c9
1 changed files with 220 additions and 174 deletions
|
|
@ -6,15 +6,28 @@ import { MODEL_PROVIDER } from './common.js';
|
||||||
* Manages a pool of API service providers, handling their health and selection.
|
* Manages a pool of API service providers, handling their health and selection.
|
||||||
*/
|
*/
|
||||||
export class ProviderPoolManager {
|
export class ProviderPoolManager {
|
||||||
|
// 默认健康检查模型配置
|
||||||
|
static DEFAULT_HEALTH_CHECK_MODELS = {
|
||||||
|
'gemini-cli': 'gemini-2.5-flash',
|
||||||
|
'openai-custom': 'gpt-3.5-turbo',
|
||||||
|
'claude-custom': 'claude-3-7-sonnet-20250219',
|
||||||
|
'kiro-api': 'claude-3-7-sonnet-20250219',
|
||||||
|
'qwen-api': 'qwen3-coder-flash',
|
||||||
|
'openai-custom-responses': 'gpt-5-low'
|
||||||
|
};
|
||||||
|
|
||||||
constructor(providerPools, options = {}) {
|
constructor(providerPools, options = {}) {
|
||||||
this.providerPools = providerPools;
|
this.providerPools = providerPools;
|
||||||
this.globalConfig = options.globalConfig || {}; // 存储全局配置
|
this.globalConfig = options.globalConfig || {}; // 存储全局配置
|
||||||
this.providerStatus = {}; // Tracks health and usage for each provider instance
|
this.providerStatus = {}; // Tracks health and usage for each provider instance
|
||||||
this.roundRobinIndex = {}; // Tracks the current index for round-robin selection for each provider type
|
this.roundRobinIndex = {}; // Tracks the current index for round-robin selection for each provider type
|
||||||
this.maxErrorCount = options.maxErrorCount || 3; // Default to 1 errors before marking unhealthy
|
this.maxErrorCount = options.maxErrorCount || 3; // Default to 3 errors before marking unhealthy
|
||||||
this.healthCheckInterval = options.healthCheckInterval || 10 * 60 * 1000; // Default to 10 minutes
|
this.healthCheckInterval = options.healthCheckInterval || 10 * 60 * 1000; // Default to 10 minutes
|
||||||
|
|
||||||
// 优化1: 添加防抖机制,避免频繁的文件 I/O 操作
|
// 日志级别控制
|
||||||
|
this.logLevel = options.logLevel || 'info'; // 'debug', 'info', 'warn', 'error'
|
||||||
|
|
||||||
|
// 添加防抖机制,避免频繁的文件 I/O 操作
|
||||||
this.saveDebounceTime = options.saveDebounceTime || 1000; // 默认1秒防抖
|
this.saveDebounceTime = options.saveDebounceTime || 1000; // 默认1秒防抖
|
||||||
this.saveTimer = null;
|
this.saveTimer = null;
|
||||||
this.pendingSaves = new Set(); // 记录待保存的 providerType
|
this.pendingSaves = new Set(); // 记录待保存的 providerType
|
||||||
|
|
@ -22,6 +35,31 @@ export class ProviderPoolManager {
|
||||||
this.initializeProviderStatus();
|
this.initializeProviderStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志输出方法,支持日志级别控制
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_log(level, message) {
|
||||||
|
const levels = { debug: 0, info: 1, warn: 2, error: 3 };
|
||||||
|
if (levels[level] >= levels[this.logLevel]) {
|
||||||
|
const logMethod = level === 'debug' ? 'log' : level;
|
||||||
|
console[logMethod](`[ProviderPoolManager] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找指定的 provider
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_findProvider(providerType, uuid) {
|
||||||
|
if (!providerType || !uuid) {
|
||||||
|
this._log('error', `Invalid parameters: providerType=${providerType}, uuid=${uuid}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const pool = this.providerStatus[providerType];
|
||||||
|
return pool?.find(p => p.uuid === uuid) || null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the status for each provider in the pools.
|
* Initializes the status for each provider in the pools.
|
||||||
* Initially, all providers are considered healthy and have zero usage.
|
* Initially, all providers are considered healthy and have zero usage.
|
||||||
|
|
@ -49,7 +87,7 @@ export class ProviderPoolManager {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log('[ProviderPoolManager] Initialized provider statuses: ok');
|
this._log('info', 'Initialized provider statuses: ok');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -59,17 +97,23 @@ export class ProviderPoolManager {
|
||||||
* @returns {object|null} The selected provider's configuration, or null if no healthy provider is found.
|
* @returns {object|null} The selected provider's configuration, or null if no healthy provider is found.
|
||||||
*/
|
*/
|
||||||
selectProvider(providerType) {
|
selectProvider(providerType) {
|
||||||
|
// 参数校验
|
||||||
|
if (!providerType || typeof providerType !== 'string') {
|
||||||
|
this._log('error', `Invalid providerType: ${providerType}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const availableProviders = this.providerStatus[providerType] || [];
|
const availableProviders = this.providerStatus[providerType] || [];
|
||||||
const availableAndHealthyProviders = availableProviders.filter(p =>
|
const availableAndHealthyProviders = availableProviders.filter(p =>
|
||||||
p.config.isHealthy && !p.config.isDisabled
|
p.config.isHealthy && !p.config.isDisabled
|
||||||
);
|
);
|
||||||
|
|
||||||
if (availableAndHealthyProviders.length === 0) {
|
if (availableAndHealthyProviders.length === 0) {
|
||||||
console.warn(`[ProviderPoolManager] No available and healthy providers for type: ${providerType}`);
|
this._log('warn', `No available and healthy providers for type: ${providerType}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优化3: 简化轮询逻辑,移除不必要的循环
|
// 简化轮询逻辑
|
||||||
const currentIndex = this.roundRobinIndex[providerType] || 0;
|
const currentIndex = this.roundRobinIndex[providerType] || 0;
|
||||||
const providerIndex = currentIndex % availableAndHealthyProviders.length;
|
const providerIndex = currentIndex % availableAndHealthyProviders.length;
|
||||||
const selected = availableAndHealthyProviders[providerIndex];
|
const selected = availableAndHealthyProviders[providerIndex];
|
||||||
|
|
@ -81,9 +125,9 @@ export class ProviderPoolManager {
|
||||||
selected.config.lastUsed = new Date().toISOString();
|
selected.config.lastUsed = new Date().toISOString();
|
||||||
selected.config.usageCount++;
|
selected.config.usageCount++;
|
||||||
|
|
||||||
console.log(`[ProviderPoolManager] Selected provider for ${providerType} (round-robin): ${JSON.stringify(selected.config)}`);
|
this._log('debug', `Selected provider for ${providerType} (round-robin): ${selected.config.uuid}`);
|
||||||
|
|
||||||
// 优化1: 使用防抖保存
|
// 使用防抖保存
|
||||||
this._debouncedSave(providerType);
|
this._debouncedSave(providerType);
|
||||||
|
|
||||||
return selected.config;
|
return selected.config;
|
||||||
|
|
@ -95,23 +139,24 @@ export class ProviderPoolManager {
|
||||||
* @param {object} providerConfig - The configuration of the provider to mark.
|
* @param {object} providerConfig - The configuration of the provider to mark.
|
||||||
*/
|
*/
|
||||||
markProviderUnhealthy(providerType, providerConfig) {
|
markProviderUnhealthy(providerType, providerConfig) {
|
||||||
const pool = this.providerStatus[providerType];
|
if (!providerConfig?.uuid) {
|
||||||
if (pool) {
|
this._log('error', 'Invalid providerConfig in markProviderUnhealthy');
|
||||||
const provider = pool.find(p => p.uuid === providerConfig.uuid);
|
return;
|
||||||
if (provider) {
|
}
|
||||||
provider.config.errorCount++;
|
|
||||||
provider.config.lastErrorTime = new Date().toISOString(); // Update last error time in config
|
|
||||||
|
|
||||||
if (provider.config.errorCount >= this.maxErrorCount) {
|
const provider = this._findProvider(providerType, providerConfig.uuid);
|
||||||
provider.config.isHealthy = false;
|
if (provider) {
|
||||||
console.warn(`[ProviderPoolManager] Marked provider as unhealthy: ${providerConfig.uuid} for type ${providerType}. Total errors: ${provider.config.errorCount}`);
|
provider.config.errorCount++;
|
||||||
} else {
|
provider.config.lastErrorTime = new Date().toISOString();
|
||||||
console.warn(`[ProviderPoolManager] Provider ${providerConfig.uuid} for type ${providerType} error count: ${provider.config.errorCount}/${this.maxErrorCount}. Still healthy.`);
|
|
||||||
}
|
if (provider.config.errorCount >= this.maxErrorCount) {
|
||||||
|
provider.config.isHealthy = false;
|
||||||
// 优化1: 使用防抖保存
|
this._log('warn', `Marked provider as unhealthy: ${providerConfig.uuid} for type ${providerType}. Total errors: ${provider.config.errorCount}`);
|
||||||
this._debouncedSave(providerType);
|
} else {
|
||||||
|
this._log('warn', `Provider ${providerConfig.uuid} for type ${providerType} error count: ${provider.config.errorCount}/${this.maxErrorCount}. Still healthy.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._debouncedSave(providerType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,43 +164,46 @@ export class ProviderPoolManager {
|
||||||
* Marks a provider as healthy.
|
* Marks a provider as healthy.
|
||||||
* @param {string} providerType - The type of the provider.
|
* @param {string} providerType - The type of the provider.
|
||||||
* @param {object} providerConfig - The configuration of the provider to mark.
|
* @param {object} providerConfig - The configuration of the provider to mark.
|
||||||
|
* @param {boolean} isInit - Whether to reset usage count (optional, default: false).
|
||||||
*/
|
*/
|
||||||
markProviderHealthy(isInit, providerType, providerConfig) {
|
markProviderHealthy(providerType, providerConfig, isInit = false) {
|
||||||
const pool = this.providerStatus[providerType];
|
if (!providerConfig?.uuid) {
|
||||||
if (pool) {
|
this._log('error', 'Invalid providerConfig in markProviderHealthy');
|
||||||
const provider = pool.find(p => p.uuid === providerConfig.uuid);
|
return;
|
||||||
if (provider) {
|
}
|
||||||
provider.config.isHealthy = true;
|
|
||||||
provider.config.errorCount = 0; // Reset error count on health recovery
|
const provider = this._findProvider(providerType, providerConfig.uuid);
|
||||||
provider.config.lastErrorTime = null; // Reset lastErrorTime when healthy
|
if (provider) {
|
||||||
if (isInit) {
|
provider.config.isHealthy = true;
|
||||||
provider.config.usageCount = 0; // Reset usage count on health recovery
|
provider.config.errorCount = 0;
|
||||||
}
|
provider.config.lastErrorTime = null;
|
||||||
console.log(`[ProviderPoolManager] Marked provider as healthy: ${provider.config.uuid} for type ${providerType}`);
|
if (isInit) {
|
||||||
|
provider.config.usageCount = 0;
|
||||||
// 优化1: 使用防抖保存
|
|
||||||
this._debouncedSave(providerType);
|
|
||||||
}
|
}
|
||||||
|
this._log('info', `Marked provider as healthy: ${provider.config.uuid} for type ${providerType}`);
|
||||||
|
|
||||||
|
this._debouncedSave(providerType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks a provider as healthy.
|
* 重置提供商的计数器(错误计数和使用计数)
|
||||||
* @param {string} providerType - The type of the provider.
|
* @param {string} providerType - The type of the provider.
|
||||||
* @param {object} providerConfig - The configuration of the provider to mark.
|
* @param {object} providerConfig - The configuration of the provider to mark.
|
||||||
*/
|
*/
|
||||||
markProviderZero(providerType, providerConfig) {
|
resetProviderCounters(providerType, providerConfig) {
|
||||||
const pool = this.providerStatus[providerType];
|
if (!providerConfig?.uuid) {
|
||||||
if (pool) {
|
this._log('error', 'Invalid providerConfig in resetProviderCounters');
|
||||||
const provider = pool.find(p => p.uuid === providerConfig.uuid);
|
return;
|
||||||
if (provider) {
|
}
|
||||||
provider.config.errorCount = 0; // Reset error count on health recovery
|
|
||||||
provider.config.usageCount = 0; // Reset usage count on health recovery
|
const provider = this._findProvider(providerType, providerConfig.uuid);
|
||||||
console.log(`[ProviderPoolManager] Marked provider as zero: ${provider.config.uuid} for type ${providerType}`);
|
if (provider) {
|
||||||
|
provider.config.errorCount = 0;
|
||||||
// 优化1: 使用防抖保存
|
provider.config.usageCount = 0;
|
||||||
this._debouncedSave(providerType);
|
this._log('info', `Reset provider counters: ${provider.config.uuid} for type ${providerType}`);
|
||||||
}
|
|
||||||
|
this._debouncedSave(providerType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,16 +213,16 @@ export class ProviderPoolManager {
|
||||||
* @param {object} providerConfig - 提供商配置
|
* @param {object} providerConfig - 提供商配置
|
||||||
*/
|
*/
|
||||||
disableProvider(providerType, providerConfig) {
|
disableProvider(providerType, providerConfig) {
|
||||||
const pool = this.providerStatus[providerType];
|
if (!providerConfig?.uuid) {
|
||||||
if (pool) {
|
this._log('error', 'Invalid providerConfig in disableProvider');
|
||||||
const provider = pool.find(p => p.uuid === providerConfig.uuid);
|
return;
|
||||||
if (provider) {
|
}
|
||||||
provider.config.isDisabled = true;
|
|
||||||
console.log(`[ProviderPoolManager] Disabled provider: ${providerConfig.uuid} for type ${providerType}`);
|
const provider = this._findProvider(providerType, providerConfig.uuid);
|
||||||
|
if (provider) {
|
||||||
// 使用防抖保存
|
provider.config.isDisabled = true;
|
||||||
this._debouncedSave(providerType);
|
this._log('info', `Disabled provider: ${providerConfig.uuid} for type ${providerType}`);
|
||||||
}
|
this._debouncedSave(providerType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,16 +232,16 @@ export class ProviderPoolManager {
|
||||||
* @param {object} providerConfig - 提供商配置
|
* @param {object} providerConfig - 提供商配置
|
||||||
*/
|
*/
|
||||||
enableProvider(providerType, providerConfig) {
|
enableProvider(providerType, providerConfig) {
|
||||||
const pool = this.providerStatus[providerType];
|
if (!providerConfig?.uuid) {
|
||||||
if (pool) {
|
this._log('error', 'Invalid providerConfig in enableProvider');
|
||||||
const provider = pool.find(p => p.uuid === providerConfig.uuid);
|
return;
|
||||||
if (provider) {
|
}
|
||||||
provider.config.isDisabled = false;
|
|
||||||
console.log(`[ProviderPoolManager] Enabled provider: ${providerConfig.uuid} for type ${providerType}`);
|
const provider = this._findProvider(providerType, providerConfig.uuid);
|
||||||
|
if (provider) {
|
||||||
// 使用防抖保存
|
provider.config.isDisabled = false;
|
||||||
this._debouncedSave(providerType);
|
this._log('info', `Enabled provider: ${providerConfig.uuid} for type ${providerType}`);
|
||||||
}
|
this._debouncedSave(providerType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,8 +250,9 @@ export class ProviderPoolManager {
|
||||||
* This method would typically be called periodically (e.g., via cron job).
|
* This method would typically be called periodically (e.g., via cron job).
|
||||||
*/
|
*/
|
||||||
async performHealthChecks(isInit = false) {
|
async performHealthChecks(isInit = false) {
|
||||||
console.log('[ProviderPoolManager] Performing health checks on all providers...');
|
this._log('info', 'Performing health checks on all providers...');
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
for (const providerType in this.providerStatus) {
|
for (const providerType in this.providerStatus) {
|
||||||
for (const providerStatus of this.providerStatus[providerType]) {
|
for (const providerStatus of this.providerStatus[providerType]) {
|
||||||
const providerConfig = providerStatus.config;
|
const providerConfig = providerStatus.config;
|
||||||
|
|
@ -211,37 +260,38 @@ export class ProviderPoolManager {
|
||||||
// Only attempt to health check unhealthy providers after a certain interval
|
// Only attempt to health check unhealthy providers after a certain interval
|
||||||
if (!providerStatus.config.isHealthy && providerStatus.config.lastErrorTime &&
|
if (!providerStatus.config.isHealthy && providerStatus.config.lastErrorTime &&
|
||||||
(now.getTime() - new Date(providerStatus.config.lastErrorTime).getTime() < this.healthCheckInterval)) {
|
(now.getTime() - new Date(providerStatus.config.lastErrorTime).getTime() < this.healthCheckInterval)) {
|
||||||
console.log(`[ProviderPoolManager] Skipping health check for ${providerConfig.uuid} (${providerType}). Last error too recent.`);
|
this._log('debug', `Skipping health check for ${providerConfig.uuid} (${providerType}). Last error too recent.`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Perform actual health check based on provider type
|
// Perform actual health check based on provider type
|
||||||
const isHealthy = await this._checkProviderHealth(providerType, providerConfig);
|
const isHealthy = await this._checkProviderHealth(providerType, providerConfig);
|
||||||
if(isHealthy === null){
|
|
||||||
console.log(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}) skipped: Check not implemented.`);
|
if (isHealthy === null) {
|
||||||
this.markProviderZero(providerType, providerConfig);
|
this._log('debug', `Health check for ${providerConfig.uuid} (${providerType}) skipped: Check not implemented.`);
|
||||||
|
this.resetProviderCounters(providerType, providerConfig);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHealthy) {
|
if (isHealthy) {
|
||||||
if (!providerStatus.config.isHealthy) {
|
if (!providerStatus.config.isHealthy) {
|
||||||
// Provider was unhealthy but is now healthy
|
// Provider was unhealthy but is now healthy
|
||||||
this.markProviderHealthy(isInit, providerType, providerConfig);
|
this.markProviderHealthy(providerType, providerConfig, isInit);
|
||||||
console.log(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}): Marked Healthy (actual check)`);
|
this._log('info', `Health check for ${providerConfig.uuid} (${providerType}): Marked Healthy (actual check)`);
|
||||||
} else {
|
} else {
|
||||||
// Provider was already healthy and still is
|
// Provider was already healthy and still is
|
||||||
this.markProviderHealthy(isInit, providerType, providerConfig);
|
this.markProviderHealthy(providerType, providerConfig, isInit);
|
||||||
console.log(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}): Still Healthy`);
|
this._log('debug', `Health check for ${providerConfig.uuid} (${providerType}): Still Healthy`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Provider is not healthy
|
// Provider is not healthy
|
||||||
console.warn(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}) failed: Provider is not responding correctly.`);
|
this._log('warn', `Health check for ${providerConfig.uuid} (${providerType}) failed: Provider is not responding correctly.`);
|
||||||
this.markProviderUnhealthy(providerType, providerConfig);
|
this.markProviderUnhealthy(providerType, providerConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}) failed: ${error.message}`);
|
this._log('error', `Health check for ${providerConfig.uuid} (${providerType}) failed: ${error.message}`);
|
||||||
// If a health check fails, mark it unhealthy, which will update error count and lastErrorTime
|
// If a health check fails, mark it unhealthy, which will update error count and lastErrorTime
|
||||||
this.markProviderUnhealthy(providerType, providerConfig);
|
this.markProviderUnhealthy(providerType, providerConfig);
|
||||||
}
|
}
|
||||||
|
|
@ -249,88 +299,85 @@ export class ProviderPoolManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建健康检查请求
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_buildHealthCheckRequest(providerType, modelName) {
|
||||||
|
const baseMessage = { role: 'user', content: 'Hello, are you ok?' };
|
||||||
|
|
||||||
|
// Gemini 使用不同的请求格式
|
||||||
|
if (providerType === 'gemini-cli') {
|
||||||
|
return {
|
||||||
|
contents: [{
|
||||||
|
role: 'user',
|
||||||
|
parts: [{ text: baseMessage.content }]
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenAI Custom Responses 使用特殊格式
|
||||||
|
if (providerType === 'openai-custom-responses') {
|
||||||
|
return {
|
||||||
|
input: [baseMessage],
|
||||||
|
model: modelName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他提供商(OpenAI、Claude、Kiro、Qwen)使用标准格式
|
||||||
|
return {
|
||||||
|
messages: [baseMessage],
|
||||||
|
model: modelName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs an actual health check for a specific provider.
|
* Performs an actual health check for a specific provider.
|
||||||
* @param {string} providerType - The type of the provider.
|
* @param {string} providerType - The type of the provider.
|
||||||
* @param {object} providerConfig - The configuration of the provider to check.
|
* @param {object} providerConfig - The configuration of the provider to check.
|
||||||
* @returns {Promise<boolean>} - True if the provider is healthy, false otherwise.
|
* @returns {Promise<boolean|null>} - True if healthy, false if unhealthy, null if check not implemented.
|
||||||
*/
|
*/
|
||||||
async _checkProviderHealth(providerType, providerConfig) {
|
async _checkProviderHealth(providerType, providerConfig) {
|
||||||
try {
|
try {
|
||||||
// Create a temporary service adapter for health check
|
// 如果未启用健康检查,返回 null
|
||||||
// 合并全局配置和 provider 配置
|
if (!providerConfig.checkHealth) {
|
||||||
const tempConfig = {
|
|
||||||
// ...this.globalConfig,
|
|
||||||
...providerConfig,
|
|
||||||
MODEL_PROVIDER: providerType,
|
|
||||||
USE_SYSTEM_PROXY_GEMINI: this.globalConfig.USE_SYSTEM_PROXY_GEMINI,
|
|
||||||
USE_SYSTEM_PROXY_OPENAI: this.globalConfig.USE_SYSTEM_PROXY_OPENAI,
|
|
||||||
USE_SYSTEM_PROXY_CLAUDE: this.globalConfig.USE_SYSTEM_PROXY_CLAUDE,
|
|
||||||
USE_SYSTEM_PROXY_QWEN: this.globalConfig.USE_SYSTEM_PROXY_QWEN,
|
|
||||||
USE_SYSTEM_PROXY_KIRO: this.globalConfig.USE_SYSTEM_PROXY_KIRO,
|
|
||||||
};
|
|
||||||
const serviceAdapter = getServiceAdapter(tempConfig);
|
|
||||||
if(!providerConfig.checkHealth){
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine a suitable model name for health check
|
// 合并全局配置和 provider 配置(简化代理配置)
|
||||||
// First, try to get it from the provider configuration
|
const proxyKeys = ['GEMINI', 'OPENAI', 'CLAUDE', 'QWEN', 'KIRO'];
|
||||||
let modelName = providerConfig.checkModelName;
|
const tempConfig = {
|
||||||
|
...providerConfig,
|
||||||
// If not specified in config, use default model names based on provider type
|
MODEL_PROVIDER: providerType
|
||||||
if (!modelName) {
|
|
||||||
switch (providerType) {
|
|
||||||
case MODEL_PROVIDER.GEMINI_CLI:
|
|
||||||
modelName = 'gemini-2.5-flash'; // Example model name for Gemini
|
|
||||||
break;
|
|
||||||
case MODEL_PROVIDER.OPENAI_CUSTOM:
|
|
||||||
modelName = 'gpt-3.5-turbo'; // Example model name for OpenAI
|
|
||||||
break;
|
|
||||||
case MODEL_PROVIDER.CLAUDE_CUSTOM:
|
|
||||||
modelName = 'claude-3-7-sonnet-20250219'; // Example model name for Claude
|
|
||||||
break;
|
|
||||||
case MODEL_PROVIDER.KIRO_API:
|
|
||||||
modelName = 'claude-3-7-sonnet-20250219'; // Example model name for Kiro API
|
|
||||||
break;
|
|
||||||
case MODEL_PROVIDER.QWEN_API:
|
|
||||||
modelName = 'qwen3-coder-flash'; // Example model name for Qwen
|
|
||||||
break;
|
|
||||||
case MODEL_PROVIDER.OPENAI_CUSTOM_RESPONSES:
|
|
||||||
modelName = 'gpt-5-low'; // Example model name for OpenAI Custom Responses
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.warn(`[ProviderPoolManager] Unknown provider type for health check: ${providerType}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform a lightweight API call to check health
|
|
||||||
const healthCheckRequest = {
|
|
||||||
contents: [{
|
|
||||||
role: 'user',
|
|
||||||
parts: [{ text: 'Hello, are you ok?' }]
|
|
||||||
}]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (providerType === MODEL_PROVIDER.OPENAI_CUSTOM_RESPONSES) {
|
// 动态添加代理配置
|
||||||
healthCheckRequest.input = [{ role: 'user', content: 'Hello, are you ok?' }];
|
proxyKeys.forEach(key => {
|
||||||
healthCheckRequest.model = modelName;
|
const proxyKey = `USE_SYSTEM_PROXY_${key}`;
|
||||||
delete healthCheckRequest.contents;
|
if (this.globalConfig[proxyKey] !== undefined) {
|
||||||
|
tempConfig[proxyKey] = this.globalConfig[proxyKey];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const serviceAdapter = getServiceAdapter(tempConfig);
|
||||||
|
|
||||||
|
// 确定健康检查使用的模型名称
|
||||||
|
const modelName = providerConfig.checkModelName ||
|
||||||
|
ProviderPoolManager.DEFAULT_HEALTH_CHECK_MODELS[providerType];
|
||||||
|
|
||||||
|
if (!modelName) {
|
||||||
|
this._log('warn', `Unknown provider type for health check: ${providerType}`);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For OpenAI and Claude providers, we need a different request format
|
// 构建健康检查请求
|
||||||
if (providerType === MODEL_PROVIDER.OPENAI_CUSTOM || providerType === MODEL_PROVIDER.CLAUDE_CUSTOM || providerType === MODEL_PROVIDER.KIRO_API || providerType === MODEL_PROVIDER.QWEN_API) {
|
const healthCheckRequest = this._buildHealthCheckRequest(providerType, modelName);
|
||||||
healthCheckRequest.messages = [{ role: 'user', content: 'Hello, are you ok?' }];
|
|
||||||
healthCheckRequest.model = modelName;
|
|
||||||
delete healthCheckRequest.contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log(`[ProviderPoolManager] Health check request for ${modelName}: ${JSON.stringify(healthCheckRequest)}`);
|
this._log('debug', `Health check request for ${modelName}: ${JSON.stringify(healthCheckRequest)}`);
|
||||||
await serviceAdapter.generateContent(modelName, healthCheckRequest);
|
await serviceAdapter.generateContent(modelName, healthCheckRequest);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[ProviderPoolManager] Health check failed for ${providerType}: ${error.message}`);
|
this._log('error', `Health check failed for ${providerType}: ${error.message}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -356,57 +403,56 @@ export class ProviderPoolManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优化1: 批量保存所有待保存的 providerType
|
* 批量保存所有待保存的 providerType(优化为单次文件写入)
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _flushPendingSaves() {
|
async _flushPendingSaves() {
|
||||||
const typesToSave = Array.from(this.pendingSaves);
|
const typesToSave = Array.from(this.pendingSaves);
|
||||||
|
if (typesToSave.length === 0) return;
|
||||||
|
|
||||||
this.pendingSaves.clear();
|
this.pendingSaves.clear();
|
||||||
this.saveTimer = null;
|
this.saveTimer = null;
|
||||||
|
|
||||||
for (const providerType of typesToSave) {
|
|
||||||
await this._saveProviderPoolsToJson(providerType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the current provider pools configuration to the JSON file.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _saveProviderPoolsToJson(providerTypeToUpdate) {
|
|
||||||
try {
|
try {
|
||||||
const filePath = this.globalConfig.PROVIDER_POOLS_FILE_PATH || 'provider_pools.json';
|
const filePath = this.globalConfig.PROVIDER_POOLS_FILE_PATH || 'provider_pools.json';
|
||||||
let currentPools = {};
|
let currentPools = {};
|
||||||
|
|
||||||
|
// 一次性读取文件
|
||||||
try {
|
try {
|
||||||
const fileContent = await fs.promises.readFile(filePath, 'utf8');
|
const fileContent = await fs.promises.readFile(filePath, 'utf8');
|
||||||
currentPools = JSON.parse(fileContent);
|
currentPools = JSON.parse(fileContent);
|
||||||
} catch (readError) {
|
} catch (readError) {
|
||||||
if (readError.code === 'ENOENT') {
|
if (readError.code === 'ENOENT') {
|
||||||
console.log('[ProviderPoolManager] provider_pools.json does not exist, creating new file.');
|
this._log('info', 'provider_pools.json does not exist, creating new file.');
|
||||||
} else {
|
} else {
|
||||||
throw readError;
|
throw readError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.providerStatus[providerTypeToUpdate]) {
|
// 更新所有待保存的 providerType
|
||||||
currentPools[providerTypeToUpdate] = this.providerStatus[providerTypeToUpdate].map(p => {
|
for (const providerType of typesToSave) {
|
||||||
// Convert Date objects to ISOString if they exist
|
if (this.providerStatus[providerType]) {
|
||||||
if (p.config.lastUsed instanceof Date) {
|
currentPools[providerType] = this.providerStatus[providerType].map(p => {
|
||||||
p.config.lastUsed = p.config.lastUsed.toISOString();
|
// Convert Date objects to ISOString if they exist
|
||||||
}
|
const config = { ...p.config };
|
||||||
if (p.config.lastErrorTime instanceof Date) {
|
if (config.lastUsed instanceof Date) {
|
||||||
p.config.lastErrorTime = p.config.lastErrorTime.toISOString();
|
config.lastUsed = config.lastUsed.toISOString();
|
||||||
}
|
}
|
||||||
return p.config;
|
if (config.lastErrorTime instanceof Date) {
|
||||||
});
|
config.lastErrorTime = config.lastErrorTime.toISOString();
|
||||||
} else {
|
}
|
||||||
console.warn(`[ProviderPoolManager] Attempted to save unknown providerType: ${providerTypeToUpdate}`);
|
return config;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._log('warn', `Attempted to save unknown providerType: ${providerType}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 一次性写入文件
|
||||||
await fs.promises.writeFile(filePath, JSON.stringify(currentPools, null, 2), 'utf8');
|
await fs.promises.writeFile(filePath, JSON.stringify(currentPools, null, 2), 'utf8');
|
||||||
console.log(`[ProviderPoolManager] provider_pools.json for ${providerTypeToUpdate} updated successfully.`);
|
this._log('info', `provider_pools.json updated successfully for types: ${typesToSave.join(', ')}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ProviderPoolManager] Failed to write provider_pools.json:', error);
|
this._log('error', `Failed to write provider_pools.json: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue