@peopl-health/nexus 2.4.4 → 2.4.5-fix
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.
|
@@ -5,6 +5,7 @@ const { createProvider } = require('../providers/createProvider.js');
|
|
|
5
5
|
const { withTracing } = require('../utils/tracingDecorator');
|
|
6
6
|
|
|
7
7
|
const { getRecordByFilter } = require('../services/airtableService.js');
|
|
8
|
+
const { logger } = require('../middleware/requestId');
|
|
8
9
|
|
|
9
10
|
const DEFAULT_MAX_RETRIES = parseInt(process.env.MAX_RETRIES || '30', 10);
|
|
10
11
|
|
|
@@ -77,7 +78,7 @@ const runAssistantAndWait = async ({
|
|
|
77
78
|
let completed = false;
|
|
78
79
|
|
|
79
80
|
try {
|
|
80
|
-
|
|
81
|
+
logger.log('[runAssistantAndWait] RUN ID', run.id, 'THREAD ID', thread.getConversationId(), 'ASSISTANT ID', thread.getAssistantId());
|
|
81
82
|
({run, completed} = await provider.checkRunStatus(assistant, thread.getConversationId(), run.id, 0, maxRetries));
|
|
82
83
|
} finally {
|
|
83
84
|
if (filter) {
|
|
@@ -101,7 +102,7 @@ const executeAssistantAttempt = async (thread, assistant, runConfig, attemptNumb
|
|
|
101
102
|
runConfig
|
|
102
103
|
});
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
logger.log(`[executeAssistantAttempt] Attempt ${attemptNumber}: completed=${result.completed}, output=${result.output || '(empty)'}`);
|
|
105
106
|
|
|
106
107
|
return result;
|
|
107
108
|
};
|
|
@@ -129,15 +130,22 @@ const runAssistantWithRetries = async (thread, assistant, runConfig, patientRepl
|
|
|
129
130
|
)(thread, assistant, runConfig, retries));
|
|
130
131
|
|
|
131
132
|
if (completed && output) break;
|
|
132
|
-
|
|
133
|
+
|
|
134
|
+
if (retries < maxRetries) {
|
|
135
|
+
const delay = retries === 1
|
|
136
|
+
? 500
|
|
137
|
+
: Math.min(1000 * Math.pow(1.5, retries - 1), 5000);
|
|
138
|
+
logger.log(`[runAssistantWithRetries] Retry ${retries}, waiting ${delay}ms`);
|
|
139
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
140
|
+
}
|
|
133
141
|
} while (retries < maxRetries && (!completed || !output));
|
|
134
142
|
|
|
135
143
|
const predictionTimeMs = Date.now() - startTime;
|
|
136
144
|
|
|
137
|
-
if (run?.last_error)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
145
|
+
if (run?.last_error) logger.log('[runAssistantWithRetries] RUN LAST ERROR:', run.last_error);
|
|
146
|
+
logger.log('[runAssistantWithRetries] RUN STATUS', completed);
|
|
147
|
+
logger.log('[runAssistantWithRetries] OUTPUT', output);
|
|
148
|
+
logger.log('[runAssistantWithRetries] TIMING', { predictionTimeMs, retries });
|
|
141
149
|
|
|
142
150
|
return { run, output, completed, retries, predictionTimeMs };
|
|
143
151
|
};
|
package/lib/index.js
CHANGED
|
@@ -23,6 +23,11 @@ const {
|
|
|
23
23
|
hasPreprocessingHandler,
|
|
24
24
|
invokePreprocessingHandler
|
|
25
25
|
} = require('./services/preprocessingHooks');
|
|
26
|
+
const {
|
|
27
|
+
requestIdMiddleware,
|
|
28
|
+
getRequestId,
|
|
29
|
+
logger
|
|
30
|
+
} = require('./middleware/requestId');
|
|
26
31
|
|
|
27
32
|
/**
|
|
28
33
|
* Main Nexus class that orchestrates all components
|
|
@@ -341,5 +346,9 @@ module.exports = {
|
|
|
341
346
|
listFlows: interactive.listFlows,
|
|
342
347
|
sendInteractive: interactive.sendInteractive,
|
|
343
348
|
registerInteractiveHandler: interactive.registerInteractiveHandler,
|
|
344
|
-
attachInteractiveRouter: interactive.attachInteractiveRouter
|
|
349
|
+
attachInteractiveRouter: interactive.attachInteractiveRouter,
|
|
350
|
+
// Request tracing
|
|
351
|
+
requestIdMiddleware,
|
|
352
|
+
getRequestId,
|
|
353
|
+
logger
|
|
345
354
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
2
|
+
const { v4: uuidv4 } = require('uuid');
|
|
3
|
+
|
|
4
|
+
const requestContext = new AsyncLocalStorage();
|
|
5
|
+
|
|
6
|
+
function getRequestId() {
|
|
7
|
+
const store = requestContext.getStore();
|
|
8
|
+
return store?.requestId || 'no-context';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function requestIdMiddleware(req, res, next) {
|
|
12
|
+
const requestId = uuidv4().substring(0, 8);
|
|
13
|
+
req.requestId = requestId;
|
|
14
|
+
res.setHeader('X-Request-Id', requestId);
|
|
15
|
+
|
|
16
|
+
requestContext.run({ requestId }, () => {
|
|
17
|
+
console.log(`[${requestId}] → ${req.method} ${req.path}`);
|
|
18
|
+
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
res.on('finish', () => {
|
|
21
|
+
const duration = Date.now() - startTime;
|
|
22
|
+
console.log(`[${requestId}] ← ${res.statusCode} ${req.method} ${req.path} (${duration}ms)`);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
next();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const logger = {
|
|
30
|
+
log: (...args) => console.log(`[${getRequestId()}]`, ...args),
|
|
31
|
+
error: (...args) => console.error(`[${getRequestId()}]`, ...args),
|
|
32
|
+
warn: (...args) => console.warn(`[${getRequestId()}]`, ...args),
|
|
33
|
+
info: (...args) => console.info(`[${getRequestId()}]`, ...args),
|
|
34
|
+
debug: (...args) => console.debug(`[${getRequestId()}]`, ...args)
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
module.exports = {
|
|
38
|
+
requestIdMiddleware,
|
|
39
|
+
getRequestId,
|
|
40
|
+
logger
|
|
41
|
+
};
|
|
@@ -59,6 +59,9 @@ const messageSchema = new mongoose.Schema({
|
|
|
59
59
|
messageSchema.index({ message_id: 1, timestamp: 1 }, { unique: true });
|
|
60
60
|
messageSchema.index({ numero: 1, createdAt: -1 });
|
|
61
61
|
|
|
62
|
+
messageSchema.index({ numero: 1, processed: 1, origin: 1 }, { name: 'numero_processed_origin_idx' });
|
|
63
|
+
messageSchema.index({ numero: 1, createdAt: -1, processed: 1 }, { name: 'numero_created_processed_idx' });
|
|
64
|
+
|
|
62
65
|
messageSchema.pre('save', function (next) {
|
|
63
66
|
if (this.timestamp) {
|
|
64
67
|
this.timestamp = moment.tz(this.timestamp, 'America/Mexico_City').toDate();
|
|
@@ -8,7 +8,8 @@ const predictionMetricsSchema = new mongoose.Schema({
|
|
|
8
8
|
prediction_time_ms: { type: Number, required: true },
|
|
9
9
|
retry_count: { type: Number, required: true, default: 1 },
|
|
10
10
|
completed: { type: Boolean, default: true },
|
|
11
|
-
error: { type: String, default: null }
|
|
11
|
+
error: { type: String, default: null },
|
|
12
|
+
timing_breakdown: { type: Object, default: {} }
|
|
12
13
|
}, { timestamps: true });
|
|
13
14
|
|
|
14
15
|
predictionMetricsSchema.index({ createdAt: -1 });
|
|
@@ -15,6 +15,7 @@ const { withTracing } = require('../utils/tracingDecorator.js');
|
|
|
15
15
|
const { processIndividualMessage } = require('../helpers/processHelper.js');
|
|
16
16
|
const { getLastMessages } = require('../helpers/messageHelper.js');
|
|
17
17
|
const { combineImagesToPDF, cleanupFiles } = require('../helpers/filesHelper.js');
|
|
18
|
+
const { logger } = require('../middleware/requestId');
|
|
18
19
|
|
|
19
20
|
const DEFAULT_MAX_RETRIES = parseInt(process.env.MAX_RETRIES || '30', 10);
|
|
20
21
|
|
|
@@ -269,6 +270,10 @@ const addInsAssistant = withTracing(
|
|
|
269
270
|
);
|
|
270
271
|
|
|
271
272
|
const replyAssistantCore = async (code, message_ = null, thread_ = null, runOptions = {}) => {
|
|
273
|
+
const timings = {};
|
|
274
|
+
const startTotal = Date.now();
|
|
275
|
+
|
|
276
|
+
timings.getThread = Date.now();
|
|
272
277
|
const thread = thread_ || await withTracing(getThread, 'get_thread_operation',
|
|
273
278
|
(threadCode) => ({
|
|
274
279
|
'thread.code': threadCode,
|
|
@@ -276,30 +281,43 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
276
281
|
'thread.provided': !!thread_
|
|
277
282
|
})
|
|
278
283
|
)(code);
|
|
284
|
+
timings.getThread = Date.now() - timings.getThread;
|
|
285
|
+
|
|
279
286
|
if (!thread) return null;
|
|
280
287
|
|
|
288
|
+
timings.getMessages = Date.now();
|
|
281
289
|
const patientReply = await getLastMessages(code);
|
|
290
|
+
timings.getMessages = Date.now() - timings.getMessages;
|
|
291
|
+
|
|
282
292
|
if (!patientReply) {
|
|
283
|
-
|
|
293
|
+
logger.log('[replyAssistantCore] No relevant data found for this assistant.');
|
|
284
294
|
return null;
|
|
285
295
|
}
|
|
286
296
|
|
|
287
297
|
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
288
298
|
|
|
299
|
+
timings.processMessages = Date.now();
|
|
300
|
+
logger.log(`[replyAssistantCore] Processing ${patientReply.length} messages sequentially`);
|
|
301
|
+
|
|
289
302
|
let patientMsg = false;
|
|
290
|
-
|
|
303
|
+
const urls = [];
|
|
304
|
+
|
|
291
305
|
for (let i = 0; i < patientReply.length; i++) {
|
|
292
306
|
const reply = patientReply[i];
|
|
293
307
|
const { isPatient, url } = await processIndividualMessage(code, reply, provider, thread);
|
|
294
|
-
|
|
308
|
+
logger.log(`[replyAssistantCore] Message ${i + 1}/${patientReply.length}: isPatient=${isPatient}, hasUrl=${!!url}`);
|
|
295
309
|
patientMsg = patientMsg || isPatient;
|
|
296
|
-
if (url) urls.push({
|
|
310
|
+
if (url) urls.push({ url });
|
|
297
311
|
}
|
|
298
312
|
|
|
313
|
+
timings.processMessages = Date.now() - timings.processMessages;
|
|
314
|
+
|
|
299
315
|
if (urls.length > 0) {
|
|
300
|
-
|
|
316
|
+
timings.pdfCombination = Date.now();
|
|
317
|
+
logger.log(`[replyAssistantCore] Processing ${urls.length} URLs for PDF combination`);
|
|
301
318
|
const { pdfBuffer, processedFiles } = await combineImagesToPDF({ code });
|
|
302
|
-
|
|
319
|
+
timings.pdfCombination = Date.now() - timings.pdfCombination;
|
|
320
|
+
logger.log(`[replyAssistantCore] PDF combination complete: ${processedFiles?.length || 0} files processed`);
|
|
303
321
|
|
|
304
322
|
if (pdfBuffer) {
|
|
305
323
|
const key = `${code}-${Date.now()}-combined.pdf`;
|
|
@@ -316,6 +334,7 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
316
334
|
|
|
317
335
|
if (!patientMsg || thread.stopped) return null;
|
|
318
336
|
|
|
337
|
+
timings.runAssistant = Date.now();
|
|
319
338
|
const assistant = getAssistantById(thread.getAssistantId(), thread);
|
|
320
339
|
const { run, output, completed, retries, predictionTimeMs } = await withTracing(
|
|
321
340
|
runAssistantWithRetries,
|
|
@@ -326,8 +345,17 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
326
345
|
'assistant.has_patient_reply': !!patientReply
|
|
327
346
|
})
|
|
328
347
|
)(thread, assistant, runOptions, patientReply);
|
|
348
|
+
timings.runAssistant = Date.now() - timings.runAssistant;
|
|
349
|
+
timings.total = Date.now() - startTotal;
|
|
350
|
+
|
|
351
|
+
logger.log('[Performance Breakdown]', {
|
|
352
|
+
code: code ? `${code.substring(0, 3)}***${code.slice(-4)}` : 'unknown',
|
|
353
|
+
messageCount: patientReply.length,
|
|
354
|
+
hasMedia: urls.length > 0,
|
|
355
|
+
retries,
|
|
356
|
+
time: `${timings.total}ms`
|
|
357
|
+
});
|
|
329
358
|
|
|
330
|
-
// Store prediction metrics
|
|
331
359
|
if (output && predictionTimeMs) {
|
|
332
360
|
await PredictionMetrics.create({
|
|
333
361
|
message_id: `${code}-${Date.now()}`,
|
|
@@ -336,7 +364,15 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
336
364
|
thread_id: thread.getConversationId(),
|
|
337
365
|
prediction_time_ms: predictionTimeMs,
|
|
338
366
|
retry_count: retries,
|
|
339
|
-
completed: completed
|
|
367
|
+
completed: completed,
|
|
368
|
+
timing_breakdown: {
|
|
369
|
+
get_thread_ms: timings.getThread,
|
|
370
|
+
get_messages_ms: timings.getMessages,
|
|
371
|
+
process_messages_ms: timings.processMessages,
|
|
372
|
+
pdf_combination_ms: timings.pdfCombination || 0,
|
|
373
|
+
run_assistant_ms: timings.runAssistant,
|
|
374
|
+
total_ms: timings.total
|
|
375
|
+
}
|
|
340
376
|
}).catch(err => console.error('[replyAssistantCore] Failed to store metrics:', err));
|
|
341
377
|
}
|
|
342
378
|
|