feat: Add test scripts for Bedrock models and model mapping functionality

This commit is contained in:
andersonby
2025-08-06 19:23:36 +08:00
parent 9a9a82c86f
commit 657b7b0a05
6 changed files with 253 additions and 76 deletions

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env node
const bedrockAccountService = require('../src/services/bedrockAccountService');
const bedrockRelayService = require('../src/services/bedrockRelayService');
const logger = require('../src/utils/logger');
async function testBedrockModels() {
try {
console.log('🧪 测试Bedrock模型配置...');
// 测试可用模型列表
const models = await bedrockRelayService.getAvailableModels();
console.log(`📋 找到 ${models.length} 个可用模型:`);
models.forEach(model => {
console.log(` - ${model.id} (${model.name})`);
});
// 测试默认模型
console.log(`\n🎯 系统默认模型: ${bedrockRelayService.defaultModel}`);
console.log(`🎯 系统默认小模型: ${bedrockRelayService.defaultSmallModel}`);
console.log('\n✅ Bedrock模型配置测试完成');
process.exit(0);
} catch (error) {
console.error('❌ Bedrock模型测试失败:', error);
process.exit(1);
}
}
// 如果直接运行此脚本
if (require.main === module) {
testBedrockModels();
}
module.exports = { testBedrockModels };

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env node
const bedrockRelayService = require('../src/services/bedrockRelayService');
function testModelMapping() {
console.log('🧪 测试模型映射功能...');
// 测试用例
const testCases = [
// 标准Claude模型名
'claude-3-5-haiku-20241022',
'claude-3-5-sonnet-20241022',
'claude-3-5-sonnet',
'claude-3-5-haiku',
'claude-sonnet-4',
'claude-opus-4-1',
'claude-3-7-sonnet',
// 已经是Bedrock格式的
'us.anthropic.claude-sonnet-4-20250514-v1:0',
'anthropic.claude-3-5-haiku-20241022-v1:0',
// 未知模型
'unknown-model'
];
console.log('\n📋 模型映射测试结果:');
testCases.forEach(testModel => {
const mappedModel = bedrockRelayService._mapToBedrockModel(testModel);
const isChanged = mappedModel !== testModel;
const status = isChanged ? '🔄' : '✅';
console.log(`${status} ${testModel}`);
if (isChanged) {
console.log(`${mappedModel}`);
}
});
console.log('\n✅ 模型映射测试完成');
}
// 如果直接运行此脚本
if (require.main === module) {
testModelMapping();
}
module.exports = { testModelMapping };

View File

