mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
feat: 处理 openai 格式请求
This commit is contained in:
@@ -27,6 +27,7 @@ function checkPermissions(apiKeyData, requiredPermission = 'gemini') {
|
|||||||
router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
|
router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
let abortController = null;
|
let abortController = null;
|
||||||
|
let account = null; // Declare account outside try block for error handling
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const apiKeyData = req.apiKey;
|
const apiKeyData = req.apiKey;
|
||||||
@@ -41,16 +42,42 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 处理请求体结构 - 支持多种格式
|
||||||
|
let requestBody = req.body;
|
||||||
|
|
||||||
|
// 如果请求体被包装在 body 字段中,解包它
|
||||||
|
if (req.body.body && typeof req.body.body === 'object') {
|
||||||
|
requestBody = req.body.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 URL 路径中提取模型信息(如果存在)
|
||||||
|
let urlModel = null;
|
||||||
|
const urlPath = req.body?.config?.url || req.originalUrl || req.url;
|
||||||
|
const modelMatch = urlPath.match(/\/([^\/]+):(?:stream)?[Gg]enerateContent/);
|
||||||
|
if (modelMatch) {
|
||||||
|
urlModel = modelMatch[1];
|
||||||
|
logger.debug(`Extracted model from URL: ${urlModel}`);
|
||||||
|
}
|
||||||
|
|
||||||
// 提取请求参数
|
// 提取请求参数
|
||||||
const {
|
const {
|
||||||
messages,
|
messages: requestMessages,
|
||||||
model = 'gemini-2.0-flash-exp',
|
contents,
|
||||||
|
model: bodyModel = 'gemini-2.0-flash-exp',
|
||||||
temperature = 0.7,
|
temperature = 0.7,
|
||||||
max_tokens = 4096,
|
max_tokens = 4096,
|
||||||
stream = false
|
stream = false
|
||||||
} = req.body;
|
} = requestBody;
|
||||||
|
|
||||||
|
// 优先使用 URL 中的模型,其次是请求体中的模型
|
||||||
|
const model = urlModel || bodyModel;
|
||||||
|
|
||||||
|
// 支持两种格式: OpenAI 的 messages 或 Gemini 的 contents
|
||||||
|
let messages = requestMessages;
|
||||||
|
if (contents && Array.isArray(contents)) {
|
||||||
|
messages = contents;
|
||||||
|
}
|
||||||
|
|
||||||
// 验证必需参数
|
// 验证必需参数
|
||||||
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
@@ -79,7 +106,7 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
|
|||||||
const sessionHash = generateSessionHash(req);
|
const sessionHash = generateSessionHash(req);
|
||||||
|
|
||||||
// 选择可用的 Gemini 账户
|
// 选择可用的 Gemini 账户
|
||||||
const account = await geminiAccountService.selectAvailableAccount(
|
account = await geminiAccountService.selectAvailableAccount(
|
||||||
apiKeyData.id,
|
apiKeyData.id,
|
||||||
sessionHash
|
sessionHash
|
||||||
);
|
);
|
||||||
@@ -153,8 +180,8 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
|
|||||||
|
|
||||||
// 处理速率限制
|
// 处理速率限制
|
||||||
if (error.status === 429) {
|
if (error.status === 429) {
|
||||||
if (req.apiKey && req.account) {
|
if (req.apiKey && account) {
|
||||||
await geminiAccountService.setAccountRateLimited(req.account.id, true);
|
await geminiAccountService.setAccountRateLimited(account.id, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,49 @@ const path = require('path');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
|
||||||
|
// 安全的 JSON 序列化函数,处理循环引用
|
||||||
|
const safeStringify = (obj, maxDepth = 3) => {
|
||||||
|
const seen = new WeakSet();
|
||||||
|
|
||||||
|
const replacer = (key, value, depth = 0) => {
|
||||||
|
if (depth > maxDepth) return '[Max Depth Reached]';
|
||||||
|
|
||||||
|
if (value !== null && typeof value === 'object') {
|
||||||
|
if (seen.has(value)) {
|
||||||
|
return '[Circular Reference]';
|
||||||
|
}
|
||||||
|
seen.add(value);
|
||||||
|
|
||||||
|
// 过滤掉常见的循环引用对象
|
||||||
|
if (value.constructor) {
|
||||||
|
const constructorName = value.constructor.name;
|
||||||
|
if (['Socket', 'TLSSocket', 'HTTPParser', 'IncomingMessage', 'ServerResponse'].includes(constructorName)) {
|
||||||
|
return `[${constructorName} Object]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归处理对象属性
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map((item, index) => replacer(index, item, depth + 1));
|
||||||
|
} else {
|
||||||
|
const result = {};
|
||||||
|
for (const [k, v] of Object.entries(value)) {
|
||||||
|
result[k] = replacer(k, v, depth + 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.stringify(replacer('', obj));
|
||||||
|
} catch (error) {
|
||||||
|
return JSON.stringify({ error: 'Failed to serialize object', message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 📝 增强的日志格式
|
// 📝 增强的日志格式
|
||||||
const createLogFormat = (colorize = false) => {
|
const createLogFormat = (colorize = false) => {
|
||||||
const formats = [
|
const formats = [
|
||||||
@@ -31,7 +74,7 @@ const createLogFormat = (colorize = false) => {
|
|||||||
|
|
||||||
// 添加元数据
|
// 添加元数据
|
||||||
if (metadata && Object.keys(metadata).length > 0) {
|
if (metadata && Object.keys(metadata).length > 0) {
|
||||||
logMessage += ` | ${JSON.stringify(metadata)}`;
|
logMessage += ` | ${safeStringify(metadata)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加其他属性
|
// 添加其他属性
|
||||||
@@ -42,7 +85,7 @@ const createLogFormat = (colorize = false) => {
|
|||||||
delete additionalData.stack;
|
delete additionalData.stack;
|
||||||
|
|
||||||
if (Object.keys(additionalData).length > 0) {
|
if (Object.keys(additionalData).length > 0) {
|
||||||
logMessage += ` | ${JSON.stringify(additionalData)}`;
|
logMessage += ` | ${safeStringify(additionalData)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return stack ? `${logMessage}\n${stack}` : logMessage;
|
return stack ? `${logMessage}\n${stack}` : logMessage;
|
||||||
|
|||||||
Reference in New Issue
Block a user