@peopl-health/nexus 5.12.4 → 5.12.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/clinical/providers/AnthropicProvider.js +2 -1
- package/lib/clinical/providers/BaseLLMProvider.js +27 -2
- package/lib/clinical/tools/deliverPatientMessageTool.js +1 -1
- package/lib/controllers/templateController.js +3 -5
- package/lib/index.d.ts +5 -5
- package/lib/models/templateModel.js +0 -1
- package/lib/services/templateService.js +0 -1
- package/package.json +1 -1
|
@@ -208,7 +208,8 @@ class AnthropicProvider extends BaseLLMProvider {
|
|
|
208
208
|
const turnContent = Array.isArray(lastUserTurn.content)
|
|
209
209
|
? lastUserTurn.content
|
|
210
210
|
: [{ type: 'text', text: String(lastUserTurn.content ?? '') }];
|
|
211
|
-
|
|
211
|
+
const alreadyNudged = turnContent.some(part => part?.type === 'text' && part.text === DELIVERY_NUDGE);
|
|
212
|
+
if (!alreadyNudged) lastUserTurn.content = [...turnContent, { type: 'text', text: DELIVERY_NUDGE }];
|
|
212
213
|
// The nudge is a model call like any other: gate thinking on it so the per-iteration counter stays honest.
|
|
213
214
|
prepThinking();
|
|
214
215
|
const { result: { result: nudged, retries: nudgeRetries }, durationMs: nudgeDurationMs } = await withTiming(() => makeAPICall(currentMessages));
|
|
@@ -16,7 +16,12 @@ const CONVERSATION_CONTINUATION = '(continuar)';
|
|
|
16
16
|
const NEGATIVE_GUIDANCE_MARKER = '**When NOT to call';
|
|
17
17
|
const TERMINAL_DELIVERY_TOOLS = new Set(['DeliverPatientMessage']);
|
|
18
18
|
const DELIVERY_NUDGE = 'sistema: el turno no puede cerrar sin un mensaje para el paciente. Llama ahora a DeliverPatientMessage con message_text en español. No repitas herramientas clínicas ya ejecutadas en este turno.';
|
|
19
|
-
const MAX_DELIVERY_NUDGES =
|
|
19
|
+
const MAX_DELIVERY_NUDGES = 2;
|
|
20
|
+
const DELIVERY_FALLBACK_MESSAGES = [
|
|
21
|
+
'Gracias por tu mensaje. Si necesitas apoyo, escríbeme cuando quieras.',
|
|
22
|
+
'Gracias por escribirme. Aquí estoy cuando necesites algo.',
|
|
23
|
+
'Recibí tu mensaje. Cuando quieras contarme más o pedir apoyo, escríbeme.',
|
|
24
|
+
];
|
|
20
25
|
|
|
21
26
|
function isSuperseded(shouldContinue) {
|
|
22
27
|
return typeof shouldContinue === 'function' && !shouldContinue();
|
|
@@ -324,7 +329,13 @@ class BaseLLMProvider {
|
|
|
324
329
|
const result = await this._executeConversation(config);
|
|
325
330
|
// DeliverPatientMessage is the curated patient reply; prefer it over any model narration in the final response.
|
|
326
331
|
const delivered = this._extractDeliveredMessage(config, result);
|
|
327
|
-
|
|
332
|
+
let extractedOutput = delivered?.trim() ? delivered : this._extractMessageOutput(result);
|
|
333
|
+
|
|
334
|
+
if (!delivered?.trim() && extractedOutput?.trim() && this._deliveryExpected(result)) {
|
|
335
|
+
logger.warn('[runConversation] Delivery tool offered but never committed — substituting safe fallback for raw narration', { attempt, suppressedLength: extractedOutput.length });
|
|
336
|
+
config?.assistant?.toolRuntimeContext?.trace?.setSignals?.({ deliveryFallbackUsed: true });
|
|
337
|
+
extractedOutput = this._deliveryFallbackMessage(config);
|
|
338
|
+
}
|
|
328
339
|
|
|
329
340
|
if (extractedOutput?.trim()) {
|
|
330
341
|
result.output_text = extractedOutput;
|
|
@@ -371,6 +382,19 @@ class BaseLLMProvider {
|
|
|
371
382
|
return (toolsExecuted || []).some(t => TERMINAL_DELIVERY_TOOLS.has(t?.tool_name) && this._toolSucceeded(t));
|
|
372
383
|
}
|
|
373
384
|
|
|
385
|
+
_deliveryExpected(result) {
|
|
386
|
+
return (result?.tool_ids || []).some(id => TERMINAL_DELIVERY_TOOLS.has(id));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
_deliveryFallbackMessage(config) {
|
|
390
|
+
const key = String(config?.assistant?.toolRuntimeContext?.turnId
|
|
391
|
+
?? config?.assistant?.toolRuntimeContext?.patientCode
|
|
392
|
+
?? config?.threadId ?? '');
|
|
393
|
+
let hash = 0;
|
|
394
|
+
for (let i = 0; i < key.length; i++) hash = (hash * 31 + key.charCodeAt(i)) | 0;
|
|
395
|
+
return DELIVERY_FALLBACK_MESSAGES[Math.abs(hash) % DELIVERY_FALLBACK_MESSAGES.length];
|
|
396
|
+
}
|
|
397
|
+
|
|
374
398
|
_isTerminalOnly(toolNames) {
|
|
375
399
|
return toolNames.length > 0 && toolNames.every(name => TERMINAL_DELIVERY_TOOLS.has(name));
|
|
376
400
|
}
|
|
@@ -531,6 +555,7 @@ module.exports = {
|
|
|
531
555
|
CONVERSATION_CONTINUATION,
|
|
532
556
|
DELIVERY_NUDGE,
|
|
533
557
|
MAX_DELIVERY_NUDGES,
|
|
558
|
+
DELIVERY_FALLBACK_MESSAGES,
|
|
534
559
|
TERMINAL_DELIVERY_TOOLS,
|
|
535
560
|
isSuperseded,
|
|
536
561
|
};
|
|
@@ -2,7 +2,7 @@ const { requireGatewayProvider } = require('../config/llmConfig');
|
|
|
2
2
|
const { renderMessage } = require('../services/composerService');
|
|
3
3
|
const { isComposerEnabled, isAgentSelfRenderEnabled } = require('../flags/composerFlags');
|
|
4
4
|
|
|
5
|
-
const EMPTY_OUTBOUND_FALLBACK = '
|
|
5
|
+
const EMPTY_OUTBOUND_FALLBACK = 'Recibí tu mensaje. Si quieres contarme más o necesitas apoyo, escríbeme.';
|
|
6
6
|
|
|
7
7
|
const definition = {
|
|
8
8
|
name: 'DeliverPatientMessage',
|
|
@@ -216,7 +216,6 @@ const submitForApproval = async (req, res) => {
|
|
|
216
216
|
$set: {
|
|
217
217
|
status: 'PENDING',
|
|
218
218
|
approvalRequest: {
|
|
219
|
-
sid: response.sid,
|
|
220
219
|
dateSubmitted: validSubmittedDate,
|
|
221
220
|
dateUpdated: validUpdatedDate,
|
|
222
221
|
rejectionReason: response.rejection_reason || ''
|
|
@@ -270,7 +269,6 @@ const checkApprovalStatus = async (req, res) => {
|
|
|
270
269
|
status.approvalRequest.date_created || status.approvalRequest.dateCreated || status.content.dateCreated
|
|
271
270
|
);
|
|
272
271
|
dbTemplate.approvalRequest = {
|
|
273
|
-
sid: status.approvalRequest.sid,
|
|
274
272
|
dateSubmitted,
|
|
275
273
|
dateUpdated: parseDate(
|
|
276
274
|
status.approvalRequest.date_updated || status.approvalRequest.dateUpdated || status.content.dateUpdated,
|
|
@@ -288,7 +286,7 @@ const checkApprovalStatus = async (req, res) => {
|
|
|
288
286
|
contentSid,
|
|
289
287
|
content: status.content,
|
|
290
288
|
approvalRequest: status.approvalRequest,
|
|
291
|
-
template: dbTemplate
|
|
289
|
+
template: dbTemplate ? _formatTemplate(dbTemplate) : null
|
|
292
290
|
});
|
|
293
291
|
} catch (err) {
|
|
294
292
|
// If Twilio check fails but we have DB record, return what we know
|
|
@@ -301,7 +299,7 @@ const checkApprovalStatus = async (req, res) => {
|
|
|
301
299
|
status: dbTemplate.status
|
|
302
300
|
},
|
|
303
301
|
approvalRequest: dbTemplate.approvalRequest,
|
|
304
|
-
template: dbTemplate,
|
|
302
|
+
template: _formatTemplate(dbTemplate),
|
|
305
303
|
warning: 'Could not fetch latest status from Twilio'
|
|
306
304
|
});
|
|
307
305
|
}
|
|
@@ -412,7 +410,7 @@ const getCompleteTemplate = async (req, res) => {
|
|
|
412
410
|
|
|
413
411
|
return res.status(200).json({
|
|
414
412
|
success: true,
|
|
415
|
-
template
|
|
413
|
+
template: _formatTemplate(template)
|
|
416
414
|
});
|
|
417
415
|
} catch (error) {
|
|
418
416
|
return handleApiError(res, error, 'Failed to get complete template');
|
package/lib/index.d.ts
CHANGED
|
@@ -116,13 +116,13 @@ declare module '@peopl-health/nexus' {
|
|
|
116
116
|
name: string;
|
|
117
117
|
description?: string;
|
|
118
118
|
parameters?: any;
|
|
119
|
-
handler: (args: any) =>
|
|
119
|
+
handler: (args: any) => string | Promise<string>;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
export interface AssistantConfigDefinition {
|
|
123
123
|
extends?: typeof BaseAssistant;
|
|
124
124
|
create?: (this: BaseAssistant, code: string, context?: any) => Promise<any>;
|
|
125
|
-
tools?: Array<AssistantToolDefinition | { name: string; definition?: any; handler: (args: any) =>
|
|
125
|
+
tools?: Array<AssistantToolDefinition | { name: string; definition?: any; handler: (args: any) => string | Promise<string> }>;
|
|
126
126
|
setup?: (this: BaseAssistant, context: { assistantId: string; thread?: any; options?: any }) => void;
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -138,7 +138,7 @@ declare module '@peopl-health/nexus' {
|
|
|
138
138
|
assistantId?: string;
|
|
139
139
|
thread?: any;
|
|
140
140
|
client?: any;
|
|
141
|
-
tools?: Array<AssistantToolDefinition | { name: string; definition?: any; handler: (args: any) =>
|
|
141
|
+
tools?: Array<AssistantToolDefinition | { name: string; definition?: any; handler: (args: any) => string | Promise<string> }>;
|
|
142
142
|
setup?: (context: { assistantId: string; thread?: any; options?: any }) => void;
|
|
143
143
|
status?: string;
|
|
144
144
|
} | any);
|
|
@@ -146,9 +146,9 @@ declare module '@peopl-health/nexus' {
|
|
|
146
146
|
thread: any;
|
|
147
147
|
status: string;
|
|
148
148
|
createdAt: Date;
|
|
149
|
-
registerTool(definition: AssistantToolDefinition | string, schema?: any, handler?: (args: any) =>
|
|
149
|
+
registerTool(definition: AssistantToolDefinition | string, schema?: any, handler?: (args: any) => string | Promise<string>): void;
|
|
150
150
|
getToolSchemas(): any[];
|
|
151
|
-
executeTool(name: string, args: any): Promise<
|
|
151
|
+
executeTool(name: string, args: any): Promise<string>;
|
|
152
152
|
getPreviousMessages(thread?: any): Promise<Array<{ role: string; content: any }>>;
|
|
153
153
|
createThread(code: string, context?: any): Promise<any>;
|
|
154
154
|
sendMessage(userId: string, message: string, options?: any): Promise<any>;
|
|
@@ -15,7 +15,6 @@ const refreshApprovalStatuses = async (templates) => {
|
|
|
15
15
|
const reqData = approvalInfo.approvalRequest;
|
|
16
16
|
const updateFields = {
|
|
17
17
|
approvalRequest: {
|
|
18
|
-
sid: reqData.sid,
|
|
19
18
|
dateSubmitted: reqData.dateCreated ? new Date(reqData.dateCreated) : new Date(),
|
|
20
19
|
dateUpdated: reqData.dateUpdated ? new Date(reqData.dateUpdated) : new Date(),
|
|
21
20
|
rejectionReason: reqData.rejectionReason || ''
|