test-g / index.js
kuefr's picture
Update index.js
7654dd4 verified
import express from 'express';
import fetch from 'node-fetch';
import dotenv from 'dotenv';
import { v4 as uuidv4 } from 'uuid';
import cors from 'cors';
// 初始化环境变量
dotenv.config();
/**
* 配置管理类
*/
class Config {
constructor() {
this.initializeApiKeys();
this.initializeAuth();
// 已使用的API Key池
this.usedApiKeys = [];
// 失效的API Key池
this.invalidApiKeys = [];
// Gemini安全设置
this.geminiSafety = [
{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: 'OFF',
},
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: 'OFF',
},
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold: 'OFF',
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold: 'OFF',
},
{
category: 'HARM_CATEGORY_CIVIC_INTEGRITY',
threshold: 'OFF',
},
];
}
/**
* 初始化API Keys
*/
initializeApiKeys() {
const apiKeysEnv = process.env.GEMINI_API_KEYS;
if (!apiKeysEnv) {
console.error('❌ 错误: 未找到 GEMINI_API_KEYS 环境变量');
process.exit(1);
}
// 通过;分割API Keys
this.apiKeys = apiKeysEnv
.split('\n')
.map(key => key.trim())
.filter(key => key.length > 0);
if (this.apiKeys.length === 0) {
console.error('❌ 错误: 没有找到有效的API Keys');
process.exit(1);
}
console.log(`✅ 成功加载 ${this.apiKeys.length} 个API Keys`);
}
/**
* 初始化认证配置
*/
initializeAuth() {
this.authToken = process.env.AUTH_TOKEN || 'sk-123456';
}
/**
* 获取可用的API Key(负载均衡)
*/
getApiKey() {
if (this.apiKeys.length === 0) {
if (this.usedApiKeys.length > 0) {
this.apiKeys.push(...this.usedApiKeys);
this.usedApiKeys = [];
} else {
return null;
}
}
const apiKey = this.apiKeys.shift();
this.usedApiKeys.push(apiKey);
return apiKey;
}
/**
* 获取第一个可用的API Key(用于模型列表请求)
*/
getFirstAvailableApiKey() {
if (this.apiKeys.length > 0) {
return this.apiKeys[0];
}
if (this.usedApiKeys.length > 0) {
return this.usedApiKeys[0];
}
return null;
}
/**
* 将API Key标记为失效
*/
markKeyAsInvalid(apiKey) {
const usedIndex = this.usedApiKeys.indexOf(apiKey);
if (usedIndex !== -1) {
this.usedApiKeys.splice(usedIndex, 1);
}
const mainIndex = this.apiKeys.indexOf(apiKey);
if (mainIndex !== -1) {
this.apiKeys.splice(mainIndex, 1);
}
if (!this.invalidApiKeys.includes(apiKey)) {
this.invalidApiKeys.push(apiKey);
}
console.warn(`⚠️ API Key 已标记为失效: ${apiKey.substring(0, 10)}...`);
}
/**
* 将API Key移回已使用池
*/
moveToUsed(apiKey) {
if (!this.usedApiKeys.includes(apiKey)) {
this.usedApiKeys.push(apiKey);
}
}
/**
* 验证授权头
*/
validateAuth(authHeader) {
if (!authHeader) {
return false;
}
const token = authHeader.replace('Bearer ', '');
return token === this.authToken;
}
/**
* 获取可用API Key数量
*/
getAvailableKeysCount() {
return this.apiKeys.length + this.usedApiKeys.length;
}
}
/**
* 图片处理器类
*/
class ImageProcessor {
/**
* 从data URL中提取MIME类型和base64数据
*/
static parseDataUrl(dataUrl) {
try {
// 匹配data:image/jpeg;base64,<base64data>格式
const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
if (!match) {
throw new Error('无效的data URL格式');
}
const mimeType = match[1];
const base64Data = match[2];
// 验证MIME类型是否为支持的图片格式
const supportedMimeTypes = [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
'image/webp',
'image/bmp',
'image/tiff'
];
if (!supportedMimeTypes.includes(mimeType.toLowerCase())) {
throw new Error(`不支持的图片格式: ${mimeType}`);
}
return {
mimeType,
data: base64Data
};
} catch (error) {
console.error('解析图片data URL错误:', error);
throw error;
}
}
/**
* 验证base64数据是否有效
*/
static validateBase64(base64String) {
try {
// 基本格式检查
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64String)) {
return false;
}
// 长度检查(base64编码长度应该是4的倍数)
return base64String.length % 4 === 0;
} catch (error) {
return false;
}
}
/**
* 从URL下载图片并转换为base64
*/
static async fetchImageAsBase64(imageUrl) {
try {
const response = await fetch(imageUrl, {
timeout: 30000, // 30秒超时
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
if (!response.ok) {
throw new Error(`获取图片失败: HTTP ${response.status}`);
}
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.startsWith('image/')) {
throw new Error(`URL返回的不是图片类型: ${contentType}`);
}
const buffer = await response.buffer();
const base64Data = buffer.toString('base64');
return {
mimeType: contentType,
data: base64Data
};
} catch (error) {
console.error('下载图片错误:', error);
throw error;
}
}
}
/**
* 消息转换器类(增强版)
*/
class MessageConverter {
/**
* 将OpenAI格式的消息转换为Gemini格式(支持图片)
*/
static async convertMessages(openaiMessages) {
const geminiMessages = [];
let currentRole = null;
let currentParts = [];
let system_instruction = null;
for (const message of openaiMessages) {
let role = message.role;
let content = message.content;
// 角色转换
if(role == 'system'){
role = 'user';
}
if (role === 'assistant') {
role = 'model';
}
// 处理内容
let parts = [];
if (typeof content === 'string') {
// 简单文本消息
parts = [{ text: content }];
} else if (Array.isArray(content)) {
// 多模态消息(包含文本和图片)
parts = await this.convertContentArray(content);
} else {
// 其他格式,转为文本
parts = [{ text: String(content) }];
}
// if (role === 'system') {
// system_instruction = parts;
// continue;
// }
// 合并相同角色的连续消息
if (role === currentRole) {
currentParts.push(...parts);
} else {
// 保存上一个角色的消息
if (currentRole !== null && currentParts.length > 0) {
geminiMessages.push({
role: currentRole,
parts: currentParts
});
}
// 开始新角色
currentRole = role;
currentParts = [...parts];
}
}
// 添加最后一个消息
if (currentRole !== null && currentParts.length > 0) {
geminiMessages.push({
role: currentRole,
parts: currentParts
});
}
return geminiMessages;
}
/**
* 转换OpenAI的content数组为Gemini的parts格式
*/
static async convertContentArray(contentArray) {
const parts = [];
for (const item of contentArray) {
try {
if (item.type === 'text') {
// 文本内容
parts.push({ text: item.text || '' });
} else if (item.type === 'image_url') {
// 图片内容
const imagePart = await this.convertImageContent(item);
if (imagePart) {
parts.push(imagePart);
}
} else {
// 其他类型,尝试转为文本
console.warn(`未知的内容类型: ${item.type},将转为文本处理`);
parts.push({ text: JSON.stringify(item) });
}
} catch (error) {
console.error('转换内容项错误:', error);
// 出错时跳过该项,避免整个请求失败
continue;
}
}
return parts;
}
/**
* 转换图片内容为Gemini格式
*/
static async convertImageContent(imageItem) {
try {
const imageUrl = imageItem.image_url?.url;
if (!imageUrl) {
throw new Error('缺少图片URL');
}
let imageData;
if (imageUrl.startsWith('data:')) {
// 处理base64数据URL
imageData = ImageProcessor.parseDataUrl(imageUrl);
// 验证base64数据
if (!ImageProcessor.validateBase64(imageData.data)) {
throw new Error('无效的base64图片数据');
}
} else if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
// 处理网络图片URL
imageData = await ImageProcessor.fetchImageAsBase64(imageUrl);
} else {
throw new Error(`不支持的图片URL格式: ${imageUrl}`);
}
// 返回Gemini格式的图片数据
return {
inlineData: {
mimeType: imageData.mimeType,
data: imageData.data
}
};
} catch (error) {
console.error('转换图片内容错误:', error);
// 返回错误信息文本,而不是抛出异常
return { text: `[图片处理失败: ${error.message}]` };
}
}
/**
* 从OpenAI请求中提取参数
*/
static extractParams(openaiRequest) {
return {
model: openaiRequest.model || 'gemini-2.5-flash',
messages: openaiRequest.messages || [],
stream: openaiRequest.stream || false,
temperature: openaiRequest.temperature,
maxTokens: openaiRequest.max_tokens,
topP: openaiRequest.top_p
};
}
}
/**
* 模型管理类
*/
class ModelManager {
constructor(config) {
this.config = config;
this.cachedModels = null;
this.cacheExpiry = null;
this.cacheTimeout = 5 * 60 * 1000; // 5分钟缓存
}
/**
* 获取模型列表
*/
async getModels() {
if (this.cachedModels && this.cacheExpiry && Date.now() < this.cacheExpiry) {
return { success: true, data: this.cachedModels };
}
const apiKey = this.config.getFirstAvailableApiKey();
if (!apiKey) {
return {
success: false,
error: '没有可用的API Key',
status: 503
};
}
try {
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
return {
success: false,
error: `获取模型列表失败: ${response.status}`,
status: response.status
};
}
const geminiResponse = await response.json();
const filteredModels = this.filterModels(geminiResponse.models || []);
this.cachedModels = filteredModels;
this.cacheExpiry = Date.now() + this.cacheTimeout;
return { success: true, data: filteredModels };
} catch (error) {
console.error('获取模型列表错误:', error);
return {
success: false,
error: '网络请求失败',
status: 500
};
}
}
/**
* 过滤模型 - 保持原始Gemini模型名,并增加视觉模型支持
*/
filterModels(models) {
const allowedPrefixes = [
'models/gemini-2.5-flash',
'models/gemini-2.0-flash',
'models/gemini-1.5-flash'
];
const excludedModels = [
'models/gemini-1.5-flash-8b'
];
const filteredModels = models.filter(model => {
const modelName = model.name;
if (excludedModels.some(excluded => modelName.startsWith(excluded))) {
return false;
}
if (modelName == "models/gemini-2.5-pro") {
return true;
}
return allowedPrefixes.some(prefix => modelName.startsWith(prefix));
});
// 转换为OpenAI格式但保持Gemini模型名
const processedModels = filteredModels.map(model => {
const modelId = model.name.replace('models/', '');
return {
id: modelId, // 直接使用Gemini模型名
object: 'model',
created: Math.floor(Date.now() / 1000),
owned_by: 'google',
permission: [
{
id: `modelperm-${modelId}`,
object: 'model_permission',
created: Math.floor(Date.now() / 1000),
allow_create_engine: false,
allow_sampling: true,
allow_logprobs: false,
allow_search_indices: false,
allow_view: true,
allow_fine_tuning: false,
organization: '*',
group: null,
is_blocking: false
}
],
root: modelId,
parent: null
};
});
return {
object: 'list',
data: processedModels
};
}
}
/**
* Gemini API 请求构建器类
*/
class GeminiRequestBuilder {
constructor(config) {
this.config = config;
}
/**
* 构建Gemini API请求体
*/
buildRequestBody(geminiMessages, params) {
const requestBody = {
contents: geminiMessages,
safetySettings: this.config.geminiSafety,
generationConfig: {}
};
if (params.temperature !== undefined) {
requestBody.generationConfig.temperature = params.temperature;
}
if (params.maxTokens !== undefined) {
requestBody.generationConfig.maxOutputTokens = params.maxTokens;
}
if (params.topP !== undefined) {
requestBody.generationConfig.topP = params.topP;
}
if (params.model == "gemini-2.5-pro") {
requestBody.generationConfig.thinkingConfig = {
thinkingBudget: 128
};
}
return requestBody;
}
/**
* 构建Gemini API URL
*/
buildApiUrl(model, apiKey, isStream = false) {
const method = isStream ? 'streamGenerateContent' : 'generateContent';
return `https://generativelanguage.googleapis.com/v1beta/models/${model}:${method}?key=${apiKey}`;
}
}
/**
* 响应转换器类
*/
class ResponseConverter {
/**
* 将Gemini流式响应块转换为OpenAI格式
*/
static convertStreamChunk(geminiData, requestId, model) {
try {
if (geminiData.candidates && geminiData.candidates[0]) {
const candidate = geminiData.candidates[0];
if (candidate.content && candidate.content.parts) {
const text = candidate.content.parts[0]?.text || '';
const openaiChunk = {
id: requestId,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: model, // 使用当前请求的模型名
choices: [{
index: 0,
delta: { content: text },
finish_reason: candidate.finishReason === 'STOP' ? 'stop' : null
}]
};
return `data: ${JSON.stringify(openaiChunk)}\n\n`;
}
}
return '';
} catch (error) {
console.error('转换流响应块错误:', error);
return '';
}
}
/**
* 将Gemini非流式响应转换为OpenAI格式
*/
static convertNormalResponse(geminiResponse, requestId, model) {
const openaiResponse = {
id: requestId,
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model: model, // 使用当前请求的模型名
choices: [],
usage: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0
}
};
if (geminiResponse.candidates && geminiResponse.candidates[0]) {
const candidate = geminiResponse.candidates[0];
if (candidate.content && candidate.content.parts) {
const text = candidate.content.parts.map(part => part.text).join('');
openaiResponse.choices.push({
index: 0,
message: {
role: 'assistant',
content: text
},
finish_reason: candidate.finishReason === 'STOP' ? 'stop' : 'length'
});
}
}
// 尝试从usage信息中获取token使用量
if (geminiResponse.usageMetadata) {
openaiResponse.usage = {
prompt_tokens: geminiResponse.usageMetadata.promptTokenCount || 0,
completion_tokens: geminiResponse.usageMetadata.candidatesTokenCount || 0,
total_tokens: geminiResponse.usageMetadata.totalTokenCount || 0
};
}
return openaiResponse;
}
/**
* 检查响应是否包含有效文本
*/
static hasValidTextContent(geminiResponse) {
if (!geminiResponse.candidates || !geminiResponse.candidates[0]) {
return false;
}
const candidate = geminiResponse.candidates[0];
if (!candidate.content || !candidate.content.parts) {
return false;
}
const text = candidate.content.parts
.map(part => part.text || '')
.join('')
.trim();
return text.length > 0;
}
/**
* 将文本拆分为假流式块
*/
static splitTextToFakeStream(text, requestId, model) {
const chunks = [];
const chunkSize = 3; // 每个块包含的字符数
for (let i = 0; i < text.length; i += chunkSize) {
const chunk = text.slice(i, i + chunkSize);
const isLast = i + chunkSize >= text.length;
const openaiChunk = {
id: requestId,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: model,
choices: [{
index: 0,
delta: { content: chunk },
finish_reason: isLast ? 'stop' : null
}]
};
chunks.push(`data: ${JSON.stringify(openaiChunk)}\n\n`);
}
return chunks;
}
}
/**
* Gemini实时流式响应解析器
*/
class GeminiRealtimeStreamParser {
constructor(response, onChunk) {
this.response = response;
this.onChunk = onChunk;
this.buffer = '';
this.bufferLv = 0;
this.inString = false;
this.escapeNext = false;
this.decoder = new TextDecoder();
}
async start() {
try {
for await (const chunk of this.response.body) {
const text = this.decoder.decode(chunk, { stream: true });
await this.processText(text);
}
await this.handleRemainingBuffer();
} catch (error) {
console.error('流式解析错误:', error);
throw error;
}
}
async processText(text) {
for (const char of text) {
if (this.escapeNext) {
if (this.bufferLv > 1) {
this.buffer += char;
}
this.escapeNext = false;
continue;
}
if (char === '\\' && this.inString) {
this.escapeNext = true;
if (this.bufferLv > 1) {
this.buffer += char;
}
continue;
}
if (char === '"') {
this.inString = !this.inString;
}
if (!this.inString) {
if (char === '{' || char === '[') {
this.bufferLv++;
} else if (char === '}' || char === ']') {
this.bufferLv--;
}
}
if (this.bufferLv > 1) {
if (this.inString && char === '\n') {
this.buffer += '\\n';
} else {
this.buffer += char;
}
} else if (this.bufferLv === 1 && this.buffer) {
this.buffer += '}';
try {
const bufferJson = JSON.parse(this.buffer);
await this.onChunk(bufferJson);
} catch (parseError) {
console.error('解析Gemini流数据错误:', parseError);
}
this.buffer = '';
}
}
}
async handleRemainingBuffer() {
if (this.buffer.trim() && this.bufferLv >= 1) {
try {
if (!this.buffer.endsWith('}')) {
this.buffer += '}';
}
const bufferJson = JSON.parse(this.buffer);
await this.onChunk(bufferJson);
} catch (parseError) {
console.error('解析最后的缓冲区数据错误:', parseError);
}
}
}
}
/**
* 认证中间件
*/
class AuthMiddleware {
constructor(config) {
this.config = config;
}
middleware() {
return (req, res, next) => {
// 跳过健康检查和预检请求
if (req.path === '/health' || req.method === 'OPTIONS') {
return next();
}
const authHeader = req.headers.authorization;
if (!this.config.validateAuth(authHeader)) {
return res.status(401).json({
error: {
message: 'Invalid authentication credentials',
type: 'invalid_request_error',
code: 'invalid_api_key'
}
});
}
next();
};
}
}
/**
* API代理服务类
*/
class ApiProxyService {
constructor() {
this.config = new Config();
this.requestBuilder = new GeminiRequestBuilder(this.config);
this.modelManager = new ModelManager(this.config);
this.authMiddleware = new AuthMiddleware(this.config);
}
/**
* 处理聊天API请求(支持图片)
*/
async handleChatRequest(req, res) {
try {
const requestId = `chatcmpl-${uuidv4()}`;
const params = MessageConverter.extractParams(req.body);
// 异步转换消息(支持图片处理)
const geminiMessages = await MessageConverter.convertMessages(params.messages);
if (!geminiMessages || geminiMessages.length === 0) {
return res.status(400).json({
error: {
message: '无效的消息格式或消息为空',
type: 'invalid_request_error',
code: 'invalid_messages'
}
});
}
const requestBody = this.requestBuilder.buildRequestBody(geminiMessages, params);
if (params.stream) {
const result = await this.handleStreamRequest(requestBody, params, requestId, res);
if (!result.success) {
res.status(result.status || 500).json({ error: result.error });
}
} else {
const result = await this.executeNormalRequest(requestBody, params, requestId);
if (result.success) {
res.json(result.data);
} else {
res.status(result.status || 500).json({ error: result.error });
}
}
} catch (error) {
console.error('处理聊天请求错误:', error);
res.status(500).json({
error: {
message: '内部服务器错误: ' + error.message,
type: 'internal_server_error',
code: 'server_error'
}
});
}
}
/**
* 处理假流式聊天API请求
*/
async handleFakeStreamChatRequest(req, res) {
try {
const requestId = `chatcmpl-${uuidv4()}`;
const params = MessageConverter.extractParams(req.body);
// 异步转换消息(支持图片处理)
const geminiMessages = await MessageConverter.convertMessages(params.messages);
if (!geminiMessages || geminiMessages.length === 0) {
return res.status(400).json({
error: {
message: '无效的消息格式或消息为空',
type: 'invalid_request_error',
code: 'invalid_messages'
}
});
}
const requestBody = this.requestBuilder.buildRequestBody(geminiMessages, params);
console.log("开始请求");
if (params.stream) {
// 假流式处理:使用非流式请求,然后模拟流式响应
const result = await this.handleFakeStreamRequest(requestBody, params, requestId, res);
if (!result.success) {
res.status(result.status || 500).json({ error: result.error });
}
} else {
// 非流式请求和原来一样
const result = await this.executeNormalRequest(requestBody, params, requestId);
if (result.success) {
res.json(result.data);
} else {
res.status(result.status || 500).json({ error: result.error });
}
}
} catch (error) {
console.error('处理假流式聊天请求错误:', error);
res.status(500).json({
error: {
message: '内部服务器错误: ' + error.message,
type: 'internal_server_error',
code: 'server_error'
}
});
}
}
/**
* 处理假流式请求
*/
async handleFakeStreamRequest(requestBody, params, requestId, res) {
try {
// 设置流式响应头
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
// 开始发送ping消息保持连接活跃
const pingInterval = setInterval(() => {
try {
res.write(': ping\n\n');
} catch (error) {
clearInterval(pingInterval);
}
}, 1000); // 每秒发送一次ping
// 执行非流式请求
const result = await this.executeNormalRequest(requestBody, params, requestId);
// 停止ping
clearInterval(pingInterval);
if (!result.success) {
res.write(`data: ${JSON.stringify({ error: result.error })}\n\n`);
res.write('data: [DONE]\n\n');
res.end();
return { success: false };
}
// 获取响应文本
const responseText = result.data.choices[0]?.message?.content || '';
if (responseText) {
// 将文本拆分为假流式块
const chunks = ResponseConverter.splitTextToFakeStream(responseText, requestId, params.model);
// 逐步发送块,模拟流式响应
for (const chunk of chunks) {
res.write(chunk);
// 添加小延迟以模拟真实的流式响应
await new Promise(resolve => setTimeout(resolve, 50));
}
}
res.write('data: [DONE]\n\n');
res.end();
return { success: true };
} catch (error) {
console.error('处理假流式请求错误:', error);
try {
res.write(`data: ${JSON.stringify({ error: '内部服务器错误: ' + error.message })}\n\n`);
res.write('data: [DONE]\n\n');
res.end();
} catch (writeError) {
console.error('写入错误响应失败:', writeError);
}
return { success: false, error: error.message };
}
}
/**
* 处理流式请求(优化后的重试逻辑)
*/
async handleStreamRequest(requestBody, params, requestId, res, retryCount = 0) {
const maxRetries = 5;
let apiKey = null;
let response = null;
// 在429错误或内容被阻止时尝试使用不同的API Key
for (let keyAttempt = 0; keyAttempt < maxRetries; keyAttempt++) {
console.log(`尝试请求 (${keyAttempt + 1}/${maxRetries})`);
apiKey = this.config.getApiKey();
if (!apiKey) {
if (this.config.getAvailableKeysCount() === 0) {
return { success: false, error: '目前暂无可用的API Key', status: 503 };
}
continue;
}
console.log(`使用API Key: ${apiKey?.substring(0, 10)}...`);
try {
const apiUrl = this.requestBuilder.buildApiUrl(params.model, apiKey, true);
response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody)
});
if (response.status === 403) {
this.config.markKeyAsInvalid(apiKey);
console.log(`API Key失效,尝试下一个 (${keyAttempt + 1}/${maxRetries})`);
continue;
}
if (response.status === 429) {
this.config.moveToUsed(apiKey);
console.log(`请求频率限制,切换API Key (${keyAttempt + 1}/${maxRetries})`);
continue;
}
if (response.status === 500) {
this.config.moveToUsed(apiKey);
return { success: false, error: '目前服务器繁忙,请稍后重试', status: 500 };
}
if (!response.ok) {
const errorText = await response.text();
console.error(`API请求失败: ${response.status}, 错误信息: ${errorText}`);
return { success: false, error: `API请求失败: ${response.status}`, status: response.status };
}
// 创建一个新的解析器来预检第一个chunk
let firstChunkData = null;
let isProhibitedContent = false;
let responseStarted = false;
const parser = new GeminiRealtimeStreamParser(response, async (geminiData) => {
// 如果是第一个chunk,检查是否被阻止
if (firstChunkData === null) {
firstChunkData = geminiData;
// 检查是否因为内容被禁止而阻止
if (geminiData.promptFeedback &&
geminiData.promptFeedback.blockReason === 'PROHIBITED_CONTENT') {
isProhibitedContent = true;
console.log(`内容被阻止 (PROHIBITED_CONTENT),切换API Key重试 (${keyAttempt + 1}/${maxRetries})`);
return; // 不处理这个chunk
}
// 第一个chunk正常,开始发送响应头
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
responseStarted = true;
// 发送第一个chunk的内容
const convertedChunk = ResponseConverter.convertStreamChunk(geminiData, requestId, params.model);
if (convertedChunk) {
res.write(convertedChunk);
}
} else {
// 后续的chunks,如果响应已开始,直接发送
if (responseStarted && !isProhibitedContent) {
const convertedChunk = ResponseConverter.convertStreamChunk(geminiData, requestId, params.model);
if (convertedChunk) {
res.write(convertedChunk);
}
}
}
});
await parser.start();
// 如果内容被阻止,尝试下一个API Key
if (isProhibitedContent) {
this.config.moveToUsed(apiKey);
if (keyAttempt < maxRetries - 1) {
continue;
}
return { success: false, error: '内容被阻止', status: 503 };
}
// 如果响应已开始,发送结束标记
if (responseStarted) {
res.write('data: [DONE]\n\n');
res.end();
return { success: true };
}
// 如果没有开始响应(异常情况),返回错误
return { success: false, error: '未收到有效响应', status: 204 };
} catch (error) {
console.error(`执行流式请求错误 (尝试 ${keyAttempt + 1}/${maxRetries}):`, error);
this.config.moveToUsed(apiKey);
if (keyAttempt < maxRetries - 1) {
continue;
}
return { success: false, error: '网络请求失败: ' + error.message, status: 500 };
}
}
return { success: false, error: '所有重试均失败', status: 500 };
}
/**
* 处理非流式请求(优化后的重试逻辑)
*/
async executeNormalRequest(requestBody, params, requestId, retryCount = 0) {
const maxRetries = 5;
let apiKey = null;
let response = null;
let geminiResponse = null;
// 修改循环条件,使其能够重试
for (let keyAttempt = 0; keyAttempt < maxRetries; keyAttempt++) {
console.log(`尝试请求 (${keyAttempt + 1}/${maxRetries})`);
apiKey = this.config.getApiKey();
if (!apiKey) {
if (this.config.getAvailableKeysCount() === 0) {
return { success: false, error: '目前暂无可用的API Key', status: 503 };
}
continue;
}
console.log(`使用API Key: ${apiKey?.substring(0, 10)}...`);
try {
const apiUrl = this.requestBuilder.buildApiUrl(params.model, apiKey, false);
response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody)
});
if (response.status === 403) {
this.config.markKeyAsInvalid(apiKey);
console.log(`API Key失效,尝试下一个 (${keyAttempt + 1}/${maxRetries})`);
continue;
}
if (response.status === 429) {
this.config.moveToUsed(apiKey);
console.log(`请求频率限制,切换API Key (${keyAttempt + 1}/${maxRetries})`);
continue;
}
if (response.status === 500) {
this.config.moveToUsed(apiKey);
return { success: false, error: '目前服务器繁忙,请稍后重试', status: 500 };
}
if (!response.ok) {
const errorText = await response.text();
console.error(`API请求失败: ${response.status}, 错误信息: ${errorText}`);
return { success: false, error: `API请求失败: ${response.status}`, status: response.status };
}
geminiResponse = await response.json();
console.log(JSON.stringify(geminiResponse, null, 2));
// 检查是否因为内容被禁止而阻止
if (geminiResponse.candidates &&
geminiResponse.candidates.finishReason === 'PROHIBITED_CONTENT') {
console.log(`内容被阻止 (PROHIBITED_CONTENT),切换API Key重试 (${keyAttempt + 1}/${maxRetries})`);
this.config.moveToUsed(apiKey);
if (keyAttempt < maxRetries - 1) {
continue;
}
return { success: false, error: '内容被阻止', status: 503 };
}
// 检查响应是否包含有效文本
if (!ResponseConverter.hasValidTextContent(geminiResponse)) {
console.log(`响应无文本内容,切换API Key重试 (${keyAttempt + 1}/${maxRetries})`);
this.config.moveToUsed(apiKey);
if (keyAttempt < maxRetries - 1) {
continue;
}
return { success: false, error: '响应无文本内容', status: 204 };
}
const openaiResponse = ResponseConverter.convertNormalResponse(geminiResponse, requestId, params.model);
return { success: true, data: openaiResponse };
} catch (error) {
console.error(`执行非流式请求错误 (尝试 ${keyAttempt + 1}/${maxRetries}):`, error);
this.config.moveToUsed(apiKey);
if (keyAttempt < maxRetries - 1) {
continue;
}
return { success: false, error: '网络请求失败: ' + error.message, status: 500 };
}
}
return { success: false, error: '所有重试均失败', status: 500 };
}
/**
* 处理模型列表请求
*/
async handleModelsRequest(req, res) {
try {
const result = await this.modelManager.getModels();
if (result.success) {
res.json(result.data);
} else {
res.status(result.status || 500).json({ error: result.error });
}
} catch (error) {
console.error('处理模型列表请求错误:', error);
res.status(500).json({ error: '内部服务器错误' });
}
}
}
/**
* Express 服务器
*/
class Server {
constructor() {
this.app = express();
this.apiProxy = new ApiProxyService();
this.setupMiddleware();
this.setupRoutes();
}
setupMiddleware() {
// CORS配置
this.app.use(cors({
origin: '*',
credentials: true,
optionsSuccessStatus: 200
}));
// JSON解析 - 增加大小限制以支持图片
this.app.use(express.json({ limit: '50mb' }));
this.app.use(express.urlencoded({ limit: '50mb', extended: true }));
// 认证中间件
this.app.use(this.apiProxy.authMiddleware.middleware());
// 请求日志中间件
this.app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.path} - ${res.statusCode} [${duration}ms]`);
});
next();
});
}
setupRoutes() {
// 原始聊天接口(支持图片)
this.app.post('/v1/chat/completions', (req, res) => {
this.apiProxy.handleChatRequest(req, res);
});
// 假流式聊天接口
this.app.post('/fakestream/v1/chat/completions', (req, res) => {
this.apiProxy.handleFakeStreamChatRequest(req, res);
});
// 原始模型列表接口
this.app.get('/v1/models', (req, res) => {
this.apiProxy.handleModelsRequest(req, res);
});
// 假流式模型列表接口(逻辑相同)
this.app.get('/fakestream/v1/models', (req, res) => {
this.apiProxy.handleModelsRequest(req, res);
});
// 健康检查接口
this.app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
availableKeys: this.apiProxy.config.apiKeys.length,
usedKeys: this.apiProxy.config.usedApiKeys.length,
invalidKeys: this.apiProxy.config.invalidApiKeys.length,
version: '2.1.0',
features: ['text', 'vision', 'stream', 'fake_stream', 'load_balancing', 'auto_retry']
});
});
// 404处理
this.app.use('*', (req, res) => {
res.status(404).json({
error: {
message: 'Not Found',
type: 'invalid_request_error',
code: 'not_found'
}
});
});
// 全局错误处理
this.app.use((err, req, res, next) => {
console.error('服务器错误:', err);
res.status(500).json({
error: {
message: '内部服务器错误',
type: 'internal_server_error',
code: 'server_error'
}
});
});
}
start(port = 3000) {
this.app.listen(port, () => {
console.log(`🚀 OpenAI to Gemini Proxy Server (Enhanced) 启动在端口 ${port}`);
console.log(`📍 聊天API: http://localhost:${port}/v1/chat/completions`);
console.log(`📍 假流式聊天API: http://localhost:${port}/fakestream/v1/chat/completions`);
console.log(`📋 模型列表: http://localhost:${port}/v1/models`);
console.log(`📋 假流式模型列表: http://localhost:${port}/fakestream/v1/models`);
console.log(`🔍 健康检查: http://localhost:${port}/health`);
console.log(`✨ 新特性: 自动重试机制 - 429错误和无内容响应时自动切换API Key`);
});
}
}
// 启动服务器
const server = new Server();
const port = process.env.PORT || 3000;
server.start(port);