RAG con Ollama: enseña a la IA a responder sobre tus documentos
Tienes documentos: PDF, artículos, notas, una base de conocimiento. Quieres hacer preguntas
y obtener respuestas basadas en esos documentos, no en el conocimiento general del modelo.
Y todo esto, localmente, sin enviar datos a la nube.
Para eso existe RAG. En este artículo, encontrarás una explicación del concepto,
un diagrama visual del pipeline y un ejemplo práctico de Python paso a paso que funciona de verdad.
Además, patrones de producción que no se encuentran en la documentación.
📚 Contenido del artículo
🎯 ¿Qué es RAG y por qué no es fine-tuning?
Respuesta corta:
RAG (Retrieval-Augmented Generation) es una forma de dar al modelo acceso
a tus documentos sin modificar el modelo en sí. Antes de cada consulta,
el sistema busca fragmentos relevantes de tu base y los añade
al contexto. El modelo responde basándose en estos fragmentos,
no solo en lo que aprendió durante su entrenamiento.
Los LLM están entrenados con billones de tokens de internet, pero no con tus
documentos internos, tu base de código o los artículos que escribiste la semana pasada. RAG cierra esta brecha.
Analogía: un abogado y un caso
Imagina un abogado experimentado. Conoce las leyes, los precedentes, la práctica general,
todo lo que ha aprendido a lo largo de los años. Pero cuando se le presenta un nuevo caso, no responde
de memoria. Lee los materiales del caso, extrae los hechos clave y solo entonces formula una posición basándose en documentos específicos.
Un LLM sin RAG es como ese mismo abogado al que se le pide que responda
sin acceso a los materiales del caso. Dirá algo, pero será una opinión general, no una respuesta sobre tu caso específico.
RAG le proporciona esos materiales.
Por qué no fine-tuning
El fine-tuning es el reentrenamiento del modelo con tus datos. Suena lógico,
pero en la práctica es caro, lento e inflexible. Si tus documentos
cambian, hay que reentrenar de nuevo. Si cometes un error en los datos,
el modelo "recordará" lo incorrecto.
RAG no toca el modelo en absoluto. Actualizaste los documentos: reindexas en minutos.
Encontraste un error: lo corriges en la fuente. El modelo sigue siendo el mismo.
Por eso, para la mayoría de las tareas donde se necesita "responder sobre documentos",
RAG es la elección correcta, no el fine-tuning.
RAG vs Fine-tuning: cuándo elegir qué
| Criterio |
RAG |
Fine-tuning |
| Complejidad de implementación |
Baja - unos días |
Alta - semanas |
| Recursos computacionales |
Mínimos - CPU es suficiente |
Requiere GPU, gastos significativos |
| Actualización de conocimientos |
Reindexación - minutos |
Reentrenamiento - días |
| Transparencia |
✔️ Se ven los documentos utilizados |
❌ Difícil explicar de dónde viene la respuesta |
| Error en los datos |
Corregiste la fuente - listo |
Reentrenamiento desde cero |
| Adecuado para |
Base de conocimiento, documentos, FAQ, búsqueda |
Cambio de estilo, tono, formato de respuestas |
| No adecuado para |
Cambiar el comportamiento del modelo en sí |
Documentos que se actualizan con frecuencia |
Cuándo RAG no resolverá la tarea
RAG no es una bala de plata. Hay varios escenarios donde no ayudará:
- ⚠️ Consultas cortas (1-2 palabras) - la búsqueda semántica
en consultas cortas es menos precisa que la búsqueda por palabras clave.
Se necesita un fallback.
- ⚠️ La pregunta va más allá de los documentos - si
la respuesta no está en la base, el modelo dirá "no lo sé" o
empezará a alucinar. El prompt debe prohibir explícitamente lo segundo.
- ⚠️ Necesidad de cambiar el estilo o comportamiento del modelo -
aquí RAG no ayudará, esta es una tarea para fine-tuning o
un prompt de sistema detallado.
Conclusión de la sección: RAG es la elección correcta cuando
se necesita responder sobre documentos específicos, y esos documentos
cambian o se actualizan. El fine-tuning es necesario cuando se necesita
cambiar el comportamiento del modelo en sí, no ampliar su conocimiento.
🎯 ¿Cómo funciona el pipeline: del documento a la respuesta?
Respuesta corta:
El pipeline RAG se divide en dos fases independientes. Indexación -
se realiza una vez o al actualizar documentos:
documento → chunks → embeddings → vector store.
Búsqueda y generación - en cada consulta del usuario:
pregunta → embedding → similarity search → prompt con contexto → respuesta.
Si entiendes dónde ocurre cada cosa, la depuración se vuelve mucho más sencilla.
Una de las primeras cosas que entendí en la práctica: el modelo nunca
"lee" el documento completo. Solo ve unos pocos
fragmentos que el sistema considera más relevantes. La calidad de la respuesta
depende directamente de la calidad de esta selección.
Fase 1 - Indexación
La indexación se realiza una vez, o programada cuando se actualiza el contenido.
Objetivo: convertir el texto humano en una forma que el vector store pueda buscar.
Paso 1. Lectura del documento
Formatos de entrada: PDF, HTML, Markdown, texto plano, páginas web.
En este paso, lo principal es obtener texto limpio sin marcado.
Etiquetas HTML, metadatos de PDF, caracteres de servicio, todo esto es ruido que empeora
la calidad de los embeddings. Jsoup para Java, BeautifulSoup para Python son
herramientas estándar de limpieza.
Paso 2. Chunking - división en fragmentos
El documento se divide en partes más pequeñas: chunks. ¿Por qué no guardar el documento completo?
Dos razones. Primero, los modelos de embedding tienen un límite de tokens:
nomic-embed-text - 8192 tokens, all-minilm - 256. Un artículo de 5000 palabras
simplemente no cabrá. Segundo, la búsqueda es más precisa en fragmentos más pequeños:
si guardas un artículo completo como un solo vector, el vector promedia todos los temas del artículo
y se vuelve menos específico.
Estándar: 512 tokens con un overlap de 50 tokens. Overlap es la superposición intencionada
entre chunks adyacentes para que la idea no se corte en el límite del fragmento.
Paso 3. Embeddings
Cada chunk se convierte en un vector numérico, una lista de 768 números
(para nomic-embed-text). Este vector codifica el *sentido* del texto,
no las palabras específicas. Por eso "desarrollo web llave en mano"
y "cómo hacer una web" tendrán vectores similares incluso sin palabras comunes.
Ahí radica la ventaja de la búsqueda semántica sobre la búsqueda LIKE.
Paso 4. Vector Store
Los vectores y el texto original de los chunks se almacenan en una base de vectores:
pgvector (extensión para PostgreSQL), Chroma, FAISS, Milvus u otra.
Si el proyecto ya tiene PostgreSQL, pgvector es la elección natural:
una sola instancia de base de datos, no hay que instalar nada nuevo.
Fase 2 - Búsqueda y generación
Esta fase se inicia con cada consulta y dura segundos.
El usuario introduce una pregunta y, tras unos pocos pasos, obtiene una respuesta.
Paso 5. Embedding de la consulta
La pregunta del usuario también se convierte en un vector,
utilizando el mismo modelo de embedding que se usó durante la indexación.
Esto es fundamental: si indexaste con nomic-embed-text,
la consulta también debe pasar por nomic-embed-text.
Diferentes modelos dan vectores incompatibles, y la búsqueda será irrelevante.
Paso 6. Similarity Search
El vector store encuentra los N vectores más cercanos al vector de la consulta.
"Más cercano" significa cosine similarity o dot product entre vectores.
Resultado: los top 5 (o los que especifiques) chunks más relevantes.
El similarity threshold filtra los resultados por debajo de un umbral de similitud;
generalmente se empieza con 0.5 y se ajusta según los logs.
Paso 7. Formación del prompt
Los chunks encontrados se insertan en el prompt del sistema como contexto.
La calidad de este prompt influye directamente en la calidad de la respuesta.
Lo mínimo que debe incluir el prompt del sistema es:
Responde SÓLO basándote en el contexto proporcionado.
Si la respuesta no está en el contexto, dílo honestamente.
No inventes información que no esté en los documentos.
Contexto:
{chunks encontrados}
Sin una prohibición explícita de inventar, el modelo alucinará.
Esto no es un error de un modelo específico, es el comportamiento general de los LLM.
Paso 8. Generación de la respuesta
El LLM (Ollama con llama3.3, mistral u otro modelo) genera una respuesta
basándose en el contexto del paso anterior.
Detalle importante: muestra al usuario las fuentes, qué artículos
o documentos específicos utilizó el sistema. Esto aporta transparencia y es una forma
de verificar de dónde obtuvo la información el modelo.
Conclusión de la sección: Dos procesos independientes:
indexación y búsqueda+generación. La mayoría de los problemas de RAG surgen
o en el paso de chunking (tamaño incorrecto), o en el paso de
similarity search (threshold incorrecto), o en el prompt del sistema
(falta la prohibición de alucinaciones). Los analizaremos en detalle a continuación.
🎯 Herramientas: LlamaIndex vs LangChain vs enfoque manual
Respuesta corta:
En 2026, la elección entre LlamaIndex y LangChain no se trata de "cuál es mejor",
sino de lo que estás construyendo. LlamaIndex está especializado en trabajar con documentos
y ofrece una mejor calidad de búsqueda con menos código. LangChain es para sistemas de agentes complejos donde RAG es uno de los componentes. El enfoque manual a través de Spring AI o la API REST directa es si Python no te satisface o necesitas control máximo sobre cada paso.
Un análisis detallado de 2026 señala:
la comparación "LangChain = orquestación, LlamaIndex = datos" que repiten
la mayoría de los artículos, ya está obsoleta. Ambos frameworks se están acercando
y cubren funcionalidades similares. La elección ahora depende de
dónde empiezas y qué es la base de tu aplicación.
LlamaIndex - si el centro son los documentos y la búsqueda
LlamaIndex se construyó en torno a la idea de que el problema principal es
conectar de forma fiable los LLM con tus datos. Todo lo demás: agentes, pipelines,
orquestación, va sobre este fundamento.
Benchmarks independientes muestran:
LlamaIndex es un 40% más rápido que LangChain en tareas de recuperación de documentos
y en 2025 obtuvo un +35% en precisión de búsqueda.
La razón: las abstracciones integradas para chunking, re-ranking y ensamblaje de contexto
están optimizadas para la recuperación, no para pipelines generales.
Ventajas:
- ✔️ Inicio más sencillo: 10 líneas para un RAG funcional
- ✔️ Re-ranking integrado, búsqueda híbrida (semántica + palabras clave)
- ✔️ Amplia lista de conectores de datos a través de LlamaHub: PDF, HTML,
Notion, Google Docs, S3, bases de datos
- ✔️ Chunking flexible: SentenceSplitter, TokenTextSplitter,
SemanticSplitter - cambias con una línea
- ✔️ Integración nativa con Ollama a través de paquetes separados
Desventajas:
- ⚠️ Para sistemas de agentes complejos con muchas herramientas externas,
es menos conveniente que LangChain
- ⚠️ La documentación es menos completa que la de LangChain para escenarios atípicos
Cuándo elegir: tienes documentos, PDF, base de conocimiento -
necesitas hacer preguntas y obtener respuestas. Blog, FAQ, documentación interna de la empresa,
guía técnica.
LangChain - si RAG es parte de un sistema más complejo
LangChain se construyó en torno a la idea de que la construcción con LLM es un problema
de flujo de trabajo: hay datos de entrada, hay datos de salida, y entre ellos, modelos, herramientas,
memoria y fuentes externas que necesitan ser orquestadas.
En 2026
esto significa LangGraph: un grafo dirigido donde los nodos son funciones
y las aristas son transiciones entre estados. RAG en LangChain es una de las herramientas
de un agente, no una abstracción central.
Ventajas:
- ✔️ El ecosistema y la comunidad más grandes: crecimiento del 220% en estrellas de GitHub
en 2024
- ✔️ LangGraph para agentes stateful complejos con ramificaciones y bucles
- ✔️ LangSmith - observabilidad, tracing, evaluación out-of-the-box
- ✔️ Soporte multimodal más fuerte que LlamaIndex
- ✔️ Mejor para memoria conversacional en conversaciones largas
Desventajas:
- ⚠️ Curva de aprendizaje más pronunciada - más conceptos para un RAG simple
- ⚠️ Para recuperación de documentos pura, requiere más código que LlamaIndex
- ⚠️ Cambios frecuentes que rompen compatibilidad: el LangChain original ha sido reemplazado por LangGraph
para escenarios de producción
Cuándo elegir: un agente de IA que llama a APIs externas,
escribe código, ejecuta consultas SQL, y además busca en documentos.
RAG aquí no es la tarea principal, sino una de las herramientas del agente.
Enfoque manual - control máximo sin framework
API REST de Ollama directamente + cualquier vector DB + lógica propia.
Este es el enfoque que subyace a Spring AI para Java, donde el framework
proporciona abstracciones listas para usar, pero tú controlas cada detalle del pipeline.
Ventajas:
- ✔️ Control total sobre cada paso - chunking, scoring,
lógica de fallback
- ✔️ Sin dependencia de Python - cualquier idioma a través de la API REST
- ✔️ Sin cambios que rompan compatibilidad por parte de un framework externo
- ✔️ Se puede implementar lógica personalizada que los frameworks no soportan
Desventajas:
- ⚠️ Más código - el chunking, la indexación por lotes, el fallback se escriben manualmente
- ⚠️ Sin re-ranking integrado ni búsqueda híbrida
- ⚠️ Más tiempo de implementación que con LlamaIndex
Cuándo elegir: proyecto Java / Spring Boot (Spring AI),
requisitos atípicos para el pipeline, o cuando el framework añade más
complejidad de la que resuelve.
Matriz de elección
| Tarea |
Recomendación |
| Respuestas sobre PDF y documentos - inicio simple |
LlamaIndex |
| Búsqueda semántica en un blog o base de conocimiento |
LlamaIndex |
| Documentación interna de la empresa, FAQ |
LlamaIndex |
| Agente de IA que llama a APIs y herramientas |
LangChain / LangGraph |
| Flujo de trabajo complejo multi-paso con memoria |
LangChain / LangGraph |
| Proyecto Java / Spring Boot |
Spring AI (enfoque manual) |
| Lógica personalizada de fallback y deduplicación |
Enfoque manual |
| Prototipo rápido en cualquier idioma |
API REST directa de Ollama |
¿Se pueden combinar?
Sí, y es una práctica común en producción.
Contabo describe una pila típica de 2026:
LlamaIndex como capa de conocimiento (indexación y búsqueda),
LangChain como capa de orquestación (agentes y flujos de trabajo),
y n8n o un servicio propio como motor de flujo de trabajo.
Pero para la mayoría de los proyectos, un framework es mejor que dos:
menos dependencias, mantenimiento más sencillo.
Conclusión de la sección: Si la tarea es "responder sobre documentos",
empieza con LlamaIndex. Si estás construyendo un agente de IA complejo donde RAG es una
de las herramientas, usa LangChain. Si estás en Java o necesitas control total,
usa Spring AI o la API REST directa a Ollama.
🎯 Вибір моделі для ембедингів: nomic-embed-text і альтернативи
Коротка відповідь:
nomic-embed-text — стандартний вибір для Ollama: легка (274 МБ),
швидка, вектор 768 вимірів. Але є важливе обмеження: погано працює
з короткими запитами і нелатинськими мовами. Для українського тексту
і коротких запитів — потрібен fallback або інша модель.
Ембединг-модель — це фундамент RAG. Погана ембединг-модель зробить
весь пайплайн непотрібним незалежно від якості LLM.
Я дізнався про це не з документації — а коли запит «LLM»
повертав нерелевантну статтю зі score 0.63,
а правильна не потрапляла навіть в топ-5.
Що таке ембединги — аналогія з фотографією
Уяви що ти зберігаєш фотографії в різних форматах.
Маленьке фото 100×100 пікселів — займає мало місця, але деталі розмиті,
схожі обличчя важко розрізнити. Фото 4000×3000 — займає набагато більше,
але кожна деталь чітка і унікальна.
З ембедингами — та сама логіка. Вектор — це «фотографія» тексту в числовому просторі.
Розмірність вектора (384, 768, 1024) — це як роздільна здатність.
Більший вектор кодує більше деталей і нюансів смислу — і пошук точніший.
Але більший вектор займає більше місця в базі і повільніше генерується.
all-minilm з вектором 384 — це JPEG 100×100: легкий і швидкий,
але губить деталі. nomic-embed-text з вектором 768 — JPEG 1920×1080:
хороший баланс. mxbai-embed-large з вектором 1024 — RAW-формат:
максимальна деталізація, але коштує вдвічі дорожче по RAM.
І ще одна важлива деталь по аналогії з фото: якщо ти зберіг всі фотографії
в одному форматі — не можна порівнювати їх з фотографіями в іншому форматі.
Так само з ембедингами: якою моделлю індексував — тією
ж моделлю потрібно ембедити запит.
Поміняєш модель — переіндексуй всю базу заново.
Порівняння ембединг-моделей для Ollama
| Модель |
Розмір |
Вектор |
Контекст |
Коли підходить |
Команда |
| nomic-embed-text |
274 МБ |
768 |
8192 токени |
Стандартний вибір, довгі документи |
ollama pull nomic-embed-text |
| mxbai-embed-large |
669 МБ |
1024 |
512 токени |
Висока точність пошуку, короткі чанки |
ollama pull mxbai-embed-large |
| all-minilm |
46 МБ |
384 |
256 токени |
Слабке залізо, швидкість важливіша за точність |
ollama pull all-minilm |
Реальні обмеження nomic-embed-text
З реального досвіду при індексації 500+ статей на чотирьох мовах —
nomic-embed-text має конкретні слабкі місця яких немає в офіційній документації:
- ⚠️ Короткі запити (1-2 слова) — запит «LLM» повертав
score 0.63 для нерелевантної статті, а правильна не потрапляла
навіть в топ-5. Векторний простір моделі не вміє добре розрізняти
короткі однослівні запити
- ⚠️ Нелатинські мови — українська і кирилиця загалом
дають нижчу якість порівняно з англійським текстом.
Запит «LLM vs RAG у 2026» знаходить правильно (score 0.69),
але короткий україномовний запит — вже проблема
- ⚠️ Змішаний контент — якщо документи на кількох мовах
одночасно, якість пошуку падає: ембединги англійського
і українського тексту живуть у різних «зонах» векторного простору
Практичний висновок: коли достатньо nomic-embed-text
nomic-embed-text підходить добре якщо:
- ✔️ Документи переважно англійською
- ✔️ Запити 3+ слів (семантичний пошук)
- ✔️ Потрібен великий контекст — 8192 токени достатньо для довгих статей
Потребує fallback або заміни якщо:
- ⚠️ Документи переважно нелатинськими мовами
- ⚠️ Короткі пошукові запити (1-2 слова) — додай keyword fallback
- ⚠️ Потрібна максимальна точність — розглянь mxbai-embed-large
Висновок розділу: Вибір ембединг-моделі — це компроміс
між точністю, розміром і швидкістю — як вибір формату і роздільної здатності фото.
nomic-embed-text — правильний старт. Але знай його обмеження
і завжди тестуй на реальних запитах своєї аудиторії
перш ніж вважати що пошук «працює».
🎯 Ключові рішення при реалізації RAG — і чого уникати
Коротка відповідь:
Універсального working коду для RAG не існує — кожен проєкт має свою
архітектуру, свою мову, свою модель зберігання і свої вимоги до пошуку.
Але є кілька рішень які потрібно прийняти в будь-якому RAG-проєкті —
і типові пастки в кожному з них.
Туторіали показують як запустити RAG за 10 хвилин.
Продакшн показує що більшість цього часу ти витратиш
на вирішення проблем яких у туторіалі немає.
Рішення 1 — Як читати і очищати документи
Незалежно від мови і фреймворку — на вході є «брудний» текст.
PDF містить колонтитули, номери сторінок, артефакти форматування.
HTML містить теги, атрибути, JavaScript, рекламні блоки.
Весь цей шум потрапляє в ембединги і знижує якість пошуку.
Що важливо вирішити заздалегідь:
- ✔️ Який формат є основним і як його чистити
- ✔️ Чи потрібно зберігати структуру (заголовки, секції)
або достатньо plain text
- ✔️ Що робити з порожніми або дуже короткими документами
Типова пастка: ігнорувати очищення на старті — і отримати
ембединги де 30% контенту це «Copyright 2024» і «Сторінка 1 з 15».
Рішення 2 — Розмір чанка і стратегія розбиття
Немає правильного розміру чанка для всіх випадків.
512 токенів — стандарт для статей. 256 — для технічної документації
де кожен абзац незалежний. 1024 — якщо важливо зберегти контекст
між абзацами.
Що важливо вирішити:
- ✔️ Розбивати по токенах або по реченнях — різний результат
на межах чанків
- ✔️ Який overlap потрібен — 0 якщо чанки незалежні,
50-100 токенів якщо думки переходять між абзацами
- ✔️ Чи потрібно зберігати метадані в кожному чанку —
title, url, category, locale — для фільтрації пізніше
Типова пастка: встановити розмір чанка один раз і більше не перевіряти.
Перевір реальні чанки після розбиття — і ти побачиш де думки обриваються.
Рішення 3 — Ідентифікатори чанків
Здається дрібницею — але стає критичним при переіндексації.
Якщо використовувати рандомні UUID при кожній індексації —
база накопичує дублікати. Через місяць один документ
може бути представлений 10 версіями одних і тих самих чанків.
Правильне рішення — детермінований ID на основі document_id і порядкового номера чанка.
При переіндексації той самий чанк отримує той самий ID і перезаписується,
а не дублюється. Як це реалізувати — в Java через
UUID.nameUUIDFromBytes(), в Python через hashlib.md5().
Конкретна реалізація залежить від стеку — принцип однаковий.
Рішення 4 — Стратегія оновлення індексу
Як визначати що потрібно переіндексувати?
Два підходи:
- ✔️ Timestamp-based: зберігати
indexed_at
і переіндексовувати де updated_at > indexed_at.
Просто і надійно — але є підводний камінь:
якщо @PreUpdate або тригер оновлює updated_at
при saveAll() — і indexed_at стає застарілим
відразу після запису. Рішення: встановлювати indexed_at
трохи у майбутнє — now() + 10 секунд.
- ✔️ Hash-based: зберігати хеш контенту і переіндексовувати
тільки якщо хеш змінився. Точніше, але складніше в реалізації.
Рішення 5 — Batch індексація
Спроба проіндексувати 500 документів одним запитом закінчується
або таймаутом Ollama або OOM. Batch по 10-20 документів —
і при падінні продовжуєш з місця зупинки,
а не з початку. Саме тому indexed_at важливіший
ніж здається — він дозволяє шедулеру підхопити звідки впав.
Рішення 6 — LAZY loading і транзакції
Якщо використовуєш ORM (JPA, SQLAlchemy, ActiveRecord) —
будь обережний з lazy-loaded зв'язками під час індексації.
Доступ до post.getTranslations() поза транзакцією
дає LazyInitializationException в JPA або
DetachedInstanceError в SQLAlchemy.
Рішення: JOIN FETCH у запиті або явне завантаження
всього необхідного всередині транзакції.
Чого уникати — коротко
- ❌ Не починай з «робочого прикладу з туторіалу» напряму в продакшн —
там немає fallback, немає batch, немає ідемпотентності
- ❌ Не ігноруй очищення тексту перед індексацією
- ❌ Не використовуй рандомні UUID для чанків якщо плануєш переіндексацію
- ❌ Не встановлюй threshold раз і назавжди — логуй score
і коригуй на реальних запитах
- ❌ Не вважай що vector search замінить keyword search повністю —
для коротких запитів потрібен fallback
- ❌ Не забувай що зміна ембединг-моделі вимагає повної переіндексації
Висновок розділу: RAG — це не «скопіював і запустив».
Це шість архітектурних рішень кожне з якими має свої пастки.
Прийми їх свідомо до початку реалізації — і заощадиш
від кількох годин до кількох днів дебагу.
🎯 Продакшн патерни: що не пишуть в туторіалах
Коротка відповідь:
Різниця між «hello world» RAG і продакшн RAG — чотири патерни:
fallback на keyword search, ідемпотентна індексація,
дедуплікація результатів і batch обробка.
Жоден з них не згадується в базових туторіалах.
Кожен з них ти відкриваєш через реальну проблему.
Я відкрив всі чотири патерни не через документацію —
а через конкретні інциденти в продакшні.
Fallback — коли запит «Spring» повертав нерелевантні результати.
Ідемпотентність — коли після місяця роботи шедулера
одна стаття була в базі 47 разів.
Дедуплікація — коли топ-5 результатів містив 4 чанки з однієї статті.
Batch — коли Ollama зависала на 500 документах.
Патерн 1 — Fallback: vector → keyword
Vector search побудований на семантичній схожості.
Він добре знаходить «веб-розробка під ключ» за запитом «як зробити сайт».
Але на запиті «Spring» або «RAG» — схожість розмивається по всьому
векторному просторі і точність падає.
Правило яке працює на практиці: запити довжиною 1-2 слова
направляти на keyword search, 3+ слів — на vector search.
Якщо vector search повернув порожній результат — fallback на keyword.
Якщо vector search впав з помилкою — fallback на keyword.
Користувач не помічає різниці, але завжди отримує відповідь.
Реалізація залежить від стеку — але принцип незмінний:
два провайдери, фасад який вирішує який використати,
і автоматичний перехід між ними.
Патерн 2 — Ідемпотентна індексація
Шедулер запускає індексацію щоночі.
Якщо при кожному запуску генерувати рандомний UUID для чанків —
через місяць та сама стаття представлена 30 версіями одних і тих самих чанків.
Пошук повертає дублікати, якість падає, база росте без причини.
Рішення: детермінований ID на основі ідентифікатора документа
і порядкового номера чанка. При переіндексації той самий чанк
отримує той самий ID і перезаписується — а не додається як новий запис.
Конкретна реалізація залежить від мови — в Java це
UUID.nameUUIDFromBytes(),
в Python — hashlib.md5() через uuid.UUID(bytes=...).
Принцип однаковий.
Патерн 3 — Дедуплікація результатів
Vector search повертає топ-N чанків — не топ-N документів.
Якщо одна стаття добре відповідає запиту — вона може дати
4 чанки з 5 у топ-5. Користувач отримає чотири посилання
на одну й ту саму сторінку.
Рішення: після отримання результатів — дедуплікація по document_id.
Для кожного документа залишаєш тільки чанк з найвищим score.
Результат: топ-5 завжди містить 5 різних документів,
а не 5 фрагментів одного.
Важливо: метадані кожного чанку мають містити document_id —
без цього дедуплікація неможлива. Продумай структуру метаданих
до початку індексації.
Патерн 4 — Batch індексація з відновленням
Ollama обробляє ембединги послідовно через CPU або GPU.
Запит на 500 документів одночасно — це або таймаут,
або OOM, або просто зависання без помилки.
Рішення: batch по 10-20 документів. Але важливіше інше —
після кожного batch одразу зберігати indexed_at
для оброблених документів. Якщо процес впав на batch 7 з 50 —
при наступному запуску шедулер підхопить з batch 8,
а не почне спочатку. Це і є різниця між «запустив і сподіваюся»
і надійною індексацією.
Висновок розділу: Чотири патерни — fallback,
ідемпотентність, дедуплікація і batch з відновленням —
це різниця між прототипом який працює на демо
і системою яка працює в продакшні місяцями.
Реалізація в кожному стеку своя — принципи однакові для всіх.
🎯 Problemas comunes: fragmentación, umbral, alucinaciones
Tres clases de problemas en RAG — y las tres se manifiestan solo con consultas reales,
no con pruebas sintéticas. Fragmentación incorrecta: el modelo recibe fragmentos
sin contexto. Umbral incorrecto: demasiado ruido o no encuentra nada.
Alucinaciones: el modelo inventa incluso teniendo contexto — debido a un prompt débil
o a fragmentos irrelevantes que de todos modos llegaron al top.
Lo más importante que entendí: la mayoría de los problemas de RAG
son invisibles sin registrar el score. Si no ves
qué valor de similitud devuelve cada resultado —
no puedes diagnosticar por qué la búsqueda da malas respuestas.
Añade el registro del score de inmediato — y ahorrarás horas de depuración.
Problema 1 — Fragmentación: cómo saber que algo va mal
Síntoma: el modelo da respuestas que parecen parcialmente correctas —
hay alguna conexión con la pregunta, pero los detalles son incorrectos o truncados.
Causa: los fragmentos se dividen en lugares desafortunados y la idea se interrumpe en el borde del fragmento.
Diagnóstico: muestra varios fragmentos reales después de la división y léelos.
Si un fragmento comienza o termina en medio de una oración — el tamaño o la estrategia de división son incorrectos.
- ✔️ Documentos cortos, artículos de blog — 512 tokens, solapamiento 50
- ✔️ Documentación técnica con secciones largas — 1024 tokens, solapamiento 100
- ✔️ Código — 256-512 tokens, divide por funciones y no por líneas
- ✔️ Solapamiento — no escatimes: 50-100 tokens de superposición garantizan
que la idea no se pierda en el borde de los fragmentos
Problema 2 — Umbral de similitud: cómo encontrar el valor correcto
Síntoma A: el modelo responde con confianza pero incorrectamente —
el umbral es demasiado bajo, los fragmentos irrelevantes entran en el contexto.
Síntoma B: el modelo dice constantemente "no encontrado" incluso ante preguntas obvias —
el umbral es demasiado alto, nada pasa el filtro.
Diagnóstico: registra el score de cada resultado — y mira la distribución.
Si los resultados correctos tienen un score de 0.55-0.65 — un umbral de 0.7 los corta a todos.
Si los resultados irrelevantes tienen un score de 0.55 — un umbral de 0.5 deja pasar el ruido.
- ✔️ Punto de partida: 0.5 — y ajusta según los registros de consultas reales
- ✔️ Consultas cortas (1-2 palabras) — baja a 0.4 o mejor usa fallback
- ✔️ Consultas semánticas largas — 0.65-0.7 da un resultado más limpio
- ✔️ Si todos los resultados están por debajo del umbral — devuelve "no encontrado"
en lugar de contexto vacío
Problema 3 — Alucinaciones incluso con contexto
Síntoma: el modelo da una respuesta segura que no corresponde a ninguno
de los fragmentos proporcionados — o combina hechos de diferentes fuentes incorrectamente.
La causa más frecuente no está en el modelo — sino en el prompt o en el hecho
de que el contexto irrelevante aún llegó al top.
Tres niveles de protección:
- ✔️ Prompt de sistema estricto — prohibición explícita de inventar:
«Responde SÓLO basándote en el contexto proporcionado.
Si no hay respuesta — dilo honestamente.
No combines información de diferentes fuentes si son contradictorias»
- ✔️ Filtro basado en score — si el score máximo
entre los resultados es inferior a 0.5, no pases el contexto a la LLM en absoluto.
Devuelve "no encontrado" — mejor una respuesta honesta que una alucinación segura
- ✔️ Muestra las fuentes — una lista de artículos o documentos
de donde se tomó la respuesta. El usuario mismo verificará
y notificará si algo está mal
Conclusión de la sección: Los tres problemas se diagnostican
de la misma manera — registrando el score y leyendo los fragmentos reales
que entran en el contexto. Añade esto desde el primer día —
y la mayoría de las preguntas "¿por qué RAG da malas respuestas?"
tendrán una respuesta obvia.
❓ Preguntas frecuentes (FAQ)
¿Se necesita GPU para RAG con Ollama?
No. La GPU acelera la generación de respuestas y los embeddings, pero no es obligatoria.
En CPU, los embeddings a través de nomic-embed-text se generan más lentamente,
pero son totalmente factibles para producción con carga moderada.
En Apple Silicon (M1/M2/M3) — es rápido incluso sin GPU dedicada.
Más detalles sobre qué modelos funcionan realmente con hardware limitado —
Ollama en 8 GB de RAM: ¿qué modelos funcionan en 2026?.
¿Cuántos documentos se pueden indexar?
No hay límites — depende del tamaño del vector store y de la RAM.
pgvector maneja millones de vectores sin problemas.
En la práctica, para un blog con 500 artículos — son ~2500 fragmentos de 512 tokens,
ocupa unos pocos MB en PostgreSQL. La generación de embeddings a través de Ollama
con este volumen tarda 15-30 minutos en CPU — aceptable para un planificador nocturno.
¿RAG o búsqueda de texto completo normal?
No "o", sino "y". RAG es mejor en consultas semánticas de 3+ palabras donde el contenido es importante —
"cómo optimizar consultas en PostgreSQL" encontrará un artículo sobre índices
incluso sin coincidencia exacta de palabras. La búsqueda de texto completo es mejor en consultas cortas
(1-2 palabras), nombres y coincidencias exactas. La estrategia óptima —
búsqueda vectorial con fallback automático a FTS.
¿Cómo actualizar el índice al cambiar documentos?
Guarda indexed_at para cada documento y ejecuta
la reindexación donde updated_at > indexed_at.
Un ID determinista a través de hash permite sobrescribir fragmentos sin duplicados.
Un matiz importante: si el ORM actualiza updated_at automáticamente
con saveAll() — establece indexed_at
un poco en el futuro para que el planificador no reindexe todo cada vez.
¿Qué modelo de Ollama elegir para RAG?
Para embeddings — nomic-embed-text como estándar, mxbai-embed-large
si se necesita mayor precisión. Para la generación de respuestas — llama3.3:8b
en 8 GB de RAM o qwen2.5:14b si se tienen 16 GB.
Comparación completa de modelos con benchmarks y recomendaciones por tareas —
¿Qué modelo de Ollama elegir en 2026?: Comparación de Llama, Qwen, DeepSeek y Mistral.
¿Existen soluciones Java para RAG con Ollama?
Sí — Spring AI con soporte nativo para Ollama y pgvector.
Un caso real de implementación de RAG para un blog en Spring AI con errores
y soluciones específicas que no están en la documentación —
en el artículo Cómo construí RAG para webscraft.org:
Spring AI + pgvector + experiencia real.
¿Por dónde empezar si nunca he trabajado con Ollama?
Primero, familiarízate con la plataforma en sí — qué es Ollama,
cómo está estructurada y qué problemas resuelve.
¿Qué es Ollama y por qué los desarrolladores están migrando masivamente a la IA local en 2026? —
una revisión sin jerga con la que es fácil empezar.
Después de eso, RAG se convierte en el siguiente paso lógico.
✅ Conclusiones
RAG es una de esas tecnologías que parece complicada hasta la primera implementación
y obvia después. El pipeline es conceptualmente simple: documento → fragmentos →
embeddings → búsqueda → respuesta. La complejidad comienza en los detalles —
y la mayoría de estos detalles los encontrarás no en la documentación,
sino a través de incidentes reales en producción.
Lo que aprendí de la implementación para el blog:
- ✔️ Empieza con nomic-embed-text — pero registra inmediatamente el score
en consultas reales. Para idiomas no latinos y consultas cortas
se necesita fallback — lo sabrás no por la documentación
- ✔️ UUID determinista para fragmentos — no aleatorio.
Sin esto, la reindexación multiplica duplicados y no entenderás de inmediato por qué
- ✔️ Fallback a búsqueda por palabras clave — la búsqueda vectorial
no reemplaza la búsqueda normal, la complementa
- ✔️ Lotes de 10-20 documentos guardando indexed_at —
para que en caso de caída se continúe desde el punto de interrupción, no desde el principio
- ✔️ Prompt de sistema estricto — sin una prohibición explícita
de inventar, el modelo alucinará. Esto no es un error de un modelo específico
Con Ollama, toda la pila es gratuita y funciona localmente —
ningún documento va a la nube. Para un blog, documentación interna
o una base de conocimiento corporativa, esta es la arquitectura correcta.
Si trabajas con Java y Spring Boot — el siguiente artículo trata precisamente de eso.
Un caso real de implementación de RAG para webscraft.org en Spring AI + pgvector:
qué errores cometí, qué trampas encontré y qué haría de otra manera —
Cómo construí RAG para webscraft.org: Spring AI + pgvector + experiencia real.
Si aún no estás familiarizado con Ollama —
empieza con la revisión de Ollama 2026.
📎 Fuentes
- LlamaIndex: Introduction to RAG — documentación oficial
- Real Python: LlamaIndex in Python — A RAG Guide — tutorial práctico
- Medium: LlamaIndex para principiantes 2025 — de cero a producción
- DEV Community: RAG con LlamaIndex, ChromaDB y Ollama
- Prem AI: LangChain vs LlamaIndex 2026 — Comparación de RAG en producción
- Contabo: LlamaIndex vs LangChain — ¿Cuál elegir en 2026?
- Ollama: nomic-embed-text — características del modelo de embedding
- Ollama: mxbai-embed-large — modelo de embedding alternativo