@peopl-health/nexus 3.13.3 → 3.13.4
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.
|
@@ -4,10 +4,10 @@ const { runAssistantWithRetries } = require('../helpers/assistantHelper');
|
|
|
4
4
|
const { getAssistantById } = require('../services/assistantResolver');
|
|
5
5
|
|
|
6
6
|
class AssistantProcessor {
|
|
7
|
-
constructor({ mode = 'local', queueAdapter = null, sendMessage = null,
|
|
8
|
-
Object.assign(this, { mode, queueAdapter, sendMessage,
|
|
7
|
+
constructor({ mode = 'local', queueAdapter = null, sendMessage = null, storeRunMetrics = null }) {
|
|
8
|
+
Object.assign(this, { mode, queueAdapter, sendMessage, storeRunMetrics });
|
|
9
9
|
if (mode === 'queue' && queueAdapter) {
|
|
10
|
-
queueAdapter.process('assistant.process', (payload) => this.
|
|
10
|
+
queueAdapter.process('assistant.process', (payload) => this._executeLocal(payload));
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -41,50 +41,29 @@ class AssistantProcessor {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
async process({ code,
|
|
44
|
+
async process({ code, runOptions = {}, messages = null }) {
|
|
45
45
|
if (!code) throw new Error('code is required for assistant processing');
|
|
46
46
|
|
|
47
47
|
return (this.mode === 'queue')
|
|
48
|
-
? await this.
|
|
49
|
-
: await this.
|
|
48
|
+
? await this._executeViaQueue({ code, runOptions })
|
|
49
|
+
: await this._executeLocal({ code, runOptions, messages });
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
async
|
|
52
|
+
async _executeLocal({ code, runOptions = {}, messages = null }) {
|
|
53
53
|
const resolved = await this.resolveThread(code);
|
|
54
54
|
if (!resolved) return null;
|
|
55
|
-
const { thread, assistant } = resolved;
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (!preProcessed.shouldProcess) return null;
|
|
60
|
-
|
|
61
|
-
const result = await this.executeLLM(thread, assistant, runOptions, preProcessed.messages);
|
|
62
|
-
if (this.storeRunMetrics) await this.storeRunMetrics(code, thread, result, preProcessed.timings);
|
|
63
|
-
return { ...result, timings: preProcessed.timings };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const result = await this.executeLLM(thread, assistant, runOptions);
|
|
67
|
-
if (this.storeRunMetrics) await this.storeRunMetrics(code, thread, result);
|
|
56
|
+
const result = await this.executeLLM(resolved.thread, resolved.assistant, runOptions, messages);
|
|
57
|
+
if (this.storeRunMetrics) await this.storeRunMetrics(code, resolved.thread, result);
|
|
68
58
|
return result;
|
|
69
59
|
}
|
|
70
60
|
|
|
71
|
-
async
|
|
61
|
+
async _executeViaQueue({ code, runOptions }) {
|
|
72
62
|
if (!this.queueAdapter) throw new Error('queueAdapter is required for queue mode');
|
|
73
|
-
const jobId = await this.queueAdapter.enqueue('assistant.process', { code,
|
|
63
|
+
const jobId = await this.queueAdapter.enqueue('assistant.process', { code, runOptions });
|
|
74
64
|
return await this.queueAdapter.waitForResult(jobId, 120000);
|
|
75
65
|
}
|
|
76
66
|
|
|
77
|
-
async processDirect({ code, runOptions = {} }) {
|
|
78
|
-
if (!code) throw new Error('code is required for direct processing');
|
|
79
|
-
|
|
80
|
-
const resolved = await this.resolveThread(code);
|
|
81
|
-
if (!resolved) return null;
|
|
82
|
-
|
|
83
|
-
const result = await this.executeLLM(resolved.thread, resolved.assistant, runOptions);
|
|
84
|
-
if (this.storeRunMetrics) await this.storeRunMetrics(code, resolved.thread, result);
|
|
85
|
-
return result;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
67
|
async sendResponse(code, result) {
|
|
89
68
|
if (!this.sendMessage) throw new Error('sendMessage function not configured');
|
|
90
69
|
if (!result?.output) return null;
|
|
@@ -77,7 +77,6 @@ class NexusMessaging {
|
|
|
77
77
|
mode: config.assistant?.mode || 'local',
|
|
78
78
|
queueAdapter: this.queueAdapter,
|
|
79
79
|
sendMessage: this.sendMessage.bind(this),
|
|
80
|
-
preProcessMessages,
|
|
81
80
|
storeRunMetrics,
|
|
82
81
|
});
|
|
83
82
|
}
|
|
@@ -384,9 +383,22 @@ class NexusMessaging {
|
|
|
384
383
|
async _handleWithCheckAfter(chatId) {
|
|
385
384
|
await this._executeWithPipeline(chatId, 'message', 'preempt',
|
|
386
385
|
async (preProcessResult, shouldContinue) => {
|
|
387
|
-
return await this._processMessages(chatId, () =>
|
|
388
|
-
this.assistantProcessor.
|
|
389
|
-
|
|
386
|
+
return await this._processMessages(chatId, async () => {
|
|
387
|
+
const resolved = await this.assistantProcessor.resolveThread(chatId);
|
|
388
|
+
if (!resolved) return null;
|
|
389
|
+
|
|
390
|
+
const preProcessed = await preProcessMessages(chatId, null, resolved.thread);
|
|
391
|
+
if (!preProcessed.shouldProcess) return null;
|
|
392
|
+
|
|
393
|
+
const result = await this.assistantProcessor.executeLLM(
|
|
394
|
+
resolved.thread, resolved.assistant,
|
|
395
|
+
{ prePromptResult: preProcessResult },
|
|
396
|
+
preProcessed.messages
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
if (storeRunMetrics) await storeRunMetrics(chatId, resolved.thread, result, preProcessed.timings);
|
|
400
|
+
return { ...result, timings: preProcessed.timings };
|
|
401
|
+
}, shouldContinue);
|
|
390
402
|
}
|
|
391
403
|
);
|
|
392
404
|
}
|
|
@@ -407,7 +419,7 @@ class NexusMessaging {
|
|
|
407
419
|
|
|
408
420
|
const result = await this._executeWithPipeline(code, 'instruction', 'queue',
|
|
409
421
|
async (preProcessResult) => {
|
|
410
|
-
return await this.assistantProcessor.
|
|
422
|
+
return await this.assistantProcessor.process({
|
|
411
423
|
code,
|
|
412
424
|
runOptions: {
|
|
413
425
|
prePromptResult: preProcessResult,
|
|
@@ -442,7 +454,7 @@ class NexusMessaging {
|
|
|
442
454
|
|
|
443
455
|
const result = await this._executeWithPipeline(code, 'system', 'queue',
|
|
444
456
|
async (preProcessResult) => {
|
|
445
|
-
return await this.assistantProcessor.
|
|
457
|
+
return await this.assistantProcessor.process({
|
|
446
458
|
code,
|
|
447
459
|
runOptions: {
|
|
448
460
|
prePromptResult: preProcessResult,
|
|
@@ -3,9 +3,6 @@ const fsSync = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { execFile } = require('child_process');
|
|
5
5
|
|
|
6
|
-
const sharp = require('sharp');
|
|
7
|
-
const { PDFDocument } = require('pdf-lib');
|
|
8
|
-
|
|
9
6
|
const { downloadFileFromS3 } = require('../config/awsConfig.js');
|
|
10
7
|
|
|
11
8
|
const { sanitizeFilename } = require('../utils/sanitizerUtils.js');
|
|
@@ -76,43 +73,6 @@ async function convertPdfToImages(pdfName, existingPdfPath = null) {
|
|
|
76
73
|
});
|
|
77
74
|
}
|
|
78
75
|
|
|
79
|
-
async function combineImagesToPDF(config) {
|
|
80
|
-
const { code, extensions = ['jpg', 'jpeg', 'png', 'tiff'], sortNumerically = true } = config;
|
|
81
|
-
const inputDir = path.join(__dirname, 'assets', 'tmp');
|
|
82
|
-
const files = await fs.readdir(inputDir);
|
|
83
|
-
|
|
84
|
-
const imageFiles = files.filter(file => {
|
|
85
|
-
const ext = path.extname(file).toLowerCase().slice(1);
|
|
86
|
-
return extensions.includes(ext) && (!code || file.startsWith(code));
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
imageFiles.sort((a, b) => {
|
|
90
|
-
if (!sortNumerically) return a.localeCompare(b);
|
|
91
|
-
const aNum = a.match(/\d+/)?.[0];
|
|
92
|
-
const bNum = b.match(/\d+/)?.[0];
|
|
93
|
-
return (aNum && bNum) ? parseInt(aNum) - parseInt(bNum) : a.localeCompare(b);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const pdfDoc = await PDFDocument.create();
|
|
97
|
-
const processedFiles = [];
|
|
98
|
-
|
|
99
|
-
for (const file of imageFiles) {
|
|
100
|
-
try {
|
|
101
|
-
const filePath = path.join(inputDir, file);
|
|
102
|
-
const imageBuffer = await fs.readFile(filePath);
|
|
103
|
-
const pngBuffer = await sharp(imageBuffer).toFormat('png').toBuffer();
|
|
104
|
-
const { width, height } = await sharp(imageBuffer).metadata();
|
|
105
|
-
const img = await pdfDoc.embedPng(pngBuffer);
|
|
106
|
-
pdfDoc.addPage([width, height]).drawImage(img, { x: 0, y: 0, width, height });
|
|
107
|
-
processedFiles.push(filePath);
|
|
108
|
-
} catch (error) {
|
|
109
|
-
logger.error(`Error processing file ${file}`, { error: error.message });
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return { pdfBuffer: await pdfDoc.save(), processedFiles };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
76
|
const cleanupFiles = async (files) => {
|
|
117
77
|
if (!files?.length) return;
|
|
118
78
|
|
|
@@ -168,7 +128,6 @@ async function downloadMediaAndCreateFile(code, reply) {
|
|
|
168
128
|
|
|
169
129
|
module.exports = {
|
|
170
130
|
convertPdfToImages,
|
|
171
|
-
combineImagesToPDF,
|
|
172
131
|
cleanupFiles,
|
|
173
132
|
downloadMediaAndCreateFile
|
|
174
133
|
};
|
|
@@ -138,6 +138,15 @@ const processMediaFilesCore = async (code, reply, provider) => {
|
|
|
138
138
|
|
|
139
139
|
if (!reply.media) return { messagesChat: [], url: null, tempFiles: [], timings };
|
|
140
140
|
|
|
141
|
+
if (reply.media?.metadata?.processedContent) {
|
|
142
|
+
return {
|
|
143
|
+
messagesChat: [{ type: 'text', text: reply.media.metadata.processedContent }],
|
|
144
|
+
url: null,
|
|
145
|
+
tempFiles: [],
|
|
146
|
+
timings
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
141
150
|
const { result: fileNames, duration } = await withTracing(
|
|
142
151
|
downloadMediaAndCreateFile, 'download_media',
|
|
143
152
|
() => ({ 'media.message_id': reply.message_id, 'media.type': reply.media?.mediaType }),
|
package/lib/index.d.ts
CHANGED
|
@@ -558,14 +558,13 @@ declare module '@peopl-health/nexus' {
|
|
|
558
558
|
mode?: 'local' | 'queue';
|
|
559
559
|
queueAdapter?: QueueAdapter;
|
|
560
560
|
sendMessage?: (messageData: MessageData) => Promise<any>;
|
|
561
|
-
preProcessMessages?: (code: string, body: any, thread: any) => Promise<{ shouldProcess: boolean; messages: any[] | null; timings: Record<string, any> }>;
|
|
562
561
|
storeRunMetrics?: (code: string, thread: any, result: any, timings?: Record<string, any>) => Promise<void>;
|
|
563
562
|
}
|
|
564
563
|
|
|
565
564
|
export interface ProcessInput {
|
|
566
565
|
code: string;
|
|
567
|
-
body?: any;
|
|
568
566
|
runOptions?: Record<string, any>;
|
|
567
|
+
messages?: any[] | null;
|
|
569
568
|
}
|
|
570
569
|
|
|
571
570
|
export interface LLMResult {
|
|
@@ -586,7 +585,6 @@ declare module '@peopl-health/nexus' {
|
|
|
586
585
|
resolveThread(code: string): Promise<{ thread: any; assistant: any } | null>;
|
|
587
586
|
executeLLM(thread: any, assistant: any, runOptions?: Record<string, any>, messages?: any[]): Promise<LLMResult>;
|
|
588
587
|
process(input: ProcessInput): Promise<LLMResult | null>;
|
|
589
|
-
processDirect(input: { code: string; runOptions?: Record<string, any> }): Promise<LLMResult | null>;
|
|
590
588
|
sendResponse(code: string, result: any): Promise<string | null>;
|
|
591
589
|
}
|
|
592
590
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const AWS = require('../config/awsConfig.js');
|
|
2
1
|
const runtimeConfig = require('../config/runtimeConfig');
|
|
3
2
|
const { Historial_Clinico_ID } = require('../config/airtableConfig');
|
|
4
3
|
|
|
@@ -12,7 +11,7 @@ const { getCurRow } = require('../helpers/assistantHelper.js');
|
|
|
12
11
|
const { getThread, switchThreadStoppedStatus, setThreadPromptId } = require('../helpers/threadHelper.js');
|
|
13
12
|
const { processThreadMessage } = require('../helpers/processHelper.js');
|
|
14
13
|
const { getLastNMessages, storeProcessedContent } = require('../helpers/messageHelper.js');
|
|
15
|
-
const {
|
|
14
|
+
const { cleanupFiles } = require('../helpers/filesHelper.js');
|
|
16
15
|
|
|
17
16
|
const { createLLMProvider } = require('../providers/createLLMProvider');
|
|
18
17
|
const { getAssistantById } = require('./assistantResolver');
|
|
@@ -159,7 +158,6 @@ const preProcessMessagesCore = async (code, message_ = null, thread) => {
|
|
|
159
158
|
}
|
|
160
159
|
|
|
161
160
|
const patientMsg = processResults.some(r => r.isPatient);
|
|
162
|
-
const urls = processResults.filter(r => r.url).map(r => ({ url: r.url }));
|
|
163
161
|
const allTempFiles = processResults.flatMap(r => r.tempFiles || []);
|
|
164
162
|
|
|
165
163
|
await Promise.all(processResults.map(r => {
|
|
@@ -174,27 +172,6 @@ const preProcessMessagesCore = async (code, message_ = null, thread) => {
|
|
|
174
172
|
|
|
175
173
|
await cleanupFiles(allTempFiles);
|
|
176
174
|
|
|
177
|
-
if (urls.length > 0) {
|
|
178
|
-
logger.info(`[preProcessMessages] Processing ${urls.length} URLs for PDF combination`);
|
|
179
|
-
const pdfStart = Date.now();
|
|
180
|
-
const pdfResult = await combineImagesToPDF({ code });
|
|
181
|
-
timings.pdf_combination_ms = Date.now() - pdfStart;
|
|
182
|
-
const { pdfBuffer, processedFiles } = pdfResult;
|
|
183
|
-
logger.info(`[preProcessMessages] PDF combination complete: ${processedFiles?.length || 0} files processed`);
|
|
184
|
-
|
|
185
|
-
if (pdfBuffer) {
|
|
186
|
-
const key = `${code}-${Date.now()}-combined.pdf`;
|
|
187
|
-
const bucket = runtimeConfig.get('AWS_S3_BUCKET_NAME');
|
|
188
|
-
if (bucket) {
|
|
189
|
-
await AWS.uploadBufferToS3(pdfBuffer, bucket, key, 'application/pdf');
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (processedFiles && processedFiles.length) {
|
|
194
|
-
await cleanupFiles(processedFiles);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
175
|
if (!patientMsg || thread.stopped) {
|
|
199
176
|
logger.info('[preProcessMessages] Skipping AI processing', { patientMsg, stopped: thread.stopped, code });
|
|
200
177
|
return { shouldProcess: false, messages: null, timings };
|