Skip to content

Commit cf4970a

Browse files
committed
Update AWS SDK dependencies and enhance AwsBedrockService for improved model handling and image processing capabilities
Add a controller for testing AwsBedrockService
1 parent 5d42aee commit cf4970a

15 files changed

+3274
-62
lines changed

backend/package-lock.json

Lines changed: 470 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"cdk:synth": "dotenv -- npx cdk synth"
2828
},
2929
"dependencies": {
30+
"@aws-sdk/client-bedrock": "^3.782.0",
3031
"@aws-sdk/client-bedrock-runtime": "^3.782.0",
3132
"@aws-sdk/client-dynamodb": "^3.758.0",
3233
"@aws-sdk/client-secrets-manager": "^3.758.0",

backend/src/app.module.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
1+
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
22
import { ConfigModule } from '@nestjs/config';
33
import configuration from './config/configuration';
44
import { AppController } from './app.controller';
@@ -11,6 +11,8 @@ import { UserController } from './user/user.controller';
1111
import { ReportsModule } from './reports/reports.module';
1212
import { HealthController } from './health/health.controller';
1313
import { AuthMiddleware } from './auth/auth.middleware';
14+
import { BedrockTestModule } from './controllers/bedrock/bedrock.module';
15+
import { BedrockTestController } from './controllers/bedrock/bedrock-test.controller';
1416

1517
@Module({
1618
imports: [
@@ -19,12 +21,27 @@ import { AuthMiddleware } from './auth/auth.middleware';
1921
load: [configuration],
2022
}),
2123
ReportsModule,
24+
BedrockTestModule,
25+
],
26+
controllers: [
27+
AppController,
28+
BedrockTestController,
29+
HealthController,
30+
PerplexityController,
31+
UserController,
2232
],
23-
controllers: [AppController, HealthController, PerplexityController, UserController],
2433
providers: [AppService, AwsSecretsService, AwsBedrockService, PerplexityService],
2534
})
2635
export class AppModule implements NestModule {
2736
configure(consumer: MiddlewareConsumer) {
28-
consumer.apply(AuthMiddleware).forRoutes('*'); // Apply to all routes
37+
consumer
38+
.apply(AuthMiddleware)
39+
.exclude(
40+
{ path: 'test-bedrock', method: RequestMethod.GET },
41+
{ path: 'test-bedrock/health', method: RequestMethod.GET },
42+
{ path: 'test-bedrock/extract-medical-info', method: RequestMethod.POST },
43+
{ path: 'health', method: RequestMethod.GET },
44+
)
45+
.forRoutes('*');
2946
}
3047
}

