localaiforum
Iniciar sesión
Guía › Trabajo con modelos

RAG: dándole tus datos al modelo

El conocimiento de un LLM se congeló en su fecha de corte de
entrenamiento; tus documentos cambian cada semana. La generación
aumentada por recuperación (RAG) lo descongela: en tiempo de consulta,
obtén las porciones relevantes de tus datos, pégalas en el prompt, y deja
que el modelo responda desde evidencia con citas. El modelo hace el
razonamiento; el índice hace la memoria.

La tubería, un ejemplo resuelto

Toma un manual de mantenimiento de 12,000 tokens y recórrelo de punta a
punta:

  1. Fragmenta. Parte primero por encabezados (las secciones sobreviven
    intactas), luego re-parte cualquier sección por encima de ~400 tokens,
    manteniendo ~60 tokens de solape (10–15%). Tamaños sensatos de
    fragmento: 200–800 tokens. Resultado: unos 35 fragmentos.
  2. Incrusta. Cada fragmento se vuelve un vector — bge-m3 emite 1,024
    floats por fragmento. En CPU esto tarda segundos para todo el manual.
  3. Indexa. Almacena vectores + texto + metadatos (archivo fuente,
    ruta de encabezados) en un almacén vectorial.
  4. Recupera. Incrusta la pregunta, obtén los k = 5 fragmentos
    superiores por similitud coseno (4–10 es el rango sensato).
  5. Genera. Ensambla el prompt: instrucciones, los 5 fragmentos con
    etiquetas de ID, la pregunta.

Fragmentar por fronteras semánticas vence a las ventanas fijas. Una tabla
separada de su fila de encabezado, o un procedimiento numerado cortado en
el paso 4, es basura irrecuperable por bueno que sea el embedder.

Dos extras que se pagan solos de inmediato: guarda la ruta de encabezados
(manual › §4 › Pernos) como metadato para poder filtrar y citar con
precisión, e incrusta la consulta exactamente como la escribió el usuario
— la «limpieza» de preprocesado suele empeorar la recuperación, no
mejorarla.

Embeddings: el requisito multilingüe

El embedder decide qué significa «similar». Para coreano y corpus mixtos
coreano/inglés la elección es estrecha — los embedders solo-inglés fallan
en silencio con consultas en coreano, devolviendo fragmentos plausibles
pero equivocados:

Modelo Dims Notas
bge-m3 1024 multilingüe, coreano fuerte — el valor local seguro
nomic-embed-text-v1.5 768 inglés sólido, contexto largo
all-MiniLM-L6-v2 384 rápido, centrado en inglés, débil en coreano

Los modelos de embedding son diminutos (0.1–0.6B parámetros); un núcleo
de CPU mastica miles de fragmentos — inferencia en CPU.
Incrusta el corpus una vez, incrusta las consultas al preguntar. Mantén
el embedder congelado por índice: cambiar de modelo a mitad de índice
produce vectores de espacios distintos, y las puntuaciones de similitud
se vuelven ruido.

Almacenes vectoriales

Almacén Cuándo
sqlite-vec un solo usuario, hasta ~1M de vectores, cero ops — es un archivo
ChromaDB prototipado local-first con filtros de metadatos
Qdrant producción: filtrado, búsqueda híbrida, escala

Los tres corren en local. Empieza con sqlite-vec; migra cuando de verdad
choques con su muro, no antes.

Ensamblar el prompt

Answer only from the excerpts below. Cite every claim as [n].
If the excerpts do not contain the answer, reply "not in the docs".

[1] (manual §4.2) Torque the M8 bolt to 12 Nm after lubrication...
[2] (manual §7.1) Do not re-use stretch bolts...

Question: what torque spec applies to the M8 bolt?

Las citas dejan a los usuarios verificar en vez de confiar. La salida de
«no está en los docs» importa: sin ella, el modelo inventa una respuesta
antes que fallar el formato.

Búsqueda híbrida y reranking

