fix(provider): 增加provider.needsRefresh重置并调整最大错误次数为10

- 在重置provider健康状态时增加needsRefresh标志重置
- 将MAX_ERROR_COUNT默认值从3调整为10,提高容错性
- 忽略400状态码的错误计数,避免客户端参数问题影响provider健康状态
This commit is contained in:
hex2077 2026-01-22 12:24:33 +08:00
parent 75e502cf10
commit ba4ec31f9a
8 changed files with 36 additions and 24 deletions

View file

@ -79,7 +79,7 @@ export async function initializeConfig(args = process.argv.slice(2), configFileP
CRON_NEAR_MINUTES: 15,
CRON_REFRESH_TOKEN: false,
PROVIDER_POOLS_FILE_PATH: null, // 新增号池配置文件路径
MAX_ERROR_COUNT: 3, // 提供商最大错误次数
MAX_ERROR_COUNT: 10, // 提供商最大错误次数
providerFallbackChain: {} // 跨类型 Fallback 链配置
};
console.log('[Config] Using default configuration.');

View file

@ -30,7 +30,7 @@ export class ProviderPoolManager {
this.providerStatus = {}; // Tracks health and usage for each provider instance
this.roundRobinIndex = {}; // Tracks the current index for round-robin selection for each provider type
// 使用 ?? 运算符确保 0 也能被正确设置,而不是被 || 替换为默认值
this.maxErrorCount = options.maxErrorCount ?? 3; // Default to 3 errors before marking unhealthy
this.maxErrorCount = options.maxErrorCount ?? 10; // Default to 10 errors before marking unhealthy
this.healthCheckInterval = options.healthCheckInterval ?? 10 * 60 * 1000; // Default to 10 minutes
// 日志级别控制
@ -974,6 +974,7 @@ export class ProviderPoolManager {
provider.config.isHealthy = true;
provider.config.errorCount = 0;
provider.config.refreshCount = 0;
provider.config.needsRefresh = false;
provider.config.lastErrorTime = null;
provider.config.lastErrorMessage = null;

View file

@ -429,6 +429,7 @@ export async function handleResetProviderHealth(req, res, currentConfig, provide
provider.isHealthy = true;
provider.errorCount = 0;
provider.refreshCount = 0;
provider.needsRefresh = false;
provider.lastErrorTime = null;
resetCount++;
}

View file

@ -325,12 +325,17 @@ export async function handleStreamRequest(res, service, model, requestBody, from
// 如果底层未标记,且不跳过错误计数,则在此处标记
if (!credentialMarkedUnhealthy && !skipErrorCount && providerPoolManager && pooluuid) {
console.log(`[Provider Pool] Marking ${toProvider} as unhealthy due to stream error (status: ${status || 'unknown'})`);
// 如果是号池模式,并且请求处理失败,则标记当前使用的提供者为不健康
providerPoolManager.markProviderUnhealthy(toProvider, {
uuid: pooluuid
});
credentialMarkedUnhealthy = true;
// 400 报错码通常是请求参数问题,不记录为提供商错误
if (status === 400) {
console.log(`[Provider Pool] Skipping unhealthy marking for ${toProvider} (${pooluuid}) due to status 400 (client error)`);
} else {
console.log(`[Provider Pool] Marking ${toProvider} as unhealthy due to stream error (status: ${status || 'unknown'})`);
// 如果是号池模式,并且请求处理失败,则标记当前使用的提供者为不健康
providerPoolManager.markProviderUnhealthy(toProvider, {
uuid: pooluuid
});
credentialMarkedUnhealthy = true;
}
} else if (credentialMarkedUnhealthy) {
console.log(`[Provider Pool] Credential ${pooluuid} already marked as unhealthy by lower layer, skipping duplicate marking`);
} else if (skipErrorCount) {
@ -458,12 +463,17 @@ export async function handleUnaryRequest(res, service, model, requestBody, fromP
// 如果底层未标记,且不跳过错误计数,则在此处标记
if (!credentialMarkedUnhealthy && !skipErrorCount && providerPoolManager && pooluuid) {
console.log(`[Provider Pool] Marking ${toProvider} as unhealthy due to unary error (status: ${status || 'unknown'})`);
// 如果是号池模式,并且请求处理失败,则标记当前使用的提供者为不健康
providerPoolManager.markProviderUnhealthy(toProvider, {
uuid: pooluuid
});
credentialMarkedUnhealthy = true;
// 400 报错码通常是请求参数问题,不记录为提供商错误
if (status === 400) {
console.log(`[Provider Pool] Skipping unhealthy marking for ${toProvider} (${pooluuid}) due to status 400 (client error)`);
} else {
console.log(`[Provider Pool] Marking ${toProvider} as unhealthy due to unary error (status: ${status || 'unknown'})`);
// 如果是号池模式,并且请求处理失败,则标记当前使用的提供者为不健康
providerPoolManager.markProviderUnhealthy(toProvider, {
uuid: pooluuid
});
credentialMarkedUnhealthy = true;
}
} else if (credentialMarkedUnhealthy) {
console.log(`[Provider Pool] Credential ${pooluuid} already marked as unhealthy by lower layer, skipping duplicate marking`);
} else if (skipErrorCount) {

View file

@ -100,7 +100,7 @@ async function loadConfiguration() {
if (cronNearMinutesEl) cronNearMinutesEl.value = data.CRON_NEAR_MINUTES || 1;
if (cronRefreshTokenEl) cronRefreshTokenEl.checked = data.CRON_REFRESH_TOKEN || false;
if (providerPoolsFilePathEl) providerPoolsFilePathEl.value = data.PROVIDER_POOLS_FILE_PATH;
if (maxErrorCountEl) maxErrorCountEl.value = data.MAX_ERROR_COUNT || 3;
if (maxErrorCountEl) maxErrorCountEl.value = data.MAX_ERROR_COUNT || 10;
if (warmupTargetEl) warmupTargetEl.value = data.WARMUP_TARGET || 0;
if (refreshConcurrencyPerProviderEl) refreshConcurrencyPerProviderEl.value = data.REFRESH_CONCURRENCY_PER_PROVIDER || 1;
@ -200,7 +200,7 @@ async function saveConfiguration() {
config.CRON_NEAR_MINUTES = parseInt(document.getElementById('cronNearMinutes')?.value || 1);
config.CRON_REFRESH_TOKEN = document.getElementById('cronRefreshToken')?.checked || false;
config.PROVIDER_POOLS_FILE_PATH = document.getElementById('providerPoolsFilePath')?.value || '';
config.MAX_ERROR_COUNT = parseInt(document.getElementById('maxErrorCount')?.value || 3);
config.MAX_ERROR_COUNT = parseInt(document.getElementById('maxErrorCount')?.value || 10);
config.POOL_SIZE_LIMIT = parseInt(document.getElementById('poolSizeLimit')?.value || 0);
config.WARMUP_TARGET = parseInt(document.getElementById('warmupTarget')?.value || 0);
config.REFRESH_CONCURRENCY_PER_PROVIDER = parseInt(document.getElementById('refreshConcurrencyPerProvider')?.value || 1);

View file

@ -294,8 +294,8 @@ const translations = {
'config.advanced.poolFilePathPlaceholder': '默认: configs/provider_pools.json',
'config.advanced.poolNote': '使用默认路径配置需添加一个空节点',
'config.advanced.maxErrorCount': '提供商最大错误次数',
'config.advanced.maxErrorCountPlaceholder': '默认: 3',
'config.advanced.maxErrorCountNote': '提供商连续错误达到此次数后将被标记为不健康,默认为 3 次',
'config.advanced.maxErrorCountPlaceholder': '默认: 10',
'config.advanced.maxErrorCountNote': '提供商连续错误达到此次数后将被标记为不健康,默认为 10 次',
'config.advanced.poolSizeLimit': '账号池轮询上限',
'config.advanced.poolSizeLimitPlaceholder': '默认: 0 (不限制)',
'config.advanced.poolSizeLimitNote': '每个提供商类型参与轮询的最大健康凭证数量0 表示不限制,使用所有健康凭证',
@ -1007,8 +1007,8 @@ const translations = {
'config.advanced.poolFilePathPlaceholder': 'Default: configs/provider_pools.json',
'config.advanced.poolNote': 'To use default path configuration, add an empty node',
'config.advanced.maxErrorCount': 'Provider Max Error Count',
'config.advanced.maxErrorCountPlaceholder': 'Default: 3',
'config.advanced.maxErrorCountNote': 'Provider will be marked as unhealthy after consecutive errors reach this count, default is 3',
'config.advanced.maxErrorCountPlaceholder': 'Default: 10',
'config.advanced.maxErrorCountNote': 'Provider will be marked as unhealthy after consecutive errors reach this count, default is 10',
'config.advanced.poolSizeLimit': 'Pool Size Limit',
'config.advanced.poolSizeLimitPlaceholder': 'Default: 0 (no limit)',
'config.advanced.poolSizeLimitNote': 'Maximum number of healthy credentials per provider type for rotation. 0 means no limit, use all healthy credentials',

View file

@ -213,8 +213,8 @@
<div class="form-group pool-section">
<label for="maxErrorCount" data-i18n="config.advanced.maxErrorCount">提供商最大错误次数</label>
<input type="number" id="maxErrorCount" class="form-control" value="3" min="1" max="10" data-i18n-placeholder="config.advanced.maxErrorCountPlaceholder" placeholder="默认: 3">
<small class="form-text" data-i18n="config.advanced.maxErrorCountNote">提供商连续错误达到此次数后将被标记为不健康,默认为 3</small>
<input type="number" id="maxErrorCount" class="form-control" value="10" min="1" max="20" data-i18n-placeholder="config.advanced.maxErrorCountPlaceholder" placeholder="默认: 10">
<small class="form-text" data-i18n="config.advanced.maxErrorCountNote">提供商连续错误达到此次数后将被标记为不健康,默认为 10</small>
</div>
<div class="form-group pool-section">

View file

@ -150,7 +150,7 @@
<tr>
<td><code>MAX_ERROR_COUNT</code></td>
<td>number</td>
<td>3</td>
<td>10</td>
<td data-i18n="tutorial.main.retry.error">提供商最大错误次数,超过后标记为不健康</td>
</tr>
</tbody>
@ -169,7 +169,7 @@
"SYSTEM_PROMPT_MODE": "overwrite",
"REQUEST_MAX_RETRIES": 3,
"REQUEST_BASE_DELAY": 1000,
"MAX_ERROR_COUNT": 3,
"MAX_ERROR_COUNT": 10,
"PROVIDER_POOLS_FILE_PATH": "configs/provider_pools.json"
}</code></pre>
</div>