AIClient-2-API/src/providers/openai/openai-core.js
hex2077 245583b96a feat(logging): 添加日志系统配置和下载功能
- 新增日志系统配置选项,支持日志级别、输出模式、文件大小等设置
- 添加当日日志文件下载功能,可通过Web界面直接下载
- 将console.log/error替换为结构化logger,提升日志可管理性
- 在日志页面添加自动滚动到底部功能
- 更新配置示例文件,包含完整的日志配置参数
2026-01-25 17:24:39 +08:00

211 lines
9.2 KiB
JavaScript

import axios from 'axios';
import logger from '../../utils/logger.js';
import * as http from 'http';
import * as https from 'https';
import { configureAxiosProxy } from '../../utils/proxy-utils.js';
import { isRetryableNetworkError } from '../../utils/common.js';
// Assumed OpenAI API specification service for interacting with third-party models
export class OpenAIApiService {
constructor(config) {
if (!config.OPENAI_API_KEY) {
throw new Error("OpenAI API Key is required for OpenAIApiService.");
}
this.config = config;
this.apiKey = config.OPENAI_API_KEY;
this.baseUrl = config.OPENAI_BASE_URL;
this.useSystemProxy = config?.USE_SYSTEM_PROXY_OPENAI ?? false;
logger.info(`[OpenAI] System proxy ${this.useSystemProxy ? 'enabled' : 'disabled'}`);
// 配置 HTTP/HTTPS agent 限制连接池大小,避免资源泄漏
const httpAgent = new http.Agent({
keepAlive: true,
maxSockets: 100,
maxFreeSockets: 5,
timeout: 120000,
});
const httpsAgent = new https.Agent({
keepAlive: true,
maxSockets: 100,
maxFreeSockets: 5,
timeout: 120000,
});
const axiosConfig = {
baseURL: this.baseUrl,
httpAgent,
httpsAgent,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
};
// 禁用系统代理以避免HTTPS代理错误
if (!this.useSystemProxy) {
axiosConfig.proxy = false;
}
// 配置自定义代理
configureAxiosProxy(axiosConfig, config, 'openai-custom');
this.axiosInstance = axios.create(axiosConfig);
}
async callApi(endpoint, body, isRetry = false, retryCount = 0) {
const maxRetries = this.config.REQUEST_MAX_RETRIES || 3;
const baseDelay = this.config.REQUEST_BASE_DELAY || 1000; // 1 second base delay
try {
const response = await this.axiosInstance.post(endpoint, body);
return response.data;
} catch (error) {
const status = error.response?.status;
const data = error.response?.data;
const errorCode = error.code;
const errorMessage = error.message || '';
// 检查是否为可重试的网络错误
const isNetworkError = isRetryableNetworkError(error);
if (status === 401 || status === 403) {
logger.error(`[OpenAI API] Received ${status}. API Key might be invalid or expired.`);
throw error;
}
// Handle 429 (Too Many Requests) with exponential backoff
if (status === 429 && retryCount < maxRetries) {
const delay = baseDelay * Math.pow(2, retryCount);
logger.info(`[OpenAI API] Received 429 (Too Many Requests). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, delay));
return this.callApi(endpoint, body, isRetry, retryCount + 1);
}
// Handle other retryable errors (5xx server errors)
if (status >= 500 && status < 600 && retryCount < maxRetries) {
const delay = baseDelay * Math.pow(2, retryCount);
logger.info(`[OpenAI API] Received ${status} server error. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, delay));
return this.callApi(endpoint, body, isRetry, retryCount + 1);
}
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
if (isNetworkError && retryCount < maxRetries) {
const delay = baseDelay * Math.pow(2, retryCount);
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
logger.info(`[OpenAI API] Network error (${errorIdentifier}). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, delay));
return this.callApi(endpoint, body, isRetry, retryCount + 1);
}
logger.error(`[OpenAI API] Error calling API (Status: ${status}, Code: ${errorCode}):`, data || error.message);
throw error;
}
}
async *streamApi(endpoint, body, isRetry = false, retryCount = 0) {
const maxRetries = this.config.REQUEST_MAX_RETRIES || 3;
const baseDelay = this.config.REQUEST_BASE_DELAY || 1000; // 1 second base delay
// OpenAI 的流式请求需要将 stream 设置为 true
const streamRequestBody = { ...body, stream: true };
try {
const response = await this.axiosInstance.post(endpoint, streamRequestBody, {
responseType: 'stream'
});
const stream = response.data;
let buffer = '';
for await (const chunk of stream) {
buffer += chunk.toString();
let newlineIndex;
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
const line = buffer.substring(0, newlineIndex).trim();
buffer = buffer.substring(newlineIndex + 1);
if (line.startsWith('data: ')) {
const jsonData = line.substring(6).trim();
if (jsonData === '[DONE]') {
return; // Stream finished
}
try {
const parsedChunk = JSON.parse(jsonData);
yield parsedChunk;
} catch (e) {
logger.warn("[OpenAIApiService] Failed to parse stream chunk JSON:", e.message, "Data:", jsonData);
}
} else if (line === '') {
// Empty line, end of an event
}
}
}
} catch (error) {
const status = error.response?.status;
const data = error.response?.data;
const errorCode = error.code;
const errorMessage = error.message || '';
// 检查是否为可重试的网络错误
const isNetworkError = isRetryableNetworkError(error);
if (status === 401 || status === 403) {
logger.error(`[OpenAI API] Received ${status} during stream. API Key might be invalid or expired.`);
throw error;
}
// Handle 429 (Too Many Requests) with exponential backoff
if (status === 429 && retryCount < maxRetries) {
const delay = baseDelay * Math.pow(2, retryCount);
logger.info(`[OpenAI API] Received 429 (Too Many Requests) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, delay));
yield* this.streamApi(endpoint, body, isRetry, retryCount + 1);
return;
}
// Handle other retryable errors (5xx server errors)
if (status >= 500 && status < 600 && retryCount < maxRetries) {
const delay = baseDelay * Math.pow(2, retryCount);
logger.info(`[OpenAI API] Received ${status} server error during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, delay));
yield* this.streamApi(endpoint, body, isRetry, retryCount + 1);
return;
}
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
if (isNetworkError && retryCount < maxRetries) {
const delay = baseDelay * Math.pow(2, retryCount);
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
logger.info(`[OpenAI API] Network error (${errorIdentifier}) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, delay));
yield* this.streamApi(endpoint, body, isRetry, retryCount + 1);
return;
}
logger.error(`[OpenAI API] Error calling streaming API (Status: ${status}, Code: ${errorCode}):`, data || error.message);
throw error;
}
}
async generateContent(model, requestBody) {
return this.callApi('/chat/completions', requestBody);
}
async *generateContentStream(model, requestBody) {
yield* this.streamApi('/chat/completions', requestBody);
}
async listModels() {
try {
const response = await this.axiosInstance.get('/models');
return response.data;
} catch (error) {
const status = error.response?.status;
const data = error.response?.data;
logger.error(`Error listing OpenAI models (Status: ${status}):`, data || error.message);
throw error;
}
}
}