lifekline / server /apiConfig.js
xiaobo ren
Fix API key handling: clean whitespace/newlines, update test models to use correct names
8602f0b
/**
* 多 API 配置和路由
* 根据模型名称自动选择正确的 API 端点和密钥
*/
/**
* 清理 API 密钥 - 移除换行符、空格等特殊字符
*/
function cleanApiKey(key) {
if (!key) return null;
return String(key).trim().replace(/\r?\n/g, '').replace(/\s+/g, '');
}
// API 配置
const API_CONFIGS = {
// Google Gemini
gemini: {
baseUrl: 'https://generativelanguage.googleapis.com/v1',
apiKey: cleanApiKey(process.env.GEMINI_API_KEY),
models: [
'gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite',
'gemini-2.0-flash', 'gemini-2.0-flash-001', 'gemini-2.0-flash-lite', 'gemini-2.0-flash-lite-001',
// 保留旧模型名称以兼容
'gemini-1.5-pro', 'gemini-1.5-flash', 'gemini-pro', 'gemini-3-pro-preview'
],
format: 'gemini', // 使用 Gemini 原生格式
},
// OpenAI (ChatGPT)
openai: {
baseUrl: 'https://api.openai.com/v1',
apiKey: cleanApiKey(process.env.OPENAI_API_KEY),
models: [
'gpt-5.1', 'gpt-5-mini', 'gpt-5-nano',
'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano',
'gpt-4', 'gpt-4-turbo', 'gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo' // 保留旧模型以兼容
],
format: 'openai', // 使用 OpenAI 格式
},
// Grok (xAI)
grok: {
baseUrl: 'https://api.x.ai/v1',
apiKey: cleanApiKey(process.env.GROK_API_KEY),
models: [
'grok-4-0709', 'grok-4-1-fast-reasoning', 'grok-4-1-fast-non-reasoning',
'grok-4-fast-reasoning', 'grok-4-fast-non-reasoning',
'grok-3', 'grok-3-mini', 'grok-code-fast-1',
'grok-4', 'grok-4-auto', 'grok-beta' // 保留旧名称以兼容
],
format: 'openai', // Grok 使用 OpenAI 兼容格式
},
// Claude (Anthropic)
claude: {
baseUrl: 'https://api.anthropic.com/v1',
apiKey: cleanApiKey(process.env.CLAUDE_API_KEY),
models: [
'claude-opus-4-5', 'claude-sonnet-4-5', 'claude-haiku-4-20250514',
'claude-opus-4-1', 'claude-opus-4', 'claude-sonnet-4',
'claude-3-5-sonnet-20241022', 'claude-3-5-sonnet-20240620',
'claude-3-opus-20240229', 'claude-3-5-haiku-20241022',
'claude-3-sonnet-20240229', 'claude-3-haiku-20240307'
],
format: 'claude', // 使用 Claude 格式
},
};
/**
* 根据模型名称获取 API 配置
*/
export function getApiConfigForModel(modelName) {
// 检查每个 API 提供商的模型列表
for (const [provider, config] of Object.entries(API_CONFIGS)) {
if (config.models.includes(modelName)) {
return { ...config, provider };
}
}
// 默认使用 Gemini
return { ...API_CONFIGS.gemini, provider: 'gemini' };
}
/**
* 构建 API 请求
*/
export function buildApiRequest(modelName, systemPrompt, userPrompt, temperature = 0.7) {
const config = getApiConfigForModel(modelName);
if (config.format === 'gemini') {
// Google Gemini 格式 - 使用 v1 API
// v1 API 使用 models/{model}:generateContent 格式
if (!config.apiKey) {
throw new Error('GEMINI_API_KEY not configured');
}
const geminiModel = modelName.replace('-latest', ''); // 移除 -latest 后缀
return {
url: `${config.baseUrl}/models/${geminiModel}:generateContent?key=${config.apiKey}`,
headers: { 'Content-Type': 'application/json' },
body: {
contents: [{
parts: [{
text: `${systemPrompt}\n\n${userPrompt}`
}]
}],
generationConfig: {
temperature: temperature,
}
}
};
} else if (config.format === 'claude') {
// Claude 格式
if (!config.apiKey) {
throw new Error('CLAUDE_API_KEY not configured');
}
return {
url: `${config.baseUrl}/messages`,
headers: {
'Content-Type': 'application/json',
'x-api-key': config.apiKey,
'anthropic-version': '2023-06-01',
},
body: {
model: modelName,
max_tokens: 8192,
temperature: temperature,
messages: [
{ role: 'user', content: `${systemPrompt}\n\n${userPrompt}` }
]
}
};
} else {
// OpenAI 兼容格式 (OpenAI, Grok)
// GPT-5 系列模型只支持 temperature=1(默认值)
const isGPT5 = modelName.startsWith('gpt-5');
const requestBody = {
model: modelName,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt },
],
};
// 只有非 GPT-5 模型才设置 temperature
if (!isGPT5) {
requestBody.temperature = temperature;
}
// 确保 API 密钥存在
if (!config.apiKey) {
throw new Error(`API key not configured for ${config.provider || 'unknown provider'}`);
}
return {
url: `${config.baseUrl}/chat/completions`,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.apiKey}`,
},
body: requestBody
};
}
}
/**
* 解析 API 响应
*/
export function parseApiResponse(responseJson, modelName) {
const config = getApiConfigForModel(modelName);
if (config.format === 'gemini') {
// Google Gemini 响应格式
return responseJson.candidates?.[0]?.content?.parts?.[0]?.text || null;
} else if (config.format === 'claude') {
// Claude 响应格式
return responseJson.content?.[0]?.text || null;
} else {
// OpenAI 兼容格式
return responseJson.choices?.[0]?.message?.content || null;
}
}
export default API_CONFIGS;