Skip to content

Commit bda3d41

Browse files
authored
Merge pull request #50 from ModusCreateOrg/ADE-166
[ADE-166] Answer only medical questions and use claude 3.7 sonnet
2 parents f63333e + d08287e commit bda3d41

File tree

2 files changed

+95
-55
lines changed

2 files changed

+95
-55
lines changed

.husky/pre-push

+7-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ if [ -n "$FRONTEND_CHANGES" ]; then
1515

1616
# Navigate to frontend directory
1717
cd frontend || exit 1
18+
19+
# Remove dependency on husky.sh
20+
# . "$(dirname -- "$0")/_/husky.sh"
1821

19-
. "$(dirname -- "$0")/_/husky.sh"
20-
21-
cd frontend && npm run lint && npm run test:ci
22+
npm run lint && npm run test:ci
2223

2324
# Capture the exit code
2425
TEST_EXIT_CODE=$?
@@ -43,7 +44,9 @@ BACKEND_CHANGES=$(git diff --name-only $RANGE | grep "^backend/" || true)
4344
if [ -n "$BACKEND_CHANGES" ]; then
4445
echo "Backend changes detected. Running backend lint..."
4546

46-
. "$(dirname -- "$0")/_/husky.sh"
47+
# Remove dependency on husky.sh
48+
# . "$(dirname -- "$0")/_/husky.sh"
49+
4750
cd backend && npm run lint
4851
else
4952
echo "No backend changes detected. Skipping backend tests."

frontend/src/common/services/ai/bedrock.service.ts

+88-51
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ const getContentFilteredMessage = (): string => {
88
return i18n.t('ai.content_filtered', { ns: 'errors' });
99
};
1010

