@peopl-health/nexus 2.4.9 → 2.4.10
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.
- package/lib/controllers/conversationController.js +23 -6
- package/lib/helpers/assistantHelper.js +35 -28
- package/lib/helpers/filesHelper.js +100 -44
- package/lib/helpers/llmsHelper.js +24 -6
- package/lib/helpers/threadRecoveryHelper.js +58 -0
- package/lib/services/assistantService.js +33 -18
- package/package.json +1 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const mongoose = require('mongoose');
|
|
2
|
-
const { fetchConversationData, processConversations } = require('../services/conversationService');
|
|
3
|
-
const { sendMessage } = require('../core/NexusMessaging');
|
|
4
|
-
const { Thread } = require('../models/threadModel');
|
|
5
2
|
const llmConfig = require('../config/llmConfig');
|
|
6
3
|
const { Historial_Clinico_ID } = require('../config/airtableConfig');
|
|
4
|
+
const { Thread } = require('../models/threadModel');
|
|
5
|
+
const { withThreadRecovery } = require('../helpers/threadRecoveryHelper');
|
|
7
6
|
const { getRecordByFilter } = require('../services/airtableService');
|
|
7
|
+
const { fetchConversationData, processConversations } = require('../services/conversationService');
|
|
8
|
+
const { sendMessage } = require('../core/NexusMessaging');
|
|
8
9
|
const { logger } = require('../utils/logger');
|
|
9
10
|
|
|
10
11
|
const Message = mongoose.models.Message;
|
|
@@ -649,7 +650,7 @@ const getOpenAIThreadMessagesController = async (req, res) => {
|
|
|
649
650
|
});
|
|
650
651
|
}
|
|
651
652
|
|
|
652
|
-
|
|
653
|
+
let thread = await Thread.findOne({
|
|
653
654
|
code: phoneNumber,
|
|
654
655
|
active: true
|
|
655
656
|
}).sort({ createdAt: -1 });
|
|
@@ -662,7 +663,7 @@ const getOpenAIThreadMessagesController = async (req, res) => {
|
|
|
662
663
|
});
|
|
663
664
|
}
|
|
664
665
|
|
|
665
|
-
|
|
666
|
+
let conversationId = thread.conversation_id;
|
|
666
667
|
logger.info('Thread found - Conversation ID:', conversationId);
|
|
667
668
|
|
|
668
669
|
const provider = llmConfig.getOpenAIProvider({ instantiate: true, variant });
|
|
@@ -683,8 +684,23 @@ const getOpenAIThreadMessagesController = async (req, res) => {
|
|
|
683
684
|
logger.info('Including runId:', runId);
|
|
684
685
|
}
|
|
685
686
|
|
|
687
|
+
let messages;
|
|
688
|
+
let threadRecreated = false;
|
|
689
|
+
|
|
686
690
|
logger.info('Calling listMessages with params:', queryParams);
|
|
687
|
-
|
|
691
|
+
messages = await withThreadRecovery(
|
|
692
|
+
async (currentThread = thread) => {
|
|
693
|
+
if (currentThread !== thread) {
|
|
694
|
+
thread = currentThread;
|
|
695
|
+
conversationId = currentThread.getConversationId();
|
|
696
|
+
queryParams.threadId = conversationId;
|
|
697
|
+
threadRecreated = true;
|
|
698
|
+
}
|
|
699
|
+
return await provider.listMessages(queryParams);
|
|
700
|
+
},
|
|
701
|
+
thread,
|
|
702
|
+
variant
|
|
703
|
+
);
|
|
688
704
|
|
|
689
705
|
logger.info(`Retrieved ${messages?.data?.length || 0} messages from OpenAI`);
|
|
690
706
|
|
|
@@ -696,6 +712,7 @@ const getOpenAIThreadMessagesController = async (req, res) => {
|
|
|
696
712
|
assistantId: thread.assistant_id,
|
|
697
713
|
messages: messages.data || messages,
|
|
698
714
|
hasMore: messages.has_more || false,
|
|
715
|
+
threadRecreated,
|
|
699
716
|
pagination: {
|
|
700
717
|
limit: parseInt(limit),
|
|
701
718
|
order
|
|
@@ -3,6 +3,7 @@ const llmConfig = require('../config/llmConfig.js');
|
|
|
3
3
|
const { Thread } = require('../models/threadModel.js');
|
|
4
4
|
const { createProvider } = require('../providers/createProvider.js');
|
|
5
5
|
const { withTracing } = require('../utils/tracingDecorator');
|
|
6
|
+
const { withThreadRecovery } = require('./threadRecoveryHelper');
|
|
6
7
|
|
|
7
8
|
const { getRecordByFilter } = require('../services/airtableService.js');
|
|
8
9
|
const { logger } = require('../utils/logger');
|
|
@@ -62,37 +63,43 @@ const runAssistantAndWait = async ({
|
|
|
62
63
|
? { ...conversationConfig, assistant }
|
|
63
64
|
: conversationConfig;
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
66
|
+
return await withThreadRecovery(
|
|
67
|
+
async (currentThread = thread) => {
|
|
68
|
+
let run = await provider.runConversation({
|
|
69
|
+
threadId: currentThread.getConversationId(),
|
|
70
|
+
assistantId: currentThread.getAssistantId(),
|
|
71
|
+
tools: tools.length > 0 ? tools : undefined,
|
|
72
|
+
...runConfigWithAssistant,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const filter = currentThread.code ? { code: currentThread.code, active: true } : null;
|
|
76
|
+
if (filter) {
|
|
77
|
+
await Thread.updateOne(filter, { $set: { run_id: run.id } });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const maxRetries = polling?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
81
|
+
let completed = false;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
logger.info('[runAssistantAndWait] Run started', { runId: run.id, threadId: currentThread.getConversationId(), assistantId: currentThread.getAssistantId() });
|
|
85
|
+
({run, completed} = await provider.checkRunStatus(assistant, currentThread.getConversationId(), run.id, 0, maxRetries));
|
|
86
|
+
} finally {
|
|
87
|
+
if (filter) {
|
|
88
|
+
await Thread.updateOne(filter, { $set: { run_id: null } });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
if (!completed) {
|
|
93
|
+
return { run: run, completed: false, output: '' };
|
|
94
|
+
}
|
|
92
95
|
|
|
93
|
-
|
|
96
|
+
const output = await provider.getRunText({ threadId: currentThread.getConversationId(), runId: run.id, fallback: '' });
|
|
94
97
|
|
|
95
|
-
|
|
98
|
+
return { completed: true, output };
|
|
99
|
+
},
|
|
100
|
+
thread,
|
|
101
|
+
variant
|
|
102
|
+
);
|
|
96
103
|
};
|
|
97
104
|
|
|
98
105
|
const executeAssistantAttempt = async (thread, assistant, runConfig, attemptNumber) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { PDFDocument } = require('pdf-lib');
|
|
2
2
|
const { execFile } = require('child_process');
|
|
3
3
|
const fs = require('fs').promises;
|
|
4
|
+
const fsSync = require('fs');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const sharp = require('sharp');
|
|
6
7
|
|
|
@@ -9,11 +10,11 @@ const { Message } = require('../models/messageModel.js');
|
|
|
9
10
|
const { sanitizeFilename } = require('../utils/sanitizer.js');
|
|
10
11
|
const { logger } = require('../utils/logger');
|
|
11
12
|
|
|
12
|
-
async function convertPdfToImages(pdfName) {
|
|
13
|
+
async function convertPdfToImages(pdfName, existingPdfPath = null) {
|
|
13
14
|
const outputDir = path.join(__dirname, 'assets', 'tmp');
|
|
14
15
|
|
|
15
16
|
const sanitizedName = sanitizeFilename(pdfName);
|
|
16
|
-
const pdfPath = path.join(outputDir, `${sanitizedName}.pdf`);
|
|
17
|
+
const pdfPath = existingPdfPath || path.join(outputDir, `${sanitizedName}.pdf`);
|
|
17
18
|
const outputPattern = path.join(outputDir, sanitizedName);
|
|
18
19
|
|
|
19
20
|
await fs.mkdir(outputDir, { recursive: true });
|
|
@@ -22,22 +23,52 @@ async function convertPdfToImages(pdfName) {
|
|
|
22
23
|
const args = ['-jpeg', pdfPath, outputPattern];
|
|
23
24
|
logger.info('[convertPdfToImages] Running: pdftoppm', args.join(' '));
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
const timeout = 30000;
|
|
27
|
+
let timedOut = false;
|
|
28
|
+
|
|
29
|
+
const child = execFile('pdftoppm', args, { timeout, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
30
|
+
if (timedOut) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
if (error) {
|
|
35
|
+
logger.error('[convertPdfToImages] Error details:', {
|
|
36
|
+
error: error.message,
|
|
37
|
+
stderr,
|
|
38
|
+
pdfPath,
|
|
39
|
+
pdfExists: fsSync.existsSync(pdfPath),
|
|
40
|
+
killed: error.killed,
|
|
41
|
+
signal: error.signal
|
|
42
|
+
});
|
|
27
43
|
return reject(new Error(`Error splitting PDF: ${stderr || error.message}`));
|
|
28
44
|
}
|
|
29
45
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
logger.info('[convertPdfToImages] pdftoppm completed successfully');
|
|
47
|
+
|
|
48
|
+
fs.readdir(outputDir)
|
|
49
|
+
.then(files => {
|
|
50
|
+
const jpgFiles = files
|
|
51
|
+
.filter(file => file.startsWith(sanitizedName) && file.endsWith('.jpg'))
|
|
52
|
+
.map(file => path.join(outputDir, file));
|
|
53
|
+
|
|
54
|
+
logger.info(`[convertPdfToImages] Found ${jpgFiles.length} image files`);
|
|
55
|
+
resolve(jpgFiles);
|
|
56
|
+
})
|
|
57
|
+
.catch(err => {
|
|
58
|
+
logger.error('[convertPdfToImages] Error reading output directory:', { error: err.message });
|
|
59
|
+
reject(new Error(`Error reading output directory: ${err.message}`));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
34
62
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
63
|
+
const timeoutId = setTimeout(() => {
|
|
64
|
+
timedOut = true;
|
|
65
|
+
child.kill('SIGTERM');
|
|
66
|
+
logger.error('[convertPdfToImages] Process timed out after 30 seconds', { pdfPath });
|
|
67
|
+
reject(new Error('PDF conversion timed out after 30 seconds'));
|
|
68
|
+
}, timeout);
|
|
38
69
|
|
|
39
|
-
|
|
40
|
-
|
|
70
|
+
child.on('exit', () => {
|
|
71
|
+
clearTimeout(timeoutId);
|
|
41
72
|
});
|
|
42
73
|
});
|
|
43
74
|
}
|
|
@@ -130,44 +161,69 @@ const cleanupFiles = async (files) => {
|
|
|
130
161
|
};
|
|
131
162
|
|
|
132
163
|
async function downloadMediaAndCreateFile(code, reply) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
164
|
+
try {
|
|
165
|
+
const resultMedia = await Message.findOne({
|
|
166
|
+
message_id: reply.message_id,
|
|
167
|
+
timestamp: reply.timestamp,
|
|
168
|
+
media: { $ne: null }
|
|
169
|
+
});
|
|
138
170
|
|
|
139
|
-
|
|
171
|
+
if (!resultMedia) return [];
|
|
140
172
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
173
|
+
if (!resultMedia.media || !resultMedia.media.key) {
|
|
174
|
+
logger.info('[downloadMediaAndCreateFile] No valid media found for message:', reply.message_id);
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
145
177
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
178
|
+
const { bucketName, key } = resultMedia.media;
|
|
179
|
+
if (!bucketName || !key) return [];
|
|
180
|
+
|
|
181
|
+
const [subType, fileName] = key.split('/');
|
|
182
|
+
|
|
183
|
+
const sanitizedCode = sanitizeFilename(code, 20);
|
|
184
|
+
const sanitizedSubType = sanitizeFilename(subType, 10);
|
|
185
|
+
const sanitizedFileName = sanitizeFilename(fileName, 50);
|
|
186
|
+
|
|
187
|
+
const sourceFile = `${sanitizedCode}-${sanitizedSubType}-${sanitizedFileName}`;
|
|
188
|
+
const downloadPath = path.join(__dirname, 'assets', 'tmp', sourceFile);
|
|
189
|
+
|
|
190
|
+
logger.info('[downloadMediaAndCreateFile] Downloading file', { sourceFile, downloadPath, bucketName, key });
|
|
191
|
+
|
|
192
|
+
await fs.mkdir(path.dirname(downloadPath), { recursive: true });
|
|
193
|
+
await downloadFileFromS3(bucketName, key, downloadPath);
|
|
160
194
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
195
|
+
const { name: baseName } = path.parse(sourceFile);
|
|
196
|
+
let fileNames = [];
|
|
197
|
+
|
|
198
|
+
if (subType === 'document' || subType === 'application') {
|
|
199
|
+
try {
|
|
200
|
+
fileNames = await convertPdfToImages(baseName, downloadPath);
|
|
201
|
+
logger.info('[downloadMediaAndCreateFile] PDF converted successfully', { imageCount: fileNames.length });
|
|
202
|
+
} catch (conversionError) {
|
|
203
|
+
logger.error('[downloadMediaAndCreateFile] PDF conversion failed:', {
|
|
204
|
+
error: conversionError.message,
|
|
205
|
+
sourceFile
|
|
206
|
+
});
|
|
207
|
+
fileNames = [];
|
|
208
|
+
} finally {
|
|
209
|
+
try {
|
|
210
|
+
await fs.unlink(downloadPath);
|
|
211
|
+
} catch (unlinkError) {
|
|
212
|
+
logger.warn('[downloadMediaAndCreateFile] Failed to delete PDF:', { error: unlinkError.message });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
fileNames = [downloadPath];
|
|
217
|
+
}
|
|
165
218
|
|
|
166
|
-
|
|
167
|
-
|
|
219
|
+
return fileNames;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
logger.error('[downloadMediaAndCreateFile] Error processing media:', {
|
|
222
|
+
error: error.message,
|
|
223
|
+
message_id: reply.message_id
|
|
224
|
+
});
|
|
225
|
+
return [];
|
|
168
226
|
}
|
|
169
|
-
|
|
170
|
-
return fileNames;
|
|
171
227
|
}
|
|
172
228
|
|
|
173
229
|
module.exports = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const llmConfig = require('../config/llmConfig.js');
|
|
2
2
|
const { logger } = require('../utils/logger');
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
const
|
|
4
|
+
const path = require('path');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
async function analyzeImage(imagePath, isSticker = false, contentType = null) {
|
|
@@ -30,14 +30,30 @@ async function analyzeImage(imagePath, isSticker = false, contentType = null) {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// Determine mime type from file extension
|
|
33
34
|
let mimeType = contentType;
|
|
34
35
|
if (!mimeType) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
const ext = path.extname(imagePath).toLowerCase();
|
|
37
|
+
const mimeMap = {
|
|
38
|
+
'.jpg': 'image/jpeg',
|
|
39
|
+
'.jpeg': 'image/jpeg',
|
|
40
|
+
'.png': 'image/png',
|
|
41
|
+
'.gif': 'image/gif',
|
|
42
|
+
'.webp': 'image/webp'
|
|
43
|
+
};
|
|
44
|
+
mimeType = mimeMap[ext] || 'image/jpeg'; // Default to jpeg for pdftoppm output
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Validate that mime type is supported by Claude
|
|
48
|
+
const supportedMimeTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
|
49
|
+
if (!supportedMimeTypes.includes(mimeType)) {
|
|
50
|
+
logger.warn('[analyzeImage] Unsupported mime type, defaulting to image/jpeg:', {
|
|
51
|
+
originalMimeType: mimeType,
|
|
52
|
+
imagePath
|
|
53
|
+
});
|
|
54
|
+
mimeType = 'image/jpeg';
|
|
40
55
|
}
|
|
56
|
+
|
|
41
57
|
if (mimeType === 'image/vnd.wap.wbmp') {
|
|
42
58
|
logger.info('Skipping image with MIME type:', mimeType);
|
|
43
59
|
return {
|
|
@@ -49,6 +65,7 @@ async function analyzeImage(imagePath, isSticker = false, contentType = null) {
|
|
|
49
65
|
};
|
|
50
66
|
}
|
|
51
67
|
// Read the image file and convert to base64
|
|
68
|
+
logger.info('[analyzeImage] Reading image file:', { imagePath: imagePath.split('/').pop() });
|
|
52
69
|
const imageBuffer = await fs.promises.readFile(imagePath);
|
|
53
70
|
const base64Image = imageBuffer.toString('base64');
|
|
54
71
|
|
|
@@ -77,6 +94,7 @@ async function analyzeImage(imagePath, isSticker = false, contentType = null) {
|
|
|
77
94
|
},
|
|
78
95
|
],
|
|
79
96
|
});
|
|
97
|
+
logger.info('[analyzeImage] Description received');
|
|
80
98
|
const description = messageDescription.content[0].text;
|
|
81
99
|
|
|
82
100
|
// For stickers, skip medical analysis and table extraction
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const { Thread } = require('../models/threadModel');
|
|
2
|
+
const { createProvider } = require('../providers/createProvider');
|
|
3
|
+
const { logger } = require('../utils/logger');
|
|
4
|
+
|
|
5
|
+
const isThreadNotFoundError = (error) => {
|
|
6
|
+
return error?.status === 404 ||
|
|
7
|
+
error?.code === 'thread_not_found' ||
|
|
8
|
+
error?.code === 'invalid_thread_id' ||
|
|
9
|
+
(error?.message && (
|
|
10
|
+
error.message.includes('No thread found') ||
|
|
11
|
+
error.message.includes('thread does not exist') ||
|
|
12
|
+
error.message.includes('Invalid thread')
|
|
13
|
+
));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const recreateThread = async (thread, variant = 'assistants') => {
|
|
17
|
+
const provider = createProvider({ variant });
|
|
18
|
+
const newConversation = await provider.createConversation({
|
|
19
|
+
metadata: { phoneNumber: thread.code, recreated: true }
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
await Thread.updateOne(
|
|
23
|
+
{ code: thread.code, active: true },
|
|
24
|
+
{ $set: { conversation_id: newConversation.id } }
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const updatedThread = await Thread.findOne({ code: thread.code, active: true });
|
|
28
|
+
logger.info('[threadRecovery] Thread recreated', {
|
|
29
|
+
oldId: thread.conversation_id,
|
|
30
|
+
newId: newConversation.id,
|
|
31
|
+
code: thread.code
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return updatedThread;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const withThreadRecovery = async (operation, thread, variant = 'assistants') => {
|
|
38
|
+
try {
|
|
39
|
+
return await operation();
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (isThreadNotFoundError(error) && thread) {
|
|
42
|
+
logger.warn('[threadRecovery] Thread not found, recreating', {
|
|
43
|
+
conversationId: thread.getConversationId?.() || thread.conversation_id,
|
|
44
|
+
code: thread.code
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const recoveredThread = await recreateThread(thread, variant);
|
|
48
|
+
return await operation(recoveredThread);
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
isThreadNotFoundError,
|
|
56
|
+
recreateThread,
|
|
57
|
+
withThreadRecovery
|
|
58
|
+
};
|
|
@@ -14,6 +14,7 @@ const { getThread, getThreadInfo } = require('../helpers/threadHelper.js');
|
|
|
14
14
|
const { withTracing } = require('../utils/tracingDecorator.js');
|
|
15
15
|
const { processThreadMessage } = require('../helpers/processHelper.js');
|
|
16
16
|
const { getLastMessages, updateMessageRecord } = require('../helpers/messageHelper.js');
|
|
17
|
+
const { withThreadRecovery } = require('../helpers/threadRecoveryHelper.js');
|
|
17
18
|
const { combineImagesToPDF, cleanupFiles } = require('../helpers/filesHelper.js');
|
|
18
19
|
const { logger } = require('../utils/logger');
|
|
19
20
|
|
|
@@ -137,13 +138,19 @@ const getAssistantById = (assistant_id, thread) => {
|
|
|
137
138
|
const createAssistant = async (code, assistant_id, messages=[], force=false) => {
|
|
138
139
|
const findThread = await Thread.findOne({ code: code });
|
|
139
140
|
logger.info('[createAssistant] findThread', findThread);
|
|
140
|
-
if (findThread && findThread.getConversationId()) {
|
|
141
|
+
if (findThread && findThread.getConversationId() && !force) {
|
|
141
142
|
logger.info('[createAssistant] Thread already exists');
|
|
142
143
|
const updateFields = { active: true, stopped: false };
|
|
143
144
|
Thread.setAssistantId(updateFields, assistant_id);
|
|
144
145
|
await Thread.updateOne({ code: code }, { $set: updateFields });
|
|
145
146
|
return findThread;
|
|
146
147
|
}
|
|
148
|
+
|
|
149
|
+
if (force && findThread?.getConversationId()) {
|
|
150
|
+
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
151
|
+
await provider.deleteConversation(findThread.getConversationId());
|
|
152
|
+
logger.info('[createAssistant] Deleted old conversation, will create new one');
|
|
153
|
+
}
|
|
147
154
|
|
|
148
155
|
const curRow = await getCurRow(Historial_Clinico_ID, code);
|
|
149
156
|
logger.info('[createAssistant] curRow', curRow[0]);
|
|
@@ -153,7 +160,6 @@ const createAssistant = async (code, assistant_id, messages=[], force=false) =>
|
|
|
153
160
|
const assistant = getAssistantById(assistant_id, null);
|
|
154
161
|
const initialThread = await assistant.create(code, curRow[0]);
|
|
155
162
|
|
|
156
|
-
// Add new messages to memory
|
|
157
163
|
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
158
164
|
for (const message of messages) {
|
|
159
165
|
await provider.addMessage({
|
|
@@ -177,29 +183,32 @@ const createAssistant = async (code, assistant_id, messages=[], force=false) =>
|
|
|
177
183
|
const updatedThread = await Thread.findOneAndUpdate(condition, {run_id: null, ...thread}, options);
|
|
178
184
|
logger.info('[createAssistant] Updated thread:', updatedThread);
|
|
179
185
|
|
|
180
|
-
// Delete previous thread
|
|
181
|
-
if (force) {
|
|
182
|
-
await provider.deleteConversation(findThread.getConversationId());
|
|
183
|
-
}
|
|
184
|
-
|
|
185
186
|
return thread;
|
|
186
187
|
};
|
|
187
188
|
|
|
188
189
|
const addMsgAssistant = async (code, inMessages, role = 'user', reply = false) => {
|
|
189
190
|
try {
|
|
190
|
-
|
|
191
|
+
let thread = await Thread.findOne({ code: code });
|
|
191
192
|
logger.info(thread);
|
|
192
193
|
if (thread === null) return null;
|
|
193
194
|
|
|
194
195
|
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
196
|
+
|
|
197
|
+
await withThreadRecovery(
|
|
198
|
+
async (recoveredThread = thread) => {
|
|
199
|
+
thread = recoveredThread;
|
|
200
|
+
for (const message of inMessages) {
|
|
201
|
+
logger.info(message);
|
|
202
|
+
await provider.addMessage({
|
|
203
|
+
threadId: thread.getConversationId(),
|
|
204
|
+
role: role,
|
|
205
|
+
content: message
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
thread,
|
|
210
|
+
process.env.VARIANT || 'assistants'
|
|
211
|
+
);
|
|
203
212
|
|
|
204
213
|
if (!reply) return null;
|
|
205
214
|
|
|
@@ -322,9 +331,15 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
322
331
|
const allTempFiles = processResults.flatMap(r => r.tempFiles || []);
|
|
323
332
|
|
|
324
333
|
if (allMessagesToAdd.length > 0) {
|
|
325
|
-
const threadId = finalThread.getConversationId();
|
|
326
334
|
logger.info(`[replyAssistantCore] Adding ${allMessagesToAdd.length} messages to thread in batch`);
|
|
327
|
-
await
|
|
335
|
+
await withThreadRecovery(
|
|
336
|
+
async (thread = finalThread) => {
|
|
337
|
+
const threadId = thread.getConversationId();
|
|
338
|
+
await provider.addMessage({ threadId, messages: allMessagesToAdd });
|
|
339
|
+
},
|
|
340
|
+
finalThread,
|
|
341
|
+
process.env.VARIANT || 'assistants'
|
|
342
|
+
);
|
|
328
343
|
}
|
|
329
344
|
|
|
330
345
|
await Promise.all(processResults.map(r => updateMessageRecord(r.reply, finalThread)));
|