modularizado de prompts
This commit is contained in:
189
src/modules/3-turn-engine/nlu/index.js
Normal file
189
src/modules/3-turn-engine/nlu/index.js
Normal file
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user