
Este artículo comparte la implementación técnica de Q&A sin alucinaciones en nuestro lector con IA: las respuestas se basan estrictamente en el texto del libro abierto y las afirmaciones clave pueden rastrearse con un clic hasta el pasaje exacto. Si desarrollas lectura con IA, Q&A documental o aplicaciones tipo RAG, esperamos que tres iteraciones y la arquitectura final te resulten útiles.
I. Evolución en tres etapas
El Q&A sin alucinaciones no se diseñó perfecto desde el primer día. Evolucionó bajo la tensión entre coste, latencia y precisión. A continuación, las tres etapas en orden cronológico—contexto para entender por qué la arquitectura actual tiene esta forma.
Etapa 1: Volcar todo el libro en el contexto (lo más simple—y lo primero que falla)
Enfoque: Cuando el usuario abre un libro y pregunta, meter todo el cuerpo de texto extraído en el system prompt o el mensaje de usuario y dejar que el modelo de chat responda. Si el libro supera unos 400.000 caracteres, truncado duro—solo queda el inicio; los capítulos posteriores son invisibles para el modelo.
Ventajas:
- Coste de implementación muy bajo, casi sin preprocesado;
- Funciona razonablemente en libros cortos y documentos simples—el modelo realmente «vio todo el libro»;
- UX simple: preguntar y obtener respuesta, sin estado «espere mientras analizamos».
Inconvenientes (pronto inaceptables):
- Respuestas lentas: Cada pregunta reenvía una carga enorme; el tiempo hasta el primer token y la latencia total crecen con la longitud del libro;
- Coste de tokens alto: Se paga la entrada del libro completo en cada pregunta;
- Libros largos muy distorsionados: Tras 400k caracteres, la segunda mitad, anexos y conclusiones casi no existen—y la UI no suele indicar claramente el truncado;
- Granularidad de búsqueda cero: El modelo debe «encontrar una aguja en un pajar» entre cientos de miles de caracteres—fácil omitir detalles y producir resúmenes plausibles sin base—exactamente lo que las apps de lectura deben evitar.
La etapa 1 sirve para un MVP, no para una solución de producto.
Etapa 2: Un LLM ligero extrae frases clave (comprimir contexto—demasiado agresivo)
Enfoque: Antes del Q&A (o al abrir por primera vez), ejecutar un modelo más barato sobre el cuerpo: dividir por capítulo spine (o trocear el libro), extraer frases clave, conservar etiquetas de posición como [fArchivo-inicio-fin], luego concatenar extractos en un contexto más corto para el Q&A posterior.
Pipeline típico: Extract → Cache → Chat. Extraer una vez (offline o bajo demanda), guardar un «paquete de frases clave», reutilizarlo en cada pregunta—como muchos prototipos de Q&A documental: comprimir primero, responder después.
Ventajas:
- Cada pregunta envía mucho menos texto; el consumo de tokens por petición baja respecto a la etapa 1;
- El preprocesado puede cachearse; sin reextracción por pregunta en el mismo libro;
- Las etiquetas de posición sientan las bases de las citas.
Inconvenientes (siguen fallando en libros largos):
- Pérdida fuerte de detalle: Las «frases clave» las elige el modelo; calificadores, contraejemplos y cadenas argumentales se pierden a menudo—respuestas «correctas pero parciales»;
- Contexto aún grande en obras largas: Incluso los paquetes de frases clave son considerables—latencia y coste se alivian, no se resuelven;
- Doble error de LLM: La extracción puede omitir; el Q&A puede leer mal los extractos—los errores se acumulan;
- Contexto estático: Pregunte el usuario por un capítulo o la estructura global, el modelo recibe siempre el mismo blob preextraído—sin estrechamiento dinámico según la pregunta.
La lección: el problema no es «si comprimimos», sino «si la compresión es bajo demanda y si podemos volver al texto fuente».
Etapa 3: Índice de segmentos + Tool bajo demanda + devolución del texto fuente (actual)
Enfoque: Inspirado en PageIndex. Frente a la etapa 2, tres cambios centrales:
- El preprocesado produce un índice estructurado (resúmenes a nivel TOC + spans de caracteres exactos), no extractos usados directamente como contexto Q&A;
- Cada pregunta usa Tool Calling para buscar bajo demanda, luego obtiene texto fuente con etiquetas de posición para responder;
- System prompt + frontend imponen el formato de cita y permiten clic → salto → resaltado en el lector.
Comparación de las tres etapas:
| Dimensión | Etapa 1 (texto completo) | Etapa 2 (frases clave) | Etapa 3 (actual) |
|---|---|---|---|
| Contexto por pregunta | Libro entero (o primera mitad truncada) | Frases clave preextraídas | Solo fragmentos de fuente relevantes |
| Precisión en libros largos | Colapso tras ~400k caracteres | Depende de la extracción; pierde detalle | Búsqueda por TOC/span; sin truncado duro del libro entero |
| Velocidad de respuesta | Lenta | Algo mejor; libros largos aún lentos | Búsqueda + contexto corto—notablemente más rápido |
| Coste de tokens | Muy alto | Medio-alto | Preprocesado amortizado + pago bajo demanda |
| Trazabilidad | Débil (citas difíciles) | Hay etiquetas pero contenido filtrado | Notas al pie → spans fuente reales |
| Complejidad técnica | Baja | Media | Alta |
Por qué nos quedamos en la etapa 3: En lectura, cero alucinaciones no es «mostrar al modelo el máximo texto», sino «antes de responder, obtener pruebas fuente para la pregunta». Las etapas 1–2 luchaban con el tamaño del contexto; la etapa 3 divide el pipeline en índice (preprocesado) → búsqueda (Tool) → prueba (fuente) → respuesta (generación restringida)—equilibrio entre precisión, coste y trazabilidad.
A continuación el detalle de la etapa 3.
II. Definición del problema: En Q&A de libros, la alucinación duele más que en un chat genérico
Los usuarios perdonan errores ocasionales en un chatbot general. En Q&A de libros, el coste es mayor:
- Preguntan qué dice este libro—no lo que vive en la memoria paramétrica del modelo;
- Una «opinión del libro» plausible puede inducir a error en notas, citas y reenvíos;
- Sin fuentes, no hay verificación—la confianza cuesta de construir.
«Cero alucinaciones» se traduce en tres reglas exigibles:
- Las preguntas sobre el libro deben consultar el libro primero: Todo lo que pueda concernir al libro abierto pasa por la búsqueda (Tool) antes de responder;
- Las respuestas deben ser trazables: Las afirmaciones clave llevan etiquetas de posición que la UI puede parsear y saltar;
- Decir cuando no se encuentra: Si el libro no lo contiene, decirlo—no disfrazar conocimiento general como «lo que dice el libro».
El resto sigue el flujo de datos de la etapa 3 y cómo se aplican estas reglas.
III. Arquitectura: Preprocesado → Tool → Generación restringida → Citas clicables
Idea central: no dejar que el modelo «responda de memoria»—obligarlo a «reunir pruebas, responder y marcar fuentes».
IV. Preprocesado: Convertir el libro en un índice de segmentos buscable
Si cada pregunta siguiera usando el contexto etapa 1 del libro entero, los libros largos revientan el presupuesto de tokens y la búsqueda es demasiado gruesa. Etapa 3: en el primer chat IA de un libro, ejecutar en segundo plano un job de resumen de segmentos—división por TOC o longitud de texto en Segments, resumir cada uno, persistir en IndexedDB local.
Cada Segment contiene resumen más posición física en el cuerpo:
| Campo | Significado |
|---|---|
startFileIndex / endFileIndex | Índice de archivo spine (PDF: un archivo por página) |
startOffset / endOffset | Inicio/fin en caracteres |
sequence | Orden de lectura lineal |
title | Título TOC |
La división equilibra precisión y coste: nodo TOC bajo ~20 KB → resumir solo ese nodo; nodos hermanos fusionados en lotes (15–20 KB) antes del LLM; bloques largos no estructurados en rangos ~30–40k caracteres.
El system prompt de resumen exige conservar etiquetas de posición inline ([fNúmero-Número-Número]) para que la fuente obtenida por Tool se alinee con offsets spine. Restricción central:
If summary content relates to a passage, keep the trailing position tag [fNumber-Number-Number] (e.g. [f1-90-109]).
Tags are atomic—do not alter, merge, or omit any character or digit.
Tras el preprocesado, el Q&A depende de un índice de segmentos estructurado, no del contexto del libro entero—requisito técnico del cero alucinaciones en libros largos.
V. Sistema de etiquetas de posición: Codificar el «de dónde» en el texto
Cero alucinaciones exige contenido de la fuente y procedencia analizable por máquina y alcanzable en la UI. Usamos etiquetas inline:
[f{fileIndex}-{startChar}-{endChar}]
Ejemplo: [f5-123-165] = archivo spine 5 (base 0), caracteres 123–165.
5.1 Cómo se escriben las etiquetas en el cuerpo
La capa de extracción añade [f{fileIndex}-{start}-{end}] al final de cada segmento:
const position = `[f${fileIndex}-${absOffset}-${absOffset + segment.length}]`;
fileLines.push(segment.text.trim() + position);
Resúmenes de preprocesado o extractos Tool: las posiciones se alinean con offsets de caracteres spine—no números de página estimados por el modelo.
5.2 Restricciones sobre la salida del modelo
El system prompt incluye Position Citation Rules—cinco puntos esenciales:
- Formato estándar: Debe usar
[f_fileIndex-startChar-endChar]; las tres partes numéricas obligatorias; - Copiar solo de fuentes actuales: Notas verbatim de mensajes system/user o retornos Tool de esta ronda;
- Sin fabricación: No calcular, editar ni inventar posiciones;
- Preferir omisión: Sin etiqueta válida en el contexto → responder con normalidad—no emitir etiquetas de posición;
- Inline con las afirmaciones: Etiquetas tras la frase relevante; sin listas de citas al final.
La UI también filtra etiquetas bipartitas inválidas ocasionales (p. ej. [f1-293]) antes del render.

