LIA responde sobre la documentación, filtrada por el rol de quien pregunta
DocIndex::contextoIA() selecciona los documentos relevantes a la pregunta y devuelve su texto. Reutiliza el filtrado por rol ya existente: al contexto que se envía al modelo solo entra lo que ese usuario podría leer por su cuenta en el módulo Soporte, así el asistente no puede revelar contenido restringido. Verificado: un recepcionista preguntando por permisos recibe solo el manual básico, sin el SQL ni los detalles internos que sí recibe un administrador. - services/GeminiService.php concentra la llamada a la API y la contabilidad del presupuesto, que antes vivía dentro de ai_chat.php y ahora comparten los dos asistentes. - La LIA del turnero suma la documentación a su contexto operativo, así responde tanto "cuánto facturamos hoy" como "cómo marco un paciente ausente". - modules/soporte/api/ai_docs.php es el asistente de la documentación: no accede a datos de pacientes ni de la operación, solo a los documentos visibles. A diferencia del anterior no exige acceso al turnero, así que lo puede usar cualquier usuario autenticado desde la página de documentación. - La respuesta cita de qué documentos salió. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
dc27184051
commit
81c007c516
@@ -161,6 +161,68 @@ final class DocIndex
|
||||
return ucfirst(str_replace('-', ' ', $slugFallback));
|
||||
}
|
||||
|
||||
/** Palabras que no aportan al puntaje de relevancia. */
|
||||
private const VACIAS = [
|
||||
'como','cual','cuales','donde','cuando','porque','para','pero','esta','este','esto',
|
||||
'con','sin','por','que','del','las','los','una','uno','del','sus','sobre','desde',
|
||||
'hacer','tengo','puedo','quiero','necesito','ayuda','favor','the','and','not','del',
|
||||
];
|
||||
|
||||
/**
|
||||
* Selecciona los documentos más relevantes para una pregunta y devuelve su
|
||||
* texto, listo para dárselo a un modelo de lenguaje.
|
||||
*
|
||||
* Respeta la visibilidad por rol: solo entra lo que el usuario podría leer
|
||||
* por su cuenta, así el asistente no puede filtrar contenido restringido.
|
||||
*
|
||||
* @return array{0: string, 1: array<int,string>} [contexto, títulos usados]
|
||||
*/
|
||||
public static function contextoIA(string $pregunta, int $maxDocs = 4, int $maxChars = 14000): array
|
||||
{
|
||||
$palabras = array_values(array_filter(
|
||||
preg_split('/[^a-záéíóúñü0-9]+/u', mb_strtolower($pregunta)),
|
||||
fn($p) => mb_strlen($p) > 2 && !in_array($p, self::VACIAS, true)
|
||||
));
|
||||
if (!$palabras) return ['', []];
|
||||
|
||||
$candidatos = [];
|
||||
foreach (self::arbol() as $sec => $cfg) {
|
||||
foreach ($cfg['docs'] as $doc) {
|
||||
[, $cuerpo] = self::leer($doc['archivo']);
|
||||
$heno = mb_strtolower($doc['titulo'] . ' ' . $cuerpo);
|
||||
$puntaje = 0;
|
||||
foreach ($palabras as $p) {
|
||||
// El título pesa mucho más que una mención en el cuerpo
|
||||
$puntaje += substr_count(mb_strtolower($doc['titulo']), $p) * 12;
|
||||
$puntaje += min(substr_count($heno, $p), 8);
|
||||
}
|
||||
if ($puntaje > 0) {
|
||||
$candidatos[] = [
|
||||
'puntaje' => $puntaje,
|
||||
'titulo' => $doc['titulo'],
|
||||
'seccion' => $cfg['titulo'],
|
||||
'cuerpo' => $cuerpo,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$candidatos) return ['', []];
|
||||
|
||||
usort($candidatos, fn($a, $b) => $b['puntaje'] <=> $a['puntaje']);
|
||||
$candidatos = array_slice($candidatos, 0, $maxDocs);
|
||||
|
||||
$porDoc = (int)floor($maxChars / count($candidatos));
|
||||
$ctx = '';
|
||||
$titulos = [];
|
||||
foreach ($candidatos as $c) {
|
||||
$texto = preg_replace('/\{\{\w+\}\}/', '', $c['cuerpo']); // los marcadores no aportan
|
||||
$texto = mb_substr(trim($texto), 0, $porDoc);
|
||||
$ctx .= "\n\n===== [{$c['seccion']}] {$c['titulo']} =====\n" . $texto;
|
||||
$titulos[] = $c['titulo'];
|
||||
}
|
||||
return [trim($ctx), $titulos];
|
||||
}
|
||||
|
||||
/**
|
||||
* Índice para el buscador: un registro por documento con su texto plano.
|
||||
* Solo incluye secciones visibles para el usuario actual.
|
||||
|
||||
Reference in New Issue
Block a user