La búsqueda vectorial pura se pierde las cadenas exactas — códigos de
error, nombres de función, números de pieza — que la búsqueda por
palabras clave clava. Híbrido = BM25 + vectores, fusionado con fusión de
rango recíproco; es casi gratis y consistentemente mejor que cualquiera
por separado.

Para calidad, añade un reranker: recupera 20–50 candidatos, reordena
hasta el top 5 con un codificador cruzado (la familia bge-reranker — la
misma historia multilingüe que bge-m3). El reranker lee fragmento y
consulta juntos, lo que un embedder de un solo vector no puede. Receta
barata que cubre la mayoría de las necesidades: recuperación híbrida de
25, rerank a 5.

Diagnostica: ¿fallo de recuperación o de generación?

Antes de afinar nada, responde una pregunta: ¿se recuperó el fragmento
correcto?

  1. Imprime los fragmentos top-k de una consulta que falla, con
    puntuaciones.
  2. Si el fragmento con la respuesta no está en la lista, es un
    problema de recuperación — arregla el fragmentado, los embeddings, o
    sube k.
  3. Si sí está en la lista pero la respuesta es equivocada, es un
    problema de generación — modelo más grande, mejor prompt, rerank para
    empujar fuera los distractores.

Esta única prueba ahorra horas de afinar la mitad equivocada de la
tubería.

Modos de fallo

Síntoma Causa Arreglo
Documentos correctos recuperados, respuesta equivocada distractores en el top-k rerank; encoge k
La respuesta existe en los docs pero nunca sale inanición del top-k o malas fronteras sube k; fragmenta por encabezados
Respuestas confiadas pero caducas índice más viejo que los docs re-indexa al cambiar
Consultas en coreano no encuentran docs en inglés embeddings monolingües bge-m3
Las citas apuntan al fragmento equivocado el fragmento perdió sus metadatos de fuente guarda fuente + encabezado por fragmento

Mide, no improvises

Conserva un conjunto dorado de 20–50 preguntas reales con respuesta
conocida. Puntúa dos números por separado: tasa de acierto de
recuperación (¿está el fragmento correcto en el top-k?) y corrección de
la respuesta. Síguelos tras cada cambio de tamaño de fragmento, embedder
o k. El trabajo de RAG sin conjunto dorado es astrología.

Cuándo ajustar fino en su lugar

El ajuste fino cambia comportamiento — formato, estilo, terminología. Es
una mala forma de añadir hechos: el modelo no puede citar aquello sobre
lo que fue afinado, y los documentos frescos significan una corrida de
entrenamiento fresca. Regla rápida: hechos → RAG, comportamiento → LoRA,
y ambos solo cuando hayas agotado el prompting: ajuste
fino
.

Pilas locales, de la más pequeña primero

Open WebUI trae subida de documentos y recuperación híbrida integradas —
empieza ahí y quizá nunca escribas código de tubería. El núcleo honesto
del RAG son ~50 líneas; escríbelo una vez para saber qué están haciendo
los frameworks:

chunks = chunk_by_header(read("manual.md"), max_tokens=400, overlap=60)
index = [(embed(c), c) for c in chunks]          # hecho una vez

def answer(question):
    hits = topk_by_cosine(embed(question), index, k=5)
    ctx = "\n\n".join("[{}] {}".format(i, c) for i, (v, c) in enumerate(hits))
    prompt = TEMPLATE.replace("{ctx}", ctx).replace("{q}", question)
    return chat(prompt)                           # un modelo local pequeño es suficiente

Alcanza LlamaIndex o LangChain cuando necesitas permisos, refresco
programado o arneses de evaluación — no para el bucle de arriba. Para
documentos en coreano en específico, los embeddings multilingües (bge-m3)
no son opcionales, y el fragmentado por encabezados debería respetar la
estructura documental coreana (encabezados 장/절) igual que respeta los
encabezados markdown ##.

Relacionados: ajuste fino · prompting · salida estructurada · inferencia en CPU