VI. Tool Calling: Primero buscar, luego responder
Cuando el chat está vinculado a un libro (resourceId presente, chatType === 'chat'), registramos dos Tools con executors antes de cada generación—bucle function calling compatible con OpenAI.
6.1 get_related_segment_summaries — Búsqueda dirigida de segmentos
Para: conceptos, personajes, trama, detalles de capítulo—intención de búsqueda clara.
Flujo:
- El modelo reformula la pregunta en términos probables en el libro («Optimize Search Queries» en el system prompt);
- Llamada Tool con
question; - Agrupar todos los resúmenes de segmentos por presupuesto de tokens (~30k tokens por lote, máx. 5 lotes);
- Por lote: petición LLM separada elige IDs relevantes (máx. 5) de
{ id, title, summary }, JSON como{"Thinking":"...","answer":["1","3"]}; - Para segmentos elegidos, extraer texto fuente etiquetado del spine—not resúmenes—como resultado Tool.
Diseño clave: el Tool devuelve fuente, no resúmenes. El modelo responde desde párrafos reales con [f…] inline, evitando la deriva «resumen → re-resumen».
6.2 get_full_book_segment_summaries — Visión global del libro
Para: «resumir el libro», «reseñar este libro», «estructura/temas globales»—vista global.
Concatenar todos los campos summary de segmentos en orden de lectura—evitar perder capítulos clave solo por relevancia por trozo.
6.3 System prompt: Libro primero, herramientas primero
Con libro vinculado, aplica Core Principles for Reading Assistant:
1. Book First, Tool First
- Any question possibly about the book must call tools first;
- Answers must rely mainly on retrieval—never invent “book content” without retrieval.
2. General Knowledge as Fallback Only
- Only for: casual chat / user explicitly skips the book / tools return nothing;
- If the book lacks it, say “not mentioned in this book” before general knowledge.
3. Direct Style
- Get to the point—avoid “based on the provided materials…” and similar filler.
La generación ejecuta el bucle Tool: tool_calls → ejecutar → añadir role: tool → continuar hasta el texto final. Con tools activos, el canal thinking está desactivado para evitar conflictos de protocolo.
VII. Trazabilidad en frontend: De la nota al resaltado
La salida [f5-123-165] del modelo no se muestra en bruto; la capa de render convierte etiquetas en citas clicables.
7.1 Render de notas
Normalizar etiquetas a enlaces Markdown como [1]([f5-123-165]), mostrar como notas numeradas; deduplicar la misma posición.
7.2 Interacción al clic
- Primer clic: Parsear
[f…]→ fileIndex + offsets → extraer texto spine → vista previa (título TOC opcional); - Misma nota otra vez: Cerrar vista previa;
- Confirmar salto: Abrir vista de lectura, resaltar rango de caracteres.
Del etiqueta copiada del modelo al texto fuente visible para el usuario, la cadena nunca pasa por otra llamada LLM—determinista y reproducible.
VIII. Casos límite y degradación honesta
Cero alucinaciones ≠ «siempre hay respuesta»—es sin prueba, sin invención:
| Escenario | Comportamiento |
|---|---|
| Resúmenes de segmentos no listos | Extraer primero texto completo y resumir |
| Tool no encuentra nada | Devolver (No relevant segment excerpts found…); el modelo debe decir «no está en el libro» |
| Etiquetas bipartitas inválidas del modelo | Filtrado en frontend; sin notas rotas |
| Charla informal | El system prompt permite conocimiento general fuera del libro |
| Exportar chat | Las notas pueden ser deep links del lector para compartir/archivar |

