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'); 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使用统计 // 记录Bedrock使用统计
if (result.usage) { if (result.usage) {

View File

@@ -19,7 +19,7 @@ class BedrockAccountService {
description = '', description = '',
region = process.env.AWS_REGION || 'us-east-1', region = process.env.AWS_REGION || 'us-east-1',
awsCredentials = null, // { accessKeyId, secretAccessKey, sessionToken } 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, isActive = true,
accountType = 'shared', // 'dedicated' or 'shared' accountType = 'shared', // 'dedicated' or 'shared'
priority = 50, // 调度优先级 (1-100数字越小优先级越高) priority = 50, // 调度优先级 (1-100数字越小优先级越高)
@@ -28,7 +28,7 @@ class BedrockAccountService {
} = options; } = options;
const accountId = uuidv4(); const accountId = uuidv4();
let accountData = { let accountData = {
id: accountId, id: accountId,
name, name,
@@ -52,9 +52,9 @@ class BedrockAccountService {
const client = redis.getClientSafe(); const client = redis.getClientSafe();
await client.set(`bedrock_account:${accountId}`, JSON.stringify(accountData)); await client.set(`bedrock_account:${accountId}`, JSON.stringify(accountData));
logger.info(`✅ 创建Bedrock账户成功 - ID: ${accountId}, 名称: ${name}, 区域: ${region}`); logger.info(`✅ 创建Bedrock账户成功 - ID: ${accountId}, 名称: ${name}, 区域: ${region}`);
return { return {
success: true, success: true,
data: { data: {
@@ -84,14 +84,14 @@ class BedrockAccountService {
} }
const account = JSON.parse(accountData); const account = JSON.parse(accountData);
// 解密AWS凭证用于内部使用 // 解密AWS凭证用于内部使用
if (account.awsCredentials) { if (account.awsCredentials) {
account.awsCredentials = this._decryptAwsCredentials(account.awsCredentials); account.awsCredentials = this._decryptAwsCredentials(account.awsCredentials);
} }
logger.debug(`🔍 获取Bedrock账户 - ID: ${accountId}, 名称: ${account.name}`); logger.debug(`🔍 获取Bedrock账户 - ID: ${accountId}, 名称: ${account.name}`);
return { return {
success: true, success: true,
data: account data: account
@@ -113,7 +113,7 @@ class BedrockAccountService {
const accountData = await client.get(key); const accountData = await client.get(key);
if (accountData) { if (accountData) {
const account = JSON.parse(accountData); const account = JSON.parse(accountData);
// 返回给前端时,不包含敏感信息,只显示掩码 // 返回给前端时,不包含敏感信息,只显示掩码
accounts.push({ accounts.push({
id: account.id, id: account.id,
@@ -141,7 +141,7 @@ class BedrockAccountService {
}); });
logger.debug(`📋 获取所有Bedrock账户 - 共 ${accounts.length}`); logger.debug(`📋 获取所有Bedrock账户 - 共 ${accounts.length}`);
return { return {
success: true, success: true,
data: accounts data: accounts
@@ -161,7 +161,7 @@ class BedrockAccountService {
} }
const account = accountResult.data; const account = accountResult.data;
// 更新字段 // 更新字段
if (updates.name !== undefined) account.name = updates.name; if (updates.name !== undefined) account.name = updates.name;
if (updates.description !== undefined) account.description = updates.description; if (updates.description !== undefined) account.description = updates.description;
@@ -186,9 +186,9 @@ class BedrockAccountService {
const client = redis.getClientSafe(); const client = redis.getClientSafe();
await client.set(`bedrock_account:${accountId}`, JSON.stringify(account)); await client.set(`bedrock_account:${accountId}`, JSON.stringify(account));
logger.info(`✅ 更新Bedrock账户成功 - ID: ${accountId}, 名称: ${account.name}`); logger.info(`✅ 更新Bedrock账户成功 - ID: ${accountId}, 名称: ${account.name}`);
return { return {
success: true, success: true,
data: { data: {
@@ -222,9 +222,9 @@ class BedrockAccountService {
const client = redis.getClientSafe(); const client = redis.getClientSafe();
await client.del(`bedrock_account:${accountId}`); await client.del(`bedrock_account:${accountId}`);
logger.info(`✅ 删除Bedrock账户成功 - ID: ${accountId}`); logger.info(`✅ 删除Bedrock账户成功 - ID: ${accountId}`);
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
logger.error(`❌ 删除Bedrock账户失败 - ID: ${accountId}`, error); logger.error(`❌ 删除Bedrock账户失败 - ID: ${accountId}`, error);
@@ -240,7 +240,7 @@ class BedrockAccountService {
return { success: false, error: 'Failed to get accounts' }; return { success: false, error: 'Failed to get accounts' };
} }
const availableAccounts = accountsResult.data.filter(account => const availableAccounts = accountsResult.data.filter(account =>
account.isActive && account.schedulable account.isActive && account.schedulable
); );
@@ -250,7 +250,7 @@ class BedrockAccountService {
// 简单的轮询选择策略 - 选择优先级最高的账户 // 简单的轮询选择策略 - 选择优先级最高的账户
const selectedAccount = availableAccounts[0]; const selectedAccount = availableAccounts[0];
// 获取完整账户信息(包含解密的凭证) // 获取完整账户信息(包含解密的凭证)
const fullAccountResult = await this.getAccount(selectedAccount.id); const fullAccountResult = await this.getAccount(selectedAccount.id);
if (!fullAccountResult.success) { if (!fullAccountResult.success) {
@@ -258,7 +258,7 @@ class BedrockAccountService {
} }
logger.debug(`🎯 选择Bedrock账户 - ID: ${selectedAccount.id}, 名称: ${selectedAccount.name}`); logger.debug(`🎯 选择Bedrock账户 - ID: ${selectedAccount.id}, 名称: ${selectedAccount.name}`);
return { return {
success: true, success: true,
data: fullAccountResult.data data: fullAccountResult.data
@@ -278,12 +278,12 @@ class BedrockAccountService {
} }
const account = accountResult.data; const account = accountResult.data;
logger.info(`🧪 测试Bedrock账户连接 - ID: ${accountId}, 名称: ${account.name}`); logger.info(`🧪 测试Bedrock账户连接 - ID: ${accountId}, 名称: ${account.name}`);
// 尝试获取模型列表来测试连接 // 尝试获取模型列表来测试连接
const models = await bedrockRelayService.getAvailableModels(account); const models = await bedrockRelayService.getAvailableModels(account);
if (models && models.length > 0) { if (models && models.length > 0) {
logger.info(`✅ Bedrock账户测试成功 - ID: ${accountId}, 发现 ${models.length} 个模型`); logger.info(`✅ Bedrock账户测试成功 - ID: ${accountId}, 发现 ${models.length} 个模型`);
return { return {
@@ -313,14 +313,14 @@ class BedrockAccountService {
// 🔐 加密AWS凭证 // 🔐 加密AWS凭证
_encryptAwsCredentials(credentials) { _encryptAwsCredentials(credentials) {
try { 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 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); const credentialsString = JSON.stringify(credentials);
let encrypted = cipher.update(credentialsString, 'utf8', 'hex'); let encrypted = cipher.update(credentialsString, 'utf8', 'hex');
encrypted += cipher.final('hex'); encrypted += cipher.final('hex');
return { return {
encrypted: encrypted, encrypted: encrypted,
iv: iv.toString('hex') iv: iv.toString('hex')
@@ -334,12 +334,28 @@ class BedrockAccountService {
// 🔓 解密AWS凭证 // 🔓 解密AWS凭证
_decryptAwsCredentials(encryptedData) { _decryptAwsCredentials(encryptedData) {
try { 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'); let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8'); decrypted += decipher.final('utf8');
return JSON.parse(decrypted); return JSON.parse(decrypted);
} catch (error) { } catch (error) {
logger.error('❌ AWS凭证解密失败', error); logger.error('❌ AWS凭证解密失败', error);

View File

@@ -7,16 +7,16 @@ class BedrockRelayService {
constructor() { constructor() {
this.defaultRegion = process.env.AWS_REGION || config.bedrock?.defaultRegion || 'us-east-1'; this.defaultRegion = process.env.AWS_REGION || config.bedrock?.defaultRegion || 'us-east-1';
this.smallFastModelRegion = process.env.ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION || this.defaultRegion; 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'; this.defaultSmallModel = process.env.ANTHROPIC_SMALL_FAST_MODEL || 'us.anthropic.claude-3-5-haiku-20241022-v1:0';
// Token配置 // Token配置
this.maxOutputTokens = parseInt(process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) || 4096; this.maxOutputTokens = parseInt(process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) || 4096;
this.maxThinkingTokens = parseInt(process.env.MAX_THINKING_TOKENS) || 1024; this.maxThinkingTokens = parseInt(process.env.MAX_THINKING_TOKENS) || 1024;
this.enablePromptCaching = process.env.DISABLE_PROMPT_CACHING !== '1'; this.enablePromptCaching = process.env.DISABLE_PROMPT_CACHING !== '1';
// 创建Bedrock客户端 // 创建Bedrock客户端
this.clients = new Map(); // 缓存不同区域的客户端 this.clients = new Map(); // 缓存不同区域的客户端
} }
@@ -25,7 +25,7 @@ class BedrockRelayService {
_getBedrockClient(region = null, bedrockAccount = null) { _getBedrockClient(region = null, bedrockAccount = null) {
const targetRegion = region || this.defaultRegion; const targetRegion = region || this.defaultRegion;
const clientKey = `${targetRegion}-${bedrockAccount?.id || 'default'}`; const clientKey = `${targetRegion}-${bedrockAccount?.id || 'default'}`;
if (this.clients.has(clientKey)) { if (this.clients.has(clientKey)) {
return this.clients.get(clientKey); return this.clients.get(clientKey);
} }
@@ -42,13 +42,17 @@ class BedrockRelayService {
sessionToken: bedrockAccount.awsCredentials.sessionToken sessionToken: bedrockAccount.awsCredentials.sessionToken
}; };
} else { } else {
// 使用默认凭证链:环境变量 -> AWS配置文件 -> IAM角色 // 检查是否有环境变量凭证
clientConfig.credentials = fromEnv(); 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); const client = new BedrockRuntimeClient(clientConfig);
this.clients.set(clientKey, client); this.clients.set(clientKey, client);
logger.debug(`🔧 Created Bedrock client for region: ${targetRegion}, account: ${bedrockAccount?.name || 'default'}`); logger.debug(`🔧 Created Bedrock client for region: ${targetRegion}, account: ${bedrockAccount?.name || 'default'}`);
return client; return client;
} }
@@ -62,7 +66,7 @@ class BedrockRelayService {
// 转换请求格式为Bedrock格式 // 转换请求格式为Bedrock格式
const bedrockPayload = this._convertToBedrockFormat(requestBody); const bedrockPayload = this._convertToBedrockFormat(requestBody);
const command = new InvokeModelCommand({ const command = new InvokeModelCommand({
modelId: modelId, modelId: modelId,
body: JSON.stringify(bedrockPayload), body: JSON.stringify(bedrockPayload),
@@ -71,7 +75,7 @@ class BedrockRelayService {
}); });
logger.debug(`🚀 Bedrock非流式请求 - 模型: ${modelId}, 区域: ${region}`); logger.debug(`🚀 Bedrock非流式请求 - 模型: ${modelId}, 区域: ${region}`);
const startTime = Date.now(); const startTime = Date.now();
const response = await client.send(command); const response = await client.send(command);
const duration = Date.now() - startTime; const duration = Date.now() - startTime;
@@ -81,7 +85,7 @@ class BedrockRelayService {
const claudeResponse = this._convertFromBedrockFormat(responseBody); const claudeResponse = this._convertFromBedrockFormat(responseBody);
logger.info(`✅ Bedrock请求完成 - 模型: ${modelId}, 耗时: ${duration}ms`); logger.info(`✅ Bedrock请求完成 - 模型: ${modelId}, 耗时: ${duration}ms`);
return { return {
success: true, success: true,
data: claudeResponse, data: claudeResponse,
@@ -105,7 +109,7 @@ class BedrockRelayService {
// 转换请求格式为Bedrock格式 // 转换请求格式为Bedrock格式
const bedrockPayload = this._convertToBedrockFormat(requestBody); const bedrockPayload = this._convertToBedrockFormat(requestBody);
const command = new InvokeModelWithResponseStreamCommand({ const command = new InvokeModelWithResponseStreamCommand({
modelId: modelId, modelId: modelId,
body: JSON.stringify(bedrockPayload), body: JSON.stringify(bedrockPayload),
@@ -114,7 +118,7 @@ class BedrockRelayService {
}); });
logger.debug(`🌊 Bedrock流式请求 - 模型: ${modelId}, 区域: ${region}`); logger.debug(`🌊 Bedrock流式请求 - 模型: ${modelId}, 区域: ${region}`);
const startTime = Date.now(); const startTime = Date.now();
const response = await client.send(command); const response = await client.send(command);
@@ -135,17 +139,17 @@ class BedrockRelayService {
if (chunk.chunk) { if (chunk.chunk) {
const chunkData = JSON.parse(new TextDecoder().decode(chunk.chunk.bytes)); const chunkData = JSON.parse(new TextDecoder().decode(chunk.chunk.bytes));
const claudeEvent = this._convertBedrockStreamToClaudeFormat(chunkData, isFirstChunk); const claudeEvent = this._convertBedrockStreamToClaudeFormat(chunkData, isFirstChunk);
if (claudeEvent) { if (claudeEvent) {
// 发送SSE事件 // 发送SSE事件
res.write(`event: ${claudeEvent.type}\n`); res.write(`event: ${claudeEvent.type}\n`);
res.write(`data: ${JSON.stringify(claudeEvent.data)}\n\n`); res.write(`data: ${JSON.stringify(claudeEvent.data)}\n\n`);
// 提取使用统计 // 提取使用统计
if (claudeEvent.type === 'message_stop' && claudeEvent.data.usage) { if (claudeEvent.type === 'message_stop' && claudeEvent.data.usage) {
totalUsage = claudeEvent.data.usage; totalUsage = claudeEvent.data.usage;
} }
isFirstChunk = false; isFirstChunk = false;
} }
} }
@@ -168,34 +172,97 @@ class BedrockRelayService {
} catch (error) { } catch (error) {
logger.error('❌ Bedrock流式请求失败:', error); logger.error('❌ Bedrock流式请求失败:', error);
// 发送错误事件 // 发送错误事件
if (!res.headersSent) { if (!res.headersSent) {
res.writeHead(500, { 'Content-Type': 'application/json' }); res.writeHead(500, { 'Content-Type': 'application/json' });
} }
res.write('event: error\n'); res.write('event: error\n');
res.write(`data: ${JSON.stringify({ error: this._handleBedrockError(error).message })}\n\n`); res.write(`data: ${JSON.stringify({ error: this._handleBedrockError(error).message })}\n\n`);
res.end(); res.end();
throw this._handleBedrockError(error); throw this._handleBedrockError(error);
} }
} }
// 选择使用的模型 // 选择使用的模型
_selectModel(requestBody, bedrockAccount) { _selectModel(requestBody, bedrockAccount) {
let selectedModel;
// 优先使用账户配置的模型 // 优先使用账户配置的模型
if (bedrockAccount?.defaultModel) { if (bedrockAccount?.defaultModel) {
return bedrockAccount.defaultModel; selectedModel = bedrockAccount.defaultModel;
logger.info(`🎯 使用账户配置的模型: ${selectedModel}`, { metadata: { source: 'account', accountId: bedrockAccount.id } });
} }
// 检查请求中指定的模型 // 检查请求中指定的模型
if (requestBody.model) { else if (requestBody.model) {
return requestBody.model; selectedModel = requestBody.model;
logger.info(`🎯 使用请求指定的模型: ${selectedModel}`, { metadata: { source: 'request' } });
} }
// 使用默认模型 // 使用默认模型
return this.defaultModel; else {
selectedModel = this.defaultModel;
logger.info(`🎯 使用系统默认模型: ${selectedModel}`, { metadata: { source: 'default' } });
}
// 如果是标准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;
} }
// 选择使用的区域 // 选择使用的区域
@@ -204,12 +271,12 @@ class BedrockRelayService {
if (bedrockAccount?.region) { if (bedrockAccount?.region) {
return bedrockAccount.region; return bedrockAccount.region;
} }
// 对于小模型,使用专门的区域配置 // 对于小模型,使用专门的区域配置
if (modelId.includes('haiku')) { if (modelId.includes('haiku')) {
return this.smallFastModelRegion; return this.smallFastModelRegion;
} }
return this.defaultRegion; return this.defaultRegion;
} }
@@ -230,7 +297,7 @@ class BedrockRelayService {
if (requestBody.temperature !== undefined) { if (requestBody.temperature !== undefined) {
bedrockPayload.temperature = requestBody.temperature; bedrockPayload.temperature = requestBody.temperature;
} }
if (requestBody.top_p !== undefined) { if (requestBody.top_p !== undefined) {
bedrockPayload.top_p = requestBody.top_p; bedrockPayload.top_p = requestBody.top_p;
} }
@@ -289,7 +356,7 @@ class BedrockRelayService {
} }
}; };
} }
if (bedrockChunk.type === 'content_block_delta') { if (bedrockChunk.type === 'content_block_delta') {
return { return {
type: 'content_block_delta', type: 'content_block_delta',
@@ -299,7 +366,7 @@ class BedrockRelayService {
} }
}; };
} }
if (bedrockChunk.type === 'message_delta') { if (bedrockChunk.type === 'message_delta') {
return { return {
type: 'message_delta', type: 'message_delta',
@@ -309,7 +376,7 @@ class BedrockRelayService {
} }
}; };
} }
if (bedrockChunk.type === 'message_stop') { if (bedrockChunk.type === 'message_stop') {
return { return {
type: 'message_stop', type: 'message_stop',
@@ -325,23 +392,23 @@ class BedrockRelayService {
// 处理Bedrock错误 // 处理Bedrock错误
_handleBedrockError(error) { _handleBedrockError(error) {
const errorMessage = error.message || 'Unknown Bedrock error'; const errorMessage = error.message || 'Unknown Bedrock error';
if (error.name === 'ValidationException') { if (error.name === 'ValidationException') {
return new Error(`Bedrock参数验证失败: ${errorMessage}`); return new Error(`Bedrock参数验证失败: ${errorMessage}`);
} }
if (error.name === 'ThrottlingException') { if (error.name === 'ThrottlingException') {
return new Error('Bedrock请求限流请稍后重试'); return new Error('Bedrock请求限流请稍后重试');
} }
if (error.name === 'AccessDeniedException') { if (error.name === 'AccessDeniedException') {
return new Error('Bedrock访问被拒绝请检查IAM权限'); return new Error('Bedrock访问被拒绝请检查IAM权限');
} }
if (error.name === 'ModelNotReadyException') { if (error.name === 'ModelNotReadyException') {
return new Error('Bedrock模型未就绪请稍后重试'); return new Error('Bedrock模型未就绪请稍后重试');
} }
return new Error(`Bedrock服务错误: ${errorMessage}`); return new Error(`Bedrock服务错误: ${errorMessage}`);
} }
@@ -349,9 +416,15 @@ class BedrockRelayService {
async getAvailableModels(bedrockAccount = null) { async getAvailableModels(bedrockAccount = null) {
try { try {
const region = bedrockAccount?.region || this.defaultRegion; const region = bedrockAccount?.region || this.defaultRegion;
// Bedrock暂不支持列出推理配置文件的API返回预定义的模型列表 // Bedrock暂不支持列出推理配置文件的API返回预定义的模型列表
const models = [ 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', id: 'us.anthropic.claude-opus-4-1-20250805-v1:0',
name: 'Claude Opus 4.1', name: 'Claude Opus 4.1',

View File

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