@peopl-health/nexus 2.4.11-logs → 2.4.11-logs-msg
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -61,27 +61,37 @@ const processImageFileCore = async (fileName, reply) => {
|
|
|
61
61
|
let imageAnalysis = null;
|
|
62
62
|
let url = null;
|
|
63
63
|
const messagesChat = [];
|
|
64
|
+
const timings = {
|
|
65
|
+
analysis_ms: 0,
|
|
66
|
+
url_generation_ms: 0
|
|
67
|
+
};
|
|
64
68
|
|
|
65
69
|
const isSticker = reply.media?.mediaType === 'sticker' ||
|
|
66
70
|
fileName.toLowerCase().includes('sticker/') ||
|
|
67
71
|
fileName.toLowerCase().includes('/sticker/');
|
|
68
72
|
|
|
69
73
|
try {
|
|
70
|
-
|
|
74
|
+
const { result: analysis, duration: analysisDuration } = await withTracing(
|
|
71
75
|
analyzeImage,
|
|
72
76
|
'analyze_image',
|
|
73
|
-
() => ({ 'image.is_sticker': isSticker, 'image.message_id': reply.message_id })
|
|
77
|
+
() => ({ 'image.is_sticker': isSticker, 'image.message_id': reply.message_id }),
|
|
78
|
+
{ returnTiming: true }
|
|
74
79
|
)(fileName, isSticker, reply.media?.contentType);
|
|
80
|
+
imageAnalysis = analysis;
|
|
81
|
+
timings.analysis_ms = analysisDuration;
|
|
75
82
|
|
|
76
83
|
const invalidAnalysis = ['NOT_MEDICAL', 'QUALITY_INSUFFICIENT'];
|
|
77
84
|
|
|
78
85
|
// Generate presigned URL only if medically relevant AND not a sticker
|
|
79
86
|
if (imageAnalysis?.medical_relevance && !isSticker) {
|
|
80
|
-
|
|
87
|
+
const { result: presignedUrl, duration: urlDuration } = await withTracing(
|
|
81
88
|
generatePresignedUrl,
|
|
82
89
|
'generate_presigned_url',
|
|
83
|
-
() => ({ 'url.bucket': reply.media.bucketName })
|
|
90
|
+
() => ({ 'url.bucket': reply.media.bucketName }),
|
|
91
|
+
{ returnTiming: true }
|
|
84
92
|
)(reply.media.bucketName, reply.media.key);
|
|
93
|
+
url = presignedUrl;
|
|
94
|
+
timings.url_generation_ms = urlDuration;
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
// Add appropriate text based on analysis
|
|
@@ -107,7 +117,8 @@ const processImageFileCore = async (fileName, reply) => {
|
|
|
107
117
|
is_sticker: isSticker,
|
|
108
118
|
medical_relevance: imageAnalysis?.medical_relevance,
|
|
109
119
|
has_table: imageAnalysis?.has_table,
|
|
110
|
-
analysis_type: imageAnalysis?.medical_analysis ? 'medical' : 'general'
|
|
120
|
+
analysis_type: imageAnalysis?.medical_analysis ? 'medical' : 'general',
|
|
121
|
+
...timings
|
|
111
122
|
});
|
|
112
123
|
|
|
113
124
|
logger.debug('processImageFile_analysis', { imageAnalysis });
|
|
@@ -124,7 +135,7 @@ const processImageFileCore = async (fileName, reply) => {
|
|
|
124
135
|
});
|
|
125
136
|
}
|
|
126
137
|
|
|
127
|
-
return { messagesChat, url };
|
|
138
|
+
return { messagesChat, url, timings };
|
|
128
139
|
};
|
|
129
140
|
|
|
130
141
|
const processImageFile = withTracing(
|
|
@@ -138,17 +149,22 @@ const processImageFile = withTracing(
|
|
|
138
149
|
|
|
139
150
|
const processAudioFileCore = async (fileName, provider) => {
|
|
140
151
|
const messagesChat = [];
|
|
152
|
+
const timings = {
|
|
153
|
+
transcribe_ms: 0
|
|
154
|
+
};
|
|
141
155
|
|
|
142
156
|
try {
|
|
143
|
-
const audioTranscript = await withTracing(
|
|
157
|
+
const { result: audioTranscript, duration: transcribeDuration } = await withTracing(
|
|
144
158
|
async () => provider.transcribeAudio({
|
|
145
159
|
file: fs.createReadStream(fileName),
|
|
146
160
|
responseFormat: 'text',
|
|
147
161
|
language: 'es'
|
|
148
162
|
}),
|
|
149
163
|
'transcribe_audio',
|
|
150
|
-
() => ({ 'audio.file_name': fileName ? fileName.split('/').pop().replace(/^[^-]+-[^-]+-/, 'xxx-xxx-') : 'unknown' })
|
|
164
|
+
() => ({ 'audio.file_name': fileName ? fileName.split('/').pop().replace(/^[^-]+-[^-]+-/, 'xxx-xxx-') : 'unknown' }),
|
|
165
|
+
{ returnTiming: true }
|
|
151
166
|
)();
|
|
167
|
+
timings.transcribe_ms = transcribeDuration;
|
|
152
168
|
|
|
153
169
|
const transcriptText = audioTranscript?.text || audioTranscript;
|
|
154
170
|
messagesChat.push({
|
|
@@ -159,7 +175,8 @@ const processAudioFileCore = async (fileName, provider) => {
|
|
|
159
175
|
logger.info('processAudioFile', {
|
|
160
176
|
fileName: fileName ? fileName.split('/').pop().replace(/^[^-]+-[^-]+-/, 'xxx-xxx-') : 'unknown',
|
|
161
177
|
transcription_success: true,
|
|
162
|
-
transcript_length: transcriptText?.length || 0
|
|
178
|
+
transcript_length: transcriptText?.length || 0,
|
|
179
|
+
...timings
|
|
163
180
|
});
|
|
164
181
|
|
|
165
182
|
logger.debug('processAudioFile_transcript', { transcriptText });
|
|
@@ -174,7 +191,7 @@ const processAudioFileCore = async (fileName, provider) => {
|
|
|
174
191
|
});
|
|
175
192
|
}
|
|
176
193
|
|
|
177
|
-
return messagesChat;
|
|
194
|
+
return { messagesChat, timings };
|
|
178
195
|
};
|
|
179
196
|
|
|
180
197
|
const processAudioFile = withTracing(
|
|
@@ -189,16 +206,24 @@ const processMediaFilesCore = async (code, reply, provider) => {
|
|
|
189
206
|
let url = null;
|
|
190
207
|
const messagesChat = [];
|
|
191
208
|
const tempFiles = [];
|
|
209
|
+
const timings = {
|
|
210
|
+
download_ms: 0,
|
|
211
|
+
image_analysis_ms: 0,
|
|
212
|
+
audio_transcription_ms: 0,
|
|
213
|
+
url_generation_ms: 0
|
|
214
|
+
};
|
|
192
215
|
|
|
193
216
|
if (!reply.is_media) {
|
|
194
|
-
return { messagesChat, url, tempFiles };
|
|
217
|
+
return { messagesChat, url, tempFiles, timings };
|
|
195
218
|
}
|
|
196
219
|
|
|
197
|
-
const fileNames = await withTracing(
|
|
220
|
+
const { result: fileNames, duration: downloadDuration } = await withTracing(
|
|
198
221
|
downloadMediaAndCreateFile,
|
|
199
222
|
'download_media',
|
|
200
|
-
() => ({ 'media.message_id': reply.message_id, 'media.type': reply.media?.mediaType })
|
|
223
|
+
() => ({ 'media.message_id': reply.message_id, 'media.type': reply.media?.mediaType }),
|
|
224
|
+
{ returnTiming: true }
|
|
201
225
|
)(code, reply);
|
|
226
|
+
timings.download_ms = downloadDuration;
|
|
202
227
|
tempFiles.push(...fileNames);
|
|
203
228
|
|
|
204
229
|
for (const fileName of fileNames) {
|
|
@@ -223,21 +248,33 @@ const processMediaFilesCore = async (code, reply, provider) => {
|
|
|
223
248
|
fileName.toLowerCase().includes('/sticker/');
|
|
224
249
|
|
|
225
250
|
if (isImageLike) {
|
|
226
|
-
const { messagesChat: imageMessages, url: imageUrl } = await processImageFile(fileName, reply);
|
|
251
|
+
const { messagesChat: imageMessages, url: imageUrl, timings: imageTimings } = await processImageFile(fileName, reply);
|
|
252
|
+
|
|
227
253
|
messagesChat.push(...imageMessages);
|
|
228
254
|
if (imageUrl) url = imageUrl;
|
|
255
|
+
|
|
256
|
+
if (imageTimings) {
|
|
257
|
+
timings.image_analysis_ms += imageTimings.analysis_ms || 0;
|
|
258
|
+
timings.url_generation_ms += imageTimings.url_generation_ms || 0;
|
|
259
|
+
}
|
|
229
260
|
} else if (fileName.includes('audio')) {
|
|
230
|
-
const audioMessages = await processAudioFile(fileName, provider);
|
|
261
|
+
const { messagesChat: audioMessages, timings: audioTimings } = await processAudioFile(fileName, provider);
|
|
262
|
+
|
|
231
263
|
messagesChat.push(...audioMessages);
|
|
264
|
+
|
|
265
|
+
if (audioTimings) {
|
|
266
|
+
timings.audio_transcription_ms += audioTimings.transcribe_ms || 0;
|
|
267
|
+
}
|
|
232
268
|
}
|
|
233
269
|
}
|
|
234
270
|
|
|
235
271
|
logger.info('processMediaFiles_complete', {
|
|
236
272
|
message_id: reply.message_id,
|
|
237
|
-
file_count: fileNames.length
|
|
273
|
+
file_count: fileNames.length,
|
|
274
|
+
...timings
|
|
238
275
|
});
|
|
239
276
|
|
|
240
|
-
return { messagesChat, url, tempFiles };
|
|
277
|
+
return { messagesChat, url, tempFiles, timings };
|
|
241
278
|
};
|
|
242
279
|
|
|
243
280
|
const processMediaFiles = withTracing(
|
|
@@ -251,6 +288,13 @@ const processMediaFiles = withTracing(
|
|
|
251
288
|
|
|
252
289
|
const processThreadMessageCore = async (code, replies, provider) => {
|
|
253
290
|
const replyArray = Array.isArray(replies) ? replies : [replies];
|
|
291
|
+
const timings = {
|
|
292
|
+
download_ms: 0,
|
|
293
|
+
image_analysis_ms: 0,
|
|
294
|
+
audio_transcription_ms: 0,
|
|
295
|
+
url_generation_ms: 0,
|
|
296
|
+
total_media_ms: 0
|
|
297
|
+
};
|
|
254
298
|
|
|
255
299
|
const results = await Promise.all(
|
|
256
300
|
replyArray.map(async (reply, i) => {
|
|
@@ -262,9 +306,17 @@ const processThreadMessageCore = async (code, replies, provider) => {
|
|
|
262
306
|
const textMessages = processTextMessage(reply);
|
|
263
307
|
const mediaResult = await processMediaFiles(code, reply, provider);
|
|
264
308
|
|
|
265
|
-
const { messagesChat: mediaMessages, url, tempFiles: mediaFiles } = mediaResult;
|
|
309
|
+
const { messagesChat: mediaMessages, url, tempFiles: mediaFiles, timings: mediaTimings } = mediaResult;
|
|
266
310
|
tempFiles = mediaFiles;
|
|
267
311
|
|
|
312
|
+
if (mediaTimings) {
|
|
313
|
+
timings.download_ms += mediaTimings.download_ms || 0;
|
|
314
|
+
timings.image_analysis_ms += mediaTimings.image_analysis_ms || 0;
|
|
315
|
+
timings.audio_transcription_ms += mediaTimings.audio_transcription_ms || 0;
|
|
316
|
+
timings.url_generation_ms += mediaTimings.url_generation_ms || 0;
|
|
317
|
+
timings.total_media_ms += (mediaTimings.download_ms + mediaTimings.image_analysis_ms + mediaTimings.audio_transcription_ms + mediaTimings.url_generation_ms);
|
|
318
|
+
}
|
|
319
|
+
|
|
268
320
|
const allMessages = [...textMessages, ...mediaMessages];
|
|
269
321
|
const role = reply.origin === 'patient' ? 'user' : 'assistant';
|
|
270
322
|
const messages = allMessages.map(content => ({ role, content }));
|
|
@@ -290,10 +342,11 @@ const processThreadMessageCore = async (code, replies, provider) => {
|
|
|
290
342
|
);
|
|
291
343
|
|
|
292
344
|
logger.info('processThreadMessage_complete', {
|
|
293
|
-
message_count: replyArray.length
|
|
345
|
+
message_count: replyArray.length,
|
|
346
|
+
...timings
|
|
294
347
|
});
|
|
295
348
|
|
|
296
|
-
return results;
|
|
349
|
+
return { results, timings };
|
|
297
350
|
};
|
|
298
351
|
|
|
299
352
|
const processThreadMessage = withTracing(
|
|
@@ -314,7 +314,7 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
314
314
|
|
|
315
315
|
logger.info(`[replyAssistantCore] Processing ${patientReply.length} messages in parallel`);
|
|
316
316
|
|
|
317
|
-
const { result:
|
|
317
|
+
const { result: processResult, duration: processMessagesMs } = await withTracing(
|
|
318
318
|
processThreadMessage,
|
|
319
319
|
'process_thread_messages',
|
|
320
320
|
(code, patientReply, provider) => ({
|
|
@@ -323,8 +323,22 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
323
323
|
}),
|
|
324
324
|
{ returnTiming: true }
|
|
325
325
|
)(code, patientReply, provider);
|
|
326
|
+
|
|
327
|
+
const { results: processResults, timings: processTimings } = processResult;
|
|
326
328
|
timings.process_messages_ms = processMessagesMs;
|
|
327
329
|
|
|
330
|
+
logger.debug('[replyAssistantCore] Process timings breakdown', { processTimings });
|
|
331
|
+
|
|
332
|
+
if (processTimings) {
|
|
333
|
+
timings.process_messages_breakdown = {
|
|
334
|
+
download_ms: processTimings.download_ms || 0,
|
|
335
|
+
image_analysis_ms: processTimings.image_analysis_ms || 0,
|
|
336
|
+
audio_transcription_ms: processTimings.audio_transcription_ms || 0,
|
|
337
|
+
url_generation_ms: processTimings.url_generation_ms || 0,
|
|
338
|
+
total_media_ms: processTimings.total_media_ms || 0
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
328
342
|
const patientMsg = processResults.some(r => r.isPatient);
|
|
329
343
|
const urls = processResults.filter(r => r.url).map(r => ({ url: r.url }));
|
|
330
344
|
const allMessagesToAdd = processResults.flatMap(r => r.messages || []);
|
|
@@ -401,6 +415,11 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
401
415
|
});
|
|
402
416
|
|
|
403
417
|
if (output && predictionTimeMs) {
|
|
418
|
+
logger.debug('[replyAssistantCore] Storing metrics with timing_breakdown', {
|
|
419
|
+
timing_breakdown: timings,
|
|
420
|
+
has_breakdown: !!timings.process_messages_breakdown
|
|
421
|
+
});
|
|
422
|
+
|
|
404
423
|
await PredictionMetrics.create({
|
|
405
424
|
message_id: `${code}-${Date.now()}`,
|
|
406
425
|
numero: code,
|