feat: bienvenida diaria; fix: el NLU filtraba JSON crudo al usuario
Bienvenida una vez por dia y por empresa, distinta por categoria (cat 1 no ve
ejemplos de descarga). Se manda aparte del retorno de process(), asi el usuario
recibe el saludo y acto seguido lo que pidio. __saludo sobrevive a
preservingReset para no repetirse tras cada informe.
Fix del leak: cuando la respuesta del modelo no parseaba como JSON se enviaba
cruda, y el usuario veia {"action":"chat","text":"... Ahora se rescata solo el
texto, incluso de JSON truncado, y si no hay nada legible se devuelve vacio
para caer al menu de fallback.
Ademas el prompt le prohibe preguntar por datos que una opcion ya pide: ante
"descargar informe de mantenimiento" debe rutear al selector de grupo en vez de
preguntar por chat.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
8825f4e821
commit
0bb1d3447e
+31
-3
@@ -360,7 +360,10 @@ PROMPT;
|
||||
$json = json_decode($clean, true);
|
||||
|
||||
if (!is_array($json) || !isset($json['action'])) {
|
||||
return ['action' => 'chat', 'text' => $clean];
|
||||
// JSON inválido o truncado. Mandar $clean tal cual le escupía el
|
||||
// {"action":"chat",...} al usuario, así que se rescata solo el texto.
|
||||
self::log("NLU: respuesta no parseable → " . mb_substr($clean, 0, 200));
|
||||
return ['action' => 'chat', 'text' => self::rescatarTexto($clean)];
|
||||
}
|
||||
|
||||
if ($json['action'] === 'route' && isset($json['key'])) {
|
||||
@@ -377,7 +380,29 @@ PROMPT;
|
||||
}
|
||||
}
|
||||
|
||||
return ['action' => 'chat', 'text' => $json['text'] ?? $clean];
|
||||
return ['action' => 'chat', 'text' => self::rescatarTexto((string)($json['text'] ?? $clean))];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrae el texto legible de una respuesta que no parseó (típicamente JSON
|
||||
* truncado por límite de tokens). Si no hay nada rescatable devuelve '',
|
||||
* y NormalBot cae al menú de fallback — mejor eso que mostrarle JSON.
|
||||
*/
|
||||
private static function rescatarTexto(string $crudo): string
|
||||
{
|
||||
$crudo = trim($crudo);
|
||||
if ($crudo === '') return '';
|
||||
|
||||
// "text":"lo que sirve → rescatar aunque falte el cierre
|
||||
if (preg_match('/"text"\s*:\s*"(.*?)(?:"\s*[,}]|$)/s', $crudo, $m)) {
|
||||
$texto = stripcslashes($m[1]);
|
||||
return str_contains($texto, '{"action"') ? '' : trim($texto);
|
||||
}
|
||||
|
||||
// Cualquier resto con pinta de JSON o de key interna no se muestra
|
||||
if (str_contains($crudo, '{"') || str_contains($crudo, '"action"')) return '';
|
||||
|
||||
return $crudo;
|
||||
}
|
||||
|
||||
private static function buildFlowCatalog(array $config, int $permType): array
|
||||
@@ -460,7 +485,10 @@ PROMPT;
|
||||
. " \"qué lotes llevan más tiempo sin corona\" → {\"action\":\"route\",\"key\":\"ciclo_mantenimiento_top\",\"entities\":{\"grupo\":\"corona\"}}\n"
|
||||
. " \"mantenimiento\" (sin grupo) → {\"action\":\"route\",\"key\":\"ciclo_mantenimiento\"}\n"
|
||||
. " \"quiero cambiar de finca\" → {\"action\":\"route\",\"key\":\"reset_finca\"}\n\n"
|
||||
. "Si no está claro o no hay opción correspondiente → responde SOLO:\n"
|
||||
. "NUNCA preguntes por un dato que una opción ya pide (grupo, finca, período):\n"
|
||||
. "ruteá a esa opción y el bot lo pregunta con su propia lista.\n"
|
||||
. " \"descargar informe de mantenimiento\" → route a ciclo_mantenimiento, NO preguntar el grupo.\n\n"
|
||||
. "Usá chat solo si el mensaje no corresponde a ninguna opción → responde SOLO:\n"
|
||||
. "{\"action\":\"chat\",\"text\":\"<respuesta corta en español, máx 2 oraciones>\"}\n\n"
|
||||
. "No inventes keys. Usa exactamente los keys de la lista.";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user