@peopl-health/nexus 3.8.4 → 3.8.6
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/patientController.js +42 -2
- package/lib/eval/EvalProvider.js +15 -10
- package/lib/index.js +1 -0
- package/lib/providers/OpenAIResponsesProvider.js +15 -10
- package/lib/routes/index.js +3 -1
- package/lib/services/promptComposerService.js +6 -3
- package/lib/services/toolRegistryService.js +77 -0
- package/package.json +1 -1
|
@@ -2,7 +2,9 @@ const { Monitoreo_ID, Symptoms_ID } = require('../config/airtableConfig');
|
|
|
2
2
|
|
|
3
3
|
const { logger } = require('../utils/logger');
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { Thread } = require('../models/threadModel');
|
|
6
|
+
|
|
7
|
+
const { getRecordByFilter, updateRecordByFilter } = require('../services/airtableService');
|
|
6
8
|
|
|
7
9
|
const getPatientInfoController = async (req, res) => {
|
|
8
10
|
try {
|
|
@@ -87,4 +89,42 @@ const getPatientTriageInfoController = async (req, res) => {
|
|
|
87
89
|
}
|
|
88
90
|
};
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
const updatePatientController = async (req, res) => {
|
|
93
|
+
try {
|
|
94
|
+
const { id } = req.params;
|
|
95
|
+
if (!id) return res.status(400).json({ success: false, error: 'WhatsApp ID is required' });
|
|
96
|
+
if (!req.body || Object.keys(req.body).length === 0)
|
|
97
|
+
return res.status(400).json({ success: false, error: 'No fields provided for update' });
|
|
98
|
+
|
|
99
|
+
const filter = `{whatsapp_id}='${id}'`;
|
|
100
|
+
|
|
101
|
+
const existing = await getRecordByFilter(Monitoreo_ID, 'patient_information', filter);
|
|
102
|
+
if (!existing || existing.length === 0)
|
|
103
|
+
return res.status(404).json({ success: false, error: 'Patient not found' });
|
|
104
|
+
|
|
105
|
+
const validFieldNames = Object.keys(existing[0]);
|
|
106
|
+
const fields = {};
|
|
107
|
+
for (const key of Object.keys(req.body)) {
|
|
108
|
+
if (validFieldNames.includes(key)) {
|
|
109
|
+
fields[key] = req.body[key];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (Object.keys(fields).length === 0)
|
|
114
|
+
return res.status(400).json({ success: false, error: 'No valid fields provided for update' });
|
|
115
|
+
|
|
116
|
+
await updateRecordByFilter(Monitoreo_ID, 'patient_information', filter, fields);
|
|
117
|
+
|
|
118
|
+
if (fields.name) {
|
|
119
|
+
await Thread.updateOne({ code: id }, { nombre: fields.name });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const updated = await getRecordByFilter(Monitoreo_ID, 'patient_information', filter);
|
|
123
|
+
res.status(200).json({ success: true, whatsappId: id, patientInfo: updated[0] });
|
|
124
|
+
} catch (error) {
|
|
125
|
+
logger.error('Error updating patient info:', { error: error.message, id: req.params?.id });
|
|
126
|
+
res.status(500).json({ success: false, error: error.message });
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
module.exports = { getPatientInfoController, getPatientTriageInfoController, updatePatientController };
|
package/lib/eval/EvalProvider.js
CHANGED
|
@@ -8,6 +8,7 @@ const { DefaultMemoryManager } = require('../memory/DefaultMemoryManager');
|
|
|
8
8
|
const { OpenAIResponsesProvider } = require('../providers/OpenAIResponsesProvider');
|
|
9
9
|
const { handleFunctionCalls } = require('../providers/OpenAIResponsesProviderTools');
|
|
10
10
|
const { composePrompt, resolveTools } = require('../services/promptComposerService');
|
|
11
|
+
const { getToolSchemas: getRegistrySchemas } = require('../services/toolRegistryService');
|
|
11
12
|
const { getAssistantById } = require('../services/assistantResolver');
|
|
12
13
|
|
|
13
14
|
const MAX_FUNCTION_ROUNDS = parseInt(process.env.MAX_FUNCTION_ROUNDS || '5', 10);
|
|
@@ -122,18 +123,22 @@ class EvalProvider {
|
|
|
122
123
|
if (this.mode !== 'context-only' && assistantId) {
|
|
123
124
|
try {
|
|
124
125
|
assistant = getAssistantById(assistantId, thread);
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
const assistantSchemas = assistant.getToolSchemas?.() || [];
|
|
127
|
+
const registrySchemas = getRegistrySchemas();
|
|
128
|
+
const schemasByName = new Map();
|
|
129
|
+
for (const s of assistantSchemas) schemasByName.set(s.function?.name, s);
|
|
130
|
+
for (const s of registrySchemas) schemasByName.set(s.function?.name, s);
|
|
131
|
+
toolSchemas = Array.from(schemasByName.values());
|
|
132
|
+
|
|
133
|
+
if (toolSchemas.length > 0) {
|
|
127
134
|
const { toolIds, filtered } = await resolveTools({ promptId: assistantId, assistant });
|
|
128
|
-
const
|
|
129
|
-
? toolIds
|
|
130
|
-
:
|
|
131
|
-
const toolNames = activeToolNames.join(', ');
|
|
132
|
-
devContent += `\n\nYou only have access to these tools: ${toolNames}. Do not call or reference any tools not listed here.`;
|
|
135
|
+
const activeToolSchemas = filtered && toolIds.length > 0
|
|
136
|
+
? toolSchemas.filter(s => toolIds.includes(s.function?.name))
|
|
137
|
+
: toolSchemas;
|
|
133
138
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
139
|
+
toolSchemas = activeToolSchemas;
|
|
140
|
+
const toolNames = activeToolSchemas.map(s => s.function?.name).join(', ');
|
|
141
|
+
devContent += `\n\nYou only have access to these tools: ${toolNames}. Do not call or reference any tools not listed here.`;
|
|
137
142
|
}
|
|
138
143
|
} catch {
|
|
139
144
|
logger.warn('[NexusEvalProvider] Failed to resolve assistant', { assistantId });
|
package/lib/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const { DefaultMemoryManager } = require('../memory/DefaultMemoryManager');
|
|
|
9
9
|
const { getLastNMessages } = require('../helpers/messageHelper');
|
|
10
10
|
|
|
11
11
|
const { composePrompt, resolveTools } = require('../services/promptComposerService');
|
|
12
|
+
const { getToolSchemas: getRegistrySchemas } = require('../services/toolRegistryService');
|
|
12
13
|
const { handleFunctionCalls } = require('./OpenAIResponsesProviderTools');
|
|
13
14
|
|
|
14
15
|
const CONVERSATION_PREFIX = 'conv_';
|
|
@@ -318,12 +319,21 @@ class OpenAIResponsesProvider {
|
|
|
318
319
|
});
|
|
319
320
|
|
|
320
321
|
const { toolIds, filtered } = await resolveTools({ promptId: assistantId, assistant });
|
|
322
|
+
|
|
323
|
+
const assistantSchemas = assistant?.getToolSchemas?.() || [];
|
|
324
|
+
const registrySchemas = getRegistrySchemas();
|
|
325
|
+
const schemasByName = new Map();
|
|
326
|
+
for (const s of assistantSchemas) schemasByName.set(s.function?.name, s);
|
|
327
|
+
for (const s of registrySchemas) schemasByName.set(s.function?.name, s);
|
|
328
|
+
const allSchemas = Array.from(schemasByName.values());
|
|
329
|
+
|
|
330
|
+
const activeToolSchemas = filtered && toolIds.length > 0
|
|
331
|
+
? allSchemas.filter(s => toolIds.includes(s.function?.name))
|
|
332
|
+
: allSchemas;
|
|
333
|
+
|
|
321
334
|
let devContent = resolvedPrompt;
|
|
322
|
-
if (
|
|
323
|
-
const
|
|
324
|
-
? toolIds
|
|
325
|
-
: Array.from(assistant.tools.keys());
|
|
326
|
-
const toolNames = activeToolNames.join(', ');
|
|
335
|
+
if (activeToolSchemas.length > 0) {
|
|
336
|
+
const toolNames = activeToolSchemas.map(s => s.function?.name).join(', ');
|
|
327
337
|
devContent += `\n\nYou only have access to these tools: ${toolNames}. Do not call or reference any tools not listed here.`;
|
|
328
338
|
}
|
|
329
339
|
|
|
@@ -335,11 +345,6 @@ class OpenAIResponsesProvider {
|
|
|
335
345
|
if (promptVariables) promptConfig.variables = promptVariables;
|
|
336
346
|
if (promptVersion) promptConfig.version = String(promptVersion);
|
|
337
347
|
|
|
338
|
-
const toolSchemas = assistant?.getToolSchemas?.() || [];
|
|
339
|
-
const activeToolSchemas = filtered && toolIds.length > 0
|
|
340
|
-
? toolSchemas.filter(s => toolIds.includes(s.function?.name))
|
|
341
|
-
: toolSchemas;
|
|
342
|
-
|
|
343
348
|
const apiCallConfig = {
|
|
344
349
|
prompt: promptConfig,
|
|
345
350
|
instructions: instructions || additionalInstructions || '',
|
package/lib/routes/index.js
CHANGED
|
@@ -61,7 +61,8 @@ const interactionRouteDefinitions = {
|
|
|
61
61
|
|
|
62
62
|
const patientRouteDefinitions = {
|
|
63
63
|
'GET /:id': 'getPatientInfoController',
|
|
64
|
-
'GET /triage/:whatsapp_id': 'getPatientTriageInfoController'
|
|
64
|
+
'GET /triage/:whatsapp_id': 'getPatientTriageInfoController',
|
|
65
|
+
'PATCH /:id': 'updatePatientController'
|
|
65
66
|
};
|
|
66
67
|
|
|
67
68
|
const templateRouteDefinitions = {
|
|
@@ -170,6 +171,7 @@ const builtInControllers = {
|
|
|
170
171
|
// Patient controllers
|
|
171
172
|
getPatientInfoController: patientController.getPatientInfoController,
|
|
172
173
|
getPatientTriageInfoController: patientController.getPatientTriageInfoController,
|
|
174
|
+
updatePatientController: patientController.updatePatientController,
|
|
173
175
|
|
|
174
176
|
// Template controllers
|
|
175
177
|
createTemplate: templateController.createTemplate,
|
|
@@ -4,6 +4,7 @@ const { logger } = require('../utils/logger');
|
|
|
4
4
|
const MapCache = require('../utils/MapCache');
|
|
5
5
|
|
|
6
6
|
const { getRecordByFilter } = require('./airtableService');
|
|
7
|
+
const { hasTool, getRegisteredToolNames } = require('./toolRegistryService');
|
|
7
8
|
|
|
8
9
|
const SNIPPET_TYPE_ORDER = ['safety', 'persona', 'context', 'clinical', 'instructions'];
|
|
9
10
|
const CACHE_TTL = 5 * 60 * 1000;
|
|
@@ -115,7 +116,10 @@ async function fetchToolMapping(promptId) {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
async function resolveTools({ promptId, assistant, status = null }) {
|
|
118
|
-
|
|
119
|
+
const hasRegistryTools = getRegisteredToolNames().length > 0;
|
|
120
|
+
const hasAssistantTools = assistant?.tools?.size > 0;
|
|
121
|
+
|
|
122
|
+
if (!hasRegistryTools && !hasAssistantTools) return { toolIds: [], filtered: false };
|
|
119
123
|
|
|
120
124
|
const mappedTools = await fetchToolMapping(promptId);
|
|
121
125
|
if (!mappedTools.length) return { toolIds: [], filtered: false };
|
|
@@ -124,12 +128,11 @@ async function resolveTools({ promptId, assistant, status = null }) {
|
|
|
124
128
|
? mappedTools.filter(t => !t.status || t.status === status)
|
|
125
129
|
: mappedTools;
|
|
126
130
|
|
|
127
|
-
const registeredNames = new Set(assistant.tools.keys());
|
|
128
131
|
const validToolIds = [];
|
|
129
132
|
|
|
130
133
|
for (const tool of activeTools) {
|
|
131
134
|
const id = tool.tool_id;
|
|
132
|
-
if (
|
|
135
|
+
if (hasTool(id) || assistant?.tools?.has(id)) {
|
|
133
136
|
validToolIds.push(id);
|
|
134
137
|
} else {
|
|
135
138
|
logger.warn('[promptComposer] Tool mapped in Airtable but not registered in code', {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const { logger } = require('../utils/logger');
|
|
2
|
+
|
|
3
|
+
const registry = new Map();
|
|
4
|
+
|
|
5
|
+
function registerTool(config) {
|
|
6
|
+
const { name, handler, description, parameters, strict } = config;
|
|
7
|
+
|
|
8
|
+
if (!name || typeof name !== 'string') {
|
|
9
|
+
throw new Error('registerTool requires a string name');
|
|
10
|
+
}
|
|
11
|
+
if (typeof handler !== 'function') {
|
|
12
|
+
throw new Error(`registerTool '${name}' requires a handler function`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (registry.has(name)) {
|
|
16
|
+
logger.warn('[toolRegistry] Overwriting existing tool', { name });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const toolParams = parameters
|
|
20
|
+
? { ...parameters }
|
|
21
|
+
: { type: 'object', properties: {} };
|
|
22
|
+
|
|
23
|
+
if (toolParams.additionalProperties === undefined) {
|
|
24
|
+
toolParams.additionalProperties = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
registry.set(name, {
|
|
28
|
+
schema: {
|
|
29
|
+
type: 'function',
|
|
30
|
+
function: {
|
|
31
|
+
name,
|
|
32
|
+
description: description || 'Custom assistant tool',
|
|
33
|
+
parameters: toolParams,
|
|
34
|
+
...(strict !== undefined && { strict })
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
execute: handler,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getToolSchemas(names) {
|
|
42
|
+
if (names && names.length > 0) {
|
|
43
|
+
return names
|
|
44
|
+
.filter(n => registry.has(n))
|
|
45
|
+
.map(n => registry.get(n).schema);
|
|
46
|
+
}
|
|
47
|
+
return Array.from(registry.values()).map(t => t.schema);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function executeTool(name, args) {
|
|
51
|
+
const tool = registry.get(name);
|
|
52
|
+
if (!tool) {
|
|
53
|
+
throw new Error(`Tool '${name}' is not registered`);
|
|
54
|
+
}
|
|
55
|
+
return await tool.execute(args);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hasTool(name) {
|
|
59
|
+
return registry.has(name);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getRegisteredToolNames() {
|
|
63
|
+
return Array.from(registry.keys());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function clearRegistry() {
|
|
67
|
+
registry.clear();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
registerTool,
|
|
72
|
+
getToolSchemas,
|
|
73
|
+
executeTool,
|
|
74
|
+
hasTool,
|
|
75
|
+
getRegisteredToolNames,
|
|
76
|
+
clearRegistry,
|
|
77
|
+
};
|