backend/src/config/configuration.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import configuration from './configuration';
44
describe('Configuration', () => {
55
// Save original environment
66
const originalEnv = { ...process.env };
7-
7+
88
beforeEach(() => {
99
// Clear environment variables before each test
1010
process.env = {};
11-
11+
1212
// Set NODE_ENV to test for the first test
1313
process.env.NODE_ENV = 'test';
1414
});
@@ -20,7 +20,7 @@ describe('Configuration', () => {
2020

2121
it('should return default values when no env variables are set', () => {
2222
const config = configuration();
23-
23+
2424
expect(config.port).toBe(3000);
2525
expect(config.environment).toBe('test');
2626
expect(config.aws.region).toBe('us-east-1'); // Default value in configuration.ts
@@ -50,4 +50,4 @@ describe('Configuration', () => {
5050
delete process.env.AWS_COGNITO_USER_POOL_ID;
5151
delete process.env.AWS_COGNITO_CLIENT_ID;
5252
});
53-
});
53+
});

backend/src/config/configuration.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,22 @@ export default () => ({
88
clientId: process.env.AWS_COGNITO_CLIENT_ID,
99
},
1010
secretsManager: {
11-
perplexityApiKeySecret: process.env.PERPLEXITY_API_KEY_SECRET_NAME || 'medical-reports-explainer/perplexity-api-key',
11+
perplexityApiKeySecret:
12+
process.env.PERPLEXITY_API_KEY_SECRET_NAME ||
13+
'medical-reports-explainer/perplexity-api-key',
1214
},
1315
aws: {
1416
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
1517
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
18+
sessionToken: process.env.AWS_SESSION_TOKEN,
1619
},
1720
bedrock: {
18-
model: process.env.AWS_BEDROCK_MODEL || 'anthropic.claude-3-7-sonnet-20250219-v1:0',
21+
model: process.env.AWS_BEDROCK_MODEL || 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
1922
maxTokens: parseInt(process.env.AWS_BEDROCK_MAX_TOKENS || '2048', 10),
20-
}
23+
inferenceProfileArn:
24+
process.env.AWS_BEDROCK_INFERENCE_PROFILE_ARN ||
25+
'arn:aws:bedrock:us-east-1:841162674562:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0',
26+
},
2127
},
2228
perplexity: {
2329
apiBaseUrl: 'https://api.perplexity.ai',
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# AWS Bedrock Test Controller
2+
3+
This controller provides a testing interface for the AWS Bedrock medical image analysis service, bypassing authentication for easy testing and debugging.
4+
5+
## Features
6+
7+
- Extracts medical information from images using AWS Bedrock AI service
8+
- Handles image uploads via a simple HTML interface
9+
- Processes JPEG, PNG, and HEIC/HEIF images
10+
- Automatic image compression for files over 2MB
11+
- No authentication required (for testing purposes only)
12+
13+
## How to Use
14+
15+
### Web Interface
16+
17+
1. Start your NestJS server
18+
2. Visit `http://localhost:YOUR_PORT/api/test-bedrock` in your browser
19+
3. Upload a medical image using the provided form
20+
4. Click "Analyze Medical Image" to process the image
21+
5. View the JSON response with extracted medical information
22+
23+
### File Size Limits
24+
25+
- **Maximum file size**: 2MB
26+
- Images larger than 2MB will be automatically compressed:
27+
- First by reducing image quality
28+
- Then by reducing dimensions if needed
29+
- Converted to WebP format for better compression
30+
31+
### API Endpoint
32+
33+
You can also directly call the API endpoint programmatically:
34+
35+
```bash
36+
curl -X POST http://localhost:YOUR_PORT/api/test-bedrock/extract-medical-info \
37+
-H "Content-Type: application/json" \
38+
-d '{
39+
"base64Image": "YOUR_BASE64_ENCODED_IMAGE_HERE",
40+
"contentType": "image/jpeg",
41+
"filename": "optional_filename.jpg"
42+
}'
43+
```
44+
45+
## Response Format
46+
47+
The API returns a structured JSON response with the following sections:
48+
49+
- `keyMedicalTerms`: Array of medical terms and their definitions
50+
- `labValues`: Array of lab values with units and normal ranges
51+
- `diagnoses`: Array of diagnoses with details and recommendations
52+
- `metadata`: Information about the confidence and any missing information
53+
54+
## Security Considerations
55+
56+
This controller is intended for **testing purposes only** and bypasses authentication mechanisms. Do not use in production environments without proper security measures.
57+
58+
## Troubleshooting
59+
60+
### Common Errors
61+
62+
#### "Request entity too large"
63+
64+
This error occurs when the request body exceeds the server's size limit. To resolve:
65+
66+
1. Use the web interface which automatically compresses large images
67+
2. Manually compress your image to under 2MB before uploading
68+
3. Use lower resolution images that contain clear text
69+
4. Convert to WebP format for better compression ratio
70+
71+
#### "File content appears to be encrypted or compressed"
72+
73+
This error occurs when AWS Bedrock cannot properly process the image format. To resolve:
74+
75+
1. Try a different image format (PNG often works better than JPEG)
76+
2. Ensure the image is not encrypted or password protected
77+
3. Take a new photo with better lighting and clarity
78+
4. Avoid screenshots of PDFs - use the original document
79+
5. Make sure the image is not heavily compressed
80+
81+
#### General Tips
82+
83+
- Ensure your AWS Bedrock credentials are properly configured
84+
- Use clear, high-quality images of medical documents
85+
- Check that uploaded images are in supported formats (JPEG, PNG, HEIC/HEIF)
86+
- Verify the image contains medical information (lab reports, prescriptions, etc.)
87+
- Make sure text is readable in the image
88+
- Avoid cropped or partial images
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Controller, Get, HttpCode, HttpStatus, BadRequestException } from '@nestjs/common';
2+
import { Logger } from '@nestjs/common';
3+
import { AwsBedrockService } from '../../services/aws-bedrock.service';
4+
5+
@Controller('api/test-bedrock')
6+
export class BedrockTestController {
7+
private readonly logger = new Logger(BedrockTestController.name);
8+
9+
constructor(private readonly awsBedrockService: AwsBedrockService) {}
10+
11+
@Get('list-models')
12+
@HttpCode(HttpStatus.OK)
13+
async listModels(): Promise<any> {
14+
try {
15+
this.logger.log('Requesting available Bedrock models');
16+
17+
// Get the list of models from the AWS Bedrock service
18+
const models = await this.awsBedrockService.listAvailableModels();
19+
20+
// Get current model information directly from the service instance
21+
const currentModel = {
22+
modelId: this.awsBedrockService['modelId'], // Access the modelId property
23+
inferenceProfileArn: this.awsBedrockService['inferenceProfileArn'], // Access the inferenceProfileArn property if it exists
24+
};
25+
26+
return {
27+
status: 'success',
28+
currentModel,
29+
models,
30+
};
31+
} catch (error: unknown) {
32+
this.logger.error('Error listing Bedrock models', {
33+
error: error instanceof Error ? error.message : 'Unknown error',
34+
});
35+
throw new BadRequestException(
36+
`Failed to list Bedrock models: ${error instanceof Error ? error.message : 'Unknown error'}`,
37+
);
38+
}
39+
}
40+
41+
@Get('health')
42+
@HttpCode(HttpStatus.OK)
43+
async checkHealth(): Promise<any> {
44+
return {
45+
status: 'ok',
46+
timestamp: new Date().toISOString(),
47+
service: 'aws-bedrock',
48+
};
49+
}
50+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { HttpException } from '@nestjs/common';
3+
import { BedrockTestController } from './bedrock.controller';
4+
import { AwsBedrockService } from '../../services/aws-bedrock.service';
5+
import { UploadMedicalImageDto } from './bedrock.dto';
6+
import { describe, it, expect, beforeEach, vi } from 'vitest';
7+
8+
describe('BedrockTestController', () => {
9+
let controller: BedrockTestController;
10+
let bedrockService: AwsBedrockService;
11+
12+
// Mock data
13+
const mockBase64Image = 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; // 1x1 transparent GIF
14+
const mockContentType = 'image/jpeg';
15+
const mockMedicalInfo = {
16+
keyMedicalTerms: [
17+
{ term: 'Hemoglobin', definition: 'Protein in red blood cells that carries oxygen' },
18+
],
19+
labValues: [
20+
{
21+
name: 'Hemoglobin',
22+
value: '14.5',
23+
unit: 'g/dL',
24+
normalRange: '12.0-15.5',
25+
isAbnormal: false,
26+
},
27+
],
28+
diagnoses: [
29+
{
30+
condition: 'Normal Blood Count',
31+
details: 'All values within normal range',
32+
recommendations: 'Continue routine monitoring',
33+
},
34+
],
35+
metadata: {
36+
isMedicalReport: true,
37+
confidence: 0.95,
38+
missingInformation: [],
39+
},
40+
};
41+
42+
beforeEach(async () => {
43+
// Create mock service with spy for extractMedicalInfo
44+
const mockBedrockService = {
45+
extractMedicalInfo: vi.fn().mockResolvedValue(mockMedicalInfo),
46+
};
47+
48+
const module: TestingModule = await Test.createTestingModule({
49+
controllers: [BedrockTestController],
50+
providers: [
51+
{
52+
provide: AwsBedrockService,
53+
useValue: mockBedrockService,
54+
},
55+
],
56+
}).compile();
57+
58+
controller = module.get<BedrockTestController>(BedrockTestController);
59+
bedrockService = module.get<AwsBedrockService>(AwsBedrockService);
60+
});
61+
62+
it('should be defined', () => {
63+
expect(controller).toBeDefined();
64+
});
65+
66+
describe('extractMedicalInfo', () => {
67+
it('should extract medical information from a valid image', async () => {
68+
// Prepare DTO
69+
const dto: UploadMedicalImageDto = {
70+
base64Image: mockBase64Image,
71+
contentType: mockContentType,
72+
filename: 'test.jpg',
73+
};
74+
75+
// Mock request object
76+
const mockRequest = {
77+
ip: '127.0.0.1',
78+
connection: { remoteAddress: '127.0.0.1' },
79+
};
80+
81+
// Call the controller method
82+
const result = await controller.extractMedicalInfo(dto, mockRequest as any);
83+
84+
// Verify service was called with correct parameters
85+
expect(bedrockService.extractMedicalInfo).toHaveBeenCalledWith(
86+
expect.any(Buffer),
87+
mockContentType,
88+
'127.0.0.1',
89+
);
90+
91+
// Verify result
92+
expect(result).toEqual(mockMedicalInfo);
93+
expect(result.keyMedicalTerms[0].term).toBe('Hemoglobin');
94+
expect(result.metadata.isMedicalReport).toBe(true);
95+
});
96+
97+
it('should handle errors from the service', async () => {
98+
// Prepare DTO
99+
const dto: UploadMedicalImageDto = {
100+
base64Image: mockBase64Image,
101+
contentType: mockContentType,
102+
};
103+
104+
// Mock request object
105+
const mockRequest = {
106+
ip: '127.0.0.1',
107+
connection: { remoteAddress: '127.0.0.1' },
108+
};
109+
110+
// Mock service error
111+
vi.spyOn(bedrockService, 'extractMedicalInfo').mockRejectedValueOnce(
112+
new HttpException('Invalid image format', 400),
113+
);
114+
115+
// Test error handling
116+
await expect(controller.extractMedicalInfo(dto, mockRequest as any)).rejects.toThrow(
117+
HttpException,
118+
);
119+
});
120+
});
121+
});

0 commit comments

Comments
 (0)