IX. Compromiso de diseño: ¿Por qué no «RAG vectorial»?
Colegas en Q&A documental suelen preguntar: si haces generación aumentada por recuperación, ¿por qué no Embedding + base vectorial Top-K?
Sí hacemos RAG—buscar antes de generar. La diferencia: «RAG» en el discurso comunitario suele implicar similaridad vectorial; nuestra etapa 3 es índice de segmentos + Tool con fuente bajo demanda—sin capa vectorial por diseño. Abajo: razones arquitectónicas, sin negar el valor del RAG vectorial.
Alcance: no «sin búsqueda», sino «sin búsqueda vectorial»
- RAG amplio: buscar → generar → lo hacemos;
- RAG vectorial: recuperación por similitud de embedding → no en esta versión.
El preprocesado construye un índice de resúmenes de segmentos; el modelo elige segmentos vía Tools y obtiene texto fuente. Hay búsqueda sin modelo de embedding separado ni mantenimiento de índice vectorial.
Razón 1: Proveedores LLM personalizados—superficie de integración pequeña
Los usuarios pueden conectar sus propias API keys, base URL personalizadas u Ollama local—el modelo de chat es su elección; coste y ruta de datos bajo control.
El RAG vectorial típico amplía la integración:
- Además del modelo de chat, suele hacer falta un modelo de embedding (otro nombre, a veces otro endpoint);
- Ollama local necesita modelo de embedding aparte más compatibilidad de dimensión/API;
- Más modos de fallo: chat OK pero búsqueda vacía—embedding, índice o dimensión; más difícil depurar que un proveedor de extremo a extremo.
Aquí, elección de segmentos y respuesta comparten una config de proveedor—no «chat en A, índice en B». Para apps LLM enchufables, eso suele pesar más que unos puntos de recall.

