@peopl-health/nexus 2.5.5 → 2.5.6-fix-switch
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/assistants/BaseAssistant.js +11 -5
- package/lib/controllers/assistantController.js +1 -4
- package/lib/controllers/conversationController.js +1 -2
- package/lib/helpers/assistantHelper.js +30 -117
- package/lib/models/messageModel.js +1 -0
- package/lib/providers/OpenAIAssistantsProvider.js +122 -121
- package/lib/providers/OpenAIResponsesProvider.js +168 -398
- package/lib/providers/OpenAIResponsesProviderTools.js +49 -96
- package/lib/services/airtableService.js +1 -1
- package/lib/services/assistantServiceCore.js +128 -40
- package/lib/templates/templateStructure.js +1 -1
- package/lib/utils/retryHelper.js +3 -2
- package/package.json +1 -1
|
@@ -2,144 +2,97 @@ const { logger } = require('../utils/logger');
|
|
|
2
2
|
|
|
3
3
|
async function executeFunctionCall(assistant, call, metadata = {}) {
|
|
4
4
|
const startTime = Date.now();
|
|
5
|
+
const name = call.name;
|
|
6
|
+
const args = call.arguments ? JSON.parse(call.arguments) : {};
|
|
7
|
+
|
|
8
|
+
let result, success = true;
|
|
5
9
|
try {
|
|
6
|
-
|
|
7
|
-
const args = call.arguments ? JSON.parse(call.arguments) : {};
|
|
8
|
-
const result = await assistant.executeTool(name, args);
|
|
9
|
-
const executionTime = Date.now() - startTime;
|
|
10
|
-
|
|
11
|
-
const toolData = {
|
|
12
|
-
tool_name: name,
|
|
13
|
-
tool_arguments: args,
|
|
14
|
-
tool_output: result,
|
|
15
|
-
execution_time_ms: executionTime,
|
|
16
|
-
success: true,
|
|
17
|
-
call_id: call.call_id,
|
|
18
|
-
executed_at: new Date()
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
functionOutput: {
|
|
23
|
-
type: 'function_call_output',
|
|
24
|
-
call_id: call.call_id,
|
|
25
|
-
output: typeof result === 'string' ? result : JSON.stringify(result)
|
|
26
|
-
},
|
|
27
|
-
toolData
|
|
28
|
-
};
|
|
10
|
+
result = await assistant.executeTool(name, args);
|
|
29
11
|
} catch (error) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const toolData = {
|
|
33
|
-
tool_name: call.name,
|
|
34
|
-
tool_arguments: call.arguments ? JSON.parse(call.arguments) : {},
|
|
35
|
-
tool_output: { error: error?.message || 'Tool execution failed' },
|
|
36
|
-
execution_time_ms: executionTime,
|
|
37
|
-
success: false,
|
|
38
|
-
call_id: call.call_id,
|
|
39
|
-
executed_at: new Date()
|
|
40
|
-
};
|
|
41
|
-
|
|
12
|
+
result = { error: error?.message || 'Tool execution failed' };
|
|
13
|
+
success = false;
|
|
42
14
|
logger.error('[OpenAIResponsesProvider] Tool execution failed', error);
|
|
43
|
-
return {
|
|
44
|
-
functionOutput: {
|
|
45
|
-
type: 'function_call_output',
|
|
46
|
-
call_id: call.call_id,
|
|
47
|
-
output: JSON.stringify({ success: false, error: error?.message || 'Tool execution failed' })
|
|
48
|
-
},
|
|
49
|
-
toolData
|
|
50
|
-
};
|
|
51
15
|
}
|
|
16
|
+
|
|
17
|
+
const toolData = {
|
|
18
|
+
tool_name: name,
|
|
19
|
+
tool_arguments: args,
|
|
20
|
+
tool_output: result,
|
|
21
|
+
execution_time_ms: Date.now() - startTime,
|
|
22
|
+
success,
|
|
23
|
+
call_id: call.call_id,
|
|
24
|
+
executed_at: new Date()
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
functionOutput: {
|
|
29
|
+
type: 'function_call_output',
|
|
30
|
+
call_id: call.call_id,
|
|
31
|
+
output: success && typeof result === 'string' ? result : JSON.stringify(success ? result : { success: false, error: result.error })
|
|
32
|
+
},
|
|
33
|
+
toolData
|
|
34
|
+
};
|
|
52
35
|
}
|
|
53
36
|
|
|
54
37
|
/**
|
|
55
|
-
* Handle
|
|
56
|
-
* @param {Object} assistant - The assistant instance with executeTool method
|
|
57
|
-
* @param {Array} conversationItems - Array of conversation items
|
|
58
|
-
* @returns {Promise<Array>} Array of function call outputs
|
|
38
|
+
* Handle function calls from conversation items or run output
|
|
59
39
|
*/
|
|
60
|
-
async function
|
|
61
|
-
|
|
62
|
-
const functionOutputs = conversationItems.filter(item => item.type === 'function_call_output');
|
|
63
|
-
|
|
64
|
-
const orphanedCalls = pendingFunctionCalls.filter(call =>
|
|
65
|
-
!functionOutputs.some(output => output.call_id === call.call_id)
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
if (orphanedCalls.length === 0) {
|
|
40
|
+
async function handleFunctionCalls(functionCalls, assistant, metadata = {}) {
|
|
41
|
+
if (!functionCalls || functionCalls.length === 0) {
|
|
69
42
|
return { outputs: [], toolsExecuted: [] };
|
|
70
43
|
}
|
|
71
44
|
|
|
72
|
-
logger.info(`[OpenAIResponsesProvider] Found ${orphanedCalls.length} pending function calls, handling them...`);
|
|
73
45
|
const outputs = [];
|
|
74
46
|
const toolsExecuted = [];
|
|
75
47
|
|
|
76
|
-
for (const call of
|
|
48
|
+
for (const call of functionCalls) {
|
|
77
49
|
const result = await executeFunctionCall(assistant, call, metadata);
|
|
78
50
|
outputs.push(result.functionOutput);
|
|
79
51
|
toolsExecuted.push(result.toolData);
|
|
80
52
|
}
|
|
81
|
-
|
|
53
|
+
|
|
82
54
|
return { outputs, toolsExecuted };
|
|
83
55
|
}
|
|
84
56
|
|
|
85
57
|
/**
|
|
86
|
-
* Handle function calls from
|
|
87
|
-
* @param {Object} assistant - The assistant instance with executeTool method
|
|
88
|
-
* @param {Object} run - The run object with output array
|
|
89
|
-
* @returns {Promise<Array>} Array of function call outputs
|
|
58
|
+
* Handle pending function calls from conversation items
|
|
90
59
|
*/
|
|
91
|
-
async function
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const outputs = [];
|
|
98
|
-
const toolsExecuted = [];
|
|
60
|
+
async function handlePendingFunctionCalls(assistant, conversationItems, metadata = {}) {
|
|
61
|
+
const outputCallIds = new Set(
|
|
62
|
+
conversationItems.filter(item => item.type === 'function_call_output').map(output => output.call_id)
|
|
63
|
+
);
|
|
99
64
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
65
|
+
const orphanedCalls = conversationItems.filter(item =>
|
|
66
|
+
item.type === 'function_call' && !outputCallIds.has(item.call_id)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (orphanedCalls.length > 0) {
|
|
70
|
+
logger.info(`[OpenAIResponsesProvider] Found ${orphanedCalls.length} pending function calls, handling them...`);
|
|
104
71
|
}
|
|
105
|
-
|
|
106
|
-
return
|
|
72
|
+
|
|
73
|
+
return handleFunctionCalls(orphanedCalls, assistant, metadata);
|
|
107
74
|
}
|
|
108
75
|
|
|
109
76
|
/**
|
|
110
77
|
* Transform tools from Assistants API format to Responses API format
|
|
111
|
-
* @param {string} variant - The API variant ('responses' or 'assistants')
|
|
112
|
-
* @param {Array} tools - Array of tool definitions
|
|
113
|
-
* @returns {Array|undefined} Transformed tools or undefined if empty
|
|
114
78
|
*/
|
|
115
79
|
function transformToolsForResponsesAPI(variant, tools) {
|
|
116
|
-
if (variant !== 'responses' || !
|
|
117
|
-
return Array.isArray(tools) && tools.length > 0 ? tools : undefined;
|
|
118
|
-
}
|
|
80
|
+
if (variant !== 'responses' || !tools?.length) return tools?.length ? tools : undefined;
|
|
119
81
|
|
|
120
82
|
return tools.map(tool => {
|
|
121
|
-
// If tool is in Assistants API format (type: 'function', function: {...})
|
|
122
83
|
if (tool.type === 'function' && tool.function) {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
description: tool.function.description,
|
|
127
|
-
parameters: tool.function.parameters
|
|
128
|
-
};
|
|
129
|
-
if (tool.function.strict !== undefined) {
|
|
130
|
-
transformed.strict = tool.function.strict;
|
|
131
|
-
}
|
|
84
|
+
const { name, description, parameters, strict } = tool.function;
|
|
85
|
+
const transformed = { name, type: 'function', description, parameters };
|
|
86
|
+
if (strict !== undefined) transformed.strict = strict;
|
|
132
87
|
return transformed;
|
|
133
88
|
}
|
|
134
|
-
// If already in Responses API format, return as-is
|
|
135
89
|
return tool;
|
|
136
90
|
});
|
|
137
91
|
}
|
|
138
92
|
|
|
139
93
|
module.exports = {
|
|
140
94
|
executeFunctionCall,
|
|
95
|
+
handleFunctionCalls,
|
|
141
96
|
handlePendingFunctionCalls,
|
|
142
|
-
handleRequiresAction,
|
|
143
97
|
transformToolsForResponsesAPI
|
|
144
98
|
};
|
|
145
|
-
|
|
@@ -7,7 +7,7 @@ async function addRecord(baseID, tableName, fields) {
|
|
|
7
7
|
if (!airtable) throw new Error('Airtable not configured. Set AIRTABLE_API_KEY');
|
|
8
8
|
const base = airtable.base(baseID);
|
|
9
9
|
const record = await base(tableName).create(fields);
|
|
10
|
-
logger.info('Record added at', tableName);
|
|
10
|
+
logger.info('Record added at', { tableName });
|
|
11
11
|
return record;
|
|
12
12
|
} catch (error) {
|
|
13
13
|
logger.error('Error adding record:', error);
|
|
@@ -11,29 +11,68 @@ const { getCurRow, runAssistantWithRetries } = require('../helpers/assistantHelp
|
|
|
11
11
|
const { getThread } = require('../helpers/threadHelper.js');
|
|
12
12
|
const { processThreadMessage } = require('../helpers/processHelper.js');
|
|
13
13
|
const { getLastMessages, updateMessageRecord } = require('../helpers/messageHelper.js');
|
|
14
|
-
const { withThreadRecovery } = require('../helpers/threadRecoveryHelper.js');
|
|
15
14
|
const { combineImagesToPDF, cleanupFiles } = require('../helpers/filesHelper.js');
|
|
16
15
|
const { getAssistantById } = require('./assistantResolver');
|
|
17
16
|
const { logger } = require('../utils/logger');
|
|
18
17
|
|
|
19
|
-
const createAssistantCore = async (code, assistant_id) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
18
|
+
const createAssistantCore = async (code, assistant_id, messages = [], force = false) => {
|
|
19
|
+
const findThread = await Thread.findOne({ code: code });
|
|
20
|
+
logger.info('[createAssistantCore] findThread', findThread);
|
|
21
|
+
|
|
22
|
+
if (findThread && findThread.getConversationId() && !force) {
|
|
23
|
+
logger.info('[createAssistantCore] Thread already exists');
|
|
24
|
+
const updateFields = { active: true, stopped: false };
|
|
25
|
+
Thread.setAssistantId(updateFields, assistant_id);
|
|
26
|
+
await Thread.updateOne({ code: code }, { $set: updateFields });
|
|
27
|
+
return { success: true, assistant_id, thread: findThread };
|
|
28
|
+
}
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
if (force && findThread?.getConversationId()) {
|
|
31
|
+
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
32
|
+
await provider.deleteConversation(findThread.getConversationId());
|
|
33
|
+
logger.info('[createAssistantCore] Deleted old conversation, will create new one');
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
const curRow = await getCurRow(Historial_Clinico_ID, code);
|
|
25
|
-
|
|
37
|
+
logger.info('[createAssistantCore] curRow', curRow[0]);
|
|
38
|
+
const nombre = curRow?.[0]?.['name'] || null;
|
|
39
|
+
const patientId = curRow?.[0]?.['record_id'] || null;
|
|
26
40
|
|
|
27
41
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
const assistant = getAssistantById(assistant_id, null);
|
|
43
|
+
const initialThread = await assistant.create(code, curRow[0]);
|
|
44
|
+
|
|
45
|
+
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
46
|
+
for (const message of messages) {
|
|
47
|
+
await provider.addMessage({
|
|
48
|
+
threadId: initialThread.id,
|
|
49
|
+
role: 'assistant',
|
|
50
|
+
content: message
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const thread = {
|
|
55
|
+
code: code,
|
|
56
|
+
patient_id: patientId,
|
|
57
|
+
nombre: nombre,
|
|
58
|
+
active: true
|
|
59
|
+
};
|
|
60
|
+
Thread.setAssistantId(thread, assistant_id);
|
|
61
|
+
Thread.setConversationId(thread, initialThread.id);
|
|
62
|
+
|
|
63
|
+
const condition = { $or: [{ code: code }] };
|
|
64
|
+
const options = { new: true, upsert: true };
|
|
65
|
+
const updatedThread = await Thread.findOneAndUpdate(condition, { run_id: null, ...thread }, options);
|
|
66
|
+
logger.info('[createAssistantCore] Updated thread:', updatedThread);
|
|
67
|
+
|
|
68
|
+
return { success: true, assistant_id, thread: updatedThread };
|
|
30
69
|
} catch (error) {
|
|
31
70
|
logger.error('[createAssistantCore] Error creating assistant', { error: error.message, assistant_id, code });
|
|
32
|
-
|
|
71
|
+
throw error;
|
|
33
72
|
}
|
|
34
73
|
};
|
|
35
74
|
|
|
36
|
-
const addMsgAssistantCore = async (code,
|
|
75
|
+
const addMsgAssistantCore = async (code, inMessages, role = 'user', reply = false, skipSystemMessage = false) => {
|
|
37
76
|
const thread = await getThread(code);
|
|
38
77
|
if (!thread) return null;
|
|
39
78
|
|
|
@@ -41,16 +80,43 @@ const addMsgAssistantCore = async (code, message, role = 'user', reply = false)
|
|
|
41
80
|
const threadId = thread.getConversationId();
|
|
42
81
|
|
|
43
82
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
83
|
+
const messages = Array.isArray(inMessages) ? inMessages : [inMessages];
|
|
84
|
+
|
|
85
|
+
await provider.addMessage({
|
|
86
|
+
threadId,
|
|
87
|
+
messages: messages.map(message => ({ role, content: message }))
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Save system messages to database for frontend visibility
|
|
91
|
+
if (!skipSystemMessage) {
|
|
92
|
+
for (let i = 0; i < messages.length; i++) {
|
|
93
|
+
const message = messages[i];
|
|
94
|
+
try {
|
|
95
|
+
const message_id = `system_${Date.now()}_${i}_${Math.random().toString(36).substring(7)}`;
|
|
96
|
+
await insertMessage({
|
|
97
|
+
nombre_whatsapp: 'System',
|
|
98
|
+
numero: code,
|
|
99
|
+
body: message,
|
|
100
|
+
message_id: message_id,
|
|
101
|
+
is_group: false,
|
|
102
|
+
is_media: false,
|
|
103
|
+
from_me: true,
|
|
104
|
+
processed: true,
|
|
105
|
+
origin: 'system',
|
|
106
|
+
assistant_id: thread.getAssistantId(),
|
|
107
|
+
raw: { role: role }
|
|
108
|
+
});
|
|
109
|
+
} catch (err) {
|
|
110
|
+
logger.error('[addMsgAssistantCore] Error saving system message:', err);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
51
113
|
}
|
|
52
|
-
|
|
53
|
-
return null;
|
|
114
|
+
|
|
115
|
+
if (!reply) return null;
|
|
116
|
+
|
|
117
|
+
const assistant = getAssistantById(thread.getAssistantId(), thread);
|
|
118
|
+
const runResult = await runAssistantWithRetries(thread, assistant, {});
|
|
119
|
+
return runResult?.output || null;
|
|
54
120
|
} catch (error) {
|
|
55
121
|
logger.error('[addMsgAssistantCore] Error adding message', { error: error.message, code, role });
|
|
56
122
|
return null;
|
|
@@ -61,13 +127,36 @@ const addInstructionCore = async (code, instruction, role = 'user') => {
|
|
|
61
127
|
const thread = await getThread(code);
|
|
62
128
|
if (!thread) return null;
|
|
63
129
|
|
|
64
|
-
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
65
|
-
const threadId = thread.getConversationId();
|
|
66
|
-
|
|
67
130
|
try {
|
|
68
|
-
await provider.addMessage({ threadId, messages: [{ role, content: instruction }] });
|
|
69
131
|
const assistant = getAssistantById(thread.getAssistantId(), thread);
|
|
70
|
-
const runResult = await runAssistantWithRetries(thread, assistant, {
|
|
132
|
+
const runResult = await runAssistantWithRetries(thread, assistant, {
|
|
133
|
+
additionalInstructions: instruction,
|
|
134
|
+
additionalMessages: [
|
|
135
|
+
{ role: role, content: instruction }
|
|
136
|
+
]
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Save instruction message to database for frontend visibility
|
|
140
|
+
try {
|
|
141
|
+
const message_id = `instruction_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
142
|
+
await insertMessage({
|
|
143
|
+
nombre_whatsapp: 'Instruction',
|
|
144
|
+
numero: code,
|
|
145
|
+
body: instruction,
|
|
146
|
+
message_id: message_id,
|
|
147
|
+
is_group: false,
|
|
148
|
+
is_media: false,
|
|
149
|
+
from_me: true,
|
|
150
|
+
processed: true,
|
|
151
|
+
origin: 'instruction',
|
|
152
|
+
assistant_id: thread.getAssistantId(),
|
|
153
|
+
raw: { role: role }
|
|
154
|
+
});
|
|
155
|
+
} catch (err) {
|
|
156
|
+
logger.error('[addInstructionCore] Error saving instruction message:', err);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
logger.info('[addInstructionCore] Run response', { output: runResult?.output });
|
|
71
160
|
return runResult?.output || null;
|
|
72
161
|
} catch (error) {
|
|
73
162
|
logger.error('[addInstructionCore] Error adding instruction', { error: error.message, code, role });
|
|
@@ -80,14 +169,16 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
80
169
|
const startTotal = Date.now();
|
|
81
170
|
|
|
82
171
|
try {
|
|
172
|
+
const threadStart = Date.now();
|
|
83
173
|
const thread = thread_ || await getThread(code);
|
|
84
|
-
timings.get_thread_ms =
|
|
174
|
+
timings.get_thread_ms = Date.now() - threadStart;
|
|
85
175
|
|
|
86
176
|
if (!thread) return null;
|
|
87
177
|
const finalThread = thread;
|
|
88
178
|
|
|
179
|
+
const messagesStart = Date.now();
|
|
89
180
|
const patientReply = await getLastMessages(code);
|
|
90
|
-
timings.get_messages_ms =
|
|
181
|
+
timings.get_messages_ms = Date.now() - messagesStart;
|
|
91
182
|
|
|
92
183
|
if (!patientReply) {
|
|
93
184
|
logger.info('[replyAssistantCore] No relevant data found for this assistant.');
|
|
@@ -96,10 +187,11 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
96
187
|
|
|
97
188
|
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
98
189
|
logger.info(`[replyAssistantCore] Processing ${patientReply.length} messages in parallel`);
|
|
190
|
+
const processStart = Date.now();
|
|
99
191
|
const processResult = await processThreadMessage(code, patientReply, provider);
|
|
100
192
|
|
|
101
193
|
const { results: processResults, timings: processTimings } = processResult;
|
|
102
|
-
timings.process_messages_ms =
|
|
194
|
+
timings.process_messages_ms = Date.now() - processStart;
|
|
103
195
|
|
|
104
196
|
logger.debug('[replyAssistantCore] Process timings breakdown', { processTimings });
|
|
105
197
|
|
|
@@ -120,14 +212,8 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
120
212
|
|
|
121
213
|
if (allMessagesToAdd.length > 0) {
|
|
122
214
|
logger.info(`[replyAssistantCore] Adding ${allMessagesToAdd.length} messages to thread in batch`);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const threadId = thread.getConversationId();
|
|
126
|
-
await provider.addMessage({ threadId, messages: allMessagesToAdd });
|
|
127
|
-
},
|
|
128
|
-
finalThread,
|
|
129
|
-
process.env.VARIANT || 'assistants'
|
|
130
|
-
);
|
|
215
|
+
const threadId = finalThread.getConversationId();
|
|
216
|
+
await provider.addMessage({ threadId, messages: allMessagesToAdd });
|
|
131
217
|
}
|
|
132
218
|
|
|
133
219
|
await Promise.all(processResults.map(r => updateMessageRecord(r.reply, finalThread)));
|
|
@@ -135,8 +221,9 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
135
221
|
|
|
136
222
|
if (urls.length > 0) {
|
|
137
223
|
logger.info(`[replyAssistantCore] Processing ${urls.length} URLs for PDF combination`);
|
|
224
|
+
const pdfStart = Date.now();
|
|
138
225
|
const pdfResult = await combineImagesToPDF({ code });
|
|
139
|
-
timings.pdf_combination_ms =
|
|
226
|
+
timings.pdf_combination_ms = Date.now() - pdfStart;
|
|
140
227
|
const { pdfBuffer, processedFiles } = pdfResult;
|
|
141
228
|
logger.info(`[replyAssistantCore] PDF combination complete: ${processedFiles?.length || 0} files processed`);
|
|
142
229
|
|
|
@@ -156,11 +243,12 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
156
243
|
if (!patientMsg || finalThread.stopped) return null;
|
|
157
244
|
|
|
158
245
|
const assistant = getAssistantById(finalThread.getAssistantId(), finalThread);
|
|
246
|
+
const runStart = Date.now();
|
|
159
247
|
const runResult = await runAssistantWithRetries(finalThread, assistant, runOptions, patientReply);
|
|
160
|
-
timings.run_assistant_ms =
|
|
248
|
+
timings.run_assistant_ms = Date.now() - runStart;
|
|
161
249
|
timings.total_ms = Date.now() - startTotal;
|
|
162
250
|
|
|
163
|
-
const {
|
|
251
|
+
const { output, completed, retries, predictionTimeMs, tools_executed } = runResult;
|
|
164
252
|
|
|
165
253
|
logger.info('[Assistant Reply Complete]', {
|
|
166
254
|
code: code ? `${code.substring(0, 3)}***${code.slice(-4)}` : 'unknown',
|
|
@@ -211,11 +299,11 @@ const switchAssistantCore = async (code, assistant_id) => {
|
|
|
211
299
|
}
|
|
212
300
|
|
|
213
301
|
const updateFields = {
|
|
214
|
-
assistant_id,
|
|
215
302
|
stopped: false,
|
|
216
303
|
updatedAt: new Date()
|
|
217
304
|
};
|
|
218
|
-
|
|
305
|
+
Thread.setAssistantId(updateFields, assistant_id);
|
|
306
|
+
|
|
219
307
|
await Thread.updateOne({ code }, { $set: updateFields });
|
|
220
308
|
logger.info('[switchAssistantCore] Assistant switched', { code, assistant_id });
|
|
221
309
|
return { success: true, assistant_id };
|
package/lib/utils/retryHelper.js
CHANGED
|
@@ -117,7 +117,8 @@ async function retryWithBackoff(operation, options = {}) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
try {
|
|
120
|
-
|
|
120
|
+
const result = await operation();
|
|
121
|
+
return { result, retries: retryCount };
|
|
121
122
|
} catch (error) {
|
|
122
123
|
const isRateLimit = error?.status === 429 || error?.code === 'rate_limit_exceeded';
|
|
123
124
|
const isRetryable = isRetryableError(error);
|
|
@@ -148,7 +149,7 @@ async function retryWithBackoff(operation, options = {}) {
|
|
|
148
149
|
|
|
149
150
|
await sleep(waitTime * 1000);
|
|
150
151
|
|
|
151
|
-
return retryWithBackoff(operation, { maxRetries, retryCount: retryCount + 1, providerName });
|
|
152
|
+
return await retryWithBackoff(operation, { maxRetries, retryCount: retryCount + 1, providerName });
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
throw error;
|