Skip to content

Commit 129a380

Browse files
committed
Enhance AwsBedrockService with improved client initialization and rate limiting
- Added requestsPerMinute configuration in backend/src/config/configuration.ts to manage API request limits. - Refactored AwsBedrockService to include methods for initializing the Bedrock client, creating credentials, and configuring model ID and inference profile ARN. - Implemented a rate limiter to control the number of requests sent to AWS Bedrock, ensuring compliance with usage limits. - Improved error handling during Bedrock model invocation for better debugging and user feedback.
1 parent 10a0124 commit 129a380

File tree

2 files changed

+164
-57
lines changed

2 files changed

+164
-57
lines changed

backend/src/config/configuration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export default () => ({
2323
inferenceProfileArn:
2424
process.env.AWS_BEDROCK_INFERENCE_PROFILE_ARN ||
2525
'arn:aws:bedrock:us-east-1:841162674562:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0',
26+
requestsPerMinute: parseInt(process.env.AWS_BEDROCK_REQUESTS_PER_MINUTE || '20', 20),
2627
},
2728
textract: {
2829
maxBatchSize: parseInt(process.env.AWS_TEXTRACT_MAX_BATCH_SIZE || '10', 10),

backend/src/services/aws-bedrock.service.ts

Lines changed: 163 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ export class AwsBedrockService {
2121
private readonly inferenceProfileArn?: string;
2222

2323
constructor(private readonly configService: ConfigService) {
24+
this.client = this.initializeBedrockClient();
25+
this.modelId = this.configureModelId();
26+
this.inferenceProfileArn = this.configureInferenceProfileArn();
27+
this.defaultMaxTokens = this.configureMaxTokens();
28+
this.rateLimiter = this.initializeRateLimiter();
29+
}
30+
31+
/**
32+
* Initialize the AWS Bedrock client with credentials
33+
*/
34+
private initializeBedrockClient(): BedrockRuntimeClient {
2435
const region = this.configService.get<string>('aws.region');
2536
const accessKeyId = this.configService.get<string>('aws.aws.accessKeyId');
2637
const secretAccessKey = this.configService.get<string>('aws.aws.secretAccessKey');
@@ -30,43 +41,89 @@ export class AwsBedrockService {
3041
throw new Error('Missing required AWS configuration');
3142
}
3243

33-
// Initialize AWS Bedrock client with credentials including session token if available
34-
this.client = new BedrockRuntimeClient({
44+
const credentials = this.createCredentialsObject(accessKeyId, secretAccessKey, sessionToken);
45+
46+
const client = new BedrockRuntimeClient({
3547
region,
36-
credentials: {
37-
accessKeyId,
38-
secretAccessKey,
39-
...(sessionToken && { sessionToken }), // Include session token if it exists
40-
},
48+
credentials,
4149
});
4250

43-
// Log credential configuration for debugging (without exposing actual credentials)
4451
this.logger.log(
4552
`AWS client initialized with region ${region} and credentials ${accessKeyId ? '(provided)' : '(missing)'}, session token ${sessionToken ? '(provided)' : '(not provided)'}`,
4653
);
4754

48-
// Set model ID from configuration with fallback to Claude 3.7
49-
this.modelId =
55+
return client;
56+
}
57+
58+
/**
59+
* Create AWS credentials object with proper typing
60+
*/
61+
private createCredentialsObject(
62+
accessKeyId: string,
63+
secretAccessKey: string,
64+
sessionToken?: string,
65+
): {
66+
accessKeyId: string;
67+
secretAccessKey: string;
68+
sessionToken?: string;
69+
} {
70+
const credentials = {
71+
accessKeyId,
72+
secretAccessKey,
73+
};
74+
75+
if (sessionToken) {
76+
return { ...credentials, sessionToken };
77+
}
78+
79+
return credentials;
80+
}
81+
82+
/**
83+
* Configure the model ID from configuration with fallback
84+
*/
85+
private configureModelId(): string {
86+
const modelId =
5087
this.configService.get<string>('aws.bedrock.model') ??
5188
'us.anthropic.claude-3-7-sonnet-20250219-v1:0';
5289

53-
// Set inference profile ARN from configuration
54-
this.inferenceProfileArn =
90+
this.logger.log(
91+
`Using AWS Bedrock model: ${modelId}${this.inferenceProfileArn ? ' with inference profile' : ''}`,
92+
);
93+
94+
return modelId;
95+
}
96+
97+
/**
98+
* Configure the inference profile ARN from configuration
99+
*/
100+
private configureInferenceProfileArn(): string | undefined {
101+
const inferenceProfileArn =
55102
this.configService.get<string>('aws.bedrock.inferenceProfileArn') ??
56103
'arn:aws:bedrock:us-east-1:841162674562:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0';
57104

58105
this.logger.log(
59-
`Using AWS Bedrock model: ${this.modelId}${this.inferenceProfileArn ? ' with inference profile' : ''}`,
106+
`Using AWS Bedrock model: ${this.modelId}${inferenceProfileArn ? ' with inference profile' : ''}`,
60107
);
61108

62-
// Set default values based on environment
63-
this.defaultMaxTokens =
64-
process.env.NODE_ENV === 'test'
65-
? 1000
66-
: (this.configService.get<number>('aws.bedrock.maxTokens') ?? 2048);
109+
return inferenceProfileArn;
110+
}
67111

68-
// Initialize rate limiter (10 requests per minute per IP)
69-
this.rateLimiter = new RateLimiter(60000, 10);
112+
/**
113+
* Configure max tokens based on environment
114+
*/
115+
private configureMaxTokens(): number {
116+
return process.env.NODE_ENV === 'test'
117+
? 1000
118+
: (this.configService.get<number>('aws.bedrock.maxTokens') ?? 2048);
119+
}
120+
121+
/**
122+
* Initialize rate limiter for API requests
123+
*/
124+
private initializeRateLimiter(): RateLimiter {
125+
const requestsPerMinute = this.configService.get<number>('aws.bedrock.requestsPerMinute') ?? 20;
126+
return new RateLimiter(60000, requestsPerMinute);
70127
}
71128

72129
/**
@@ -80,52 +137,79 @@ export class AwsBedrockService {
80137
// Format request body based on the selected model
81138
const body = this.formatRequestBody(prompt);
82139

140+
// Create command parameters
141+
const commandParams = this.createCommandParams(modelId, body);
142+
83143
// Create the command
84-
const command = new InvokeModelCommand({
85-
modelId,
86-
body,
87-
...(this.inferenceProfileArn && {
88-
inferenceProfileArn: this.inferenceProfileArn,
89-
}),
90-
});
144+
const command = new InvokeModelCommand(commandParams);
91145

92146
// Send request to AWS Bedrock
93147
const response = await this.client.send(command);
94148

95149
return response;
96150
} catch (error: unknown) {
97-
// Handle specific errors
98-
if (error instanceof Error) {
99-
this.logger.error(`Bedrock model invocation failed: ${error.message}`, {
100-
modelId,
101-
errorName: error.name,
102-
stack: error.stack,
103-
});
104-
105-
// Provide more helpful error messages based on error type
106-
if (error.name === 'AccessDeniedException') {
107-
throw new BadRequestException(
108-
'Access denied to AWS Bedrock. Check your credentials and permissions.',
109-
);
110-
} else if (error.name === 'ThrottlingException') {
111-
throw new BadRequestException(
112-
'Request throttled by AWS Bedrock. Please try again in a few moments.',
113-
);
114-
} else if (error.name === 'ValidationException') {
115-
throw new BadRequestException(
116-
`AWS Bedrock validation error: ${error.message}. Check your request parameters.`,
117-
);
118-
} else if (error.name === 'ServiceQuotaExceededException') {
119-
throw new BadRequestException(
120-
'AWS Bedrock service quota exceeded. Try again later or request a quota increase.',
121-
);
122-
}
123-
}
151+
this.handleBedrockError(error, modelId);
152+
}
153+
}
154+
155+
/**
156+
* Create command parameters for Bedrock invocation
157+
*/
158+
private createCommandParams(
159+
modelId: string,
160+
body: any,
161+
): {
162+
modelId: string;
163+
body: any;
164+
inferenceProfileArn?: string;
165+
} {
166+
const commandParams = {
167+
modelId,
168+
body,
169+
};
170+
171+
// Add inference profile if available
172+
if (this.inferenceProfileArn) {
173+
return { ...commandParams, inferenceProfileArn: this.inferenceProfileArn };
174+
}
175+
176+
return commandParams;
177+
}
178+
179+
/**
180+
* Handle errors from Bedrock invocation
181+
*/
182+
private handleBedrockError(error: unknown, modelId: string): never {
183+
if (error instanceof Error) {
184+
this.logger.error(`Bedrock model invocation failed: ${error.message}`, {
185+
modelId,
186+
errorName: error.name,
187+
stack: error.stack,
188+
});
124189

125-
throw new BadRequestException(
126-
`Failed to invoke AWS Bedrock: ${error instanceof Error ? error.message : 'Unknown error'}`,
127-
);
190+
// Provide more helpful error messages based on error type
191+
if (error.name === 'AccessDeniedException') {
192+
throw new BadRequestException(
193+
'Access denied to AWS Bedrock. Check your credentials and permissions.',
194+
);
195+
} else if (error.name === 'ThrottlingException') {
196+
throw new BadRequestException(
197+
'Request throttled by AWS Bedrock. Please try again in a few moments.',
198+
);
199+
} else if (error.name === 'ValidationException') {
200+
throw new BadRequestException(
201+
`AWS Bedrock validation error: ${error.message}. Check your request parameters.`,
202+
);
203+
} else if (error.name === 'ServiceQuotaExceededException') {
204+
throw new BadRequestException(
205+
'AWS Bedrock service quota exceeded. Try again later or request a quota increase.',
206+
);
207+
}
128208
}
209+
210+
throw new BadRequestException(
211+
`Failed to invoke AWS Bedrock: ${error instanceof Error ? error.message : 'Unknown error'}`,
212+
);
129213
}
130214

131215
/**
@@ -156,4 +240,26 @@ export class AwsBedrockService {
156240
private hashIdentifier(identifier: string): string {
157241
return createHash('sha256').update(identifier).digest('hex');
158242
}
243+
244+
/**
245+
* Generates a response using AWS Bedrock
246+
*/
247+
async generateResponse(prompt: string): Promise<string> {
248+
// Check rate limiting
249+
if (!this.rateLimiter.tryRequest('global')) {
250+
throw new BadRequestException('Rate limit exceeded. Please try again later.');
251+
}
252+
253+
const response = await this.invokeBedrock(prompt);
254+
255+
// Parse the response
256+
const responseBody = JSON.parse(Buffer.from(response.body).toString('utf-8'));
257+
258+
// Extract the generated content
259+
if (responseBody.content && responseBody.content.length > 0) {
260+
return responseBody.content[0].text;
261+
}
262+
263+
throw new BadRequestException('Failed to generate a response from AWS Bedrock');
264+
}
159265
}

0 commit comments

Comments
 (0)