Tag: RAG

  • Qué es el graph engineering: el mapa que tu agente no tiene

    Qué es el graph engineering: el mapa que tu agente no tiene

    Le pedí a un agente que renombrara una función. getUserDatafetchUserProfile. Dos minutos de trabajo.

    Hizo grep, encontró siete referencias, las cambió, corrió los tests. Verde. Commit.

    Reventó al día siguiente. La función también se invocaba desde un mapa de handlers, handlers[action], con el nombre viajando como string dentro de un JSON de configuración. Grep encontró siete referencias. Había doce.

    El agente no falló por falta de contexto ni por usar un modelo flojo. Falló porque grep solo compara cadenas y nadie le dio un mapa de relaciones. De eso va el graph engineering.

    Qué es el graph engineering (y el lío que hay con el nombre)

    Graph engineering es la práctica de representar tu código y tu documentación como un grafo explícito de relaciones: los nodos son símbolos —archivos, funciones, clases, conceptos— y las aristas son las relaciones reales entre ellos: importa, llama, hereda, contiene, referencia.

    En vez de que el agente busque texto y adivine, navega aristas.

    Antes de seguir, un aviso honesto: el término no tiene una definición canónica única en 2026. Se usa para dos cosas distintas.

    La primera es el grafo de orquestación. Nodos como unidades de ejecución, aristas como flujo de control: LangGraph, org graphs, work graphs. Ahí el "graph engineering" es diseñar cómo se conectan varios agentes. Es la conversación que arrancó Peter Steinberger en julio de 2026 con una pregunta de seis palabras"Are we still talking loops or did we shift to graphs yet?" — y que es la continuación natural de lo que conté en loop engineering.

    La segunda es el grafo de recuperación. Nodos como símbolos de tu código, aristas como dependencias reales. Aquí no se decide qué agente actúa después: se decide qué sabe el agente antes de tocar nada.

    Este post va de la segunda. Y no compiten: una es control de flujo, la otra es recuperación. Misma palabra, dos capas del stack.

    Si necesitas el atajo: cuando hables de LangGraph o de coordinar varios agentes, es la primera. Cuando hables de qué código ve tu agente antes de editar, es la segunda.

    Las tres preguntas que ni grep ni los embeddings responden

    Hay tres preguntas sobre tu código que ni la búsqueda por texto ni la búsqueda semántica pueden responder:

    1. Si cambio esto, ¿qué se rompe? El radio de impacto a uno, dos o tres saltos. Grep te da el primer nivel. El transitivo no lo ve nadie.
    2. ¿Quién llama a quién? El call graph completo, con su dirección. Grep te dice que dos archivos mencionan AuthService. No te dice cuál lo consume y cuál lo define.
    3. ¿Qué depende de qué — y qué no depende de nada? Los nodos con grado cero son código muerto, y salen solos. Buscar código muerto con grep es un ejercicio de paciencia.

    Las tres son preguntas sobre topología, no sobre contenido. Por eso hacen falta aristas — y por eso las dos herramientas que usas hoy se quedan cortas.

    Tu agente tiene dos formas de encontrar código, y las dos tienen el mismo agujero.

    Grep busca coincidencia exacta de texto. Es preciso, rápido y determinista. No sabe nada de significado ni de estructura. Si la referencia está construida en runtime, no existe para grep.

    Los embeddings buscan parecido semántico. Encuentran la función de autenticación aunque se llame verificarCredenciales. Pero "se parece" no es "está conectado con". Un chunk sobre logging y otro sobre logging viven cerca en el espacio vectorial aunque uno nunca llame al otro. Es la limitación estructural de RAG que ya toqué en RAG vs fine-tuning.

    Las dos herramientas responden "¿dónde aparece esto?". Ninguna responde "¿con qué está conectado esto?".

    Resumido, con la tercera vía al lado:

    Grep Embeddings Grafo de código
    Pregunta que responde ¿Dónde aparece esta cadena? ¿Dónde hay algo parecido a esto? ¿Con qué está conectado esto?
    Qué necesitas saber antes El nombre exacto Una descripción aproximada Que el símbolo exista
    Ve el segundo salto No No Sí — affected --depth 2
    Ve llamadas indirectas No No Sí, marcadas como INFERRED
    Nivel de certeza Binario: aparece o no aparece Puntuación de similitud EXTRACTED o INFERRED
    Coste de mantenerlo Cero Reindexar + coste de embeddings Re-extracción AST, sin LLM
    Dónde se rompe La referencia se construye en runtime Dos cosas se parecen pero no se llaman Código muy dinámico: DI por string, metaprogramación

    Anatomía del grafo: nodos, aristas y confianza

    Un grafo de código tiene tres piezas: nodos (los símbolos: archivos, funciones, clases), aristas (las relaciones entre ellos) y un nivel de confianza por arista.

    Lo concreto. Construí un grafo con graphify —CLI open source, parseo AST local con tree-sitter, sin vector store— sobre un proyecto pequeño que tengo por ahí. Pequeño a propósito: quería poder verificar a mano cada arista antes de creerme nada. Salieron 115 nodos y 240 aristas.

    Los nodos llevan poco: id, etiqueta, archivo de origen y línea. Lo interesante está en las aristas.

    {
      "source": "src_chunker",
      "target": "src_chunker_needs_chunking",
      "relation": "contains",
      "confidence": "EXTRACTED",
      "source_file": "src/chunker.py",
      "source_location": "L10"
    }
    

    Los ocho tipos de relación que aparecieron en ese grafo:

    Relación Qué conecta ¿La ve grep?
    imports / imports_from Archivo → módulo o símbolo importado Sí, si el nombre aparece literal
    contains Archivo → función o clase que declara Parcialmente
    calls Función → función que invoca Solo el primer nivel
    references Símbolo usado sin invocarlo Sí, si el nombre aparece literal
    inherits Clase → clase base
    method Clase → método que le pertenece
    indirect_call Llamada resuelta en runtime No

    Fíjate en la última fila. indirect_call es exactamente la llamada que grep no ve.

    Y ahora el campo que más me interesa de todo esto, el que casi nadie menciona: confidence. Cada arista viene marcada como EXTRACTED o INFERRED. En mi grafo: 197 extraídas, 43 inferidas.

    EXTRACTED significa que la relación está literalmente en el AST. El parser la leyó, no la dedujo. INFERRED significa que la resolvió el motor uniendo puntos — una llamada cuyo destino tuvo que deducirse.

    Eso cambia cómo usas el resultado. Una arista EXTRACTED la das por buena. Una INFERRED es una hipótesis con nombre y apellidos que puedes ir a verificar al archivo y la línea que te da. Ni los embeddings ni grep te dan esa distinción: grep afirma sin matices, y el score de un embedding te dice cuánto se parece algo, nunca de dónde sale la relación. Aquí lo que se etiqueta es la procedencia.

    Un explain sobre un nodo devuelve esto:

    Node: needs_chunking()
      Source:    src/chunker.py L10
      Degree:    6
    
    Connections (6):
      <-- main() [calls] [INFERRED]
      <-- transcribe() [calls] [INFERRED]
      <-- chunker.py [contains] [EXTRACTED]
      --> Path [references] [EXTRACTED]
      <-- test_needs_chunking_false_for_small_file() [calls] [INFERRED]
      <-- test_needs_chunking_true_for_large_file() [calls] [INFERRED]
    

    Seis líneas. Ahí está el vecindario directo de esa función, con la dirección de cada arista y el nivel de confianza de cada una. Para llegar a lo mismo con grep necesitas varias pasadas y saber de antemano qué buscar.

    Pero el vecindario directo no es el radio de impacto. Para eso hay un comando aparte, que es el que responde literalmente a la pregunta 1: un recorrido inverso por las aristas que tú elijas, a la profundidad que tú digas.

    graphify affected "needs_chunking" --depth 2 --relation calls
    
    Affected nodes for needs_chunking()
    Relations: calls
    Depth: 2
    - test_needs_chunking_false_for_small_file() [calls] tests/test_chunker.py:L24
    - test_needs_chunking_true_for_large_file() [calls] tests/test_chunker.py:L30
    - main() [calls] transcribe.py:L17
    - transcribe() [calls] watch.py:L36
    - test_output_flag_saves_to_specified_path() [calls] tests/test_integration.py:L34
    - test_file_not_found_exits_with_code_1() [calls] tests/test_integration.py:L48
    - test_unsupported_format_exits_with_code_1() [calls] tests/test_integration.py:L55
    - test_api_key_not_in_output() [calls] tests/test_integration.py:L65
    - .on_created() [calls] watch.py:L72
    

    Mira la diferencia. De las seis conexiones del explain, solo cuatro eran llamadas entrantes. El affected a dos saltos da nueve, y las cinco nuevas son las interesantes: los cuatro tests de integración y el handler .on_created() del watcher no tocan needs_chunking directamente, llegan a través de main() y transcribe().

    Ese es el segundo nivel. El que revienta en producción al día siguiente y el que ninguna búsqueda por texto te va a dar, porque no hay ninguna cadena que buscar: la relación existe en la topología, no en el código fuente de esos archivos.

    Aquí está la tesis, y quiero decirla sin vender humo: el grafo no te garantiza encontrar la referencia indirecta. Te da una categoría donde esa relación puede existir y quedar marcada. Grep ni siquiera tiene esa categoría. Esa es toda la diferencia, y es suficiente.

    Cómo usar un grafo de código con un agente de coding, en 3 pasos

    Tres piezas.

    Uno: construyes el grafo y lo dejas en el repo. graphify-out/graph.json más un reporte en markdown. Es un artefacto de tu proyecto, como el lockfile.

    uv tool install graphifyy   # doble "y" mientras reclaman el nombre en PyPI;
                                # el comando y el skill siguen siendo graphify
    graphify install            # registra el skill en tu agente
    graphify update .           # re-extrae solo lo que cambió, sin LLM
    

    Dos: le das al agente una regla de precedencia. Sin esto no sirve de nada, porque el modelo tira de grep por costumbre. En el CLAUDE.md del proyecto:

    - Para preguntas sobre el código, ejecuta primero `graphify query "<pregunta>"`.
      Usa `graphify path "<A>" "<B>"` para relaciones, `graphify explain "<X>"`
      para un concepto concreto y `graphify affected "<X>"` antes de modificar o
      borrar algo. Devuelven un subgrafo acotado, mucho más pequeño que el reporte
      completo o la salida cruda de grep.
    - Después de modificar código, ejecuta `graphify update .`.
    

    Esa regla es la diferencia entre tener un grafo y usarlo. Es la misma idea de fondo que trabajo en el curso de Construye con IA: el agente no es más listo por tener más herramientas, sino por tener reglas claras de cuándo usar cuál.

    Tres: el grafo entra en la ventana como subgrafo, no como volcado. Un explain devuelve seis líneas donde un grep te vuelca cada aparición del término y tú decides después: recuperas menos tokens y mejores, que es el objetivo del context engineering.

    Ojo con una cosa: graphify query no devuelve una respuesta en prosa. Devuelve un recorrido BFS con los nodos encontrados. Es una herramienta de recuperación dentro del harness, no un chatbot. Quien interpreta el subgrafo sigue siendo el modelo.

    Y un apunte de higiene: el proyecto publica cifras de benchmark en su README. Son autoreportadas. Trátalas como lo que son y mide en tu repo.

    Cuándo NO merece la pena montar un grafo de código

    No todo proyecto necesita esto. Cuatro casos donde el grafo estorba más de lo que ayuda.

    Proyectos pequeños. Si el código entra entero en la ventana, el agente ya tiene el grafo en la cabeza y mejor resuelto. Montar recuperación para veinte archivos es sobreingeniería.

    Código muy dinámico. Metaprogramación intensa, inyección de dependencias por string, event buses, decoradores que reescriben comportamiento en runtime. El AST no puede ver lo que solo existe cuando el proceso arranca. El grafo saldrá con más aristas INFERRED que EXTRACTED, o directamente con huecos. Sigue siendo mejor que grep, pero baja mucho el techo.

    Y sí: el bug con el que abrí este post vive justo en esta frontera. Un nombre viajando dentro de un JSON no está en ningún AST. Lo que cambia es que el grafo marca ese hueco como INFERRED o lo deja sin arista, y eso es una señal que puedes leer. Grep te devuelve siete referencias con la misma cara de seguridad que si fueran las doce.

    Si no puedes mantenerlo actualizado. Un grafo obsoleto es peor que no tener grafo, porque el agente confía en él. Necesitas graphify update en un hook de pre-commit, en CI o con graphify watch. Si esto no está automatizado, no lo montes: en dos semanas tienes un mapa de un territorio que ya no existe.

    Si lo que buscas es "qué debería hacer este sistema". El grafo describe el código que existe, no la intención. Para eso el artefacto es la spec — que es, por cierto, otra forma de estructura explícita, y la razón por la que escribí el libro de Spec-Driven Development. El grafo cuenta el presente. La spec define el futuro.

    Y un apunte de madurez: graphify va por la 0.9.x. No es 1.0 todavía, y se nota. Herramienta útil, no infraestructura estable.

    Cómo empezar con graph engineering hoy

    Coge tu repo más feo. El que da miedo tocar.

    Construye el grafo, ejecuta un explain sobre la función que más te intimida y mira su grado. Si el número te sorprende, acabas de descubrir por qué ese refactor lleva meses aplazado.

    Si quieres ver cómo encaja esto con el resto del stack —agentes, MCP, memoria, specs— lo trabajamos a fondo en Dominicode Labs, con proyectos reales y no con ejemplos de juguete.

    Preguntas frecuentes

    ¿Graph engineering es lo mismo que GraphRAG?

    No exactamente. GraphRAG es la implementación de Microsoft que usa un LLM para extraer entidades y relaciones de texto no estructurado, detectar comunidades y resumirlas. Está pensado para corpus documentales.

    Graph engineering es el concepto general de estructurar conocimiento como grafo. Aplicado a código, el grafo se extrae del AST de forma determinista, sin LLM y sin coste por token. GraphRAG es una implementación posible, no la única ni la más barata para código.

    ¿Qué diferencia hay entre graph engineering y loop engineering?

    El loop engineering diseña el bucle de ejecución del agente: qué hace, cómo verifica el resultado y cuándo vuelve a intentarlo. El graph engineering, en la acepción de este post, diseña lo que el agente sabe antes de entrar en ese bucle: un mapa de relaciones de tu código en vez de una búsqueda de texto.

    No compiten. Un agente con un buen bucle y sin mapa repite el mismo error más rápido. Si tus fallos vienen de contexto estructural incompleto, el grafo rinde antes que otra iteración del loop.

    ¿Funciona con TypeScript o solo con Python?

    Los ejemplos de este post salen de un proyecto en Python, pero la extracción es por AST con tree-sitter y las gramáticas que trae cubren los lenguajes habituales: Python, TypeScript, JavaScript, Go, Rust, Java, C, C++, Ruby, C#, Kotlin, Scala y PHP.

    Con TypeScript hay un matiz: cuanto más tira el proyecto de inyección por token, decoradores y factories, más aristas caen en INFERRED. El grafo sigue siendo mejor que grep, pero léelo sabiendo qué parte es hipótesis.

    ¿Necesito una base de datos de grafos como Neo4j?

    Para un repo, no. El grafo de un proyecto normal cabe en un JSON en disco y se recorre con un BFS en memoria. Herramientas como graphify funcionan así, sin servidor y sin dependencias externas.

    Neo4j tiene sentido cuando el grafo es un producto en sí mismo, se consulta desde varios servicios o supera lo que quieres cargar en memoria. Para dar contexto estructural a un agente en tu máquina, es infraestructura que no necesitas.

    ¿El grafo sustituye a los embeddings y a la búsqueda semántica?

    No, y montarlo como sustituto es un error. Responden preguntas distintas.

    Los embeddings responden "¿dónde hay algo parecido a esto?" y toleran que no sepas los nombres exactos. El grafo responde "¿con qué está conectado esto?" y exige que el símbolo exista. Lo razonable es tener las dos vías y una regla de precedencia: para preguntas de estructura, grafo; para exploración difusa, semántica; para strings literales, grep.

    ¿Cada cuánto hay que reconstruir el grafo?

    En cada cambio de código relevante, y automatizado. La re-extracción incremental de código no necesita LLM, así que el coste es tiempo de CPU, no dinero.

    Lo práctico es un hook de pre-commit, un paso en CI o un proceso en watch mientras trabajas. Reconstruirlo a mano cuando te acuerdas es la vía rápida a un grafo obsoleto, y un grafo obsoleto le miente al agente con toda la confianza del mundo.

    ¿Sirve en monorepos grandes?

    Es donde más rinde, precisamente porque el código ya no cabe en la ventana de contexto y grep devuelve ruido. La pega es operativa: la visualización HTML se vuelve pesada por encima de unos miles de nodos, y para eso está la opción de saltarla y quedarte solo con el JSON consultable, que es lo que consume el agente.

    Y si tu organización tiene varios repos en vez de uno solo, puedes fusionar sus grafos en uno para cruzar dependencias entre paquetes.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Por qué la IA se inventa cosas — y por qué no es un fallo

    Por qué la IA se inventa cosas — y por qué no es un fallo

    Le pides las tres sentencias más relevantes sobre un asunto. Te devuelve tres: tribunal, número, año, en el formato exacto en que se citan estas cosas.

    Dos existen.

    La tercera no. Y es indistinguible de las otras dos.

    No está peor escrita. No lleva una nota al pie que diga "esta me la he inventado". No hay cambio de tono, ni duda, ni titubeo. Tiene la misma pinta de ser verdad que las otras dos.

    Y aquí está lo que casi nadie cuenta cuando explica por qué la IA se inventa cosas: no falló nada. El sistema hizo exactamente lo mismo que hace cuando acierta.

    Esto no es un ejercicio teórico. El 22 de junio de 2023, el juez P. Kevin Castel impuso una sanción de 5.000 dólares —solidariamente a dos abogados y a su despacho— en el caso Mata v. Avianca, nº 1:22-cv-01461 del Distrito Sur de Nueva York (678 F. Supp. 3d 443). Habían presentado un escrito con seis sentencias inventadas de principio a fin, con citas internas a resoluciones que tampoco existen.

    Lo mejor viene ahora: uno de ellos le preguntó a ChatGPT si esos casos eran reales. Respondió que sí, y añadió que podían encontrarse en Westlaw, LexisNexis y el Federal Reporter.

    Y aquí está el detalle que casi nadie cuenta: el juez no sancionó por el error. Escribió que usar una herramienta de IA no tiene "nada de intrínsecamente impropio". Sancionó porque, después de que la parte contraria y dos órdenes del tribunal cuestionaran la existencia de esas sentencias, siguieron defendiéndolas.

    Nadie hackeó nada. El modelo no se rompió. Solo hizo su trabajo.


    ¿Por qué la IA se inventa cosas?

    Porque un modelo de lenguaje no busca la respuesta correcta: genera la continuación más probable, pieza a pieza, sobre una distribución de probabilidad aprendida en el entrenamiento. Cuando lo plausible coincide con lo cierto, decimos que acierta. Cuando no coincide, decimos que alucina. Es el mismo mecanismo, el mismo nivel de seguridad en el tono y el mismo aspecto en la pantalla.

    Una alucinación de la IA es exactamente eso: una salida plausible y falsa —una cita, una fecha, un identificador, un método de librería— generada con el mismo procedimiento y con la misma confianza aparente que una salida correcta.

    Lo importante es lo que no hay: en ningún punto del proceso existe un paso que pregunte "¿esto es verdad?".

    No es que se salte la comprobación. Es que la comprobación no está en el diseño. Nadie la quitó porque nunca estuvo.

    Si lo tienes claro en términos de predicción — la misma idea que hay detrás de cualquier algoritmo de machine learning — deja de ser sorprendente. Un sistema entrenado para que la salida sea verosímil produce salidas verosímiles. Ni más ni menos.

            el modelo optimiza UNA cosa:
         que la continuación sea plausible
                        │
            ┌───────────┴─────────────┐
       coincide con              no coincide
        la verdad                con la verdad
            │                         │
        "acierta"                 "alucina"
            └────── el mismo ─────────┘
                   mecanismo
                        │
            y en ningún punto de este
            recorrido hay un paso que
            pregunte: ¿esto es verdad?
    

    Dos nombres distintos para el mismo comportamiento. La diferencia no la pone el modelo: la pone el mundo, al coincidir o no con lo que salió.


    Qué es una alucinación de la IA: el retrato robot que no se parece a nadie

    La analogía que mejor me funciona cuando lo explico en una reunión: un dibujante de retratos robot buenísimo que nunca ha visto al sospechoso.

    Tiene una técnica excelente. Conoce las proporciones, sabe qué rasgos aparecen juntos. Le das una descripción vaga y te devuelve un retrato limpio, coherente, con una nariz que encaja con esos pómulos.

    El retrato está bien hecho. Es convincente. Y puede no parecerse a nadie.

    Eso es una alucinación. No un borrón, no un garabato: un dibujo correcto de una persona que no existe.


    Lo contraintuitivo: alucina más cuando la pregunta tiene forma de respuesta

    Aquí es donde casi todo el mundo tiene el modelo mental invertido.

    La intuición dice: alucina cuando no sabe. Falso. O al menos, insuficiente.

    Alucina más cuando la pregunta parece tener una respuesta con una forma muy clara. Una referencia bibliográfica tiene una forma reconocible. Un artículo de una ley tiene una forma. Una fecha tiene una forma. Un número de sentencia tiene una forma.

    Y lo plausible es exactamente lo que el modelo optimiza. Dale un hueco con forma nítida y lo rellenará con algo que tenga esa forma.

    Corolario práctico y algo perverso: preguntar por una normativa que no existe es la manera más fiable de provocar una alucinación. No porque el modelo sea tonto, sino porque la pregunta le da la plantilla y él es muy bueno rellenando plantillas.

    Hay datos que lo respaldan. En Why Language Models Hallucinate (Kalai, Nachum, Vempala y Zhang, 4 de septiembre de 2025) —tres de los cuatro autores firman por OpenAI; Vempala, por Georgia Tech— los autores le preguntaron tres veces a DeepSeek-V3 por el cumpleaños de uno de ellos, indicándole explícitamente que respondiera solo si lo sabía. Obtuvieron tres fechas: "03-07", "15-06" y "01-01". Ninguna correcta.

    Con el título de su tesis doctoral, tres modelos distintos devolvieron tres títulos distintos. Ninguno acertó ni el título ni el año. Y el detalle que más dice: los tres inventados sonaban mejor que el de verdad.

    Y por si crees que esto solo pasa con datos oscuros: al preguntar cuántas D hay en "DEEPSEEK", los modelos del estudio respondieron 2, 3, y en algunos casos 6 y 7. La respuesta es 1. No es un problema de que le falte información. Está delante.


    No es mentir, y no es un disparate

    Dos precisiones que cambian la conversación con cualquier stakeholder.

    No es mentir. Mentir exige dos cosas: saber la verdad y decir otra cosa a propósito. Aquí no hay ninguna de las dos. No hay intención, y no hay una representación interna de "la verdad" separada de la salida que se pueda contradecir.

    Y las alucinaciones nunca son disparates. Esto es lo que las hace peligrosas. Si el modelo te dijera que el artículo aplicable es el 4.912 de una ley con 90 artículos, lo cazarías al instante.

    Lo que hace es devolverte el artículo 27.3. Verosímil. Bien formateado. En medio de tres párrafos que sí son correctos.

    Ese es el riesgo real: no la barbaridad evidente, sino el dato razonable que sobrevive a la revisión rápida y acaba en producción, en un informe o delante de un cliente. Y se agrava con el tiempo, porque cuando la herramienta lleva doscientos aciertos seguidos, revisar se convierte en echar un vistazo.


    Excelente cuando basta lo plausible. Peligroso cuando tiene que ser exacto

    Esta es la línea que de verdad importa, y la que deberías tener pegada al monitor antes de decidir dónde metes un LLM.

    Lo que le pides ¿Basta con que sea plausible? Veredicto
    Redactar, reformular, dar forma a un borrador Sí — lo plausible es lo bueno Úsalo sin miedo
    Resumir un documento con datos dentro La prosa sí; las cifras y los nombres, no Comprueba cada dato contra el original
    Traducir, ordenar ideas, dar nombre a cosas Sí, con repaso Úsalo
    Generar código que luego compila y se testea Sí — tienes verificador Úsalo, el compilador es tu red
    Una fecha, una cifra, un artículo de una ley No Verificador obligatorio
    Una referencia, un ID, una versión de librería No Verificador obligatorio

    Fíjate en la fila del código, porque es la que explica por qué los modelos funcionan tan bien escribiendo código y tan mal citando fuentes. En código tienes un verificador que se ejecuta: compilador, tipos, tests, linter. La alucinación se cae sola en dos segundos.

    En una cita bibliográfica no hay compilador. Nadie la ejecuta. Solo alguien leyéndola y asintiendo.

    Si tu caso de uso no tiene verificador, tú eres el verificador. Y tú te cansas.


    ¿Se arregla? El paper que medio internet cita al revés

    Existe la versión pesimista: "es intrínseco, no esperes que se arregle". Y existe el paper de Kalai y compañía, que se cita constantemente para apoyar esa frase, diciendo lo contrario.

    Lo que sostienen es esto: las alucinaciones no son un misterio. Empiezan como errores de clasificación binaria bajo presión estadística — si en los datos de entrenamiento un hecho aparece una sola vez, el modelo no tiene con qué distinguirlo de una invención. Su ejemplo: si el 20% de las fechas de nacimiento aparecen exactamente una vez en el preentrenamiento, cabe esperar que el modelo base alucine en al menos el 20% de esas fechas.

    Y luego viene la parte incómoda. Persisten, dicen, por cómo se puntúa a los modelos. Los benchmarks que dominan los leaderboards corrigen en binario: acierto o fallo. Un "no lo sé" puntúa igual que un fallo — cero. Revisaron las diez evaluaciones que dominan esos leaderboards —GPQA, MMLU-Pro, IFEval, Omni-MATH, BBH, MATH, MuSR, SWE-bench, HLE y WildBench— y en nueve de las diez reconocer incertidumbre no da ningún crédito. La única que da algo es WildBench, y con un matiz cruel: su rúbrica puede puntuar más bajo un "no lo sé" que una respuesta mediocre con datos inventados.

    Con esa regla, adivinar siempre es la estrategia óptima. Estamos entrenando buenos examinandos, no sistemas fiables.

    Su propuesta es tan poco glamurosa que por eso nadie la tuitea: cambiar la puntuación de los benchmarks que ya existen, declarando en el enunciado el umbral de confianza — responde solo si tienes más de t de confianza, el fallo resta t/(1−t) puntos, el acierto suma 1 y el "no lo sé" suma 0. Con t = 0.9, cada fallo cuesta nueve.

    Así que sí: la parte del problema que viene de los incentivos es corregible, y eso es una buena noticia.

    Lo que no cambia es el diseño de fondo. Puedes premiar la abstención y conseguir que el modelo diga "no lo sé" muchísimo más a menudo. No conviertes eso en un paso de comprobación de hechos que antes no existía. Mientras la salida se genere por probabilidad, tu arquitectura tiene que contemplar que a veces será plausible y falsa.

    No es una razón para no usarlo. Es una razón para diseñar con eso dentro.


    Cómo evitar alucinaciones en tu código: 5 decisiones para mañana

    Aquí es donde este post se separa de los cincuenta artículos que explican qué son las alucinaciones y terminan con un "revisa siempre las respuestas". Gracias, muy útil.

    Cinco decisiones concretas. Y antes, lo que caza cada una — porque ninguna las caza todas:

    Defensa Qué caza Qué NO caza
    Schema de salida (Zod) Respuestas con la forma equivocada Un ID inventado con la forma correcta
    Consulta a la fuente SKUs, IDs y referencias que no existen Datos que existen pero no aplican
    Cita literal verificada Atribuir algo real a una fuente que no lo dice Una fuente que dice algo falso
    Rama "no lo sé" en el schema El relleno por campo obligatorio La invención cuando el modelo "cree" saber
    Test de abstención Que el sistema invente en preguntas sin respuesta Errores en preguntas que sí tienen respuesta

    1. Verificar en lugar de confiar

    Cada dato factual que salga del modelo y entre en tu sistema pasa por tres filtros, en este orden: schema, tipos, fuente.

    El schema te da forma. Los tipos te dan garantías en compilación. Y la fuente te da la única cosa que el modelo no puede darte: verdad.

    import { z } from 'zod';
    
    // El schema valida la forma. NO valida que el ID exista.
    const Producto = z.object({
      sku: z.string().regex(/^[A-Z]{3}-\d{6}$/),
      precio: z.number().positive(),
    });
    
    const extraido = Producto.parse(salidaDelModelo); // ✅ forma correcta
    const real = await db.productos.findBySku(extraido.sku); // ✅ existencia
    if (!real) throw new SkuInventadoError(extraido.sku); // error tuyo, no de Zod
    

    Un SKU inventado pasa el regex sin problema. Tiene exactamente la forma de un SKU — recuerda: forma clara, invención fiable. La única defensa es la consulta.

    2. Grounding: dale las fuentes delante y exígele la cita

    Si el modelo tiene el texto real en la ventana de contexto, no necesita inventar. Eso es RAG y por qué gana a fine-tuning para problemas de conocimiento — y si quieres verlo montado con código, tienes la implementación completa aquí.

    Pero pedir la cita no basta. Hay que comprobarla:

    // Sin normalizar, un espacio doble o una tilde tumban la comprobación.
    const normalizar = (s: string) =>
      s.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
        .replace(/\s+/g, ' ')
        .trim()
        .toLowerCase();
    
    const cita = normalizar(respuesta.citaLiteral);
    
    // Ojo: includes('') es true. Una cita vacía "aparece" en cualquier documento.
    const citaVerificada =
      cita.length >= 30 &&
      fuentes.some(f =>
        f.id === respuesta.fuenteId && normalizar(f.texto).includes(cita)
      );
    

    Determinista, barato, sin llamadas extra. Si la cita literal no aparece en el documento que dice citar, la respuesta se descarta. Con esto cazas la clase de fallo más caro que existe: la respuesta correcta atribuida a una fuente que no dice eso.

    3. Déjale una salida: "no lo sé" tiene que ser una opción legal

    Este es el error más repetido en las integraciones que reviso: un schema con todos los campos obligatorios y ninguna rama para la ignorancia.

    Cada campo obligatorio sin salida es una invitación a rellenar. Si tu tipo dice que articulo: string es obligatorio, has convertido "no lo sé" en una respuesta imposible de expresar.

    const Respuesta = z.discriminatedUnion('estado', [
      z.object({
        estado: z.literal('encontrado'),
        articulo: z.string(),
        citaLiteral: z.string().min(30), // una cita de cinco caracteres "aparece" en casi cualquier documento
        fuenteId: z.string(),
      }),
      z.object({
        estado: z.literal('no_esta_en_las_fuentes'),
        queFaltaria: z.string(),
      }),
    ]);
    

    Y dilo también en el prompt, explícito: si la información no está en los documentos proporcionados, responde con estado no_esta_en_las_fuentes. No completes con conocimiento propio.

    Dos frases. Baja las invenciones de forma muy visible. Es la misma lógica de los umbrales de confianza del paper, aplicada a tu endpoint.

    4. Dónde no lo metes sin verificador

    Ninguna de estas cosas entra a un sistema por generación directa: identificadores, precios, cantidades, fechas límite, versiones de dependencias, nombres de métodos de una librería, artículos de normativa, referencias.

    Lo de los nombres de métodos merece un párrafo. Cuando le pides código con una librería poco común, el modelo te devuelve el método que debería existir según todas las APIs parecidas que ha visto. Bien nombrado, con la firma coherente, perfectamente plausible. Y no existe.

    Y hay una variante peor con los nombres de paquete: el compilador no te salva de un npm install de una dependencia que el modelo se ha inventado y que alguien ya ha registrado con ese nombre exacto, esperando precisamente eso.

    Da igual que bajes la temperatura a 0. Eso te quita variabilidad, no te da verdad: te devuelve la misma respuesta plausible casi siempre. Si era falsa, ahora es falsa de forma reproducible. Y ni el "casi siempre" está garantizado: en una API la salida a temperatura 0 todavía puede cambiar según cómo se agrupen las peticiones concurrentes en el servidor.

    5. Que los tests no comparen la salida literal

    Un test que hace expect(salida).toBe("...") sobre una respuesta generada está roto de nacimiento. Cambias de modelo o de versión y se cae sin que nada haya empeorado.

    Testea propiedades, no cadenas:

    • La salida valida contra el schema, siempre.
    • Toda cita literal aparece en la fuente que dice citar.
    • Ningún fuenteId sale del conjunto de fuentes inyectadas.
    • Y el que más información da: un conjunto de preguntas cuya respuesta no está en el corpus, donde lo que se comprueba es que el sistema se abstiene. Si tu tasa de abstención en ese conjunto es baja, tu sistema está inventando y todavía no lo sabes.

    Esa cuarta propiedad es el equivalente en tu repo de lo que propone el paper para los benchmarks: dejar de premiar el acierto por adivinar.

    Decidir esto antes de escribir código —qué salida es aceptable y cómo se comprueba— en lugar de parchearlo cuando ya ha explotado, es exactamente el trabajo que describo en Spec-Driven Development.

    Y en el curso Construye con IA montamos estos verificadores dentro del flujo. Es la diferencia entre un prototipo que impresiona en la demo y algo que puedes dejar corriendo.


    La única conclusión que importa

    Deja de preguntarte si el modelo alucina. Alucina, porque es la misma operación con la que acierta.

    Y no es que sea inevitable —el propio paper describe un sistema que puede abstenerse—. Es que tú no puedes construir asumiendo que ya está resuelto.

    La pregunta correcta es otra: ¿en qué punto de mi sistema se detecta un dato falso, y qué pasa si ese punto no existe?

    Si la respuesta es "lo detecta la persona que lo lea", no tienes un sistema. Tienes un borrador con muy buena presentación.

    Elige hoy el dato factual más crítico que salga de un modelo en tu código y ponle un verificador determinista. Uno. Media hora de trabajo. Vas a dormir mejor.

    Y si te has quedado con ganas de ordenar el resto del terreno — grounding, RAG, salida estructurada, evaluación y el resto de los 120 conceptos colocados por zonas, con sus conexiones dibujadas — tengo un mapa de la IA en una hoja para imprimir, gratis aquí. Funciona muy bien para pasársela a quien aprueba estos presupuestos.


    Preguntas frecuentes

    ¿Por qué la IA se inventa cosas?

    Porque un modelo de lenguaje genera la continuación más probable de un texto, token a token, sobre una distribución de probabilidad aprendida en el entrenamiento. Su objetivo es que la salida resulte plausible, no que sea verdadera. Cuando lo plausible coincide con lo cierto, acierta; cuando no coincide, alucina. En ningún momento del proceso hay un paso que verifique si lo generado es verdad: esa comprobación no forma parte del diseño, así que hay que añadirla fuera del modelo.

    ¿Qué son las alucinaciones de la IA exactamente?

    Son salidas plausibles y falsas: una referencia con el formato exacto de una referencia real, una cifra verosímil, una cita bien construida, un método de librería que podría existir. Nunca son disparates evidentes, y por eso son peligrosas. El riesgo no es que el modelo diga algo absurdo, que cualquiera detectaría, sino que introduzca un dato razonable en medio de varios párrafos correctos y ese dato sobreviva a la revisión rápida hasta llegar a producción.

    ¿La IA miente cuando alucina?

    No. Mentir exige saber la verdad y decir otra cosa de forma deliberada, y en un modelo de lenguaje no se da ninguna de las dos condiciones: no hay intención, y no existe una representación interna de la verdad separada de la salida que se pueda contradecir. Por eso tampoco sirve enfadarse con el modelo ni pedirle que "no invente". Lo que sirve es cambiar el diseño alrededor: darle las fuentes, exigirle cita y verificarla con código.

    ¿Cuándo alucina más un modelo de lenguaje?

    Contra lo que parece, no solo cuando no sabe algo. Alucina más cuando la pregunta parece tener una respuesta con una forma muy clara y reconocible: una fecha, un artículo de una ley, un número de sentencia, una referencia bibliográfica. Como el modelo optimiza plausibilidad, un hueco con forma nítida se rellena con algo que tenga esa forma. Preguntar por una normativa que no existe es una de las maneras más fiables de provocar una invención.

    ¿Se pueden eliminar las alucinaciones del todo?

    Se reducen mucho y no se eliminan. Funcionan tres palancas: grounding (darle las fuentes en el contexto), exigir cita literal y verificarla contra el documento, y permitir explícitamente "no lo sé" en el prompt y en el schema de salida. El paper Why Language Models Hallucinate (2025) añade una cuarta a nivel de industria: cambiar la puntuación de los benchmarks, que hoy casi todos dan cero tanto al fallo como al "no lo sé" y por tanto premian adivinar.

    ¿Bajar la temperatura a 0 evita las alucinaciones?

    No. La temperatura controla cuánta variabilidad hay al muestrear el siguiente token, no si el contenido es cierto. Con temperatura 0 el modelo elige en cada paso el token más probable, así que tiendes a obtener siempre la misma respuesta —aunque ni eso está garantizado en una API, donde el resultado depende de cómo se agrupen las peticiones concurrentes—. Si esa respuesta es falsa, ahora es falsa de forma consistente, que engaña más porque parece estabilidad.

    ¿Alucinan menos los modelos de razonamiento?

    Menos en lo que se puede calcular: si la respuesta se deduce paso a paso, más cómputo ayuda. Pero razonar no añade un paso de comprobación contra el mundo, así que en datos que solo se pueden saber —una fecha, una referencia, un identificador— el problema es idéntico. Why Language Models Hallucinate lo atribuye a los incentivos de evaluación, no a la capacidad: mientras un "no lo sé" puntúe igual que un fallo, adivinar sigue siendo la estrategia óptima para cualquier modelo, razone o no.

    ¿Cómo pruebo que mi integración con un LLM no inventa datos?

    No compares la salida literal con una cadena esperada: se rompe en cada cambio de modelo sin que nada haya empeorado. Testea propiedades. Que la salida valide contra el schema, que toda cita literal aparezca en la fuente que dice citar, que ningún identificador de fuente esté fuera del conjunto inyectado, y sobre todo mantén un conjunto de preguntas cuya respuesta no exista en tu corpus, midiendo que el sistema se abstiene en lugar de rellenar.


    Si quieres ver estos verificadores funcionando dentro de proyectos reales, con la arquitectura y el código completos, es parte de lo que trabajamos en Dominicode Labs. Problemas de producción, decisiones que puedes aplicar esta semana.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • RAG vs fine-tuning vs contexto: cuándo usar cada uno

    RAG vs fine-tuning vs contexto: cuándo usar cada uno

    Un cliente me mostró su arquitectura hace unos meses. Había pasado seis semanas haciendo fine-tuning de un modelo para que respondiera preguntas sobre la documentación interna de su empresa.

    Seis semanas. Un dataset de 4.000 pares de pregunta-respuesta construidos a mano. Costes de entrenamiento en GPU. Y al final, el sistema seguía inventándose respuestas cuando la pregunta tocaba un documento que no estaba en el training data.

    Le pregunté por qué no había usado RAG. Me dijo que pensó que fine-tuning era "la solución profesional". Que RAG era para hacer demos rápidas.

    Esa diferencia entre RAG y fine-tuning se malentiende constantemente, y el malentendido sale caro. Pero hay algo peor: casi nadie considera la tercera opción, que es la más barata de las tres y resuelve más casos de los que parece.


    ¿Cuál es la diferencia entre RAG, fine-tuning y contexto?

    La diferencia entre RAG y fine-tuning es qué problema resuelve cada uno: RAG le da al modelo información que no tiene, fine-tuning le cambia la forma de comportarse. Y hay una tercera vía que va antes de las dos: pegar el material en el contexto de la petición. Esta es la tabla que conviene tener delante en una reunión de presupuesto.

    Qué es Cuándo Coste Dónde se rompe
    Contexto Se lo pegas tú en la petición Poco material, uso puntual Bajo, pero lo pagas en cada llamada No cabe, o se pierde lo del medio
    RAG Lo busca en tus documentos al preguntar Mucho material, y que cambia Medio, sobre todo el pipeline Si busca mal, responde mal
    Fine-tuning Ajustas el modelo con ejemplos Formato y tono muy propios, o clasificar y extraer en tu dominio Alto, y se repite con cada modelo nuevo No sirve para meter datos

    La regla, en una línea: contexto para lo puntual, RAG para lo que sabes, fine-tuning para cómo lo dices.

    Si alguien propone fine-tuning para que el modelo conozca vuestros datos, hay una conversación pendiente antes de firmar nada.


    El error conceptual que lo complica todo

    La mayoría de developers que se acercan a este problema lo enmarcan mal desde el principio.

    Piensan en términos de "qué técnica es más potente". Y ahí ya van por el camino equivocado.

    La pregunta correcta no es cuál es más potente. Es: ¿qué problema tienes exactamente?

    Si tu modelo no sabe cosas que necesita saber — información privada, documentos internos, datos recientes — tienes un problema de conocimiento. Contexto o RAG lo resuelven.

    Si tu modelo sabe las cosas pero no las comunica como necesitas — tono diferente, formato específico, comportamiento distinto al por defecto — tienes un problema de comportamiento. Fine-tuning lo resuelve.

    Son problemas distintos. Las soluciones no son intercambiables. Y aquí está la frase que ahorra más dinero de todo este terreno:

    El fine-tuning enseña comportamiento, no información.

    Si le haces fine-tuning con el catálogo de productos, no acabas con un modelo que se sepa el catálogo. Acabas con uno que habla como tu catálogo e inventa referencias con el estilo exacto de las tuyas. Bastante peor que no hacer nada, porque las invenciones son más creíbles.


    Contexto: la opción que deberías agotar primero

    Es la que se salta todo el mundo, y en muchos proyectos es la única que hacía falta.

    Contexto es, literalmente, pegar la información en la petición. El manual, el fragmento de código, las tres facturas de ejemplo. Sin infraestructura, sin pipeline, sin base de datos vectorial. Es lo que la literatura llama in-context learning y lo que en la práctica se acaba llamando prompt stuffing.

    Sus dos límites reales:

    No cabe todo. La ventana de contexto es el número máximo de tokens que entran en una sola petición, sumando lo que envías y lo que el modelo genera. Los modelos actuales manejan ventanas grandes, pero grande no es infinito y el coste sube con lo que metes.

    Se paga en cada llamada. No es una inversión que amortices: es un peaje por petición. Mil consultas al día con el mismo manual de 20.000 tokens delante son veinte millones de tokens al día de material repetido.

    Ese segundo problema tiene solución y casi nadie la aplica, así que le dedico una sección propia más abajo.

    Y una confusión que conviene cortar aquí: la ventana de contexto no es memoria. Una ventana enorme te deja meter mucho de una vez. Memoria es que algo sobreviva a cerrar la sesión. Son cosas distintas y una no da la otra. Cuando un proveedor te venda un asistente "con memoria", la pregunta útil no es si la tiene, sino qué guarda, dónde y durante cuánto tiempo.


    Qué es RAG (Retrieval-Augmented Generation) y cuándo usarlo

    RAG no modifica el modelo. El modelo base sigue siendo exactamente el mismo.

    Lo que hace es intervenir en el momento en que llega una pregunta. Antes de pasársela al modelo, busca en una base de datos vectorial los fragmentos de tus documentos más relevantes para esa consulta, y los inyecta en el prompt. El modelo entonces responde con acceso real a esa información.

    Usuario pregunta: "¿Cuál es la política de devoluciones?"
                             ↓
                 Sistema RAG busca en vectorDB
                             ↓
          Encuentra: chunk del doc "politica-devoluciones-2026.pdf"
                             ↓
        Prompt al modelo: "Contexto: [chunk]. Pregunta: ¿Cuál es...?"
                             ↓
                Modelo responde con información real
    

    La ventaja clave: tus documentos pueden cambiar mañana. Actualizas la base vectorial. El modelo ya tiene acceso a la nueva información. Sin reentrenar nada.

    Visto así, RAG es contexto automatizado: en vez de pegar tú el fragmento correcto, un buscador lo elige por ti en cada petición. Por eso la pregunta de diseño en RAG no es qué modelo usas, es si tu buscador encuentra lo que hace falta.

    Si quieres verlo montado con código, tengo el paso a paso en implementación de RAG en Angular, y las estrategias de búsqueda híbrida en RAG avanzado. Y si estás eligiendo el modelo para el componente generativo, este análisis sobre el mejor modelo LLM local en 2026 te ayuda a no sobreingenierizar la infraestructura.

    Esto es lo que lo hace ideal para documentación interna, bases de conocimiento, FAQs, soporte técnico — cualquier caso donde la información cambia y necesitas que el modelo cite fuentes reales en lugar de fabricar respuestas.

    El límite de RAG está en que no cambia cómo se comporta el modelo. Si necesitas que responda en un tono muy específico, siga un formato exacto, o haga razonamientos que el modelo base no hace bien de forma natural, RAG no te ayuda. Solo le das más información. No lo entrenas.


    Qué es fine-tuning de LLMs y cuándo tiene sentido aplicarlo

    Fine-tuning sí modifica el modelo. Tomas un modelo base preentrenado y lo sigues entrenando con tu propio dataset, ajustando sus pesos para que aprenda los patrones que te interesan.

    El resultado es un modelo diferente. Uno que ha interiorizado un estilo, un formato, un tipo de razonamiento específico. No necesitas darle instrucciones en el prompt porque ya las tiene grabadas en sus pesos.

    # Sin fine-tuning: necesitas el prompt completo
    prompt = """Eres un asistente técnico especializado en Kubernetes.
    Responde siempre con: 1) causa del problema, 2) solución paso a paso,
    3) cómo prevenirlo. Usa terminología técnica precisa. No añadas
    disclaimers. El tono es directo, de senior a senior.
    
    Problema: Mi pod no arranca después de actualizar la imagen..."""
    
    # Con fine-tuning: el modelo ya sabe cómo comportarse
    prompt = "Problema: Mi pod no arranca después de actualizar la imagen..."
    

    El modelo fine-tuneado responde directamente en el formato correcto porque ese comportamiento está en sus pesos. No porque se lo estés recordando en cada llamada.

    Lo que fine-tuning no resuelve: inyectar conocimiento factual nuevo. Si entrenas el modelo en el estilo de tu empresa pero no en los documentos de tu empresa, seguirá sin saber qué contienen esos documentos. Habrá aprendido a comunicarse como tú quieres, pero no a responder con información real que no tenía.

    Y no es entrenar desde cero. Eso cuesta una fortuna y lo hacen muy pocas organizaciones en el planeta. Esto es un retoque sobre un modelo existente.


    La noticia que cambia el cálculo: OpenAI está cerrando su fine-tuning

    Si tomaste esta decisión antes de mayo de 2026, vuelve a tomarla. El tablero ha cambiado.

    OpenAI está retirando su plataforma de fine-tuning self-serve. Lo dice en su propia página de deprecations, con fechas:

    Fecha Qué pasa
    7 de mayo de 2026 Las organizaciones que nunca habían hecho fine-tuning ya no pueden empezar
    2 de julio de 2026 Y tampoco las que llevan 60 días sin ejecutar inferencia sobre un modelo fine-tuneado
    6 de enero de 2027 Los clientes activos dejan de poder crear jobs de fine-tuning

    La inferencia sobre modelos ya fine-tuneados sigue funcionando hasta que se deprecie el modelo base. Y en la guía de fine-tuning la propia OpenAI dice que la plataforma "no es accesible para usuarios nuevos", con una lista de modelos soportados que se ha quedado en la familia gpt-4.1 más gpt-4o para visión y o4-mini para refuerzo.

    Lo interesante es a dónde te manda su propia documentación: al ciclo de evals más prompt engineering. Contexto relevante, instrucciones claras, ejemplos few-shot y, textualmente, "start with gpt-5.6 for new work". Es decir, la primera columna de la tabla de arriba, ejecutada sobre el modelo más nuevo que tengas a mano.

    Y un matiz antes de que alguien lo lea como "el fine-tuning ya no sirve": la misma guía sigue listando como caso válido entrenar un modelo más pequeño, más barato y más rápido para una tarea concreta donde uno grande no sale a cuenta. Eso es exactamente el Caso 4 de más abajo.

    Qué significa esto en la práctica:

    • Si estabas planteándote fine-tuning en OpenAI y no lo has hecho nunca, esa puerta ya está cerrada. No es una decisión pendiente.
    • El fine-tuning sigue existiendo fuera de OpenAI — modelos abiertos con LoRA o QLoRA, y otros proveedores.
    • Pero cuando el proveedor más grande te dice que uses un modelo mejor con mejores prompts en lugar de ajustar uno peor, merece la pena escuchar el argumento antes de montar un pipeline de entrenamiento.

    No es que el fine-tuning haya dejado de servir. Es que el rango de casos donde gana se ha estrechado, y los modelos base han absorbido buena parte de lo que antes justificaba entrenarlos.


    RAG vs fine-tuning: la matriz de decisión con cuatro casos reales

    Hay cuatro combinaciones que aparecen una y otra vez en proyectos reales.

    Caso 1: Chatbot sobre documentación interna

    Necesitas que el modelo responda preguntas sobre tus PDFs, wikis, Notion, Confluence. La información cambia regularmente. El tono puede ser el del modelo base.

    Solución: RAG. Indexas los documentos en una vectorDB (pgvector, Pinecone, Weaviate), configuras el pipeline de retrieval, y el modelo responde con fuentes reales.

    Pero antes de montarlo: si son cuatro documentos que caben en el contexto y cambian una vez al trimestre, empieza por contexto y ahórrate el pipeline. RAG paga cuando el material no cabe o cambia a diario.

    Caso 2: Generador de código en el estilo de tu empresa

    Quieres que el modelo genere código que siga tus convenciones internas, use tus abstracciones propias, evite los patrones que prohíbes.

    Solución clásica: fine-tuning. Pero prueba primero con un documento de convenciones en el contexto y tres o cuatro ejemplos antes/después. Los modelos actuales siguen instrucciones de formato mucho mejor que los de hace dos años.

    Veredicto hoy: contexto primero. Fine-tuning solo si el prompt con convenciones y ejemplos falla de forma medible, no porque el resultado te parezca mejorable.

    Caso 3: Asistente de soporte que responde sobre tus productos Y en tu tono

    Quieres las dos cosas: información factual que cambia, y un comportamiento de comunicación muy específico.

    Solución: RAG para la información, y el comportamiento en el prompt de sistema. Si tras iterar el prompt el formato sigue siendo inconsistente y tienes miles de ejemplos buenos, entonces fine-tuning para la parte de comportamiento. En ese orden, no al revés.

    Caso 4: Clasificador de texto o extractor de entidades

    Necesitas clasificar tickets, extraer entidades de contratos, tareas de NLP muy específicas.

    Aquí es donde fine-tuning sigue defendiéndose mejor: para clasificación y extracción, un modelo pequeño ajustado a tu dominio suele salir más barato en inferencia que uno grande con prompts largos, y más consistente. Es el caso con mejor retorno de los cuatro.


    Los costes reales

    Contexto:

    • Desarrollo: prácticamente nulo. Es construir un prompt.
    • Inferencia: pagas los tokens de entrada en cada petición, así que el coste escala con el volumen, no con el tamaño del proyecto.
    • Mantenimiento: cambiar el texto.
    • Problema principal: el material repetido se paga una y otra vez — salvo que uses caché.

    RAG:

    • Configurar el pipeline de chunking, embedding y retrieval: días de desarrollo, no semanas.
    • Inferencia: coste del modelo base más las llamadas a la vectorDB, que son baratas.
    • Mantenimiento: actualizar la base vectorial cuando cambian los documentos, y es automatizable.
    • Problema principal: la calidad del retrieval. Si buscas mal, el modelo responde mal aunque los documentos sean perfectos.

    Fine-tuning:

    • Construir el dataset de entrenamiento: semanas. Es el cuello de botella real, no la GPU.
    • Entrenamiento: la estructura del coste es por tokens procesados o por horas de GPU según proveedor. Los precios concretos cambian cada pocos meses, así que consúltalos en el proveedor el día que decidas: cualquier cifra que leas en un post de hace medio año está mal.
    • Inferencia: más cara que el mismo modelo sin ajustar. En un proveedor gestionado el modelo fine-tuneado tiene su propia tarifa por token, más alta que la del base, sin que tú hostees nada; con modelos abiertos lo pagas en infraestructura. Por los dos caminos, más que el punto de partida.
    • Problema principal: te ancla a una versión. Cada modelo nuevo obliga a repetir el proceso entero mientras el resto del mundo avanza gratis.

    Ese último punto es el que más se subestima. El coste del fine-tuning no es el entrenamiento: es quedarte fuera de la siguiente generación de modelos.


    La caché de contexto: la palanca que casi nadie activa

    Si tienes un sistema que atiende mil consultas al día y en cada una envía las mismas instrucciones, el mismo manual y los mismos ejemplos, estás pagando por procesar ese material mil veces.

    La caché de contexto guarda el trabajo ya hecho sobre la parte que se repite y lo reutiliza. La parte repetida sale bastante más barata a partir de la segunda vez.

    La condición es exigente y es donde falla todo el mundo: lo repetido tiene que ir siempre al principio y sin variar ni un carácter.

    ✅ CACHEABLE
       [instrucciones fijas][manual fijo][ejemplos fijos][consulta variable]
    
    ❌ NO CACHEABLE
       [fecha y hora][instrucciones fijas][manual fijo][consulta variable]
        ↑ un timestamp al principio invalida el bloque entero
    

    Meter la fecha, un identificador de sesión o el nombre del usuario al comienzo del bloque fijo lo invalida todo. Y nadie te avisa: simplemente sigues pagando el precio completo.

    Hay además un suelo del que casi no se habla: por debajo de unos cientos o unos miles de tokens —el umbral cambia por proveedor y por modelo— la caché no se activa. Tampoco salta un error. Se procesa a precio completo y a otra cosa.

    Las condiciones exactas —qué se cachea, cuánto dura, cuánto descuenta— son distintas en cada proveedor y cambian cada pocos meses. Contrasta antes de contar con el ahorro.

    Si tu factura de IA empieza a crecer, esta es la primera pregunta que hay que hacer en la reunión, antes de discutir de modelos: ¿estamos aprovechando la caché de contexto?


    Qué pasa cuando las combinas

    La combinación que se ve en sistemas de producción serios sigue un patrón concreto. Y es parte de una arquitectura más amplia — si quieres entender cómo el LLM encaja con el resto del sistema, el post sobre qué es un agent harness lo explica con detalle.

    • Contexto para las instrucciones y el comportamiento, con la parte fija cacheada
    • RAG para la información factual que cambia
    • Fine-tuning solo si el comportamiento sigue siendo inconsistente después de agotar las dos anteriores

    Un ejemplo real: un asistente jurídico. El prompt de sistema fija el formato del análisis y la terminología, y va cacheado porque no cambia. RAG conectado a la base de legislación actualizada y a los expedientes del despacho. Fine-tuning, en este caso, ni aparece: el modelo base ya redacta en registro jurídico si se le pide bien.

    Esa es la arquitectura que más veo en productos de IA que funcionan. No es glamorosa. En el curso Construye con IA: de la idea al producto con Claude Code trabajo estas decisiones desde la fase de especificación — antes de escribir una línea de código — para que no llegues a la semana seis arrepintiéndote de la técnica que elegiste.


    El árbol de decisión que uso en consultoría

    Cuando alguien me pregunta qué usar, estas son las preguntas en orden:

    1. ¿Cabe el material en el contexto y cambia poco?

    • Sí → contexto, y cachea la parte fija. Ya está, no montes nada más.
    • No cabe, o cambia a diario → siguiente pregunta.

    2. ¿El problema es que el modelo no tiene la información, o que no se comporta como quieres?

    • No tiene la información → RAG
    • No se comporta bien → siguiente pregunta

    3. ¿Has iterado el prompt de sistema en serio, con ejemplos few-shot?

    • No → hazlo antes. Es gratis y resuelve más de lo que la gente espera.
    • Sí, y sigue inconsistente → siguiente pregunta

    4. ¿Tienes miles de ejemplos de calidad y un problema bien definido que no va a cambiar?

    • No → sigue con prompting. Fine-tuning sin dataset bueno es dinero quemado.
    • Sí → fine-tuning es defendible, siempre que el proveedor te deje.

    La mayoría de los casos que veo en producción se resuelven en los dos primeros escalones. Fine-tuning es potente, pero exige un problema muy bien definido, datos de calidad y tiempo para construirlos.


    Tabla comparativa detallada: RAG vs fine-tuning

    RAG Fine-tuning
    Problema que resuelve El modelo no tiene la información El modelo no se comporta como quieres
    Modifica el modelo No
    Cuándo usar Datos dinámicos, documentos, bases de conocimiento Estilo, formato, clasificación de dominio
    Coste de inicio Medio (pipeline) Alto (dataset + entrenamiento)
    Mantenimiento Fácil (actualizar vectorDB) Costoso (reentrenar cuando cambia el problema)
    Tiempo hasta producción Días Semanas
    Te ancla a un modelo No
    Combinar con el otro
    Disponible en OpenAI (julio 2026) Cerrado a usuarios nuevos; los activos, hasta el 6 de enero de 2027

    Guarda esta tabla. Te va a ahorrar más de una conversación.

    Y si te ha servido ordenar estas tres piezas, hay un mapa con las otras ciento diecinueve: conceptos de IA colocados por zonas, con las conexiones dibujadas, en una hoja para imprimir. Está aquí, gratis — funciona especialmente bien para pasársela a la gente de producto que aprueba estos presupuestos.


    Preguntas frecuentes

    ¿Cuál es la diferencia entre RAG y fine-tuning?

    RAG no toca el modelo: busca fragmentos relevantes en tus documentos y los inyecta en el prompt antes de generar, así que resuelve problemas de conocimiento y se actualiza cambiando un documento. Fine-tuning sí modifica el modelo, ajustando sus pesos con tus ejemplos, y resuelve problemas de comportamiento — tono, formato, criterio de clasificación. La confusión más cara del sector es usar fine-tuning para meter información: no acabas con un modelo que sepa tus datos, sino con uno que inventa datos con tu estilo.

    ¿Cuándo usar fine-tuning de verdad?

    Cuando se cumplen las cuatro condiciones a la vez: el problema es de comportamiento y no de información, ya has iterado el prompt de sistema con ejemplos few-shot y sigue inconsistente, tienes miles de ejemplos de calidad, y la tarea está lo bastante definida como para que no cambie en unos meses. El caso con mejor retorno es la clasificación o extracción de dominio, donde un modelo pequeño ajustado sale más barato en inferencia que uno grande con prompts largos.

    ¿Es verdad que OpenAI está cerrando el fine-tuning?

    Sí, la plataforma self-serve. Según su página de deprecations, desde el 7 de mayo de 2026 las organizaciones que no habían hecho fine-tuning antes ya no pueden crear jobs, y el 6 de enero de 2027 dejarán de poder hacerlo también los clientes activos. La inferencia sobre modelos ya ajustados sigue hasta que se deprecie el modelo base. OpenAI redirige en su lugar a su ciclo de evals y prompt engineering —contexto relevante, instrucciones claras, ejemplos few-shot— y a empezar por su modelo más reciente. El fine-tuning sigue disponible fuera de OpenAI, con modelos abiertos y otros proveedores.

    ¿Qué es la caché de contexto y cuánto ahorra?

    Es guardar el trabajo de procesamiento ya hecho sobre la parte del prompt que se repite en todas las peticiones —instrucciones, manuales, ejemplos— para no volver a pagarla entera cada vez. El requisito es que ese bloque vaya al principio y sea idéntico carácter por carácter: meter un timestamp o un ID de sesión delante lo invalida y sigues pagando el precio completo sin que nadie te avise. Cuánto descuenta y cuánto dura la caché varía por proveedor y cambia cada pocos meses, así que conviene comprobarlo en la documentación antes de meter el ahorro en un presupuesto.

    ¿Qué sale más barato, RAG o fine-tuning?

    RAG, casi siempre, y por dos motivos distintos: el arranque son días de pipeline frente a semanas de dataset, y no te ancla a una versión del modelo, así que no repites el gasto con cada generación nueva. Fine-tuning solo gana en coste cuando la tarea es de clasificación o extracción y puedes servir un modelo pequeño en lugar de uno grande con prompts largos. Antes de comparar las dos, mira si tu prompt fijo es cacheable: en sistemas con mucho volumen esa palanca sola suele mover más la factura que la elección de técnica.

    ¿Cuánto cuesta hacer fine-tuning?

    Tiene tres partidas y solo una no caduca. El dataset son semanas de trabajo humano y es el cuello de botella real. El entrenamiento se factura por tokens procesados o por horas de GPU según el proveedor. La inferencia va a una tarifa más alta que la del modelo base. Cualquier cifra concreta que leas en un post de hace medio año está mal, así que consulta el pricing del proveedor el día que decidas — pero cuenta con que la partida del dataset no la abarata nadie.

    ¿Cambia algo con los modelos de razonamiento?

    Bastante, y en la dirección de necesitar menos fine-tuning. Siguen instrucciones complejas mucho mejor que las generaciones anteriores, así que se comen buena parte de los casos de comportamiento que antes justificaban entrenar un modelo. Lo que no cambia es el acceso a la información: un modelo de razonamiento no conoce tus documentos privados ni lo que pasó esta semana. Para eso RAG sigue siendo la única vía.

    ¿Puedo usar RAG con cualquier LLM?

    Sí. RAG es agnóstico al modelo: funciona con cualquiera que acepte un prompt de texto, sea de OpenAI, Anthropic, Google o un modelo abierto que corras tú. Lo único que necesita es una ventana de contexto suficiente para recibir los fragmentos recuperados junto con la pregunta, y los modelos actuales van sobrados para eso en la mayoría de casos.

    ¿La ventana de contexto es lo mismo que memoria?

    No, y se confunden constantemente. La ventana de contexto es cuánto cabe en una sola petición. Memoria es que algo sobreviva a cerrar la sesión, y eso no está dentro del modelo: es un almacén externo que alguien montó, más una decisión sobre qué merece la pena guardar y recuperar. Una ventana enorme no te da memoria, y por eso un asistente puede seguirte el hilo durante media hora y no conocerte de nada al día siguiente.

    ¿RAG siempre alucina menos que el modelo base?

    Reduce las alucinaciones sobre hechos concretos de tus documentos, porque el modelo tiene el texto real delante. Pero no elimina las que vienen de razonamientos o inferencias mal hechas: si el modelo falla razonando, RAG no le ayuda, porque eso es un problema de capacidad y no de conocimiento. Recuperar bien y responder bien son dos cosas distintas.

    ¿Qué vectorDB conviene para empezar?

    pgvector si ya usas PostgreSQL, porque no añade infraestructura nueva, o un servicio gestionado como Pinecone si prefieres no operar nada. Weaviate y Chroma son buenas opciones open-source para auto-hosting. Evita sobreingenierizar esto al principio: pgvector resuelve la mayoría de los casos sin complejidad extra, y su documentación oficial cubre la instalación en unos minutos.


    Si quieres ver estos patrones aplicados en proyectos reales con código y arquitectura completa, en Dominicode Labs trabajamos este tipo de decisiones técnicas con la comunidad. Proyectos reales, problemas reales, decisiones que puedes aplicar esta semana.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Implementación de RAG en el Frontend con Angular para Chat con PDFs

    Implementación de RAG en el Frontend con Angular para Chat con PDFs

    Implementación de RAG (Retrieval-Augmented Generation) en el Frontend con Angular

    Tiempo estimado de lectura: 5 min

    • Ideas clave:
    • RAG combina recuperación semántica (vector DB) y generación (LLM); en el frontend debe evitar exponer claves y delegar embeddings/search/generation a una BFF/Edge Function.
    • Flujo: preprocesado offline → indexación (vector DB) → consulta runtime a Edge Function → streaming del LLM al frontend.
    • Angular actúa como orquestador UI: JWT al BFF, consumo de ReadableStream, y Signals para estado y streaming.
    • Seguridad multi-tenant: RLS/metadata.filter y nunca incluir keys en el bundle cliente.

    Implementación de RAG (Retrieval-Augmented Generation) en el Frontend con Angular: aquí verás un diseño pragmático y seguro para que tus usuarios “chateen” con PDFs y bases de conocimiento sin exponer claves privadas, y con una experiencia de streaming y baja latencia.

    Resumen rápido (lectores con prisa)

    RAG une un índice vectorial para recuperar contexto relevante con un LLM que genera respuestas. Úsalo cuando necesites respuestas ancladas en documentación privada. Importa porque permite precisión y control de costos. Funciona: preprocesado e indexado server-side, BFF/Edge Function para retrieval + prompt building + streaming, y frontend que consume el stream y mantiene estado con Signals.

    Implementación de RAG (Retrieval-Augmented Generation) en el Frontend con Angular — resumen arquitectónico

    RAG combina recuperación semántica (vector DB) y generación (LLM). En el frontend esto se traduce en tres responsabilidades claras:

    • Orquestar la UI y el estado reactivo.
    • Llamar a una capa segura (BFF / Edge Function) que haga embeddings, búsqueda y generación.
    • Mostrar la respuesta en streaming con Signals para una UX fluida.

    Nunca pongas claves de OpenAI, Pinecone o Supabase en el bundle. Usa BFF/Edge Functions. Patrones y herramientas: Supabase (pgvector + Edge Functions + RLS), Pinecone, OpenAI Embeddings, SSE/Streams MDN, Angular Signals.

    Paso a paso: flujo de datos y responsabilidades

    Preprocesado (server-side, offline)

    • Extrae texto del PDF (pdfminer, tika, or pdf-lib).
    • Segmenta en chunks (200–1000 tokens según modelo).
    • Calcula embeddings y guarda vectores con metadata: { documentId, chunkId, text, userId }.
    • Upsert en la vector DB (Pinecone o Supabase pgvector).

    Consulta desde Angular (runtime)

    • Usuario pregunta en la UI.
    • Angular envía la petición a la Edge Function (BFF) con el JWT del usuario.
    • La Edge Function:
      • a) crea embedding de la consulta,
      • b) hace búsqueda semántica filtrada por metadata (userId) en la vector DB,
      • c) construye prompt con los top-K chunks,
      • d) llama al LLM (streaming) y reenvía el stream al cliente.

    Presentación (cliente)

    • Angular consume el stream y muestra la respuesta en tiempo real.
    • Signals mantiene conversación, estados y métricas.

    Código práctico: servicio Angular para consumir stream RAG

    Este es el patrón cliente: Angular delega todo a una URL segura y procesa un ReadableStream en Signals.

    <!-- rag-client.service.ts -->
    import { Injectable, signal } from '@angular/core';
    
    export interface ChatMessage { role: 'user'|'assistant'; content: string; }
    
    @Injectable({ providedIn: 'root' })
    export class RagClientService {
      public convo = signal<ChatMessage[]>([]);
      public loading = signal(false);
    
      async ask(question: string, docId: string, token: string) {
        this.convo.update(c => [...c, { role: 'user', content: question }]);
        this.convo.update(c => [...c, { role: 'assistant', content: '' }]);
        this.loading.set(true);
    
        const res = await fetch(`/api/rag?doc=${docId}`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
          body: JSON.stringify({ question })
        });
    
        if (!res.body) { this.loading.set(false); throw new Error('No stream'); }
        const reader = res.body.getReader();
        const dec = new TextDecoder();
        let done = false;
    
        while (!done) {
          const { value, done: rDone } = await reader.read();
          done = rDone;
          if (value) {
            const chunk = dec.decode(value, { stream: true });
            this.append(chunk);
          }
        }
        this.loading.set(false);
      }
    
      private append(text: string) {
        this.convo.update(c => {
          const last = [...c];
          const idx = last.length - 1;
          last[idx] = { ...last[idx], content: last[idx].content + text };
          return last;
        });
      }
    }
    

    Seguridad y multi-tenant: cómo proteger datos y consultas

    • Edge Functions (Supabase / Vercel) firman y validan JWT. Angular solo envía el token del usuario.
    • En Supabase usa Row Level Security (RLS) para que la consulta vectorial devuelva solo vectores del userId (https://supabase.com/docs).
    • En Pinecone añade metadata.filter (userId) en las queries y realiza autorización en tu backend.
    • Nunca aceptes keys “hosted” en el cliente; si necesitas BYOK (Bring Your Own Key), que el usuario lo suministre y limite permisos.

    Buenas prácticas de diseño

    • Chunking y contexto: corta por sentencias y 200–500 tokens. Guarda overlap para preservar contexto.
    • Top-K + score threshold: recupera 3–8 chunks y descarta con score bajo para evitar ruido.
    • Fallback y control de costos: si la DB no devuelve contexto útil, responde con “No encontré información” en lugar de llamar al LLM.
    • Telemetría: registra latencia de retrieval vs generation, porcentaje de respuestas sin contexto.
    • UX: muestra progreso de retrieval y luego streaming del LLM; evita spinners largos.

    Resumen y criterio

    Implementación de RAG en el frontend con Angular no es “cliente habla con Pinecone”. Es dividir responsabilidades: preprocesado e indexación en backend, retrieval y prompt-building en BFF/Edge Function, y presentación + streaming en Angular. Esa separación protege claves, permite RLS/tenant isolation y ofrece una UX moderna con Signals y ReadableStreams.

    Si quieres ejemplos de Edge Functions (Supabase) o plantillas para indexado de PDF, lo siguiente es lo lógico: un script server-side que extrae texto, chunkea, crea embeddings (OpenAI) y hace upsert a la vector DB. Cuando tengas ese bloque, el frontend es trivial: JWT + fetch streaming + Signals. Implementa eso y tendrás un “chat” con tus PDFs que realmente sirve en producción.

    Para recursos citados en el artículo: Supabase (pgvector + Edge Functions + RLS), Pinecone, OpenAI Embeddings, SSE/Streams MDN, Angular Signals.

    Continúa explorando plantillas y laboratorios técnicos en Dominicode Labs para ver implementaciones prácticas y scripts de indexado que complementan este flujo.

    FAQ

    ¿Qué es RAG y cuándo debería usarlo?

    RAG (Retrieval-Augmented Generation) combina una base de conocimiento indexada en vectores para recuperar contexto relevante y un LLM para generar respuestas. Úsalo cuando necesites respuestas ancladas en documentación privada, control de factualidad y reducción de costos frente a enviar todo el prompt al LLM.

    ¿Dónde deben vivír las claves de OpenAI / Pinecone?

    Las claves deben residir en el backend (BFF o Edge Functions). Nunca las incluyas en el bundle cliente. El frontend solo envía el JWT del usuario y la Edge Function realiza llamadas a los servicios con las claves seguras.

    ¿Cómo evito fugas de datos entre tenants?

    Usa Row Level Security (RLS) en Supabase o filtros de metadata (por ejemplo userId) en Pinecone y valida JWT en la Edge Function para asegurar que las queries devuelvan solo vectores autorizados.

    ¿Qué tamaño de chunk es recomendado?

    Segmenta entre 200–1000 tokens según el modelo; una recomendación práctica es 200–500 tokens con overlap para preservar contexto y mantener relevancia.

    ¿Qué hacer si la vector DB no devuelve contexto útil?

    Implementa un fallback: responde con “No encontré información” en lugar de llamar al LLM para evitar costos y respuestas potencialmente incorrectas sin contexto.

    ¿Cómo mostrar streaming en Angular?

    Consume el ReadableStream desde fetch, lee chunks con un TextDecoder y actualiza el estado reactivo (Signals) a medida que llegan datos para una experiencia en tiempo real.

    ¿Qué telemetría es esencial?

    Registra latencia de retrieval vs generation, porcentaje de respuestas sin contexto y métricas de coste por llamada al LLM para evaluar trade-offs y optimizaciones.

  • Mejorando la Recuperación de Información con RAG Avanzado

    Mejorando la Recuperación de Información con RAG Avanzado

    RAG Avanzado: Híbrido, Jerárquico y Multi-Vector

    RAG Avanzado: Híbrido, Jerárquico y Multi-Vector. Si eso suena a demasiada ingeniería comparado con “chunk + embeddings”, es porque lo es —y la diferencia entre demo y producto está justo ahí. En producción no vale con que el LLM “suene bien”; necesitas precisión, contexto y control de coste. Este artículo explica qué técnicas añadir, por qué y cuándo.

    Tiempo estimado de lectura: 4 min

    • Ideas clave:
    • La recuperación es el 80% de una buena respuesta: combina sparse (BM25) y dense (embeddings).
    • Arquitectura jerárquica (child + parent) equilibra precisión de fragmento y contexto coherente.
    • Re-ranking con cross-encoders y compresión de contexto reducen hallucinations y coste token.

    Introducción

    La calidad de las respuestas generadas por un sistema RAG depende mayoritariamente de la recuperación. Los vectores aportan semántica; los métodos sparse (BM25) aportan exactitud. En producción necesitas precisión, contexto y control de coste; esto implica añadir capas: híbrido, jerarquía, re-ranking, rewriting y compresión de contexto.

    Resumen rápido (lectores con prisa)

    RAG avanzado combina búsquedas sparse (BM25) y dense (embeddings) para precisión y semántica. Usa indexing jerárquico child→parent para fragmentos precisos con contexto. Re-rank con cross-encoders para reducir ruido. Reescribe y descompone queries; comprime contexto antes del LLM.

    RAG Avanzado: implementación práctica

    1) Hybrid retrieval: BM25 + embeddings

    Problema: buscas “ERR-9921” y el vector devuelve “error de sistema” porque semánticamente es parecido. Solución: híbrido.

    • Sparse: BM25/Elasticsearch para coincidencias literales.
    • Dense: embeddings (OpenAI, Cohere, etc.) para intención.
    • Fusión: Reciprocal Rank Fusion (RRF) o combinación ponderada. Pinecone hybrid search

    Patrón práctico:

    • Ejecuta BM25 y vector search en paralelo.
    • Normaliza scores.
    • Fusiona con RRF.
    • Si BM25 tiene match exacto para tokens sensibles (IDs, SKUs), priorízalo.

    2) Multi-vector / Parent-Child indexación

    Dilema clásico: chunks pequeños = mejor match; chunks grandes = mejor contexto. La arquitectura jerárquica arregla ambos.

    • Indexa embeddings a nivel child (p. ej. 200 tokens).
    • Mantén parent docs grandes (p. ej. 2000 tokens) con metadata.
    • Al recuperar un child relevante, sube el parent completo al contexto.

    Implementación: LangChain ParentDocumentRetriever — LangChain ParentDocumentRetriever

    Beneficio: precisión de fragmento + contexto coherente para razonamiento.

    3) Re-ranking con cross-encoders

    Bi-encoders: rápidos, aproximados. Cross-encoders: lentos, precisos.

    Flujo:

    1. Fast retrieval → top N (50).
    2. Cross-encoder rerank en top N.
    3. Selecciona top K final para el LLM.

    Herramientas: sentence-transformers, Cohere Rerank. Costo: aumenta latencia; recompensa: reduce ruido que provoca hallucinations.

    4) Query rewriting y decomposition

    Muchos fallos vienen por queries ambiguas. No todos los problemas se arreglan en el índice.

    • Query rewrite: un LLM rápido reescribe la consulta con contexto de la sesión.
    • Multi-query: genera 3–5 variantes de la pregunta y busca por cada una.
    • Decomposition: divide preguntas complejas en sub-queries paralelas.

    Técnica HyDE (Hypothetical Document Embeddings) es útil: genera la “respuesta hipotética”, embeddea eso y busca. Idea en práctica: mejora recall sin cambiar el índice.

    5) Context compression antes del LLM

    Enviar 10 documentos de 8k tokens es suicida. Comprime:

    • Filtrado selectivo: extrae párrafos con más evidencia.
    • Summarization condensado (con cuidado: pierde citas).
    • Algoritmos de compresión semántica como LLMLingua

    Objetivo: maximizar densidad informativa dentro de la ventana del LLM.

    Criterio para decidir qué añadir (roadmap pragmático)

    No implementes todo a la vez. Sigue este orden iterativo:

    1. Naive RAG. Mide recall@5 y tasa de hallucination.
    2. Si fallan exact matches → añade Hybrid (BM25).
    3. Si falta contexto coherente → añade Parent-Child multi-vector.
    4. Si llega ruido que confunde al LLM → añade Re-ranking.
    5. Si tokens exceden la ventana → añade Context Compression.

    Mide antes y después. No hay excusas.

    Operacional: latencia, coste y caching

    • Re-ranking y cross-encoders aumentan latencia 10x en la etapa de ranking. Mitiga con caching de top-N por query fingerprint.
    • Hybrid search añade coste infra (Elasticsearch + vector DB). Mide Cost/Accuracy.
    • Batch embeddings nocturnos para contenido estático. Mantén refresh policies.
    • Telemetría: trace request → retrieval steps → rerank → LLM call. LangFuse y LangSmith ayudan a visualizar traces (LangFuse, LangSmith).

    Integración con agentes y workflows (n8n)

    Pipeline ejemplo en n8n:

    1. Node: Query Rewrite (LLM pequeño)
    2. Node: Hybrid Search (ES + Pinecone/Qdrant)
    3. Node: Rerank (Cross-Encoder)
    4. Node: Parent Expander + Context Compressor
    5. Node: LLM final (generation)
    6. Node: Post-check (schema validation / guardrails)

    Versiona prompts, registra fingerprints y alertas en drift.

    Referencias operativas

    Implementar RAG avanzado es menos glamour y más disciplina: medir, añadir la capa correcta y repetir. Hazlo así y tu sistema dejará de improvisar respuestas y empezará a dar respuestas que puedes explicarle al CTO.

    Dominicode Labs

    Si trabajas con pipelines de agentes, workflows o IA aplicada, considera explorar integraciones y experimentos prácticos en Dominicode Labs. Es una continuación lógica para prototipos operacionales y pruebas de telemetría.

    FAQ

     

    Respuesta: ¿Por qué combinar BM25 con embeddings?

    BM25 proporciona coincidencias literales útiles para IDs, SKUs y frases exactas; los embeddings capturan intención y sinónimos. El híbrido reduce falsos positivos semánticos y mejora precisión en búsquedas sensibles.

     

    Respuesta: ¿Qué ventaja tiene la indexación parent-child?

    Permite mantener chunks pequeños para alta precisión en matching mientras se conserva contexto amplio subiendo el documento parent al LLM cuando un child es relevante.

     

    Respuesta: ¿Cuándo usar cross-encoders para re-ranking?

    Úsalos cuando el top-N recuperado contenga ruido que provoca hallucinations o respuestas incorrectas. Son adecuados para reducir falsos positivos aunque aumenten latencia y coste.

     

    Respuesta: ¿Qué es HyDE y por qué usarlo?

    HyDE (Hypothetical Document Embeddings) genera una “respuesta hipotética” desde un LLM, la embeddea y busca con esa representación. Mejora recall sin tocar el índice.

     

    Respuesta: ¿Cómo mitigar la latencia al re-rankear?

    Cachea top-N por huella de consulta, ejecuta re-ranking asíncrono donde sea posible y usa cross-encoders solo en escenarios críticos o por lotes nocturnos para contenido estático.

     

    Respuesta: ¿Qué técnicas de compresión de contexto son seguras?

    Filtrado selectivo de párrafos con evidencia, resúmenes condensados con control de citas y algoritmos semánticos como LLMLingua. Ten cuidado: la compresión puede perder citas y detalles verificables.