refactor: 优化提供商加载逻辑并修复流式重试问题

- 将静态提供商配置更新逻辑移至加载函数,避免重复执行
- 修复流式请求重试时已发送数据导致响应损坏的问题
- 增强模型列表和内容生成的服务适配器选择逻辑
- 改进错误处理和日志记录
This commit is contained in:
hex2077 2026-03-04 13:26:08 +08:00
parent 694f0c9099
commit 9ca8b86a4f
2 changed files with 64 additions and 38 deletions

View file

@ -297,6 +297,7 @@ export async function handleStreamRequest(res, service, model, requestBody, from
let fullResponseJson = '';
let fullOldResponseJson = '';
let responseClosed = false;
let anyDataSent = retryContext?.anyDataSent || false; // 跟踪是否已向客户端发送过任何数据
// 重试上下文:包含 CONFIG 和重试计数
// maxRetries: 凭证切换最大次数(跨凭证),默认 5 次
@ -439,6 +440,7 @@ export async function handleStreamRequest(res, service, model, requestBody, from
if (!clientDisconnected.value && !res.writableEnded) {
try {
res.write(`event: ${chunk.type}\n`);
anyDataSent = true;
} catch (writeErr) {
logger.error('[Stream] Failed to write event:', writeErr.message);
clientDisconnected.value = true;
@ -453,6 +455,7 @@ export async function handleStreamRequest(res, service, model, requestBody, from
if (!clientDisconnected.value && !res.writableEnded) {
try {
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
anyDataSent = true;
} catch (writeErr) {
logger.error('[Stream] Failed to write data:', writeErr.message);
clientDisconnected.value = true;
@ -482,9 +485,9 @@ export async function handleStreamRequest(res, service, model, requestBody, from
return;
}
// 如果已经发送了内容,不进行重试(避免响应数据损坏
if (fullResponseText.length > 0) {
logger.info(`[Stream Retry] Cannot retry: ${fullResponseText.length} bytes already sent to client`);
// 如果已经发送了数据(包括 metadata不进行重试避免响应数据损坏或顺序错误
if (anyDataSent) {
logger.info(`[Stream Retry] Cannot retry: data already sent to client`);
// 直接发送错误并结束
const errorPayload = createStreamErrorResponse(error, fromProvider);
if (!res.writableEnded) {
@ -553,7 +556,8 @@ export async function handleStreamRequest(res, service, model, requestBody, from
CONFIG,
currentRetry: currentRetry + 1,
maxRetries,
clientDisconnected // 传递断开状态
clientDisconnected, // 传递断开状态
anyDataSent // 传递数据发送状态
};
// 递归调用,使用新的服务
@ -821,12 +825,23 @@ export async function handleModelListRequest(req, res, service, endpointType, CO
logger.info(`[ModelList] Aggregating models for 'auto' mode...`);
clientModelList = await providerPoolManager.getAllAvailableModels(endpointType);
} else {
// --- 原有的单提供商逻辑 ---
// --- 单提供商逻辑 ---
const toProvider = CONFIG.MODEL_PROVIDER;
// service 可能未在上层预先注入(例如仅改了路径 provider 前缀),这里兜底获取
let resolvedService = service;
if (!resolvedService) {
const { getApiService } = await import('../services/service-manager.js');
resolvedService = await getApiService(CONFIG, null, { skipUsageCount: true });
}
if (!resolvedService || typeof resolvedService.listModels !== 'function') {
throw new Error(`[ModelList] Service adapter is unavailable or does not implement listModels() for provider: ${toProvider}`);
}
// 1. Get the model list in the backend's native format.
const nativeModelList = await service.listModels();
const nativeModelList = await resolvedService.listModels();
// 2. Convert the model list to the client's expected format, if necessary.
clientModelList = nativeModelList;
if (!getProtocolPrefix(toProvider).includes(getProtocolPrefix(fromProvider))) {
@ -896,17 +911,20 @@ export async function handleContentGenerationRequest(req, res, service, endpoint
let actualCustomName = CONFIG.customName;
// 2.5. 如果使用了提供商池,根据模型重新选择提供商(支持 Fallback
// 注意:这里开启 acquireSlot: true会占用并发名额或进入队列
if (providerPoolManager && (CONFIG.MODEL_PROVIDER === MODEL_PROVIDER.AUTO || (CONFIG.providerPools && CONFIG.providerPools[CONFIG.MODEL_PROVIDER]))) {
// 2.5. 根据模型选择服务适配器:
// - service 缺失时(例如上游未预先注入)进行兜底选择
// - 使用号池/AUTO 时按模型重选并支持 fallback
// 注意:仅在号池场景开启 acquireSlot占用并发名额或进入队列
const shouldSelectByPool = providerPoolManager && (CONFIG.MODEL_PROVIDER === MODEL_PROVIDER.AUTO || (CONFIG.providerPools && CONFIG.providerPools[CONFIG.MODEL_PROVIDER]));
if (!service || shouldSelectByPool) {
const { getApiServiceWithFallback } = await import('../services/service-manager.js');
const result = await getApiServiceWithFallback(CONFIG, model, { acquireSlot: true });
const result = await getApiServiceWithFallback(CONFIG, model, { acquireSlot: shouldSelectByPool });
service = result.service;
toProvider = result.actualProviderType;
actualUuid = result.uuid || pooluuid;
actualCustomName = result.serviceConfig?.customName || CONFIG.customName;
// 如果发生了模型级别的 fallback需要更新请求使用的模型
if (result.actualModel && result.actualModel !== model) {
logger.info(`[Content Generation] Model Fallback: ${model} -> ${result.actualModel}`);
@ -916,7 +934,7 @@ export async function handleContentGenerationRequest(req, res, service, endpoint
if (result.isFallback) {
logger.info(`[Content Generation] Fallback activated: ${CONFIG.MODEL_PROVIDER} -> ${toProvider} (uuid: ${actualUuid})`);
} else {
logger.info(`[Content Generation] Re-selected service adapter based on model: ${model}`);
logger.info(`[Content Generation] Selected service adapter based on model: ${model}`);
}
}

View file

@ -16,6 +16,8 @@ import { setServiceMode } from './event-handlers.js';
let initialServerTime = null;
let initialUptime = null;
let initialLoadTime = null;
let isStaticProviderConfigsUpdated = false;
let cachedSupportedProviders = null;
/**
* 加载系统信息
@ -186,11 +188,35 @@ function updateTimeDisplay() {
*/
async function loadProviders() {
try {
const [providers, supportedProviders] = await Promise.all([
window.apiClient.get('/providers'),
window.apiClient.get('/providers/supported')
]);
renderProviders(providers, supportedProviders);
const providers = await window.apiClient.get('/providers');
// 动态更新其他模块的提供商信息,只需更新一次
if (!isStaticProviderConfigsUpdated) {
cachedSupportedProviders = await window.apiClient.get('/providers/supported');
const providerConfigs = getProviderConfigs(cachedSupportedProviders);
// 动态更新凭据文件管理的提供商类型筛选项
updateProviderFilterOptions(providerConfigs);
// 动态更新仪表盘页面的路径路由调用示例
renderRoutingExamples(providerConfigs);
// 动态更新仪表盘页面的可用模型列表提供商信息
updateModelsProviderConfigs(providerConfigs);
// 动态更新配置教程页面的提供商信息
updateTutorialProviderConfigs(providerConfigs);
// 动态更新用量查询页面的提供商信息
updateUsageProviderConfigs(providerConfigs);
// 动态更新配置管理页面的提供商选择标签
updateConfigProviderConfigs(providerConfigs);
isStaticProviderConfigsUpdated = true;
}
renderProviders(providers, cachedSupportedProviders);
} catch (error) {
console.error('Failed to load providers:', error);
}
@ -349,24 +375,6 @@ function renderProviders(providers, supportedProviders = []) {
}
});
// 动态更新凭据文件管理的提供商类型筛选项
updateProviderFilterOptions(providerConfigs);
// 动态更新仪表盘页面的路径路由调用示例
renderRoutingExamples(providerConfigs);
// 动态更新仪表盘页面的可用模型列表提供商信息
updateModelsProviderConfigs(providerConfigs);
// 动态更新配置教程页面的提供商信息
updateTutorialProviderConfigs(providerConfigs);
// 动态更新用量查询页面的提供商信息
updateUsageProviderConfigs(providerConfigs);
// 动态更新配置管理页面的提供商选择标签
updateConfigProviderConfigs(providerConfigs);
// 更新统计卡片数据
const activeProviders = hasProviders ? Object.keys(providers).length : 0;
updateProviderStatsDisplay(activeProviders, totalHealthy, totalAccounts);