@@ -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