196 lines
6.2 KiB
JavaScript
196 lines
6.2 KiB
JavaScript
/**
|
|
* NLU Modular - Punto de entrada principal
|
|
*
|
|
* Orquesta el Router + Specialists para procesar mensajes de usuario.
|
|
* Reemplaza a llmNluV3 con una arquitectura modular y prompts editables.
|
|
*/
|
|
|
|
import { routerClassify, quickDomainDetect } from "./router.js";
|
|
import { greetingNlu } from "./specialists/greeting.js";
|
|
import { ordersNlu } from "./specialists/orders.js";
|
|
import { shippingNlu } from "./specialists/shipping.js";
|
|
import { paymentNlu } from "./specialists/payment.js";
|
|
import { browseNlu } from "./specialists/browse.js";
|
|
import { createEmptyNlu } from "./schemas.js";
|
|
|
|
// Re-exportar utilidades útiles
|
|
export { loadPrompt, invalidatePromptCache, AVAILABLE_VARIABLES } from "./promptLoader.js";
|
|
export { PROMPT_KEYS, DEFAULT_MODELS } from "../../0-ui/db/promptsRepo.js";
|
|
|
|
/**
|
|
* Procesa un mensaje con el sistema NLU modular
|
|
*
|
|
* @param {Object} params
|
|
* @param {Object} params.input - Input del NLU
|
|
* @param {string} params.input.last_user_message - Mensaje del usuario
|
|
* @param {string} params.input.conversation_state - Estado actual de la conversación
|
|
* @param {Object} params.input.pending_context - Contexto de items pendientes
|
|
* @param {string} params.input.locale - Locale (default: es-AR)
|
|
* @param {number} params.tenantId - ID del tenant
|
|
* @param {Object} params.storeConfig - Configuración de la tienda (para variables)
|
|
* @returns {Object} { nlu, raw_text, model, usage, schema, validation, routing }
|
|
*/
|
|
export async function llmNluModular({ input, tenantId, storeConfig = {} } = {}) {
|
|
const text = input?.last_user_message || "";
|
|
const state = input?.conversation_state || "IDLE";
|
|
const startTime = Date.now();
|
|
|
|
// Tracking para debug
|
|
const routing = {
|
|
quick_detect: null,
|
|
router_result: null,
|
|
final_domain: null,
|
|
specialist_used: null,
|
|
};
|
|
|
|
try {
|
|
// 1) Quick detection: si es un caso obvio, evitar llamar al router LLM
|
|
const quickDomain = quickDomainDetect(text, state);
|
|
routing.quick_detect = quickDomain;
|
|
|
|
// Casos donde podemos saltar el router:
|
|
// - Saludos simples
|
|
// - Números solos (1, 2) en estados SHIPPING/PAYMENT
|
|
// - Patrones muy claros
|
|
const skipRouter = shouldSkipRouter(text, state, quickDomain);
|
|
|
|
let domain;
|
|
if (skipRouter) {
|
|
domain = quickDomain;
|
|
routing.router_result = { skipped: true, quick_domain: quickDomain };
|
|
} else {
|
|
// 2) Router LLM: clasificar dominio
|
|
const routerResult = await routerClassify({ tenantId, text, state, storeConfig });
|
|
domain = routerResult.domain;
|
|
routing.router_result = routerResult;
|
|
}
|
|
|
|
routing.final_domain = domain;
|
|
|
|
// 3) Dispatch al specialist correspondiente
|
|
let result;
|
|
|
|
switch (domain) {
|
|
case "greeting":
|
|
routing.specialist_used = "greeting";
|
|
result = await greetingNlu({ tenantId, text, storeConfig });
|
|
break;
|
|
|
|
case "orders":
|
|
routing.specialist_used = "orders";
|
|
result = await ordersNlu({ tenantId, text, storeConfig });
|
|
break;
|
|
|
|
case "shipping":
|
|
routing.specialist_used = "shipping";
|
|
result = await shippingNlu({ tenantId, text, storeConfig });
|
|
break;
|
|
|
|
case "payment":
|
|
routing.specialist_used = "payment";
|
|
result = await paymentNlu({ tenantId, text, storeConfig });
|
|
break;
|
|
|
|
case "browse":
|
|
routing.specialist_used = "browse";
|
|
result = await browseNlu({ tenantId, text, storeConfig });
|
|
break;
|
|
|
|
default:
|
|
// Fallback: usar orders como default si hay texto con posibles productos
|
|
routing.specialist_used = "orders_fallback";
|
|
result = await ordersNlu({ tenantId, text, storeConfig });
|
|
// Pero marcar como "other" si el resultado no es claro
|
|
if (result.nlu.confidence < 0.7) {
|
|
result.nlu.intent = "other";
|
|
}
|
|
}
|
|
|
|
// Agregar metadata de routing
|
|
result.routing = routing;
|
|
result.schema = "modular_v1";
|
|
result.processing_time_ms = Date.now() - startTime;
|
|
|
|
return result;
|
|
|
|
} catch (error) {
|
|
console.error("[nluModular] Error:", error);
|
|
|
|
// Fallback completo
|
|
const nlu = createEmptyNlu();
|
|
nlu.intent = "other";
|
|
nlu.confidence = 0;
|
|
|
|
return {
|
|
nlu,
|
|
raw_text: "",
|
|
model: null,
|
|
usage: null,
|
|
schema: "modular_v1",
|
|
validation: { ok: false, error: error.message },
|
|
routing: { ...routing, error: error.message },
|
|
processing_time_ms: Date.now() - startTime,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determina si podemos saltar el router LLM y usar quick detection
|
|
*/
|
|
function shouldSkipRouter(text, state, quickDomain) {
|
|
const t = String(text || "").trim();
|
|
|
|
// Saludos simples (sin productos)
|
|
if (quickDomain === "greeting" && t.length < 20) {
|
|
return true;
|
|
}
|
|
|
|
// Números solos en estados específicos
|
|
if (/^[12]$/.test(t)) {
|
|
if (state === "SHIPPING" || state === "PAYMENT") {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// "efectivo" o "tarjeta" solos en estado PAYMENT
|
|
if (state === "PAYMENT" && /^(efectivo|tarjeta|link|transfer)$/i.test(t)) {
|
|
return true;
|
|
}
|
|
|
|
// "delivery" o "retiro" solos en estado SHIPPING
|
|
if (state === "SHIPPING" && /^(delivery|retiro|buscar|sucursal)$/i.test(t)) {
|
|
return true;
|
|
}
|
|
|
|
// En estado SHIPPING, si quickDomain ya detectó "shipping" (dirección), confiar en eso
|
|
// Esto evita que el router LLM clasifique direcciones como productos
|
|
if (state === "SHIPPING" && quickDomain === "shipping") {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Versión compatible con la firma de llmNluV3
|
|
* Para usar con el feature flag sin cambiar mucho código
|
|
*/
|
|
export async function llmNluModularCompat({ input, model } = {}) {
|
|
// Extraer tenantId del input si está disponible, o usar 1 como default
|
|
// En producción, esto debería pasarse explícitamente
|
|
const tenantId = input?.tenantId || 1;
|
|
|
|
// Construir storeConfig básico (en producción se cargaría de la DB)
|
|
const storeConfig = {
|
|
name: input?.store_name || "la carnicería",
|
|
botName: input?.bot_name || "Piaf",
|
|
hours: input?.store_hours || "",
|
|
address: input?.store_address || "",
|
|
};
|
|
|
|
return llmNluModular({ input, tenantId, storeConfig });
|
|
}
|
|
|
|
// Export default para compatibilidad
|
|
export default llmNluModular;
|