mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: Add test scripts for Bedrock models and model mapping functionality
This commit is contained in:
35
scripts/test-bedrock-models.js
Normal file
35
scripts/test-bedrock-models.js
Normal 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 };
|
||||
47
scripts/test-model-mapping.js
Normal file
47
scripts/test-model-mapping.js
Normal 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 };
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,数字越小优先级越高)
|
||||
@@ -28,7 +28,7 @@ class BedrockAccountService {
|
||||
} = options;
|
||||
|
||||
const accountId = uuidv4();
|
||||
|
||||
|
||||
let accountData = {
|
||||
id: accountId,
|
||||
name,
|
||||
@@ -52,9 +52,9 @@ class BedrockAccountService {
|
||||
|
||||
const client = redis.getClientSafe();
|
||||
await client.set(`bedrock_account:${accountId}`, JSON.stringify(accountData));
|
||||
|
||||
|
||||
logger.info(`✅ 创建Bedrock账户成功 - ID: ${accountId}, 名称: ${name}, 区域: ${region}`);
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
@@ -84,14 +84,14 @@ class BedrockAccountService {
|
||||
}
|
||||
|
||||
const account = JSON.parse(accountData);
|
||||
|
||||
|
||||
// 解密AWS凭证用于内部使用
|
||||
if (account.awsCredentials) {
|
||||
account.awsCredentials = this._decryptAwsCredentials(account.awsCredentials);
|
||||
}
|
||||
|
||||
logger.debug(`🔍 获取Bedrock账户 - ID: ${accountId}, 名称: ${account.name}`);
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: account
|
||||
@@ -113,7 +113,7 @@ class BedrockAccountService {
|
||||
const accountData = await client.get(key);
|
||||
if (accountData) {
|
||||
const account = JSON.parse(accountData);
|
||||
|
||||
|
||||
// 返回给前端时,不包含敏感信息,只显示掩码
|
||||
accounts.push({
|
||||
id: account.id,
|
||||
@@ -141,7 +141,7 @@ class BedrockAccountService {
|
||||
});
|
||||
|
||||
logger.debug(`📋 获取所有Bedrock账户 - 共 ${accounts.length} 个`);
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: accounts
|
||||
@@ -161,7 +161,7 @@ class BedrockAccountService {
|
||||
}
|
||||
|
||||
const account = accountResult.data;
|
||||
|
||||
|
||||
// 更新字段
|
||||
if (updates.name !== undefined) account.name = updates.name;
|
||||
if (updates.description !== undefined) account.description = updates.description;
|
||||
@@ -186,9 +186,9 @@ class BedrockAccountService {
|
||||
|
||||
const client = redis.getClientSafe();
|
||||
await client.set(`bedrock_account:${accountId}`, JSON.stringify(account));
|
||||
|
||||
|
||||
logger.info(`✅ 更新Bedrock账户成功 - ID: ${accountId}, 名称: ${account.name}`);
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
@@ -222,9 +222,9 @@ class BedrockAccountService {
|
||||
|
||||
const client = redis.getClientSafe();
|
||||
await client.del(`bedrock_account:${accountId}`);
|
||||
|
||||
|
||||
logger.info(`✅ 删除Bedrock账户成功 - ID: ${accountId}`);
|
||||
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
logger.error(`❌ 删除Bedrock账户失败 - ID: ${accountId}`, error);
|
||||
@@ -240,7 +240,7 @@ class BedrockAccountService {
|
||||
return { success: false, error: 'Failed to get accounts' };
|
||||
}
|
||||
|
||||
const availableAccounts = accountsResult.data.filter(account =>
|
||||
const availableAccounts = accountsResult.data.filter(account =>
|
||||
account.isActive && account.schedulable
|
||||
);
|
||||
|
||||
@@ -250,7 +250,7 @@ class BedrockAccountService {
|
||||
|
||||
// 简单的轮询选择策略 - 选择优先级最高的账户
|
||||
const selectedAccount = availableAccounts[0];
|
||||
|
||||
|
||||
// 获取完整账户信息(包含解密的凭证)
|
||||
const fullAccountResult = await this.getAccount(selectedAccount.id);
|
||||
if (!fullAccountResult.success) {
|
||||
@@ -258,7 +258,7 @@ class BedrockAccountService {
|
||||
}
|
||||
|
||||
logger.debug(`🎯 选择Bedrock账户 - ID: ${selectedAccount.id}, 名称: ${selectedAccount.name}`);
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: fullAccountResult.data
|
||||
@@ -278,12 +278,12 @@ class BedrockAccountService {
|
||||
}
|
||||
|
||||
const account = accountResult.data;
|
||||
|
||||
|
||||
logger.info(`🧪 测试Bedrock账户连接 - ID: ${accountId}, 名称: ${account.name}`);
|
||||
|
||||
// 尝试获取模型列表来测试连接
|
||||
const models = await bedrockRelayService.getAvailableModels(account);
|
||||
|
||||
|
||||
if (models && models.length > 0) {
|
||||
logger.info(`✅ Bedrock账户测试成功 - ID: ${accountId}, 发现 ${models.length} 个模型`);
|
||||
return {
|
||||
@@ -313,14 +313,14 @@ 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');
|
||||
encrypted += cipher.final('hex');
|
||||
|
||||
|
||||
return {
|
||||
encrypted: encrypted,
|
||||
iv: iv.toString('hex')
|
||||
@@ -334,12 +334,28 @@ 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');
|
||||
|
||||
|
||||
return JSON.parse(decrypted);
|
||||
} catch (error) {
|
||||
logger.error('❌ AWS凭证解密失败', error);
|
||||
|
||||
@@ -7,16 +7,16 @@ class BedrockRelayService {
|
||||
constructor() {
|
||||
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.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配置
|
||||
this.maxOutputTokens = parseInt(process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) || 4096;
|
||||
this.maxThinkingTokens = parseInt(process.env.MAX_THINKING_TOKENS) || 1024;
|
||||
this.enablePromptCaching = process.env.DISABLE_PROMPT_CACHING !== '1';
|
||||
|
||||
|
||||
// 创建Bedrock客户端
|
||||
this.clients = new Map(); // 缓存不同区域的客户端
|
||||
}
|
||||
@@ -25,7 +25,7 @@ class BedrockRelayService {
|
||||
_getBedrockClient(region = null, bedrockAccount = null) {
|
||||
const targetRegion = region || this.defaultRegion;
|
||||
const clientKey = `${targetRegion}-${bedrockAccount?.id || 'default'}`;
|
||||
|
||||
|
||||
if (this.clients.has(clientKey)) {
|
||||
return this.clients.get(clientKey);
|
||||
}
|
||||
@@ -42,13 +42,17 @@ class BedrockRelayService {
|
||||
sessionToken: bedrockAccount.awsCredentials.sessionToken
|
||||
};
|
||||
} 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);
|
||||
this.clients.set(clientKey, client);
|
||||
|
||||
|
||||
logger.debug(`🔧 Created Bedrock client for region: ${targetRegion}, account: ${bedrockAccount?.name || 'default'}`);
|
||||
return client;
|
||||
}
|
||||
@@ -62,7 +66,7 @@ class BedrockRelayService {
|
||||
|
||||
// 转换请求格式为Bedrock格式
|
||||
const bedrockPayload = this._convertToBedrockFormat(requestBody);
|
||||
|
||||
|
||||
const command = new InvokeModelCommand({
|
||||
modelId: modelId,
|
||||
body: JSON.stringify(bedrockPayload),
|
||||
@@ -71,7 +75,7 @@ class BedrockRelayService {
|
||||
});
|
||||
|
||||
logger.debug(`🚀 Bedrock非流式请求 - 模型: ${modelId}, 区域: ${region}`);
|
||||
|
||||
|
||||
const startTime = Date.now();
|
||||
const response = await client.send(command);
|
||||
const duration = Date.now() - startTime;
|
||||
@@ -81,7 +85,7 @@ class BedrockRelayService {
|
||||
const claudeResponse = this._convertFromBedrockFormat(responseBody);
|
||||
|
||||
logger.info(`✅ Bedrock请求完成 - 模型: ${modelId}, 耗时: ${duration}ms`);
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: claudeResponse,
|
||||
@@ -105,7 +109,7 @@ class BedrockRelayService {
|
||||
|
||||
// 转换请求格式为Bedrock格式
|
||||
const bedrockPayload = this._convertToBedrockFormat(requestBody);
|
||||
|
||||
|
||||
const command = new InvokeModelWithResponseStreamCommand({
|
||||
modelId: modelId,
|
||||
body: JSON.stringify(bedrockPayload),
|
||||
@@ -114,7 +118,7 @@ class BedrockRelayService {
|
||||
});
|
||||
|
||||
logger.debug(`🌊 Bedrock流式请求 - 模型: ${modelId}, 区域: ${region}`);
|
||||
|
||||
|
||||
const startTime = Date.now();
|
||||
const response = await client.send(command);
|
||||
|
||||
@@ -135,17 +139,17 @@ class BedrockRelayService {
|
||||
if (chunk.chunk) {
|
||||
const chunkData = JSON.parse(new TextDecoder().decode(chunk.chunk.bytes));
|
||||
const claudeEvent = this._convertBedrockStreamToClaudeFormat(chunkData, isFirstChunk);
|
||||
|
||||
|
||||
if (claudeEvent) {
|
||||
// 发送SSE事件
|
||||
res.write(`event: ${claudeEvent.type}\n`);
|
||||
res.write(`data: ${JSON.stringify(claudeEvent.data)}\n\n`);
|
||||
|
||||
|
||||
// 提取使用统计
|
||||
if (claudeEvent.type === 'message_stop' && claudeEvent.data.usage) {
|
||||
totalUsage = claudeEvent.data.usage;
|
||||
}
|
||||
|
||||
|
||||
isFirstChunk = false;
|
||||
}
|
||||
}
|
||||
@@ -168,34 +172,97 @@ class BedrockRelayService {
|
||||
|
||||
} catch (error) {
|
||||
logger.error('❌ Bedrock流式请求失败:', error);
|
||||
|
||||
|
||||
// 发送错误事件
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
}
|
||||
|
||||
|
||||
res.write('event: error\n');
|
||||
res.write(`data: ${JSON.stringify({ error: this._handleBedrockError(error).message })}\n\n`);
|
||||
res.end();
|
||||
|
||||
|
||||
throw this._handleBedrockError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 选择使用的模型
|
||||
_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' } });
|
||||
}
|
||||
|
||||
// 使用默认模型
|
||||
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) {
|
||||
return bedrockAccount.region;
|
||||
}
|
||||
|
||||
|
||||
// 对于小模型,使用专门的区域配置
|
||||
if (modelId.includes('haiku')) {
|
||||
return this.smallFastModelRegion;
|
||||
}
|
||||
|
||||
|
||||
return this.defaultRegion;
|
||||
}
|
||||
|
||||
@@ -230,7 +297,7 @@ class BedrockRelayService {
|
||||
if (requestBody.temperature !== undefined) {
|
||||
bedrockPayload.temperature = requestBody.temperature;
|
||||
}
|
||||
|
||||
|
||||
if (requestBody.top_p !== undefined) {
|
||||
bedrockPayload.top_p = requestBody.top_p;
|
||||
}
|
||||
@@ -289,7 +356,7 @@ class BedrockRelayService {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (bedrockChunk.type === 'content_block_delta') {
|
||||
return {
|
||||
type: 'content_block_delta',
|
||||
@@ -299,7 +366,7 @@ class BedrockRelayService {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (bedrockChunk.type === 'message_delta') {
|
||||
return {
|
||||
type: 'message_delta',
|
||||
@@ -309,7 +376,7 @@ class BedrockRelayService {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (bedrockChunk.type === 'message_stop') {
|
||||
return {
|
||||
type: 'message_stop',
|
||||
@@ -325,23 +392,23 @@ class BedrockRelayService {
|
||||
// 处理Bedrock错误
|
||||
_handleBedrockError(error) {
|
||||
const errorMessage = error.message || 'Unknown Bedrock error';
|
||||
|
||||
|
||||
if (error.name === 'ValidationException') {
|
||||
return new Error(`Bedrock参数验证失败: ${errorMessage}`);
|
||||
}
|
||||
|
||||
|
||||
if (error.name === 'ThrottlingException') {
|
||||
return new Error('Bedrock请求限流,请稍后重试');
|
||||
}
|
||||
|
||||
|
||||
if (error.name === 'AccessDeniedException') {
|
||||
return new Error('Bedrock访问被拒绝,请检查IAM权限');
|
||||
}
|
||||
|
||||
|
||||
if (error.name === 'ModelNotReadyException') {
|
||||
return new Error('Bedrock模型未就绪,请稍后重试');
|
||||
}
|
||||
|
||||
|
||||
return new Error(`Bedrock服务错误: ${errorMessage}`);
|
||||
}
|
||||
|
||||
@@ -349,9 +416,15 @@ class BedrockRelayService {
|
||||
async getAvailableModels(bedrockAccount = null) {
|
||||
try {
|
||||
const region = bedrockAccount?.region || this.defaultRegion;
|
||||
|
||||
|
||||
// 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',
|
||||
|
||||
@@ -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') {
|
||||
if (form.value.accessKeyId) {
|
||||
data.accessKeyId = form.value.accessKeyId
|
||||
}
|
||||
if (form.value.secretAccessKey) {
|
||||
data.secretAccessKey = form.value.secretAccessKey
|
||||
// 只有当有凭证变更时才构造 awsCredentials 对象
|
||||
if (form.value.accessKeyId || form.value.secretAccessKey || form.value.sessionToken) {
|
||||
data.awsCredentials = {}
|
||||
if (form.value.accessKeyId) {
|
||||
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) {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user