Ingesta de manuales
Cómo se cargan los manuales en la base vectorial knowledge_base.
El script pnpm ingest:manuals
scripts/ingest-manuals.ts es offline, idempotente y se corre a mano.
- Recorre recursivamente
Aura-reloaded/documentation/manuals/(resuelto como../documentation/manualsdesdescripts/). - Parsea cada
.mden chunks (ver convención abajo). - Embeddea
título + sección + contenidocon OpenAI. - Upsert en
knowledge_baseconON CONFLICT (id) DO UPDATE.
cd Aura-reloaded
pnpm ingest:manuals # requiere DATABASE_URL y OPENAI_API_KEY
Es seguro re-correrlo: al ser idempotente por hash, actualiza solo los chunks que
cambiaron. Los chunks huérfanos no se borran solos (si renombrás/borrás una sección, el
id cambia y la fila vieja queda). Para una limpieza total: TRUNCATE knowledge_base; +
re-ingestar.
Convención de formato Markdown (CRÍTICO)
El parser es estricto. Si el .md no la respeta, se pierden chunks en silencio:
#(H1) → título del manual. Debe haber uno solo por archivo. Varios pisan el título y rompen la última sección.##(H2) → una sección = un chunk.- Todo lo que está fuera de un
##se descarta (incluido lo que va entre el#y el primer##). No pongas info importante antes del primer##. ###(H3) NO es sección → su contenido se absorbe en el chunk anterior. Usar##.- Secciones con tabla markdown → un chunk por fila (
Columna: Valor). Ideal para tablas de errores de equipos. - Evitar
---(regla horizontal): dentro de una sección de solo-tabla genera un chunk basura"---". Las secciones ya se delimitan por##.
Ejemplo bien formado: un # de título + varios ## (specs, tabla de errores, notas).
Recuperación
knowledge_base se consulta top-1 por coseno y sin filtro de category. Como
devuelve un solo chunk, la granularidad importa: contenido interno/administrativo que no
responde consultas de pacientes conviene no ingestarlo (compite por el único chunk).
Ver Pipeline.
Footguns
- Modelo de embeddings distinto entre ingesta y runtime. El script lee la env var
OPENAI_EMBEDDING_MODEL; el runtime leeEMBEDDING_MODEL. Ambos defaultean atext-embedding-3-small, así que sin setear nada coinciden. Pero si cambiás el modelo en uno y no en el otro, los embeddings quedan en espacios distintos y el coseno da basura. Si tocás el modelo, seteá ambas vars igual y re-ingestá todo. - Colisión de filas de tabla. El id de cada fila usa el contenido completo de la
fila (
rowContent::idx). Si en el pasado usaba solo la primera columna, filas que compartían ese valor (ej. "Luz roja fija" para varios errores) colisionaban y se pisaban. Si ves conteos porsourcemás bajos de lo esperado, sospechá de esto:SELECT source, count(*) FROM knowledge_base GROUP BY source. - Contenido fuera de
##se descarta. Un solo#por archivo. - Re-ingestar tras editar un
.md.
Receta: agregar/actualizar un manual
- Crear/editar el
.mdendocumentation/manuals/<categoria>/<archivo>.mdrespetando la convención (un#de título,##por chunk, tablas para datos tabulares). cd Aura-reloaded && pnpm ingest:manuals.- Verificar: probar una consulta que debería matchear y mirar el log
[Knowledge] HIT/MISScon el score, o el panel/operations(cobertura/hit del RAG). - Si no matchea: revisar el chunking (¿quedó bajo un
##?), el score vsKNOWLEDGE_THRESHOLD, y que el modelo de embeddings sea el mismo en ingesta y runtime.
Check de sanidad post-ingesta
-- Conteo por archivo (esperá ver todas las categorías)
SELECT source, count(*) FROM knowledge_base GROUP BY source ORDER BY source;
-- Basura que NO debería existir → esperado: 0 filas
SELECT id, source, section, left(content, 50)
FROM knowledge_base
WHERE content LIKE '|%' -- tablas crudas mal parseadas
OR btrim(content) = '---' -- separadores
OR length(btrim(content)) < 5; -- chunks vacíos/triviales