Skip to content

Carga y limpieza de documentos ​

La calidad de un sistema RAG depende en gran medida de la ingesta. Si los documentos llegan con ruido, duplicados o estructura rota, el retrieval devolvera fragmentos pobres aunque el embedding y el LLM sean excelentes.

Este capitulo cubre formatos habituales, estrategias de limpieza y metadatos utiles para filtrar y citar fuentes.

Objetivo del pipeline de ingesta ​

txt
Fuente original -> Parseo -> Limpieza -> Normalizacion -> Metadatos -> Listo para chunking

Cada etapa debe ser reproducible y versionada. Si cambias el parser de PDF, probablemente debas reindexar.

Formatos habituales ​

FormatoVentajasRiesgos
MarkdownEstructura clara, poco ruidoEnlaces rotos, frontmatter inconsistente
HTMLMuy comun en wikisTags, menus, scripts, contenido duplicado
PDFContratos, informes legacyColumnas, tablas, OCR defectuoso
DOCXDocumentacion corporativaEstilos ocultos, encabezados irregulares
CSV/JSONDatos tabularesNecesita plantilla de texto por fila
CodigoRepos, APIsMezclar comentarios y codigo sin contexto

Carga basica en Python ​

python
from pathlib import Path

def load_text_file(path: Path) -> str:
    return path.read_text(encoding="utf-8")

def load_markdown_dir(directory: Path) -> list[dict]:
    documents = []
    for file in directory.rglob("*.md"):
        documents.append({
            "id": str(file.relative_to(directory)),
            "text": load_text_file(file),
            "source": str(file),
            "format": "markdown",
        })
    return documents

Para PDF suele usarse pymupdf, pdfplumber o servicios OCR cuando el texto no es seleccionable.

Limpieza recomendada ​

1. Normalizar espacios y saltos de linea ​

python
import re

def normalize_whitespace(text: str) -> str:
    text = text.replace("\r\n", "\n").replace("\r", "\n")
    text = re.sub(r"[ \t]+\n", "\n", text)
    text = re.sub(r"\n{3,}", "\n\n", text)
    return text.strip()

2. Eliminar ruido estructural ​

En HTML o exportaciones de wiki, elimina:

  • Menus, footers, breadcrumbs.
  • Bloques de "ultima actualizacion" repetidos en cada pagina.
  • Scripts, estilos y comentarios HTML.
  • Tablas de navegacion lateral.

3. Detectar duplicados ​

Duplicar el mismo parrafo en muchos chunks degrada el retrieval. Opciones:

  • Hash por parrafo normalizado.
  • Similitud aproximada (MinHash, simhash).
  • Deduplicacion por URL o document_id.
python
def dedupe_paragraphs(text: str) -> str:
    seen: set[str] = set()
    kept: list[str] = []
    for block in text.split("\n\n"):
        key = " ".join(block.split()).lower()
        if key and key not in seen:
            seen.add(key)
            kept.append(block)
    return "\n\n".join(kept)

4. Preservar estructura util ​

No elimines informacion que ayuda al contexto:

  • Titulos (#, ##) como referencia semantica.
  • Listas y tablas (mejor convertir tablas a texto estructurado).
  • Bloques de codigo si documentan APIs o comandos.

Metadatos utiles ​

Asigna metadatos en la ingesta, no despues:

json
{
  "document_id": "postgresql-backup",
  "source": "bases-de-datos/postgresql/09-administracion-backup-y-restore.md",
  "title": "Administracion backup y restore",
  "area": "bases-de-datos",
  "language": "es",
  "updated_at": "2026-03-15",
  "access_level": "internal"
}

Metadatos habituales:

  • source, title, section
  • product, version, language
  • updated_at, owner
  • access_level o tenant_id para filtrar por permisos

Control de calidad en ingesta ​

Antes de chunkear, valida:

  1. Longitud minima: descartar paginas vacias o con menos de N caracteres utiles.
  2. Ratio alfanumerico: detectar PDFs escaneados mal OCR (mucho simbolo basura).
  3. Encoding: forzar UTF-8; registrar archivos problematicos.
  4. Muestra manual: revisar 10 documentos aleatorios por lote.
python
def quality_score(text: str) -> float:
    if not text:
        return 0.0
    alnum = sum(ch.isalnum() for ch in text)
    return alnum / len(text)

Umbral orientativo: si quality_score < 0.5, marcar para revision humana.

Ingesta incremental ​

En produccion los documentos cambian. Disena la ingesta para:

  • Detectar archivos nuevos o modificados (hash, mtime).
  • Reprocesar solo lo afectado.
  • Mantener document_version en el indice vectorial.
  • Borrar o invalidar chunks obsoletos.
txt
Cambio en fuente -> Reingesta documento -> Nuevos chunks -> Upsert en vector store -> Delete chunks antiguos

Seguridad en la carga ​

  • No indexar secretos (.env, claves API, tokens).
  • Aplicar filtros de PII si el dominio lo requiere.
  • Separar indices por tenant o aplicar filtros de metadatos en cada consulta.
  • Registrar que usuario o proceso disparo cada ingesta.

Buenas practicas ​

  • Un parser por tipo de fuente; no mezclar logica en un unico script monolitico.
  • Guardar el texto limpio intermedio para depurar retrieval.
  • Versiona reglas de limpieza junto al codigo.
  • Documenta que fuentes estan incluidas y excluidas.
  • Automatiza la ingesta en CI o jobs programados.

Errores habituales ​

  • Parsear PDFs sin revisar una muestra representativa.
  • Indexar paginas de login, 404 o "sin permisos".
  • Perder titulos al limpiar HTML demasiado agresivamente.
  • No guardar source y ser incapaz de citar o depurar respuestas.
  • Reindexar todo cada noche sin necesidad (coste y tiempo).

Ejercicio guiado ​

  1. Elige una carpeta de 5–10 archivos Markdown del repositorio.
  2. Implementa load_markdown_dir, normalize_whitespace y dedupe_paragraphs.
  3. Anade metadatos source, title y area.
  4. Calcula quality_score y lista archivos por debajo del umbral.
  5. Inspecciona manualmente dos archivos con puntuacion baja.

Siguiente paso ​

Con documentos limpios y metadatos listos, el capitulo 3 trata como dividirlos en chunks y generar embeddings.