11+
// This function provides a healthcare-focused system prompt
12+
const getHealthcareSystemPrompt = (): string => {
13+
return `You are a healthcare assistant that only responds to medical and healthcare-related questions.
14+
If a user asks a question that is not directly related to healthcare, medicine, medical reports,
15+
health conditions, treatments, or medical terminology, respond with:
16+
"${i18n.t('ai.non_healthcare_topic', { ns: 'errors', defaultValue: "I couldn't find an answer. Please try rephrasing your question or consult your healthcare provider." })}"
17+
18+
Only provide information about healthcare topics, and always mention that users should consult healthcare professionals for personalized medical advice.`;
19+
};
20+
1121
export interface ChatMessage {
1222
role: 'user' | 'assistant' | 'system';
1323
content: string;
@@ -20,21 +30,44 @@ export interface ChatSession {
2030
updatedAt: Date;
2131
}
2232

23-
// Interfaces for Bedrock API responses
24-
interface BedrockResult {
25-
tokenCount: number;
26-
outputText: string;
27-
completionReason: 'CONTENT_FILTERED' | 'COMPLETE' | 'LENGTH' | 'STOP_SEQUENCE' | string;
33+
// Interfaces for Claude 3.7 Sonnet response
34+
interface ClaudeContentBlock {
35+
type: string;
36+
text?: string;
37+
reasoningContent?: {
38+
reasoningText: string;
39+
};
40+
}
41+
42+
interface ClaudeResponse {
43+
id: string;
44+
type: string;
45+
role: string;
46+
content: ClaudeContentBlock[];
47+
model: string;
48+
stop_reason: string;
49+
stop_sequence: string | null;
50+
usage: {
51+
input_tokens: number;
52+
output_tokens: number;
53+
};
2854
}
2955

30-
interface BedrockResponse {
31-
inputTextTokenCount: number;
32-
results: BedrockResult[];
56+
// Claude request body interface
57+
interface ClaudeRequestBody {
58+
anthropic_version: string;
59+
max_tokens: number;
60+
messages: {
61+
role: 'user' | 'assistant' | 'system';
62+
content: { type: string; text: string; }[];
63+
}[];
64+
temperature: number;
65+
top_p: number;
66+
system?: string;
3367
}
3468

3569
class BedrockService {
3670
private client: BedrockRuntimeClient | null = null;
37-
private readonly MODEL_ID = 'amazon.titan-text-lite-v1';
3871
private sessions: Map<string, ChatSession> = new Map();
3972
private isTestEnvironment: boolean;
4073
private contentFilteredCount: number = 0; // Track number of filtered responses
@@ -86,57 +119,74 @@ class BedrockService {
86119
}
87120
}
88121

89-
private handleBedrockResponse(parsedResponse: BedrockResponse): string {
90-
// Check if we have results
91-
if (!parsedResponse.results || !parsedResponse.results.length) {
92-
throw new Error('Invalid response structure: missing results');
122+
private handleClaudeResponse(response: ClaudeResponse): string {
123+
// Check if response has content
124+
if (!response.content || !response.content.length) {
125+
throw new Error('Invalid response structure: missing content');
93126
}
94127

95-
const result = parsedResponse.results[0];
96-
97128
// Check for content filtering
98-
if (result.completionReason === "CONTENT_FILTERED") {
129+
if (response.stop_reason === "content_filtered") {
99130
// Increment counter for analytics
100131
this.contentFilteredCount++;
101132

102133
// Return the translated message
103134
return getContentFilteredMessage();
104135
}
105136

137+
// Extract text from content blocks
138+
const textContent = response.content
139+
.filter(block => block.type === 'text' && block.text)
140+
.map(block => block.text)
141+
.join('\n');
106142

107-
return result.outputText;
143+
return textContent || '';
108144
}
109145

110-
private async invokeModel(prompt: string): Promise<string> {
146+
private async invokeModel(messages: ChatMessage[], systemPrompt?: string): Promise<string> {
111147
// In test environment, return a mock response
112148
if (this.isTestEnvironment || !this.client) {
113-
return `This is a test response to: "${prompt}"`;
149+
return `This is a test response to: "${messages[messages.length - 1]?.content || 'No message'}"`;
114150
}
115151

152+
// Format messages for Claude API
153+
const formattedMessages = messages.map(msg => ({
154+
role: msg.role,
155+
content: [{ type: 'text', text: msg.content }]
156+
}));
157+
158+
// Prepare request body for Claude 3.7 Sonnet
159+
const requestBody: ClaudeRequestBody = {
160+
anthropic_version: "bedrock-2023-05-31",
161+
max_tokens: 4096,
162+
messages: formattedMessages,
163+
temperature: 0.7,
164+
top_p: 0.9
165+
};
166+
167+
// Add system prompt if provided
168+
if (systemPrompt) {
169+
requestBody.system = systemPrompt;
170+
}
171+
172+
// Use the cross-region inference profile ID as the model ID (following AWS docs)
173+
// Do not specify inferenceProfileArn separately
116174
const input = {
117-
modelId: this.MODEL_ID,
175+
modelId: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
118176
contentType: 'application/json',
119177
accept: 'application/json',
120-
body: JSON.stringify({
121-
inputText: prompt,
122-
textGenerationConfig: {
123-
maxTokenCount: 4096,
124-
stopSequences: [],
125-
temperature: 0.7,
126-
topP: 1,
127-
},
128-
}),
178+
body: JSON.stringify(requestBody),
129179
};
130180

131181
try {
132182
const command = new InvokeModelCommand(input);
133183
const response = await this.client.send(command);
134184
const responseBody = new TextDecoder().decode(response.body);
135-
const parsedResponse = JSON.parse(responseBody) as BedrockResponse;
185+
const parsedResponse = JSON.parse(responseBody) as ClaudeResponse;
136186

137-
return this.handleBedrockResponse(parsedResponse);
187+
return this.handleClaudeResponse(parsedResponse);
138188
} catch (error) {
139-
console.error('Error invoking Bedrock model:', error);
189+
console.error('Error invoking Claude model:', error);
140190
throw error;
141191
}
142192
}
@@ -152,7 +202,7 @@ class BedrockService {
152202
return sessionId;
153203
}
154204

155-
public async sendMessage(sessionId: string, message: string): Promise<string> {
205+
public async sendMessage(sessionId: string, message: string, systemPrompt?: string): Promise<string> {
156206
const session = this.sessions.get(sessionId);
157207
if (!session) {
158208
throw new Error('Chat session not found');
@@ -164,11 +214,11 @@ class BedrockService {
164214
content: message,
165215
});
166216

167-
// Prepare context for the model
168-
const context = this.prepareContext(session.messages);
169-
170-
// Get response from Bedrock
171-
const response = await this.invokeModel(context);
217+
// Use healthcare system prompt by default, or allow custom override
218+
const effectiveSystemPrompt = systemPrompt || getHealthcareSystemPrompt();
219+
220+
// Get response from Claude
221+
const response = await this.invokeModel(session.messages, effectiveSystemPrompt);
172222

173223
// Add assistant response to context
174224
session.messages.push({
@@ -183,19 +233,6 @@ class BedrockService {
183233
return response;
184234
}
185235

186-
private prepareContext(messages: ChatMessage[]): string {
187-
// Format the conversation history into a prompt
188-
const formattedMessages = messages.map(msg => {
189-
const role = msg.role === 'assistant' ? 'Assistant' : 'Human';
190-
return `${role}: ${msg.content}`;
191-
});
192-
193-
// Add a final prompt for the assistant
194-
formattedMessages.push('Assistant:');
195-
196-
return formattedMessages.join('\n');
197-
}
198-
199236
public getChatSession(sessionId: string): ChatSession | undefined {
200237
return this.sessions.get(sessionId);
201238
}

0 commit comments

Comments
 (0)