Categoría: IA Agentica

  • Embedding models, vector stores y retrievers

    Embedding models

    Los embedding models son el componente que transforma el contenido obtenido de los document en representaciones vectoriales densas que capturan significado semántico. En sistemas modernos de RAG dentro de LangChain, los embeddings son la base sobre la que se construye todo el sistema de recuperación.

    En términos operativos, un embedding model proyecta texto en un espacio vectorial donde la distancia entre vectores refleja similitud semántica. Esto permite ejecutar búsquedas por similitud (normalmente usando cosine similarity o dot product) en bases de datos vectoriales. La calidad de esta proyección determina directamente la capacidad del sistema para recuperar contexto relevante.

    Relación entre embeddings y organización en el vector store

    Un aspecto crítico que suele pasarse por alto es que los embeddings no se almacenan como “un vector por documento”, sino como un vector por fragmento (chunk). Esto significa que un mismo documento genera múltiples embeddings que se indexan de forma independiente en el vector store.

    En sistemas como Chroma, todos estos vectores se almacenan dentro de una misma colección (índice), independientemente del documento de origen. No existen “tablas separadas por documento”; en su lugar, cada entrada del índice contiene:

    • el embedding
    • el contenido (page_content)
    • la metadata asociada
    • un identificador (ID)

    De forma conceptual:

    [vector, texto, metadata, id]

    Cuando se indexan múltiples documentos, todos sus chunks se mezclan dentro del mismo espacio vectorial:

    ID         | Texto     | Metadata
    -------------------------------------------
    docA_1     | chunk A1  | {source: docA}
    docA_2     | chunk A2  | {source: docA}
    docB_1     | chunk B1  | {source: docB}
    

    Esto implica que la “separación” entre documentos no es física, sino lógica, y se realiza mediante metadata.

    Tipos de embedding models

    En producción, no todos los embeddings son equivalentes; se diferencian por arquitectura, objetivo y rendimiento.

    General-purpose embeddings: Modelos optimizados para tareas generales de similitud semántica como OpenAI embeddings (text-embedding models), se usan en RAG estándar y búsqueda semántica.

    Instruction-tuned embeddings: Modelos ajustados con instrucciones para mejorar tareas específicas (query vs document alignment). Tiene la ventaja de hacer mejor matching entre pregunta y contexto

    Domain-specific embeddings: Entrenados en dominios concretos: legal, médico, código. Esto hace que mejoren el recall en casos especializados

    Multilingual embeddings: permiten búsqueda cruzada entre idiomas.

    Parámetros clave

    • Dimensionalidad: Más dimensiones → más capacidad semántica, pero mayor coste.
    • Normalización: Muchos modelos requieren normalización del vector para usar cosine similarity correctamente.
    • Tokenización: El embedding depende del tokenizer del modelo → afecta cómo se representa el texto.

    Decisiones críticas en producción

    • Query vs Document embeddings: En sistemas avanzados: embeddings distintos para queries y documentos
    • Chunking alignment: El rendimiento depende directamente de cómo has hecho el text splitting un chunk mal definido lleva a un embedding pobre y la consecuencia es un retrieval malo
    • Batch vs real-time:
      • Indexing → los documentos se procesan en batch para generar embeddings de forma eficiente y reducir coste computacional.
      • Queries → las consultas del usuario se embeben en tiempo real para realizar búsqueda semántica inmediata sobre la base vectorial.

    Errores comunes

    • Usar embeddings genéricos en dominios especializados → reduce la precisión semántica porque el modelo no captura bien el vocabulario ni las relaciones propias del dominio.
    • No normalizar vectores → provoca que métricas como cosine similarity den resultados incorrectos o inconsistentes en la búsqueda. (encode_kwargs={"normalize_embeddings": True})
    • Usar chunks demasiado grandes o pequeños → los grandes introducen ruido y los pequeños pierden contexto, degradando el retrieval.
    • No evaluar recall@k → impide medir si el sistema realmente recupera información relevante, ocultando fallos críticos en producción. Ver en retrievers.

    Vector stores

    Los vector stores son el componente encargado de almacenar y consultar embeddings de forma eficiente. En un sistema RAG dentro de LangChain, no basta con generar vectores; es necesario indexarlos en una estructura que permita búsquedas por similitud a gran escala con baja latencia.

    En términos operativos, un vector store gestiona tres elementos: el vector (embedding), el contenido original (texto) y la metadata asociada. A partir de ahí, permite ejecutar consultas semánticas donde una query se transforma en embedding y se compara contra el índice para recuperar los elementos más cercanos.

    Tipos, elección y uso en producción

    La elección del vector store no es trivial; afecta a latencia, escalabilidad, coste y operativa. En la práctica, la decisión se reduce a dónde se ejecuta (local vs gestionado) y qué volumen/latencia necesitas.

    Tipos de vector stores

    Local (on-device): Se ejecutan en la propia máquina, sin dependencias externas. Ideales para prototipos, entornos offline o setups local-first.

    • Ejemplos: Chroma, FAISS
    • Ventajas: cero coste de API, baja latencia local, control total
    • Limitaciones: escalabilidad y concurrencia limitadas

    Gestionados (API / SaaS): Servicios externos optimizados para indexación y búsqueda vectorial a gran escala.

    • Ejemplos: Pinecone, Weaviate (cloud), Qdrant Cloud
    • Ventajas: alta disponibilidad, escalado automático, operaciones gestionadas
    • Limitaciones: coste y dependencia de red

    Híbridos (self-hosted / cloud opcional): Permiten ejecutar localmente o desplegar en servidor propio con capacidades de escalado.

    • Ejemplos: Qdrant, Weaviate (self-hosted)
    • Ventajas: equilibrio entre control y escalabilidad
    • Limitaciones: necesitas gestionar infraestructura

    Comparativa rápida

    TipoEjemploLatenciaEscalabilidadTipoUso recomendado
    LocalChroma, FAISSMuy bajaBajagratisdesarrollo, local RAG
    APIPinecone, Weaviate CloudMediaAltapagoproducción a escala
    HíbridoQdrant, WeaviateBaja–MediaAltavariableproducción self-hosted

    Criterios de elección

    • Usa local (Chroma / FAISS) si: trabajas en desarrollo o laboratorio, necesitas privacidad total y los dataset son pequeños/medio
    • Usa API (Pinecone / Weaviate Cloud) si: necesitas alta disponibilidad, tienes mucho volumen de datos y múltiples usuarios concurrentes
    • Usa híbrido (Qdrant / Weaviate self-hosted) si: quieres control + escalabilidad, despliegas en servidor propio y buscas evitar costes SaaS

    Insight importante

    El vector store no mejora embeddings ni corrige errores de chunking solo acelera la búsqueda; la calidad depende del pipeline previo.

    Ingesta / Indexación (escritura en el vector store)

    La fase de ingesta es donde se construye el índice vectorial a partir de los datos ya preprocesados (cleaning + splitting). En este punto, los textos o documentos se transforman en embeddings y se almacenan junto con su metadata en el vector store.

    Esta operación suele ejecutarse en batch durante el indexing, no en tiempo real, y es crítica porque define la calidad y consistencia del sistema de retrieval: cualquier error aquí (mal chunking, embeddings inconsistentes, falta de metadata) se propagará al resto del pipeline.


    Métodos de ingesta

    MétodoDescripción
    add_textsConvierte textos en embeddings y los añade al índice existente.
    aadd_textsVersión asíncrona de add_texts.
    add_documentsAñade o actualiza documentos (incluyendo metadata) en el vector store.
    aadd_documentsVersión asíncrona de add_documents.
    from_textsCrea un vector store directamente a partir de una lista de textos.
    afrom_textsVersión asíncrona de from_texts.
    from_documentsInicializa un vector store desde documentos estructurados.
    afrom_documentsVersión asíncrona de from_documents.

    Crear vector store desde loaders + splitters

    from langchain_community.vectorstores import Chroma
    from langchain_community.embeddings import HuggingFaceEmbeddings
    
    embeddings = HuggingFaceEmbeddings(
        model_name="BAAI/bge-small-en",
        model_kwargs={"device": "cpu"},
        encode_kwargs={"normalize_embeddings": True}
    )
    
    vectorstore = Chroma.from_documents(
                  chunks, 
                  embedding=embeddings
    )
    

    Insight operativo

    En producción, estos métodos se utilizan en pipelines de indexing offline, donde grandes volúmenes de datos se procesan en batch para evitar latencia y optimizar costes.

    docsearch = Chroma.from_documents(
        chunks,
        embedding=embedding_model,
        persist_directory="./chroma_db"
    )

    Eliminación y gestión de datos

    La gestión de datos en un vector store es fundamental para mantener la coherencia del índice a lo largo del tiempo. A diferencia de bases de datos tradicionales, los embeddings no suelen actualizarse “en sitio”; en la práctica, cualquier cambio en el contenido implica eliminar los vectores antiguos y reindexar los nuevos. Por ello, las operaciones de eliminación no son solo tareas de limpieza, sino una parte crítica del ciclo de vida del dato en sistemas RAG.

    MétodoDescripción
    deleteElimina vectores del índice por ID o mediante condiciones sobre metadata.
    adeleteVersión asíncrona de delete.

    Uso típico: eliminar chunks específicos de un documento.

    vectorstore.delete(ids=["docA_1", "docA_2"])

    Elimina todos los vectores asociados a un documento completo.

    vectorstore.delete(where={"source": "docA"})

    Patrón de actualización (delete + reindex)

    # eliminar versión antigua
    vectorstore.delete(where={"source": "docA"})
    
    # volver a indexar contenido actualizado
    vectorstore.add_texts(
        texts=["nuevo contenido actualizado"],
        metadatas=[{"source": "docA"}]
    )
    

    Acceso directo por ID

    El acceso por ID permite recuperar documentos de forma determinista sin pasar por el proceso de búsqueda semántica. En lugar de calcular similitudes vectoriales, se accede directamente a los elementos almacenados en el índice usando sus identificadores únicos. Esta operación es clave para tareas de trazabilidad y control, ya que permite verificar exactamente qué contenido fue indexado o recuperado en etapas anteriores del pipeline.

    Métodos de acceso

    MétodoDescripción
    get_by_idsRecupera documentos directamente por sus IDs.
    aget_by_idsVersión asíncrona de get_by_ids.

    Recuperar documentos por ID

    docs = vectorstore.get_by_ids(["docA_1", "docB_2"])
    
    for doc in docs:
        print(doc.page_content, doc.metadata)

    Uso en debugging: Permite verificar el origen del documento, metadatos asociados y contenido extacto indexado.

    doc = vectorstore.get_by_ids(["docA_1"])[0]
    
    print(doc.metadata)

    Reconstrucción de contexto: reconstruir un documento original a partir de chunks, auditar resultados del retrieval.

    ids = ["docA_1", "docA_2"]
    
    chunks = vectorstore.get_by_ids(ids)
    
    full_text = " ".join([c.page_content for c in chunks])
    

    Búsqueda semántica (core del RAG)

    La búsqueda semántica es el núcleo de cualquier sistema RAG: es el mecanismo que permite recuperar información relevante a partir de una consulta, no por coincidencia exacta de palabras, sino por similitud en el espacio vectorial. En esta fase, la query se transforma en embedding y se compara contra el índice para encontrar los fragmentos más cercanos. Este proceso implementa, en la práctica, algoritmos de k-nearest neighbors (k-NN) o sus variantes aproximadas (ANN) para escalar a grandes volúmenes de datos.

    Búsqueda básica

    La forma más directa de retrieval es recuperar los documentos más similares a una query.

    MétodoDescripción
    similarity_searchDevuelve los documentos más similares a una query.
    asimilarity_searchVersión asíncrona de similarity_search.
    similarity_search_by_vectorRealiza la búsqueda usando un embedding ya calculado.
    asimilarity_search_by_vectorVersión asíncrona de búsqueda por vector.
    results = vectorstore.similarity_search(
        "What is a text splitter?",
        k=3
    )
    # k muy bajo → pierdes información relevante
    # k muy alto → el LLM recibe ruido
    
    for doc in results:
        print(doc.page_content)
    
    kUso
    1muy preciso, poco contexto
    3estándar
    5–10más contexto, más ruido

    Usar by_vector cuando ya tienes el embedding calculado y quieres optimizar rendimiento.

    query_vector = embeddings.embed_query("What is LCEL?")
    
    results = vectorstore.similarity_search_by_vector(query_vector)
    

    Búsqueda con scoring

    Estas variantes añaden información cuantitativa sobre la similitud, lo que permite evaluar y depurar el comportamiento del retrieval.

    MétodoDescripción
    similarity_search_with_scoreDevuelve documentos junto con su distancia o score técnico.
    asimilarity_search_with_scoreVersión asíncrona.
    similarity_search_with_relevance_scoresDevuelve scores normalizados entre 0 y 1.
    asimilarity_search_with_relevance_scoresVersión asíncrona.

    Se usa para evaluación de calidad (recall, ranking), análisis de relevancia y tuning del sistema

    results = vectorstore.similarity_search_with_relevance_scores(
        "What are embeddings?",
        k=3
    )
    
    for doc, score in results:
        print(score, doc.page_content)

    MMR (Max Marginal Relevance)

    MMR introduce un criterio adicional: no solo busca relevancia respecto a la query, sino también diversidad entre los resultados.

    MétodoDescripción
    max_marginal_relevance_searchSelecciona documentos optimizando relevancia y diversidad.
    amax_marginal_relevance_searchVersión asíncrona.
    max_marginal_relevance_search_by_vectorMMR usando embeddings en lugar de texto.
    amax_marginal_relevance_search_by_vectorVersión asíncrona.

    Ejemplo con: max_marginal_relevance_search. Evita resultados redundantes, mejora la cobertura del contexto y es útil cuando los documentos son similares entre sí.

    results = vectorstore.max_marginal_relevance_search(
        "Explain embeddings",
        k=3
    )

    Método genérico

    El método search permite unificar distintas estrategias bajo una sola interfaz.

    MétodoDescripción
    searchPermite elegir el tipo de búsqueda (similarity, MMR, etc.).
    asearchVersión asíncrona.

    search() permite seleccionar dinámicamente entre similarity, MMR, scoring.

    results = vectorstore.search(
        "What is RAG?",
        search_type="mmr"
    )

    Filtering (restricción por metadata)

    El filtering permite restringir el espacio de búsqueda en el vector store utilizando metadata asociada a cada chunk. A diferencia de la búsqueda semántica, que opera sobre embeddings, los filtros actúan como una condición lógica que limita qué subconjunto de datos se considera antes (o durante) el cálculo de similitud.

    Cómo funciona: Cada chunk indexado incluye metadata:

    Document(
        page_content="...",
        metadata={"source": "docA", "section": "intro"}
    )

    El filtro se aplica en la búsqueda: solo se evalúan los vectores que cumplen esa condición.

    results = vectorstore.similarity_search(
        "text splitting",
        k=3,
        filter={"source": "docA"}
    )

    Uso con retriever

    retriever = vectorstore.as_retriever(
        search_kwargs={
            "k": 3,
            "filter": {"source": "docA"}
        }
    )
    
    docs = retriever.invoke("What is a text splitter?")
    

    Casos de uso

    • separación entre documentos
    • sistemas multi-usuario (multi-tenant)
    • filtrado por secciones (capítulos, páginas)
    • control de contexto en RAG

    Consideraciones

    • Los filtros dependen de la metadata → si no la defines bien, no funcionan
    • No sustituyen la similitud → la complementan
    • Su implementación puede variar según el vector store

    Insight clave

    • embeddings → determinan relevancia
    • filtering → determina contexto

    Ambos son necesarios para un retrieval correcto.

    Atributos

    Los vector stores no solo almacenan datos, sino que también mantienen información sobre los componentes con los que fueron construidos. El atributo más relevante es embeddings, que hace referencia al modelo de embeddings asociado al índice.

    Este atributo permite garantizar que las operaciones de retrieval se realicen con el mismo espacio vectorial con el que se indexaron los datos, evitando inconsistencias difíciles de detectar.

    AtributoDescripción
    embeddingsModelo de embeddings asociado al vector store.
    print(vectorstore.embeddings)

    Para qué se utiliza

    • Consistencia → asegura que las queries se embeben con el mismo modelo usado en la indexación
    • Debugging → permite verificar qué modelo está realmente en uso
    • Auditoría → ayuda a rastrear configuraciones en sistemas complejos

    Caso práctico

    query_vector = vectorstore.embeddings.embed_query("What is RAG?")

    Error común

    Cambiar el modelo de embeddings sin reindexar da como resultado búsquedas incorrectas o incoherentes.

    index → creado con modelo A  
    query → generada con modelo B  

    Retriever

    El método as_retriever transforma el vector store en un componente de alto nivel que encapsula la lógica de búsqueda y lo hace directamente integrable en pipelines de RAG. En lugar de llamar manualmente a métodos como similarity_search, el retriever actúa como una interfaz estándar que recibe una query y devuelve los documentos relevantes, desacoplando la capa de almacenamiento de la lógica del sistema.

    Este patrón es clave en LangChain, ya que permite componer fácilmente pipelines donde el retrieval se integra como una pieza más dentro del flujo de datos hacia el LLM.

    MétodoDescripción
    as_retrieverConvierte el vector store en un retriever configurable para pipelines RAG.

    Ejemplo básico

    retriever = vectorstore.as_retriever(
        search_type="mmr",
        search_kwargs={"k": 3}
    )
    
    docs = retriever.invoke("What is a text splitter?")
    

    Qué está haciendo internamente el retriever: (Es un wrapper sobre similarity_search.)

    1. Recibe la query
    2. La convierte en embedding
    3. Ejecuta búsqueda en el vector store
    4. Devuelve los documentos más relevantes

    Insight estructural

    Aunque la interfaz de VectorStore en LangChain puede parecer extensa, en la práctica se reduce a tres operaciones fundamentales que reflejan el ciclo de vida del dato en un sistema RAG.

    Write (indexing)

    Corresponde a la fase de ingesta, donde los datos se transforman en embeddings y se insertan en el índice vectorial. Se ejecuta normalmente en batch durante el indexing offline.

    • add_* → inserción incremental
    • from_* → construcción inicial del índice

    Read (retrieval)

    Es la fase de consulta, donde se recupera información relevante a partir de una query. Es el núcleo del sistema RAG.

    • similarity_* → búsqueda por similitud
    • mmr_* → búsqueda con diversidad
    • search → interfaz general

    Manage (mantenimiento)

    Permite modificar o inspeccionar el índice. Es clave para actualización, limpieza y debugging.

    • delete → eliminación de vectores
    • get_by_ids → acceso directo por ID

  • Text Splitters

    Los text splitters en LangChain son un componente estructural dentro de cualquier pipeline de RAG: determinan cómo se segmenta el conocimiento antes de ser embebido, indexado y recuperado. En sistemas en producción, esta decisión no es neutra; el splitting define directamente la calidad del retrieval, la densidad semántica de los embeddings y, en última instancia, la precisión del sistema.

    A nivel de arquitectura, el flujo es conocido:

    ingesta → splitting → embeddings → indexación → retrieval

    pero lo relevante es que el splitter introduce la primera transformación irreversible del dato. Cualquier pérdida de coherencia en esta etapa se propaga aguas abajo.

    El punto de abstracción es la interfaz TextSplitter, que define un contrato simple: transformar texto en una lista de fragmentos. Sin embargo, en entornos reales, la elección de implementación no es intercambiable. El uso de CharacterTextSplitter, aunque históricamente común, ha quedado relegado a casos triviales debido a su naturaleza puramente mecánica: corta por longitud fija sin respetar unidades semánticas, lo que degrada la calidad de los embeddings y reduce la efectividad del retrieval.

    El estándar de facto en producción es RecursiveCharacterTextSplitter, precisamente porque introduce una heurística jerárquica que preserva la estructura del lenguaje el mayor tiempo posible. En lugar de imponer cortes arbitrarios, aplica una estrategia de degradación progresiva: intenta dividir por párrafos, luego por saltos de línea, después por frases y, solo cuando no hay alternativa, por caracteres. Este comportamiento minimiza la fragmentación semántica sin renunciar al control sobre el tamaño del chunk.

    from langchain_text_splitters import RecursiveCharacterTextSplitter
    
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200,
        separators=["\n\n", "\n", ".", " ", ""]
    )
    
    chunks = splitter.split_text(text)
    

    Un aspecto clave en todos los splitters es la configuración de chunk_size y chunk_overlap, donde el primero define el tamaño del fragmento y el segundo preserva contexto entre fragmentos; en práctica profesional, valores típicos oscilan entre 500–1500 tokens con un solapamiento del 10–30%.

    Los separators definen la jerarquía de cortes que el algoritmo intenta aplicar en orden, no son simplemente delimitadores; son una estrategia de degradación progresiva. En este caso, conceptualmente, le estamos diciendo a la función: “intenta dividir primero por unidades semánticas grandes; si no puedes cumplir el chunk_size, baja de nivel progresivamente hasta poder hacerlo”

    El algoritmo sigue esta lógica:

    1. Intenta dividir usando "\n\n" (párrafos)
    2. Si los chunks siguen siendo demasiado grandes → usa "\n" (líneas)
    3. Si aún no cabe → usa "." (frases)
    4. Luego " " (palabras)
    5. Finalmente "" (caracteres individuales, último recurso)

    Splitters

    TokenTextSplitter

    Cuando el control fino sobre tokens es crítico (coste o límites del modelo), se utiliza TokenTextSplitter, que trabaja directamente con tokenizadores:

    from langchain_text_splitters import TokenTextSplitter
    
    splitter = TokenTextSplitter(
        chunk_size=512,
        chunk_overlap=50
    )
    

    Internamente se apoya en una abstracción de Tokenizer, y también puedes usar funciones como:

    from langchain_text_splitters import split_text_on_tokens
    

    Esto garantiza que nunca excedas el contexto real del modelo.

    MarkdownHeaderTextSplitter

    Más allá de longitud, LangChain introduce splitters estructurales que respetan formato del documento; por ejemplo, MarkdownHeaderTextSplitter divide en función de headers, manteniendo jerarquía lógica:

    from langchain_text_splitters import MarkdownHeaderTextSplitter
    
    splitter = MarkdownHeaderTextSplitter(
        headers_to_split_on=[("#", "h1"), ("##", "h2")]
    )
    
    docs = splitter.split_text(markdown_text)
    

    Esto permite que cada chunk incluya metadata semántica (sección, subsección, etc.).

    HTMLHeaderTextSplitter

    En HTML, el equivalente es HTMLHeaderTextSplitter, que detecta etiquetas <h1>, <h2>, etc., y genera documentos jerárquicos con metadata asociada; si no encuentra headers, devuelve el documento completo, lo cual lo hace robusto ante inputs inconsistentes.

    from langchain_text_splitters import HTMLHeaderTextSplitter
    
    splitter = HTMLHeaderTextSplitter(
        headers_to_split_on=[
            ("h1", "section"),
            ("h2", "subsection"),
        ]
    )
    
    docs = splitter.split_text(html_string)

    Para casos más avanzados, HTMLSemanticPreservingSplitter mantiene estructura completa (incluyendo links, imágenes o multimedia), y solo recurre a splitting recursivo si se supera el tamaño máximo, priorizando integridad semántica.

    RecursiveJsonSplitter

    Cuando trabajas con datos estructurados, RecursiveJsonSplitter permite dividir JSON preservando jerarquía, lo cual es crítico en agentes que dependen de contexto estructurado:

    from langchain_text_splitters import RecursiveJsonSplitter
    
    splitter = RecursiveJsonSplitter(max_chunk_size=500)
    chunks = splitter.split_json(json_data)
    

    Aquí el objetivo no es solo dividir, sino mantener relaciones entre claves.

    Splitters específicos por lenguaje o dominio

    LangChain también incluye splitters específicos por lenguaje o dominio, lo cual es clave en sistemas profesionales; por ejemplo, PythonCodeTextSplitter divide respetando sintaxis de Python (funciones, clases), mientras que JSFrameworkTextSplitter extiende el splitting recursivo para entender JSX, Vue o Svelte, detectando componentes como separadores naturales:

    from langchain_text_splitters import PythonCodeTextSplitter
    
    splitter = PythonCodeTextSplitter()
    chunks = splitter.split_text(code)
    

    Esto evita romper bloques de código de forma incorrecta.

    Splitters para procesamiento de texto

    En procesamiento lingüístico más avanzado, existen integraciones con NLP clásico como SpacyTextSplitter o NLTKTextSplitter, que segmentan por oraciones usando modelos lingüísticos:

    from langchain_text_splitters import SpacyTextSplitter
    
    splitter = SpacyTextSplitter(pipeline="sentencizer")
    chunks = splitter.split_text(text)
    

    Para casos específicos, también existen splitters especializados como:

    • LatexTextSplitter → divide respetando estructura LaTeX
    • KonlpyTextSplitter → optimizado para coreano
    • SentenceTransformersTokenTextSplitter → alineado con tokenizadores de modelos de embeddings
    • ExperimentalMarkdownSyntaxTextSplitter → mantiene whitespace exacto y extrae metadata avanzada (headers, code blocks, reglas horizontales)

    Text splitters de langchain

    NombreDescripción
    TextSplitterInterfaz base para dividir texto en chunks; define el comportamiento común de todos los splitters.
    CharacterTextSplitterDivide texto por número de caracteres; simple, pero no respeta semántica.
    RecursiveCharacterTextSplitterDivide recursivamente usando separadores jerárquicos (párrafos, líneas, frases); estándar en producción.
    TokenTextSplitterDivide texto en función de tokens usando un tokenizador; útil para controlar límites de modelos.
    SentenceTransformersTokenTextSplitterVariante basada en tokenizadores de modelos de sentence-transformers; optimizada para embeddings.
    SpacyTextSplitterUsa Spacy para segmentar texto en oraciones; más preciso lingüísticamente.
    NLTKTextSplitterSegmenta texto utilizando NLTK; útil para procesamiento basado en frases.
    MarkdownTextSplitterDivide texto siguiendo la estructura Markdown (headers, secciones).
    MarkdownHeaderTextSplitterDivide Markdown basado en headers específicos, generando chunks con metadata jerárquica.
    ExperimentalMarkdownSyntaxTextSplitterSplitter avanzado que preserva formato original, whitespace y extrae metadata (headers, código, reglas).
    HTMLHeaderTextSplitterDivide HTML según etiquetas de encabezado (<h1>, <h2>, etc.), generando estructura jerárquica.
    HTMLSectionSplitterDivide HTML basado en tags y tamaños de fuente; requiere lxml.
    HTMLSemanticPreservingSplitterMantiene estructura semántica HTML completa (links, imágenes, etc.) y solo divide si es necesario.
    RecursiveJsonSplitterDivide JSON en fragmentos manteniendo su estructura jerárquica; útil para datos estructurados.
    PythonCodeTextSplitterDivide código Python respetando su sintaxis (funciones, clases).
    JSFrameworkTextSplitterDivide código de frameworks JS (React, Vue, Svelte) detectando componentes y sintaxis.
    LatexTextSplitterDivide texto respetando estructura de documentos LaTeX.
    KonlpyTextSplitterSplitter especializado para texto en coreano usando la librería KoNLPy.

    Finalmente, en sistemas complejos de agentes construidos con LangGraph, los text splitters no solo afectan al retrieval, sino a la memoria externa del agente; elegir un splitter adecuado implica decidir cómo el agente “percibe” el conocimiento disponible.

  • Data Cleaning en RAG

    En cualquier sistema de RAG, la calidad del resultado final depende directamente de la calidad del dato de entrada. Antes de aplicar técnicas de segmentación, generación de embeddings o recuperación de información, es imprescindible garantizar que el contenido extraído esté correctamente estructurado y libre de ruido.

    En la práctica, los datos rara vez llegan en un formato listo para ser utilizados. Ya provengan de documentos PDF, páginas web u otras fuentes, el contenido suele presentar problemas como fragmentación del texto, elementos irrelevantes o estructuras inconsistentes. Estos problemas no son visibles a simple vista en todos los casos, pero tienen un impacto directo en el rendimiento del sistema.

    Esto significa que, aunque el loader funcione correctamente, el resultado puede ser un texto difícil de utilizar en un sistema RAG como en este ejemplo:

    print(document[6].page_content[:300]) 
    zonas  premium:  cada  metro  valía  (y  vale)  mucho  más  en  Madrid,  Baleares  y  País  
    Vasco
     
    que
     
    en
     
    cualquier
     
    otra
     
    región.
     

    Por tanto, antes de avanzar hacia fases más avanzadas del sistema, es necesario validar y procesar adecuadamente el contenido extraído. Un pipeline sólido no comienza con el modelo, sino con datos bien preparados.

    Antes de generar embeddings, es fundamental aplicar una fase de limpieza del texto. El objetivo es reconstruir, en la medida de lo posible, una estructura natural del lenguaje.

    • Una estrategia básica de limpieza incluye:
    • Eliminación de saltos de línea innecesarios
    • Normalización de espacios
    • Unificación del texto en una secuencia continua

    Este enfoque elimina múltiples espacios, saltos de línea y fragmentaciones simples, devolviendo un texto más coherente. Para casos más complejos, se pueden aplicar reglas adicionales, como la eliminación de patrones repetitivos o la reconstrucción de párrafos.

    Métodos de limpieza de datos tras la carga (post-load)

    Una vez cargados los documentos mediante un loader, el siguiente paso crítico es aplicar técnicas de limpieza que permitan transformar el texto en una forma coherente y útil para el sistema RAG.

    No existe un único método universal. La limpieza debe adaptarse al tipo de fuente (PDF, web, datos estructurados), pero sí existen patrones comunes que se aplican en la mayoría de los casos:

    Normalización básica de espacios

    Eliminar espacios duplicados, saltos de línea y fragmentación simple. Se debe hacer siempre. Es el primer paso obligatorio.

    text = document.page_content
    
    clean_text = " ".join(text.split())

    Soluciona:

    • Saltos de línea (\n)
    • Espacios múltiples
    • Texto fragmentado

    Eliminación explícita de saltos de línea

    Reconstruir frases que han sido cortadas artificialmente. Se utiliza en PDFs con líneas rotas y texto extraído por coordenadas

    text = document.page_content
    
    clean_text = text.replace("\n", " ")
    clean_text = " ".join(clean_text.split())
    

    Eliminación de patrones repetitivos (headers/footers)

    Eliminar contenido repetido en todas las páginas. Como: “Página 1”, “Confidencial”, nombres de empresa, etc. Mejora la calidad del embedding.

    import re
    
    text = document.page_content
    
    clean_text = re.sub(r'Página \d+', '', text)
    clean_text = re.sub(r'Confidencial', '', clean_text)
    

    Filtrado de líneas irrelevantes

    Eliminar líneas demasiado cortas o sin valor semántico como títulos sueltos, fragmentos incompletos y ruido.

    lines = document.page_content.split("\n")
    
    clean_lines = [line for line in lines if len(line.strip()) > 30]
    
    clean_text = " ".join(clean_lines)
    

    Reconstrucción de párrafos

    Unir líneas que pertenecen al mismo párrafo. Esta acción mejora coherencia y contexto para embeddings

    lines = document.page_content.split("\n")
    
    paragraph = ""
    
    for line in lines:
        if line.strip():
            paragraph += line.strip() + " "
        else:
            paragraph += "\n"
    
    clean_text = paragraph
    

    Limpieza con expresiones regulares (regex)

    Eliminar patrones complejos o caracteres no deseados. Usalo en PDFs sucios, OCR y datos con simbolos.

    import re
    
    text = document.page_content
    
    # eliminar espacios múltiples
    text = re.sub(r'\s+', ' ', text)
    
    # eliminar caracteres raros
    text = re.sub(r'[^\w\s.,€%-]', '', text)
    

    Eliminación de contenido irrelevante por secciones

    Eliminar partes completas del documento (ej. índice). Uso en informes largos y documentos estructurados.

    text = document.page_content
    
    if "Índice" in text:
        text = text.split("Introducción")[-1]
    

    Limpieza específica para web (HTML)

    Eliminar etiquetas HTML y contenido no útil. Elimina etiquetas, scripts y navegacion.

    from bs4 import BeautifulSoup
    
    html = document.page_content
    soup = BeautifulSoup(html, "html.parser")
    
    text = soup.get_text()
    
    clean_text = " ".join(text.split())
    

    Pipeline de limpieza básico recomendado

    text = document.page_content
    text = text.replace("\n", " ")
    text = " ".join(text.split())
    

    Versión más robusta

    import re
    
    text = document.page_content
    
    text = text.replace("\n", " ")
    text = re.sub(r'\s+', ' ', text)
    text = re.sub(r'Página \d+', '', text)
    

    La limpieza de datos no es un paso opcional dentro de un sistema RAG, sino una fase crítica que determina la calidad del resto del pipeline.

    Aplicar técnicas básicas como la normalización de espacios o la eliminación de ruido puede mejorar significativamente la coherencia del texto y, por tanto, la precisión del sistema.

    En entornos reales, la combinación de varios métodos de limpieza es la práctica habitual, adaptándose siempre al tipo de documento y a la calidad del contenido extraído.

  • Document Loaders

    En LangChain, un Document es una estructura que representa una unidad de información compuesta por dos elementos: el contenido del texto (page_content) y un conjunto de metadatos (metadata) que aportan contexto adicional, como el origen, identificadores o fechas.

    Este formato estandarizado permite trabajar de manera uniforme con distintos tipos de datos, ya provengan de archivos, páginas web, bases de datos o otras aplicaciones. Para obtener estos documentos desde fuentes reales, LangChain utiliza los llamados Document Loaders, que son componentes diseñados para cargar información desde múltiples orígenes y convertirla automáticamente en objetos Document que posteriormente serán utilizados en pipelines de Retrieval-Augmented Generation (RAG).

    BaseLoader

    Antes de trabajar con loaders concretos, es importante entender que todos ellos heredan de una clase base común: BaseLoader. Esta clase define el comportamiento estándar que deben seguir todos los loaders dentro del ecosistema de LangChain.

    BaseLoader no es un loader que se utilice directamente, sino una interfaz (clase abstracta) que establece cómo deben implementarse los métodos de carga de documentos. El propósito principal de BaseLoader es garantizar que todos los loaders:

    • Devuelvan datos en formato Document
    • Sigan un patrón consistente
    • Sean intercambiables dentro del pipeline
    BaseLoader → <Name>Loader

    Ejemplos:

    • TextLoader
    • PyPDFLoader
    • WebBaseLoader

    Su salida siempre es una colección de objetos:

    • Document.page_content → contenido textual
    • Document.metadata → información contextual

    Este diseño permite que cualquier loader sea intercambiable dentro del pipeline. De esta forma, los datos externos se integran fácilmente en los flujos de trabajo con modelos de lenguaje, siendo la base para aplicaciones más avanzadas como sistemas de recuperación de información (RAG), análisis de datos o asistentes inteligentes.

    Lazy loading

    El diseño de BaseLoader está orientado a evitar cargar todos los documentos en memoria de golpe. Por eso, el método fundamental que deben implementar los loaders es: lazy_load()

    Métodos de BaseLoader

    MétodoDescripciónCuándo usarloNotas
    load()Carga todos los documentos y los devuelve en una lista de Document.Prototipos, datasets pequeños, pruebas rápidas.Es un método de conveniencia. Internamente usa lazy_load(). No debe sobrescribirse.
    lazy_load()Devuelve un generador de documentos (uno a uno).Producción, grandes volúmenes de datos, eficiencia en memoria.Método clave que deben implementar los loaders. Base del sistema.
    aload()Versión asíncrona de load().Aplicaciones async, APIs, procesamiento concurrente.Requiere entorno async (await).
    alazy_load()Versión asíncrona de lazy_load().Sistemas escalables, streaming de datos, alto rendimiento.Permite iteración asíncrona (async for).

    Loaders

    En cualquier sistema RAG, el punto de partida es siempre el mismo: los datos. Antes de hablar de embeddings, retrieval o generación de respuestas, es necesario convertir las fuentes de información en un formato que el sistema pueda procesar. En este contexto, los documentos PDF representan uno de los formatos más habituales en entornos empresariales.

    En el ecosistema de LangChain, los loaders forman parte de la capa de ingestión de datos. Su función no es solo “leer archivos”, sino preparar la información para que pueda ser utilizada posteriormente en procesos de segmentación, embedding y recuperación.

    Unstructured’s Loaders

    Los loaders denominados Unstructured dentro del ecosistema de LangChain son herramientas especializadas que permiten mejorar significativamente la extracción. Este tipo de loader no se limita a leer el texto plano, sino que intenta interpretar la estructura del documento. El resultado es un contenido más limpio, menos fragmentado y semánticamente más consistente, lo que impacta directamente en la calidad de los embeddings y en la eficacia del sistema RAG.

    Ventajas principales:

    • Mejor interpretación del layout: Este loader intenta entender la estructura del documento, no solo extraer texto plano. Esto permite preservar mejor párrafos, títulos y bloques de contenido.
    • Menor fragmentación del texto: Reduce significativamente los problemas de palabras separadas o saltos de línea artificiales.
    • Mayor calidad semántica: Al generar texto más coherente, los embeddings resultantes son más representativos, lo que mejora la recuperación en RAG.
    • Preparado para documentos reales:  Está diseñado para trabajar con documentos del mundo empresarial: informes, contratos, manuales, etc.
    LoaderDescripción
    UnstructuredPDFLoaderExtrae contenido de PDFs interpretando la estructura del documento (títulos, párrafos, listas). Ideal para informes complejos.
    UnstructuredHTMLLoaderProcesa archivos HTML locales identificando contenido semántico relevante, evitando ruido estructural.
    UnstructuredURLLoaderCarga contenido desde URLs aplicando parsing inteligente para aislar el contenido principal de la web.
    UnstructuredWordDocumentLoaderExtrae contenido de documentos Word manteniendo la estructura lógica del texto.
    UnstructuredEmailLoaderProcesa correos electrónicos (.eml, .msg), incluyendo cuerpo del mensaje y opcionalmente adjuntos.
    UnstructuredImageLoaderExtrae texto de imágenes (OCR) y organiza el contenido en elementos semánticos.

    Consideraciones

    • Mayor coste computacional
    • Puede requerir dependencias adicionales
    • No siempre necesario para documentos simples

    Modo de operación

    Los loaders Unstructured suelen trabajar en dos modos:

    • single: Devuelve todo el documento como un único Document.
    • elements (recomendado): Divide el contenido en elementos estructurados:
      • Title
      • NarrativeText
      • ListItem
      • Table
    loader = UnstructuredPDFLoader("file.pdf", mode="single" | "elements")

    Loaders de archivos locales

    LoaderDescripción
    PyPDFLoaderLoader básico para PDFs. Extrae texto directamente desde el archivo, sin interpretar estructura compleja.
    PDFMinerLoaderUtiliza pdfminer para extraer texto con mayor control, útil para PDFs con más detalle técnico.
    PyMuPDFLoaderLoader avanzado que permite extraer texto, imágenes y tablas desde PDFs.
    PDFPlumberLoaderEspecializado en extracción precisa de texto y tablas en PDFs.
    UnstructuredPDFLoaderLoader avanzado que interpreta la estructura del documento (títulos, párrafos, etc.).
    AmazonTextractPDFLoaderUtiliza AWS Textract para analizar PDFs, incluyendo OCR y estructura compleja.
    MathpixPDFLoaderExtrae PDFs usando Mathpix, especialmente útil para documentos técnicos o con fórmulas.
    DedocPDFLoaderLoader avanzado que detecta estructura documental automáticamente.
    TextLoaderCarga archivos de texto plano (.txt). Simple y directo.
    PythonLoaderCarga archivos Python respetando encoding y estructura básica.
    NotebookLoaderCarga notebooks de Jupyter (.ipynb) como documentos.
    Docx2txtLoaderExtrae texto de archivos Word (.docx) de forma sencilla.
    UnsredWordDocumentLoaderLoader avanzado para Word que interpreta la estructura del contenido.
    UnstructuredPowerPointLoaderCarga presentaciones PowerPoint interpretando diapositivas y contenido.
    UnstructuredExcelLoaderExtrae contenido de archivos Excel con estructura semántica.
    CSVLoaderConvierte cada fila de un CSV en un Document.
    JSONLoaderCarga archivos JSON usando esquemas definidos (ej. jq).
    DataFrameLoaderConvierte un DataFrame (pandas) en documentos.
    PolarsDataFrameLoaderSimilar a DataFrameLoader pero para Polars.
    PySparkDataFrameLoaderPermite trabajar con grandes volúmenes de datos desde PySpark.
    UnstructuredMarkdownLoaderCarga archivos Markdown interpretando su estructura.
    UnstructuredHTMLLoaderExtrae contenido desde archivos HTML locales.
    UnstructuredXMLLoaderProcesa archivos XML manteniendo estructura semántica.
    UnstructuredEPubLoaderCarga libros electrónicos (EPUB) interpretando capítulos y contenido.

    Sintaxis general para los loaders de ficheros locales

    Se cumple en el 90% de loaders de archivos locales, debes ver la documentación al utilizarlo.

    loader = Loader(ruta_o_fuente)
    documents = loader.load()

    Ejemplo conceptual:

    from langchain_community.document_loaders import <Loader>
    
    loader = <Loader>("ruta/al/archivo")
    documents = loader.load()

    En el momento de utilizarlos debes consultar la documentación de los parámetros adicionales en cada caso. Ejemplo para extracción de datos en un DataFrame de Pandas.

    from langchain_community.document_loaders import DataFrameLoader
    
    loader = DataFrameLoader(df, page_content_column="text")
    documents = loader.load()

    Web Loaders

    Los Web Loaders permiten incorporar información directamente desde internet a un sistema RAG. Esto incluye páginas web, blogs, documentación técnica o cualquier contenido accesible mediante una URL.

    A diferencia de los loaders de archivos locales, aquí no trabajas con un documento estático, sino con contenido dinámico, estructurado en HTML y, en muchos casos, generado parcialmente mediante JavaScript. Para cada caso debes seleccionar el tipo correcto diferenciado en cómo acceden y procesan el contenido:

    • Loaders básicos → funcionan bien con páginas estáticas
    • Loaders con renderizado JS → necesarios para webs modernas
    • Crawlers → permiten recorrer múltiples páginas automáticamente
    LoaderDescripción
    WebBaseLoaderLoader básico para cargar páginas web. Extrae el HTML y lo convierte en texto.
    AsyncHtmlLoaderVariante asíncrona para cargar múltiples páginas web de forma eficiente.
    PlaywrightURLLoaderCarga páginas renderizadas con JavaScript usando Playwright. Ideal para webs dinámicas.
    SeleniumURLLoaderSimilar a Playwright, utiliza Selenium para renderizar contenido dinámico.
    BrowserlessLoaderUtiliza un navegador remoto (Browserless) para cargar páginas complejas.
    BrowserbaseLoaderLoader basado en navegador headless en la nube (Browserbase).
    RecursiveUrlLoaderCrawler que navega recursivamente por enlaces dentro de una web.
    SitemapLoaderCarga todas las URLs definidas en un sitemap XML.

    Problemas específicos del contenido web:

    • Ruido HTML: El contenido incluye elementos que no aportan valor:
      • menús de navegación
      • banners de cookies
      • footers
      • enlaces secundarios
    • Contenido dinámico: Muchas webs modernas no cargan todo el contenido directamente en el HTML, sino que lo generan mediante JavaScript. El resultado es:
      • loaders básicos no ven el contenido
      • necesitas loaders con renderizado (Playwright, Selenium)
    • Riesgos de seguridad (SSRF): Los loaders tipo crawler pueden acceder a múltiples URLs automáticamente. Esto conlleva riesgos como:
      • acceso a recursos internos
      • carga de URLs maliciosas
      • problemas de seguridad en producción

    Buenas prácticas para trabajar correctamente con Web Loaders en RAG:

    • No indexar páginas completas sin filtrar
    • Validar siempre el contenido extraído
    • Aplicar limpieza antes del chunking
    • Usar renderizado JS cuando sea necesario
    • Limitar el uso de crawlers a dominios controlados

    Aquí tienes la sección completa de Loaders de bases de datos, lista para integrar en tu unidad:

    Loaders de bases de datos

    Los loaders de bases de datos permiten integrar datos estructurados directamente en un sistema RAG. A diferencia de los documentos tradicionales (PDF, web), aquí la información proviene de tablas, donde cada fila representa una unidad lógica de datos, o sea cada fila → un Document. Esto convierte datos estructurados en texto procesable por el modelo.

    LoaderDescripción
    SQLDatabaseLoaderEjecuta consultas SQL y convierte cada fila del resultado en un Document. Compatible con múltiples motores vía SQLAlchemy.
    DuckDBLoaderPermite cargar datos desde bases DuckDB, transformando resultados en documentos.
    SnowflakeLoaderExtrae datos desde Snowflake y convierte cada fila en texto procesable.
    AthenaLoaderCarga resultados de consultas en AWS Athena, ideal para grandes volúmenes de datos en la nube.
    CassandraLoaderPermite trabajar con datos distribuidos en Cassandra, convirtiendo filas en documentos.
    MongoDBLoaderCarga documentos desde MongoDB (NoSQL), transformando cada registro en un Document.

    Sintaxis:

    loader = Loader(conexion, query=...)
    documents = loader.load()
    from langchain_community.document_loaders import SQLDatabaseLoader
    from langchain_community.utilities import SQLDatabase
    
    db = SQLDatabase.from_uri("sqlite:///mi_base.db")
    
    loader = SQLDatabaseLoader(db, query="SELECT * FROM reservas")
    documents = loader.load()
    

    Buenas prácticas (nivel pro)

    • Convertir datos a texto limpio
    • No hacer SELECT * en producción
    • Limitar resultados (LIMIT)
    • Elegir columnas relevantes

    Loaders de sistemas empresariales (SaaS)

    Los loaders de sistemas empresariales permiten integrar información directamente desde herramientas utilizadas en entornos corporativos. En lugar de trabajar con archivos locales o bases de datos, estos loaders acceden a plataformas como sistemas de documentación, almacenamiento en la nube o herramientas de colaboración. A diferencia de otros loaders, aquí necesitas acceso a sistemas externos.

    • API tokens
    • OAuth
    • credenciales de usuario
    • cookies de sesión
    LoaderDescripción
    NotionDBLoaderCarga contenido desde una base de datos de Notion, convirtiendo cada página o entrada en un Document.
    NotionDirectoryLoaderCarga exportaciones completas de Notion (directorios), incluyendo múltiples páginas y documentos.
    ConfluenceLoaderExtrae páginas y contenido de espacios en Confluence, incluyendo opcionalmente adjuntos.
    SlackDirectoryLoaderCarga mensajes desde un export de Slack, convirtiendo conversaciones en documentos.
    DropboxLoaderPermite cargar archivos almacenados en Dropbox, incluyendo documentos y PDFs.
    OneDriveLoaderAccede a archivos en Microsoft OneDrive y los transforma en documentos.
    SharePointLoaderCarga contenido desde SharePoint, incluyendo documentos corporativos y bibliotecas de archivos.

    Ejemplo básico (Notion)

    from langchain_community.document_loaders import NotionDBLoader
    
    loader = NotionDBLoader(
        integration_token="your_token",
        database_id="your_database_id"
    )
    
    documents = loader.load()
    

    Consideraciones importantes

    • Calidad del contenido: estos sistemas suelen contener texto desordenado, duplicados y mensajes irrelevantes por lo que es necesaria una limpieza posterior
    • El volumen de datos puede ser muy alto: miles de mensajes, documentos largos y múltiples fuentes. En este caso el filtrado es importante.
    • Privacidad y seguridad: Dado que trabajas con datos sensibles como información interna, conversaciones privadas y documentos confidenciales, es fundamental controlar accesos.

    Buenas prácticas

    • Filtrar por fechas o relevancia
    • Usar metadata (autor, canal, fecha)
    • Limpiar antes de embeddings
    • Limitar el scope de datos

    Loaders de repositorios y código

    Los loaders de repositorios permiten trabajar con código fuente y artefactos asociados (issues, documentación) dentro de un sistema RAG. Son especialmente útiles para construir asistentes técnicos, copilots o sistemas de búsqueda sobre bases de código.

    Estos loaders extraen archivos de repositorios locales o remotos y los convierten en objetos Document, manteniendo información relevante como rutas de archivo, nombres o metadatos del repositorio.

    LoaderDescripción
    GitLoaderCarga archivos de un repositorio Git (local o clonado desde remoto), convirtiendo cada archivo en un Document.
    GithubFileLoaderPermite cargar archivos específicos desde un repositorio de GitHub mediante la API.
    GitHubIssuesLoaderExtrae issues de un repositorio de GitHub, incluyendo títulos, descripciones y comentarios.

    Características clave

    • Cada archivo → un Document
    • Cada issue → un Document
    • Metadata incluye ruta, repositorio, contexto técnico

    Ejemplo básico (GitLoader)

    from langchain_community.document_loaders import GitLoader
    
    loader = GitLoader(
        repo_path="./mi_repo",
        branch="main"
    )
    
    documents = loader.load()
    

    Consideraciones importantes

    • Código ≠ texto normal: El código tiene estructura lógica (funciones, clases, sintaxis) por lo que requiere chunking específico y cuidado en embeddings
    • Ruido en repositorios: archivos irrelevantes (.gitignore, configs…), dependencias y código duplicado. Es importante filtrar archivos.
    • Contexto técnico: El valor no está solo en el texto, sino en la estructura del proyecto, la relación entre archivos y el contexto de uso

    Buenas prácticas

    • Filtrar extensiones (.py, .js, .md)
    • Excluir carpetas innecesarias (node_modules, .git)
    • Usar metadata (ruta del archivo)
    • Adaptar el chunking al código

    Loaders de contenido multimedia (audio, imagen, vídeo)

    Los loaders de contenido multimedia permiten incorporar a un sistema RAG información que originalmente no está en formato de texto, como audio, imágenes o vídeos. Dado que los modelos de lenguaje trabajan con texto, estos loaders realizan una transformación previa: convierten contenido multimedia en texto procesable. Esto se consigue mediante tecnologías como:

    • transcripción de audio (speech-to-text)
    • OCR (reconocimiento de texto en imágenes)
    • generación automática de descripciones
    LoaderDescripción
    YoutubeLoaderExtrae transcripciones de vídeos de YouTube y las convierte en texto.
    AssemblyAIAudioLoaderCarga transcripciones existentes desde AssemblyAI.
    AssemblyAIAudioTranscriptLoaderTranscribe archivos de audio (locales o URL) usando AssemblyAI.
    YoutubeAudioLoaderDescarga audio de vídeos de YouTube para su posterior procesamiento.
    UnstructuredImageLoaderExtrae texto de imágenes mediante OCR.
    ImageCaptionLoaderGenera descripciones automáticas de imágenes usando modelos de captioning.

    Tipos de transformación

    • Audio → texto: Convierte voz en texto como llamadas, podcast o reuniones.
    • Imagen → texto: En esta caso hay dos enfoques:
      • OCR → extrae texto real
      • Captioning → describe la imagen
    • Vídeo → texto: generalmente mediante: transcripción del audio o subtítulos

    Ejemplo básico (YouTube)

    from langchain_community.document_loaders import YoutubeLoader
    
    loader = YoutubeLoader.from_youtube_url(
        "https://www.youtube.com/watch?v=XXXX",
        add_video_info=True
    )
    
    documents = loader.load()
    

    Ejemplo básico (audio)

    from langchain_community.document_loaders import AssemblyAIAudioTranscriptLoader
    
    loader = AssemblyAIAudioTranscriptLoader(
        file_path="audio.mp3"
    )
    
    documents = loader.load()
    

    Consideraciones importantes

    • Calidad del texto generado: La calidad depende del modelo de conversión ya que puede haber errores en transcripción, ruido en audio o OCR imperfecto lo que impacta directamente en embeddings.
    • Coste y latencia: Estos procesos suelen ser más lentos y más costosos debido a APIs externas.
    • Contexto limitado: El texto generado puede perder matices, simplificar contenido y omitir información visual.

    Buenas prácticas

    • Revisar la calidad de la transcripción
    • Limpiar el texto antes de embeddings
    • Añadir metadata (fuente, timestamp, tipo)
    • Dividir correctamente en chunks

    Aquí tienes la sección completa, alineada con el resto de tu unidad:

    Loaders de datos públicos y fuentes externas

    Los loaders de datos públicos permiten integrar información procedente de fuentes abiertas como enciclopedias, repositorios científicos o plataformas de contenido online. A diferencia de los loaders empresariales, aquí se trabaja con datos accesibles públicamente, lo que facilita la experimentación y el desarrollo de prototipos.

    LoaderDescripción
    WikipediaLoaderCarga contenido de páginas de Wikipedia a partir de una búsqueda o término específico.
    PubMedLoaderRecupera artículos y abstracts del repositorio biomédico PubMed.
    ArxivLoaderCarga papers científicos desde arXiv, extrayendo contenido desde PDFs o abstracts.
    RedditPostsLoaderExtrae publicaciones y comentarios de Reddit desde un subreddit específico.
    HNLoaderCarga contenido de Hacker News, incluyendo noticias y comentarios.
    from langchain_community.document_loaders import WikipediaLoader
    
    loader = WikipediaLoader(query="LangChain", load_max_docs=2)
    documents = loader.load()
    

    Consideraciones importantes

    • Calidad variable del contenido: dependiendo de la fuente, el contenido puede ser generalista, informal o altamente técnico.
    • Ruido y relevancia: No todo lo que se carga es útil se puede cargar comentarios irrelevantes, contenido duplicado o información no estructurada
    • Dependencia externa: APIs , límites de uso y disponibilidad del servicio

    Buenas prácticas

    • Limitar número de documentos (load_max_docs)
    • Filtrar por relevancia
    • Limpiar contenido antes de embeddings
    • Usar metadata para contexto (fuente, fecha, autor)

    Loaders empresariales avanzados

    Los loaders empresariales avanzados permiten integrar sistemas especializados que forman parte del stack tecnológico de una empresa. A diferencia de los loaders SaaS más generales (como Notion o Slack), estos se centran en herramientas específicas de negocio, como CRM, analítica, diseño o procesamiento de datos.

    LoaderDescripción
    Airbyte<Name>LoaderConectores basados en Airbyte para integrar múltiples fuentes (Salesforce, HubSpot, Stripe, Zendesk, etc.).
    AirtableLoaderCarga datos desde tablas de Airtable, convirtiendo cada registro en un Document.
    FigmaFileLoaderExtrae información desde archivos de diseño en Figma, incluyendo estructuras y contenido textual.
    DatadogLogsLoaderCarga logs desde Datadog, transformando eventos y registros en documentos analizables.

    Para utilizar Airbyte<Name>Loader, se necesita instalar el paquete airbyte-cdk que es el Connector Development Kit (CDK) de Airbyte. Una librería de Python diseñada para construir conectores (sources) de datos de forma estandarizada.

    airbyte-cdk es una herramienta de bajo nivel para crear conectores de datos robustos y escalables. Dentro de un sistema RAG, su papel no es directo, sino que actúa como capa intermedia que permite integrar fuentes externas complejas de forma estandarizada.

    Si estás construyendo sistemas avanzados o integraciones personalizadas, es una pieza clave. Si estás consumiendo datos con loaders ya existentes, probablemente ni siquiera necesites interactuar con él directamente.

    Ejemplo: cargar datos de HubSpot en RAG

    from langchain_community.document_loaders import AirbyteHubspotLoader
    
    loader = AirbyteHubspotLoader(
        api_key="TU_API_KEY",
        stream_name="contacts"  # también: deals, companies
    )
    
    documents = loader.load()
    
    
    

    Devuelve cada registro de HubSpot como un Document. Puedes cambiar el tipo de dato a extraer (“companies”, “deals”, etc. )

    Document(
        page_content="firstname: Juan, lastname: Pérez, email: [email protected], company: Hotel Sol",
        metadata={
            "source": "hubspot",
            "object_type": "contact"
        }
    )

    Mejora clave (nivel pro)

    Cuando trabajas con datos de sistemas como HubSpot, el contenido suele venir en formato técnico:

    firstname: Juan, lastname: Pérez, company: Hotel Sol, email: [email protected]

    Básicamente funciona, pero no es óptimo. Los modelos de lenguaje funcionan mejor cuando el texto tiene estructura natural y contexto semántico.

    firstname = doc.metadata.get("firstname", "")
    lastname = doc.metadata.get("lastname", "")
    company = doc.metadata.get("company", "")
    email = doc.metadata.get("email", "")
    
    doc.page_content = (
        f"Cliente {firstname} {lastname} trabaja en {company}. "
        f"Su email es {email}."
    )

    Texto natural mejor para embedding.

    Cliente Juan Pérez trabaja en Hotel Sol. Su email es [email protected].

    Consideraciones importantes

    • Dependencia de APIs: considera tener límites de uso, latencia y cambios en endpoints
    • Calidad del dato: estos sistemas contienen datos incompletos, campos técnicos y estructuras complejas, por lo que requieren transformación antes de embeddings.
    • Seguridad: datos sensibles (clientes, pagos, logs) o credenciales de acceso. Es imprescindible gestionar permisos correctamente

    Buenas prácticas

    • Seleccionar solo campos relevantes
    • Transformar datos en lenguaje natural
    • Añadir metadata útil (fecha, tipo, origen)
    • Limitar volumen de datos

    Loaders de almacenamiento y cloud

    Los loaders de almacenamiento y cloud permiten cargar documentos desde sistemas de almacenamiento remoto, como servicios en la nube o buckets de archivos. En lugar de acceder a archivos locales, estos loaders trabajan con datos alojados en infraestructuras externas.

    LoaderDescripción
    S3FileLoaderCarga un archivo específico desde un bucket de Amazon S3.
    S3DirectoryLoaderCarga múltiples archivos desde un bucket o carpeta en S3.
    CloudBlobLoaderPermite acceder a blobs desde distintas fuentes cloud (S3, URLs, etc.).
    OBSFileLoaderCarga archivos desde Huawei Object Storage (OBS).

    Ejemplo básico (S3)

    from langchain_community.document_loaders import S3FileLoader
    
    loader = S3FileLoader(
        bucket="mi-bucket",
        key="ruta/archivo.pdf"
    )
    
    documents = loader.load()
    

    Consideraciones importantes

    • Autenticación: Necesitas credenciales configuradas como AWS credentials, roles IAM, tokens, etc.
    • Tipos de archivos: Estos loaders no procesan directamente el contenido solo lo recuperan, luego necesitarás un loader según el tipo de archivo objetivo (PDF loader, text loader, etc.)
    • Latencia: acceso remoto + transferencias de archivos que pueden ser más lento que en entornos locales.

    Buenas prácticas

    • Limitar el número de archivos
    • Filtrar por tipo (PDF, TXT…)
    • Combinar con otros loaders
    • Aplicar limpieza posterior

    Loaders de comunicación y chats

    Los loaders de comunicación y chats permiten incorporar conversaciones reales dentro de un sistema RAG. Estas fuentes incluyen mensajes de plataformas como WhatsApp, Telegram, Discord o Facebook, donde el conocimiento no está en documentos formales, sino en interacciones entre personas.

    LoaderDescripción
    WhatsAppChatLoaderCarga conversaciones exportadas de WhatsApp en formato de texto.
    TelegramChatLoaderProcesa chats exportados de Telegram (normalmente en JSON o texto).
    DiscordChatLoaderCarga historiales de chat de Discord desde exports.
    FacebookChatLoaderExtrae conversaciones de Facebook Messenger desde archivos exportados.

    Ejemplo básico (WhatsApp)

    El loader WhatsAppChatLoader no accede a la app ni a la API de WhatsApp. Trabaja sobre exportaciones de chats generadas manualmente.

    from langchain_community.document_loaders import WhatsAppChatLoader
    
    loader = WhatsAppChatLoader("data/chat_whatsapp.txt")
    documents = loader.load()
    

    Ejemplo archivo exportado:

    [10/01/2024, 10:30] Juan: ¿Tenemos disponibilidad este fin de semana?
    [10/01/2024, 10:32] Hotel: Sí, tenemos habitaciones libres.
    [10/01/2024, 10:33] Juan: Perfecto, ¿precio?

    Resultado:

    Document(
        page_content="Juan pregunta por disponibilidad. El hotel responde que hay habitaciones disponibles.",
        metadata={"source": "whatsapp", "date": "2024-01-10"}
    )

    Preprocesamiento (El texto raw no es óptimo).

    clean_docs = []
    
    for doc in documents:
        text = doc.page_content
        
        # limpieza básica
        text = " ".join(text.split())
        
        doc.page_content = text
        clean_docs.append(doc)

    Problemas típicos

    • Ruido conversacional: saludos, emojis, mensajes irrelevantes.
    • Fragmentación: El contexto está dividido en múltiples mensajes y necesitas reconstruir la conversación.
    • Lenguaje informal: abreviaturas, errores, frases incompletas.
    • Necesidad de preprocesamiento: Aquí la limpieza es especialmente importante:
      • unir mensajes relacionados
      • eliminar ruido
      • resumir conversaciones
      • estructurar el contexto

    Buenas prácticas

    • agrupar mensajes por conversación
    • filtrar mensajes irrelevantes
    • añadir metadata (usuario, fecha)
    • convertir a lenguaje más estructurado

    Aquí tienes la sección completa, coherente con el resto del bloque:

    Loaders utilitarios

    Los loaders utilitarios no están diseñados para una fuente de datos específica, sino para optimizar y gestionar el proceso de carga. Actúan como herramientas auxiliares que permiten trabajar con múltiples documentos, combinar fuentes o mejorar el rendimiento del sistema. No cargan datos por sí mismos, sino que orquestan otros loaders.

    Estos loaders permiten:

    • cargar grandes volúmenes de archivos
    • combinar múltiples fuentes en un solo flujo
    • mejorar el rendimiento mediante paralelización
    LoaderDescripción
    DirectoryLoaderCarga todos los archivos de un directorio, aplicando loaders automáticamente según el tipo de archivo.
    MergedDataLoaderCombina múltiples loaders en uno solo, unificando diferentes fuentes de datos.
    ConcurrentLoaderPermite cargar documentos en paralelo para mejorar el rendimiento.

    Ejemplo carga masiva con DirectoryLoader:

    from langchain_community.document_loaders import DirectoryLoader
    
    loader = DirectoryLoader(
        path="./data",
        glob="**/*.pdf"
    )
    
    documents = loader.load()

    Ejemplo combinar fuentes:

    from langchain_community.document_loaders import MergedDataLoader
    from langchain_community.document_loaders import PyPDFLoader, TextLoader
    
    loader1 = PyPDFLoader("file.pdf")
    loader2 = TextLoader("file.txt")
    
    merged_loader = MergedDataLoader(loaders=[loader1, loader2])
    
    documents = merged_loader.load()

    Ejemplo paralelización:

    from langchain_community.document_loaders import ConcurrentLoader
    
    loader = ConcurrentLoader.from_loaders([
        PyPDFLoader("file1.pdf"),
        PyPDFLoader("file2.pdf")
    ])
    
    documents = loader.load()

    Consideraciones importantes

    • Control del volumen: Cargar demasiados archivos puede saturar la memoria y ralentizar el sistema.
    • Mezcla de fuentes: al combinar loaders pueden tener formatos distintos y requerir limpieza unificada
    • Paralelización: mejora velocidad pero aumenta consumo de recursos

    Buenas prácticas

    • filtrar archivos antes de cargar.
    • controlar el tamaño del dataset
    • aplicar limpieza homogénea
    • usar paralelización solo cuando sea necesario
  • Cadenas y Agentes en LangChain

    Para construir aplicaciones con IA, utilizando LangChain necesitas entender tres piezas clave:

    • Cadenas (Chains)
    • Memoria (Memory)
    • Agentes (Agents)

    Este artículo explica cómo funcionan, cómo se conectan aplicándolo en nuestro entorno RAG híbrido.

    Cadenas en LangChain

    En LangChain, una cadena es simplemente una secuencia de llamadas donde la salida de una se convierte en la entrada del siguiente para crear un flujo de información continuo en un pipeline.

    2. Ejemplo completo: sistema de recetas por ubicación

    Vamos a recrear el ejemplo del video paso a paso.

    🎯 Objetivo

    Dado un país, queremos:

    1. Obtener un plato típico
    2. Generar la receta
    3. Calcular el tiempo de cocción

    🔹 Cadena 1: obtener el plato

    Entrada:

    China
    

    Prompt:

    Dime un plato famoso de {location}
    

    Salida esperada:

    Pato de Pekín
    

    🔹 Cadena 2: generar la receta

    Usa la salida anterior como entrada.

    Entrada:

    Pato de Pekín
    

    Prompt:

    Dame una receta sencilla para {meal}
    

    Salida:

    Receta paso a paso del plato
    

    🔹 Cadena 3: estimar tiempo de cocción

    Entrada:

    Receta generada
    

    Prompt:

    ¿Cuánto tiempo se tarda en cocinar esta receta? {recipe}
    

    Salida:

    Tiempo estimado (ej: 90 minutos)
    

    🔗 Resultado final (cadena completa)

    China → Pato de Pekín → Receta → 90 minutos
    

    Esto es una cadena secuencial.


    🧠 Qué está pasando realmente

    • Cada paso reduce incertidumbre
    • El modelo trabaja con contexto más concreto
    • El resultado final es mucho más preciso

    3. Cómo se construye esto en código (simplificado)

    # Cadena 1
    location_chain → output: meal
    
    # Cadena 2
    dish_chain → input: meal → output: recipe
    
    # Cadena 3
    recipe_chain → input: recipe → output: time
    
    # Cadena completa
    overall_chain = location_chain → dish_chain → recipe_chain
    

    🔍 Tip clave

    Usar verbose=True te permite ver todo el flujo:

    • Qué entra
    • Qué sale
    • Cómo se transforma

    Esto es clave para depurar.


    4. Memoria en LangChain: el contexto continuo

    Aquí viene uno de los puntos más importantes.

    Los LLM por sí solos no recuerdan nada.

    LangChain introduce memoria para resolver esto.


    🧠 Qué hace la memoria

    • Lee contexto previo
    • Mejora la entrada actual
    • Guarda la conversación

    🔄 Flujo con memoria

    Entrada usuario → Memoria → Cadena → Resultado → Memoria
    

    📦 Ejemplo real

    IA: Hola
    Usuario: ¿Cuál es la capital de Francia?
    

    La memoria guarda:

    • Mensaje IA
    • Mensaje usuario

    Y las siguientes respuestas usan ese historial.


    🧩 Clase clave

    LangChain usa:

    ChatMessageHistory
    

    Permite:

    • Guardar mensajes humanos
    • Guardar mensajes de IA
    • Mantener contexto conversacional

    🧠 Resultado

    El sistema deja de ser reactivo…
    y pasa a ser conversacional.


    5. Agentes en LangChain: el salto a sistemas inteligentes

    Aquí es donde todo cambia.

    Un agente es:

    Un sistema donde el modelo decide qué hacer y qué herramientas usar.


    🔥 Diferencia clave

    CadenasAgentes
    Flujo fijoFlujo dinámico
    Tú defines pasosEl modelo decide pasos
    PredecibleAdaptativo

    6. Cómo funciona un agente

    Un agente:

    1. Recibe una pregunta
    2. Decide qué hacer
    3. Usa herramientas externas
    4. Devuelve una respuesta

    🧠 Ejemplo conceptual

    Pregunta:

    ¿Cuál es la población de Italia?
    

    El agente:

    1. Decide buscar datos
    2. Consulta base de datos o API
    3. Procesa resultado
    4. Responde

    ⚠️ Importante

    El modelo:

    • Decide acciones
    • Genera texto

    Pero no ejecuta directamente → usa herramientas.


    7. Ejemplo real: agente con DataFrame (muy potente)

    Este es especialmente relevante para análisis de datos.


    🎯 Objetivo

    Consultar datos usando lenguaje natural.


    🧩 Configuración

    create_pandas_dataframe_agent(modelo, dataframe)
    

    💬 Consulta

    ¿Cuántas filas hay en el dataframe?
    

    ⚙️ Qué ocurre por dentro

    1. El modelo interpreta la pregunta
    2. Genera código Python
    3. Ejecuta ese código
    4. Devuelve el resultado

    📊 Resultado

    Hay 139 filas en el DataFrame
    

    🔥 Esto es clave

    Estás pasando de:

    ❌ Preguntar
    ✅ Ejecutar lógica real sobre datos


    8. Cómo encajan todo junto

    Flujo completo de una aplicación

    Usuario → Memoria → Cadena o Agente → Herramientas → Resultado → Memoria
    

    9. Cuándo usar cada cosa

    Usa Cadenas cuando:

    • El flujo es claro
    • Sabes los pasos
    • Quieres control

    Usa Memoria cuando:

    • Hay conversación
    • Necesitas contexto
    • Hay múltiples interacciones

    Usa Agentes cuando:

    • No sabes los pasos exactos
    • Necesitas decisiones dinámicas
    • Integras herramientas externas

    10. Recapitulación

    • Las cadenas conectan pasos de procesamiento
    • La memoria mantiene contexto entre interacciones
    • Los agentes toman decisiones y usan herramientas
    • Juntos forman la base de aplicaciones reales con IA

    Cierre

    Aquí está el cambio clave:

    👉 Con cadenas organizas el pensamiento
    👉 Con memoria mantienes el contexto
    👉 Con agentes automatizas decisiones

    Y cuando combinas los tres…
    ya no estás usando IA.

    Estás construyendo sistemas inteligentes.

  • Método LCEL en LangChain

    Después de ver los componentes de LangChain. Ahora toca entender cómo se conectan realmente para construir aplicaciones. Aquí entra el LangChain Expression Language (LCEL): el enfoque moderno y recomendado para diseñar flujos de trabajo con LLMs.

    Construir una cadena con LCEL

    LCEL es un patrón de construcción que permite conectar componentes mediante el operador de tubería (|). Es una forma declarativa de construir pipelines donde los datos fluyen de un componente a otro de forma explícita y legible.

    LCEL aporta:

    • Mayor componibilidad
    • Flujo de datos más claro
    • Más flexibilidad
    • Código más mantenible

    Un flujo típico sigue estos pasos:

    1. Definir una plantilla con variables ({})
    2. Crear una instancia de prompt
    3. Conectar componentes con |
    4. Ejecutar la cadena con inputs

    El núcleo: el operador de tubería (|)

    El operador | Conecta componentes en secuencia.

    prompt | modelo | parser
    1. Se construye el prompt
    2. Se envía al modelo
    3. Se procesa la salida

    Runnables: las piezas básicas

    En LCEL todo se basa en runnables. Estas son interfaces que permiten que cualquier componente sea conectable dentro de un pipeline. Ejemplos:

    • Modelos (LLM)
    • Funciones
    • Parsers
    • Recuperadores

    Tipos de composición

    Ejecución secuencial (RunnableSequence)

    Cada paso depende del anterior:

    Entrada → Paso 1 → Paso 2 → Salida

    En LCEL:

    paso1 | paso2

    Ejecución paralela (RunnableParallel)

    Un mismo input se procesa en múltiples ramas al mismo tiempo.

    {
      "summary": tarea1,
      "translation": tarea2,
      "sentiment": tarea3
    }

    Coerción automática de tipos

    Una de las grandes ventajas de LCEL. Es que convierte automáticamente:

    • Diccionarios → RunnableParallel
    • Funciones → RunnableLambda

    Por lo que no necesitas crear manualmente cada componente.

    Ejemplo completo: pipeline simple

    Caso: generar un contenido:

    formatear_prompt | modelo | parser

    Qué ocurre

    1. Una función formatea el prompt (RunnableLambda)
    2. El modelo genera la respuesta
    3. El parser limpia o estructura la salida

    Ejemplo: procesamiento paralelo

    Entrada:

    "El producto es bueno pero llegó tarde"

    Pipeline:

    {
      "summary": prompt1 | modelo,
      "translation": prompt2 | modelo,
      "sentiment": prompt3 | modelo
    }

    Resultado

    • Resumen
    • Traducción
    • Sentimiento

    Todo en una sola ejecución.

    Flujo interno de datos

    LCEL hace explícito el flujo:

    Input → Transformación → Modelo → Output → Post-procesado

    Esto mejora:

    • Debugging
    • Escalabilidad
    • Control

    Ventajas clave de LCEL

    • Claridad: El flujo se entiende leyendo el código.
    • Reutilización: Puedes crear componentes reutilizables y pipelines estándar
    • Flexibilidad: secuencias, paralelismo y transformaciones
    • Capacidades avanzadas: LCEL permite ejecución paralela, soporte asíncrono, streaming y trazabilidad automática

    Cuándo usar LCEL

    Ideal para:

    • Flujos de prompts
    • Automatización de tareas
    • Sistemas modulares
    • Procesamiento de texto

    No ideal para:

    • Flujos con lógica compleja (condicionales, estados, etc.). En ese caso, usar: LangGraph. Pero LCEL sigue siendo la base dentro de cada nodo.

  • Componentes principales de LangChain

    Antes de trabajar con pipelines avanzados o LCEL, necesitas dominar los bloques fundamentales de LangChain. Aquí entiendes qué componentes existen y cómo encajan entre sí.

    Componentes principales de LangChain

    LangChain no es una sola cosa, es un ecosistema de componentes. Los principales son:

    • Documents
    • Chains
    • Agents
    • Language model (LLMs)
      • Chat Model
      • Embedding Models
    • Chat Message
    • Prompt Templates
    • Output Parsers

    Modelos de Lenguaje

    Son la base de los LLMs, reciben y generan texto. Sirven para generar contenido, resumir documentos y completar tareas. LangChain utiliza OpenAI, Google, Meta e IBM como lenguajes primarios.

    Los modelos se pueden personalizar ajustando parámetros que modifican su forma de responder, algunos son:

    • temperature: Aleatoriedad del modelo, más temperura = más creatividad.
    • max_tokens: Longitud máxima de la respuesta en tokens
    • top_p: Diversidad de palabras considerando la probabilidad acumulada.
    • top_k: Diversidad de palabras considerando la frecuencia
    • frequency_penalty: Penalización de frecuencia
    • presence_penalty: Penalización de presencia

    Modelos de Chat

    Son una evolución de los LLM tradicionales. Convierten texto plano en conversaciones estructuradas, entienden contexto y mantienen coherencia entre mensajes.

    Embedding Models

    What Are Vector Embeddings? Models & More Explained

    Estos modelos no generan texto. Su única función es transformar contenido en vectores (listas de números) que representan el significado semántico.

    Se utilizan exclusivamente para RAG (Retrieval Augmented Generation). Sirven para buscar información en bases de datos vectoriales comparando qué tan “cerca” está la pregunta del usuario de tus documentos.

    CaracterísticaChat ModelEmbedding Model
    InputLista de MensajesTexto plano
    OutputUn mensaje de texto Un vector (lista de floats)
    FunciónRazonar, generar, decidirComparar similitudes
    Uso principalEl “cerebro” del agenteLa “búsqueda” de memoria

    Cargar un modelo:

    from langchain_openai import ChatOpenAI
    
    # Cargar variables de entorno
    load_dotenv()
    
    # Crear modelo directamente
    llm = ChatOpenAI(
        model="deepseek-chat",
        openai_api_key=os.getenv("DEEPSEEK_API_KEY"),
        openai_api_base="https://api.deepseek.com",
    
        # Parámetros de generación
        temperature=0.3,
        max_tokens=100,
        top_p=0.9,
        frequency_penalty=0.2,
        presence_penalty=0.1
    )

    En la variable llm se carga el modelo que después se llama con el método .invoke()

    response = llm.invoke(messages)
    print(response.content)

    Mensajes de chat

    Un modelo de chat no trabaja con texto plano, sino con mensajes estructurados. Cada tipo de mensaje tiene un rol (quién habla) y un contenido (qué dice).

    Tipos de mensajes

    • "system"→ define comportamiento y contexto del modelo
    • "human"→ entrada del usuario
    • "ai"→ respuesta del modelo
    • “tool”→ interacción con sistemas externos
    from langchain_core.prompts import ChatPromptTemplate
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", "Eres experto en análisis de datos."),
    
        # Usuario
        ("human", "{question}"),
    
        # Respuesta previa del modelo (contexto conversacional)
        ("ai", "La varianza mide la dispersión de los datos."),
    
        # Resultado de una herramienta (tool calling)
        ("tool", '{"mean": 10, "std": 2}', {"tool_name": "stats_calculator"})
    ])

    No todos se usan siempre:

    • En el 90% de los casos → solo "system" + "human"
    • "ai" → cuando hay memoria conversacional
    • "tool" → solo en flujos con herramientas (agents / tool calling)

    Plantillas de prompts

    Las plantillas convierten entradas en instrucciones claras:

    • Estandarizan prompts
    • Permiten reutilización
    • Introducen variables dinámicas
    prompt = ChatPromptTemplate.from_messages([
        ("system", "Eres un experto en {domain}. Responde en tono {tone}."),
        ("human", "Pregunta: {question}\nContexto: {context}")
    ])
    
    chain = prompt | llm
    
    response = chain.invoke({
        "domain": "machine learning",
        "tone": "conciso",
        "question": "¿Qué es overfitting?",
        "context": "Modelo entrenado con pocos datos"
    })
    
    print(response.content)

    Tipos principales

    • Plantillas de cadena (string): para texto simple.
    • Plantillas de chat: para estructuras conversacionales.
      • Mensajes del sistema
      • Mensajes humanos
      • Mensajes de IA
    • Plantillas de pocos ejemplos (few-shot): incluyen ejemplos para guiar al modelo.

    Output Parsers

    Los Output Parsers en LangChain son el mecanismo para convertir la salida del LLM (texto libre) en estructuras controladas y utilizables (JSON, objetos tipados, listas, etc.).

    Son críticos cuando necesitas fiabilidad, integración con código y postprocesado determinista. Se guardan en una variable que se añade al final de la cadena.

    parser = JsonOutputParser()

    Chains (Cadenas)

    En LangChain, las Chains son la forma de encadenar los componentes (prompts, modelos, parsers, funciones) para construir un flujo completo desde entrada → transformación → salida. En el enfoque moderno (LCEL), una chain no es una clase rígida, sino una composición declarativa de “runnables”.

    prompt = ChatPromptTemplate.from_messages([
        ("system", "Responde como experto en estadística."),
        ("human", "{question}")
    ])
    
    llm = ChatOpenAI(model="gpt-4o-mini")
    
    parser = StrOutputParser()
    
    chain = prompt | llm | parser
    
    response = chain.invoke({"question": "¿Qué es la media?"})
    
    print(response)

    Documents

    En LangChain, un Document es la unidad estándar de datos que encapsula contenido (page_content) y metadatos (metadata), y actúa como formato común para que todos los módulos sean interoperables.

    • Se crea al ingerir información externa.
    • Se fragmenta para optimizar el procesamiento.
    • Mantiene siempre su metadata para permitir filtrado, ranking y trazabilidad.
    • Se transforma en vectores sin perder contexto.
    • Finalmente se recupera para inyectarse en prompts dentro de arquitecturas RAG.

    Agentes

    En LangChain, los Agentes (Agents) son el componente que permite a un LLM tomar decisiones iterativas y usar herramientas para resolver tareas complejas. A diferencia de una chain (pipeline fija), un agente decide dinámicamente qué hacer en cada paso.

    Un Agente es un orquestador basado en LLM que:

    1. razona sobre el problema
    2. elige acciones (tools)
    3. ejecuta
    4. observa resultados
    5. repite hasta llegar a una respuesta

    Diferencia agente vs Chains

    ConceptoChainAgent
    FlujoFijoDinámico
    ControlDeterministaAdaptativo
    Uso de toolsManualAutomático
    ComplejidadBaja-mediaAlta
  • Introducción a LangChain y LCEL

    Con LangChain pasamos de usar modelos de lenguaje a construir aplicaciones completas con ellos. Es un framework de código abierto en Python diseñado para facilitar el desarrollo de aplicaciones basadas en modelos de lenguaje (LLM). Proporciona componentes e interfaces para integrar modelos de lenguaje dentro de sistemas reales.

    Permite:

    • Localizar información relevante en grandes volúmenes de texto
    • Responder preguntas complejas combinando datos y generación
    • Automatizar procesos que implican múltiples pasos

    LangChain funciona encadenando operaciones como:

    1. Recuperar información
    2. Extraer datos relevantes
    3. Procesar el contenido
    4. Generar una respuesta

    Este flujo convierte un modelo de lenguaje en un sistema completo.

    Beneficios principales de LangChain

    • Modularidad: LangChain está diseñado como un sistema de piezas que reduce drásticamente el tiempo de desarrollo. Puedes construir por bloques, reutilizar componentes y modificar partes sin romper el conjunto
    • Extensibilidad: Añade nuevas funcionalidades fácilmente integrándose con APIS y sistemas externos sin rehacer toda la arquitectura.
    • Descomposición de problemas: LangChain replica un patrón humano: divide tareas complejas en pasos más pequeños. Esto mejorando la precisión de las respuestas, el control del proceso y la interpretabilidad del sistema.
    • Integración con bases de datos vectoriales: LangChain se integra con bases de datos vectoriales para realizar búsquedas semánticas, recuperar eficientemente la información con rápido acceso a grandes volúmenes de datos.

    Esto permite construir sistemas como:

    • Chat con documentos
    • Asistentes inteligentes
    • Sistemas de conocimiento empresarial

    Cómo funciona un sistema con LangChain

    Un flujo típico sería:

    1. Usuario hace una pregunta
    2. El sistema busca información relevante
    3. Procesa los datos
    4. Genera un prompt optimizado
    5. El modelo responde

    Este enfoque combina:

    • Recuperación de información
    • Procesamiento
    • Generación de lenguaje

    Casos de uso reales

    • Resumen de contenido: Analizar documentos largos, extraer lo importante y simplificar contenido complejo
    • Extracción de datos: Convierte texto en información estructurada: estadísticas clave, métricas y datos accionables
    • Sistemas de preguntas y respuestas: responden sobre bases de conocimiento, mantienen contexto conversacional y refinan respuestas dinámicamente
    • Generación automatizada de contenido: redacción de emails, generación de ideas y documentación técnica
    • Trabajar con otros tipos de datos: Aunque LangChain está centrado en texto, también puede trabajar con audio, imágenes y vídeo

    Embeddings y búsqueda semántica

    LangChain utiliza embeddings para representar información como vectores y comparar significado en lugar de palabras exactas. Esto permite encontrar información relevante aunque esté expresada de forma diferente para mejorar la calidad de las respuestas

    LangChain Expression Language (LCEL)

    LCEL es el enfoque moderno dentro de LangChain para construir pipelines de IA. Es un patrón que permite conectar componentes mediante el operador de tubería (|) para crear flujos de datos claros y legibles.
    Lo que permite estructurar esos sistemas de forma limpia, componible y escalable. LCEL aporta:

    • Mayor claridad en el flujo de datos
    • Mejor componibilidad
    • Más flexibilidad
    • Código más limpio

    LCEL funciona como una tubería de datos. Cada componente recibe una entrada, la transforma y pasa el resultado al siguiente

    prompt | modelo | parser

    Construir una cadena con LCEL

    Un patrón típico incluye:

    1. Definir una plantilla de prompt
    2. Crear el prompt con variables
    3. Conectar componentes con |
    4. Ejecutar la cadena con inputs

    Ventajas de LCEL

    Deja de pensar en “hacer prompts”. Empieza a pensar en: diseñar pipelines de transformación de información

    • Legibilidad: El flujo es explícito: input → transformación → modelo → salida
    • Reutilización: Puedes reusar bloques, crear patrones estándar y escalar fácilmente
    • Flexibilidad: Puedes combinar: cadenas en secuencial, paralelo y transformaciones personalizadas
    • Automatización avanzada: LCEL permite ejecución paralela, soporte asíncrono, streaming de respuestas y trazabilidad automática
  • Aprendizaje en Contexto y Métodos Avanzados de Ingeniería de Prompts

    Cuando trabajas con modelos de lenguaje como GPT-3.5, no estás “programando” en el sentido tradicional. Estás guiando el comportamiento del modelo a través del lenguaje. Y aquí es donde entran dos conceptos fundamentales:

    • Aprendizaje en contexto (In-Context Learning)
    • Ingeniería de prompts (Prompt Engineering)

    ¿Qué es el Aprendizaje en Contexto?

    El aprendizaje en contexto es una técnica mediante la cual un modelo aprende a realizar una tarea sin necesidad de ser reentrenado, simplemente a partir de ejemplos incluidos dentro del propio prompt, lo que haces es:

    • Incluir ejemplos dentro del prompt
    • Mostrarle al modelo qué tipo de salida esperas
    • Dejar que el modelo generalice ese patrón en tiempo de inferencia

    Ejemplo sencillo

    Clasifica el sentimiento:
    
    Ejemplo 1:
    Texto: Me encanta este producto
    Sentimiento: Positivo
    
    Ejemplo 2:
    Texto: No funciona como esperaba
    Sentimiento: Negativo
    
    Ahora:
    Texto: Es aceptable, pero mejorable
    Sentimiento:

    El modelo no ha sido entrenado específicamente para esto en ese momento. Está aprendiendo del contexto que le das.

    Ventajas y Limitaciones

    Ventajas clave

    • No requiere entrenamiento adicional
    • Reduce costes computacionales y tiempo
    • Permite adaptar modelos rápidamente a nuevas tareas
    • Mejora el rendimiento sin tocar el modelo base

    Limitaciones importantes

    • Capacidad limitada del contexto (no puedes meter infinitos ejemplos)
    • Tareas complejas pueden requerir:
      • Fine-tuning
      • Ajustes con gradiente (machine learning tradicional)

    Componentes del Prompt

    Un prompt bien diseñado tiene cuatro elementos clave:

    1. Instrucciones: Indican claramente qué debe hacer el modelo.
    2. Contexto: Proporciona información adicional para interpretar la tarea.
    3. Datos de entrada: Es la información concreta que el modelo debe procesar.
    4. Indicador de salida: Marca dónde debe responder el modelo.

    Ejemplo:

    Instrucción:
    Clasifica la siguiente reseña...
    
    Contexto:
    Producto recién lanzado...
    
    Entrada:
    "El producto llegó tarde pero la calidad superó mis expectativas."
    
    Salida:
    Sentimiento:
    

    Ingeniería de prompts

    La ingeniería de prompts es la disciplina que se encarga de crear, refinar y estructurar prompts para obtener respuestas más precisas, relevantes y útiles de un modelo de lenguaje:

    • Formular correctamente el problema
    • Reducir ambigüedad
    • Guiar el razonamiento del modelo
    • Controlar el tipo de salida

    Una buena ingeniería de prompts:

    • Aumenta la precisión
    • Mejora la relevancia de las respuestas
    • Reduce errores y malentendidos
    • Permite prescindir de fine-tuning en muchos casos

    Esto es especialmente útil en aplicaciones como:

    • Atención al cliente automatizada
    • Análisis de datos
    • Generación de contenido
    • Investigación

    Métodos Avanzados de Ingeniería de Prompts

    Optimizar un prompt para obtener resultados consistentes, precisos y escalables. Estos métodos marcan la diferencia entre un uso básico de la IA y un uso profesional.

    Zero-Shot Prompting

    El zero-shot prompting consiste en pedirle al modelo que realice una tarea sin proporcionarle ejemplos previos. El modelo utiliza únicamente:

    • Su conocimiento previo
    • La instrucción que le das

    Ejemplo

    Clasifica como verdadero o falso:
    
    La Torre Eiffel está en Berlín.
    
    Cuándo usarlo
    • Tareas simples
    • Preguntas directas
    • Cuando quieres rapidez y no necesitas precisión extrema
    Limitación
    • Menor control sobre la salida
    • Mayor probabilidad de error en tareas complejas

    One-Shot Prompting

    El one-shot prompting añade un único ejemplo para enseñar al modelo el patrón esperado. Introduce una plantilla mental y reduce ambigüedad

    Ejemplo

    Traduce del inglés al francés:
    
    Ejemplo:
    Hello → Bonjour
    
    Ahora:
    Where is the nearest supermarket?
    
    Cuándo usarlo
    • Cuando el formato importa
    • Cuando quieres consistencia básica
    • Para tareas repetitivas

    Few-Shot Prompting

    El few-shot prompting amplía el concepto anterior: le das al modelo varios ejemplos antes de la tarea real.

    • Mejora la generalización
    • Aumenta la precisión
    • Reduce errores de interpretación

    Ejemplo

    Clasifica la emoción:
    
    Ejemplo 1:
    Texto: Estoy muy feliz hoy
    Emoción: Alegría
    
    Ejemplo 2:
    Texto: Esto me da miedo
    Emoción: Miedo
    
    Ejemplo 3:
    Texto: Estoy frustrado con este resultado
    Emoción: Frustración
    
    Ahora:
    Texto: Esa película fue tan aterradora que tuve que taparme los ojos
    Emoción:
    
    Cuándo usarlo
    • Clasificación
    • Análisis de texto
    • Tareas donde el contexto es clave

    Chain of Thought (CoT)

    El Chain of Thought Prompting obliga al modelo a explicar su razonamiento paso a paso. Los LLM no “piensan” como humanos, pero simulan razonamiento mejor cuando lo estructuran en pasos.

    Ejemplo

    Una tienda tenía 22 manzanas.
    Vendió 15.
    Luego recibió 8 más.
    
    ¿Cuántas tiene ahora? Explica paso a paso.
    
    Ventajas
    • Mayor precisión en problemas complejos
    • Transparencia en la respuesta
    • Menos errores lógicos
    Cuándo usarlo
    • Matemáticas
    • Lógica
    • Análisis paso a paso
    • Decisiones complejas

    Autoconsistencia

    La autoconsistencia es una técnica para aumentar la fiabilidad:

    • Generas varias respuestas independientes
    • Comparas resultados
    • Te quedas con la más consistente

    Ejemplo

    • Problema:
      • Cuando yo tenía 6 años, mi hermana tenía la mitad. Ahora tengo 70. ¿Qué edad tiene ella?
    • Se le pide al modelo:
      • Resolverlo varias veces
      • Explicar cada razonamiento
    • Luego:
      • Se comparan las respuestas
      • Se valida la coherencia
    Qué aporta:
    • Reduce errores
    • Mejora confianza en el resultado
    • Detecta inconsistencias

    Herramientas clave en ingeniería de prompts

    Para trabajar de forma profesional, no basta con escribir prompts en un chat. Necesitas herramientas.

    Plataformas principales

    • OpenAI Playground
    • LangChain
    • Hugging Face Model Hub
    • IBM AI Classroom

    Qué permiten hacer

    • Probar prompts en tiempo real
    • Comparar resultados entre modelos
    • Iterar rápidamente
    • Medir rendimiento

    Cómo elegir el método adecuado

    No todos los prompts sirven para todo.

    MétodoCuándo usarlo
    Zero-shotTareas simples
    One-shotFormato específico
    Few-shotPrecisión y consistencia
    Chain of ThoughtProblemas complejos
    AutoconsistenciaValidación crítica