@@ -145,7 +145,7 @@ async function handleMessagesRequest(req, res) {
throw new Error('Failed to get Bedrock account details');
}
const result = await bedrockRelayService.handleStreamRequest(req.body, bedrockAccountResult.data, req.headers, res);
const result = await bedrockRelayService.handleStreamRequest(req.body, bedrockAccountResult.data, res);
// 记录Bedrock使用统计
if (result.usage) {

View File

@@ -19,7 +19,7 @@ class BedrockAccountService {
description = '',
region = process.env.AWS_REGION || 'us-east-1',
awsCredentials = null, // { accessKeyId, secretAccessKey, sessionToken }
defaultModel = 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
defaultModel = 'us.anthropic.claude-sonnet-4-20250514-v1:0',
isActive = true,
accountType = 'shared', // 'dedicated' or 'shared'
priority = 50, // 调度优先级 (1-100数字越小优先级越高)
@@ -313,9 +313,9 @@ class BedrockAccountService {
// 🔐 加密AWS凭证
_encryptAwsCredentials(credentials) {
try {
const key = Buffer.from(config.security.encryptionKey, 'utf8');
const key = crypto.createHash('sha256').update(config.security.encryptionKey).digest();
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher(this.ENCRYPTION_ALGORITHM, key);
const cipher = crypto.createCipheriv(this.ENCRYPTION_ALGORITHM, key, iv);
const credentialsString = JSON.stringify(credentials);
let encrypted = cipher.update(credentialsString, 'utf8', 'hex');
@@ -334,8 +334,24 @@ class BedrockAccountService {
// 🔓 解密AWS凭证
_decryptAwsCredentials(encryptedData) {
try {
const key = Buffer.from(config.security.encryptionKey, 'utf8');
const decipher = crypto.createDecipher(this.ENCRYPTION_ALGORITHM, key);
// 检查数据格式
if (!encryptedData || typeof encryptedData !== 'object') {
logger.error('❌ 无效的加密数据格式:', encryptedData);
throw new Error('Invalid encrypted data format');
}
// 检查必要字段
if (!encryptedData.encrypted || !encryptedData.iv) {
logger.error('❌ 缺少加密数据字段:', {
hasEncrypted: !!encryptedData.encrypted,
hasIv: !!encryptedData.iv
});
throw new Error('Missing encrypted data fields');
}
const key = crypto.createHash('sha256').update(config.security.encryptionKey).digest();
const iv = Buffer.from(encryptedData.iv, 'hex');
const decipher = crypto.createDecipheriv(this.ENCRYPTION_ALGORITHM, key, iv);
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');

View File

@@ -9,7 +9,7 @@ class BedrockRelayService {
this.smallFastModelRegion = process.env.ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION || this.defaultRegion;
// 默认模型配置
this.defaultModel = process.env.ANTHROPIC_MODEL || 'us.anthropic.claude-3-7-sonnet-20250219-v1:0';
this.defaultModel = process.env.ANTHROPIC_MODEL || 'us.anthropic.claude-sonnet-4-20250514-v1:0';
this.defaultSmallModel = process.env.ANTHROPIC_SMALL_FAST_MODEL || 'us.anthropic.claude-3-5-haiku-20241022-v1:0';
// Token配置
@@ -42,8 +42,12 @@ class BedrockRelayService {
sessionToken: bedrockAccount.awsCredentials.sessionToken
};
} else {
// 使用默认凭证链:环境变量 -> AWS配置文件 -> IAM角色
// 检查是否有环境变量凭证
if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
clientConfig.credentials = fromEnv();
} else {
throw new Error('AWS凭证未配置。请在Bedrock账户中配置AWS访问密钥或设置环境变量AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY');
}
}
const client = new BedrockRuntimeClient(clientConfig);
@@ -184,18 +188,81 @@ class BedrockRelayService {
// 选择使用的模型
_selectModel(requestBody, bedrockAccount) {
let selectedModel;
// 优先使用账户配置的模型
if (bedrockAccount?.defaultModel) {
return bedrockAccount.defaultModel;
selectedModel = bedrockAccount.defaultModel;
logger.info(`🎯 使用账户配置的模型: ${selectedModel}`, { metadata: { source: 'account', accountId: bedrockAccount.id } });
}
// 检查请求中指定的模型
if (requestBody.model) {
return requestBody.model;
else if (requestBody.model) {
selectedModel = requestBody.model;
logger.info(`🎯 使用请求指定的模型: ${selectedModel}`, { metadata: { source: 'request' } });
}
// 使用默认模型
else {
selectedModel = this.defaultModel;
logger.info(`🎯 使用系统默认模型: ${selectedModel}`, { metadata: { source: 'default' } });
}
// 使用默认模型
return this.defaultModel;
// 如果是标准Claude模型名需要映射为Bedrock格式
const bedrockModel = this._mapToBedrockModel(selectedModel);
if (bedrockModel !== selectedModel) {
logger.info(`🔄 模型映射: ${selectedModel}${bedrockModel}`, { metadata: { originalModel: selectedModel, bedrockModel } });
}
return bedrockModel;
}
// 将标准Claude模型名映射为Bedrock格式
_mapToBedrockModel(modelName) {
// 标准Claude模型名到Bedrock模型名的映射表
const modelMapping = {
// Claude Sonnet 4
'claude-sonnet-4': 'us.anthropic.claude-sonnet-4-20250514-v1:0',
'claude-sonnet-4-20250514': 'us.anthropic.claude-sonnet-4-20250514-v1:0',
// Claude Opus 4.1
'claude-opus-4': 'us.anthropic.claude-opus-4-1-20250805-v1:0',
'claude-opus-4-1': 'us.anthropic.claude-opus-4-1-20250805-v1:0',
'claude-opus-4-1-20250805': 'us.anthropic.claude-opus-4-1-20250805-v1:0',
// Claude 3.7 Sonnet
'claude-3-7-sonnet': 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
'claude-3-7-sonnet-20250219': 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
// Claude 3.5 Sonnet v2
'claude-3-5-sonnet': 'us.anthropic.claude-3-5-sonnet-20241022-v2:0',
'claude-3-5-sonnet-20241022': 'us.anthropic.claude-3-5-sonnet-20241022-v2:0',
// Claude 3.5 Haiku
'claude-3-5-haiku': 'us.anthropic.claude-3-5-haiku-20241022-v1:0',
'claude-3-5-haiku-20241022': 'us.anthropic.claude-3-5-haiku-20241022-v1:0',
// Claude 3 Sonnet
'claude-3-sonnet': 'us.anthropic.claude-3-sonnet-20240229-v1:0',
'claude-3-sonnet-20240229': 'us.anthropic.claude-3-sonnet-20240229-v1:0',
// Claude 3 Haiku
'claude-3-haiku': 'us.anthropic.claude-3-haiku-20240307-v1:0',
'claude-3-haiku-20240307': 'us.anthropic.claude-3-haiku-20240307-v1:0'
};
// 如果已经是Bedrock格式直接返回
if (modelName.startsWith('us.anthropic.') || modelName.startsWith('anthropic.')) {
return modelName;
}
// 查找映射
const mappedModel = modelMapping[modelName];
if (mappedModel) {
return mappedModel;
}
// 如果没有找到映射,返回原始模型名(可能会导致错误,但保持向后兼容)
logger.warn(`⚠️ 未找到模型映射: ${modelName},使用原始模型名`, { metadata: { originalModel: modelName } });
return modelName;
}
// 选择使用的区域
@@ -352,6 +419,12 @@ class BedrockRelayService {
// Bedrock暂不支持列出推理配置文件的API返回预定义的模型列表
const models = [
{
id: 'us.anthropic.claude-sonnet-4-20250514-v1:0',
name: 'Claude Sonnet 4',
provider: 'anthropic',
type: 'bedrock'
},
{
id: 'us.anthropic.claude-opus-4-1-20250805-v1:0',
name: 'Claude Opus 4.1',

View File

@@ -1377,11 +1377,13 @@ const createAccount = async () => {
data.userAgent = form.value.userAgent || null
data.rateLimitDuration = form.value.rateLimitDuration || 60
} else if (form.value.platform === 'bedrock') {
// Bedrock 账户特定数据
data.accessKeyId = form.value.accessKeyId
data.secretAccessKey = form.value.secretAccessKey
// Bedrock 账户特定数据 - 构造 awsCredentials 对象
data.awsCredentials = {
accessKeyId: form.value.accessKeyId,
secretAccessKey: form.value.secretAccessKey,
sessionToken: form.value.sessionToken || null
}
data.region = form.value.region
data.sessionToken = form.value.sessionToken || null
data.defaultModel = form.value.defaultModel || null
data.smallFastModel = form.value.smallFastModel || null
data.priority = form.value.priority || 50
@@ -1511,18 +1513,22 @@ const updateAccount = async () => {
// Bedrock 特定更新
if (props.account.platform === 'bedrock') {
// 只有当有凭证变更时才构造 awsCredentials 对象
if (form.value.accessKeyId || form.value.secretAccessKey || form.value.sessionToken) {
data.awsCredentials = {}
if (form.value.accessKeyId) {
data.accessKeyId = form.value.accessKeyId
data.awsCredentials.accessKeyId = form.value.accessKeyId
}
if (form.value.secretAccessKey) {
data.secretAccessKey = form.value.secretAccessKey
data.awsCredentials.secretAccessKey = form.value.secretAccessKey
}
if (form.value.sessionToken !== undefined) {
data.awsCredentials.sessionToken = form.value.sessionToken || null
}
}
if (form.value.region) {
data.region = form.value.region
}
if (form.value.sessionToken) {
data.sessionToken = form.value.sessionToken
}
// 模型配置(支持设置为空来使用系统默认)
data.defaultModel = form.value.defaultModel || null
data.smallFastModel = form.value.smallFastModel || null