Razón 2: Los embeddings atan el índice—cambiar de proveedor cuesta caro
En RAG vectorial, los vectores no son un formato intermedio universal—son coordenadas bajo un modelo de embedding. Índice con A, consulta con B: la similitud suele ser incomparable—a menudo re-embedding completo, y dimensiones (768 / 1024 / 1536 …) fijan el esquema de almacenamiento.
La etapa 3 persiste resúmenes estructurados + spans de caracteres, no vectores; cambiar modelo de chat no reconstruye el índice; la cadena de prueba (posiciones fuente) se mantiene—alineado con «probar distintos LLM en cualquier momento».
Razón 3: El enrutamiento estructurado suele bastar en documentos largos con TOC
E-books y PDF suelen tener estructura de capítulos; el preprocesado da títulos de segmento + resúmenes. Para «qué dice el capítulo X» o «cómo define el libro Y», elegir segmentos del catálogo y tirar de la fuente funciona bien en la práctica; el Tool devuelve fuente con [f…], el cero alucinaciones sigue anclado en spans de caracteres.
Los vectores ayudan en semántica difusa, multilingüe, coincidencia literal larga; en lectores TOC + preprocesado + trazabilidad fuerte, invertir en Tool + devolución de fuente + reglas de cita suele tener mejor ROI.
Futuro: Recuperación híbrida, no reescritura
Podríamos añadir recuperación vectorial gruesa (embedding solo para Top-N capítulos candidatos), terminando siempre en elegir segmento → fuente → traza clicable—reglas de cero alucinaciones sin cambio. Si se añade: embedding opcional, avisos explícitos de reindexar al cambiar modelo—evitar recuperación errónea silenciosa.
Hasta entonces: cualquier API chat compatible OpenAI funciona; cambiar modelo de chat no reconstruye el índice local.
X. Resumen
| Paso | Método | Rol |
|---|---|---|
| Preprocesado | División TOC/longitud + caché de segmentos | Libros largos buscables y localizables |
| Etiquetas de posición | [fArchivo-inicio-fin] en la fuente | Procedencia analizable por máquina |
| Tool | Segmentos / resúmenes de libro por pregunta, devolver fuente | Forzar pruebas antes de responder |
| System prompt | Libro primero, sin etiquetas falsas, decir cuando falta | Restringir generación |
| Frontend | Nota → vista previa → salto y resaltado | El usuario verifica pruebas |
| Sin búsqueda vectorial | Un proveedor; cambiar modelo chat sin reindexar | Menor coste de integración y migración |
«Cero alucinaciones» no significa que el modelo nunca se equivoque—significa que la ingeniería ata la salida a una cadena de pruebas: sin búsqueda → no fingir contenido del libro; con búsqueda → posiciones fuente verificables.
Si desarrollas lectura con IA o Q&A documental, esperamos que el camino texto completo → frases clave → Tool-first bajo demanda, más etiquetas de posición inline + devolución de fuente, sea una implementación de referencia útil.
Estas son lecciones del lector IA Foxycape—solo como referencia. Prueba el lector en la página de descarga.
