@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
- imageAnalysis = await withTracing(
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
- url = await withTracing(
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: processResults, duration: processMessagesMs } = await withTracing(
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "2.4.11-logs",
3
+ "version": "2.4.11-logs-msg",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",