Inicio / Artículos

RAG agente: Permitir que el LLM decida cuándo y dónde recuperar la información.

Por qué el RAG estático falla en preguntas de múltiples pasos y sensibles al tiempo, y cómo un agente LangGraph enruta entre Qdrant y la búsqueda en la web, con límites y advertencias.

Editor: REACTAPP.TOP9 min de lectura2167 palabras

Fuentes: Your RAG Pipeline Is Lying to You. The Problem Is Not the Embeddings — Agentic RAG

“RAG es, en su mayor parte, un problema ya resuelto”. Esa frase se menciona en las revisiones de equipo, y la respuesta honesta es: depende de lo que entienda uno por recuperación de información. Quienes han utilizado pipelines RAG en entornos de producción durante algún tiempo, ya sea para resúmenes de registros médicos, flujos de trabajo de lenguaje natural a SQL o sistemas de investigación con múltiples agentes, suelen toparse con el mismo límite.

Ese límite no son los embeddings. Es la suposición de que la recuperación de información ocurre una sola vez, en un paso fijo, a partir de una sola fuente.

El RAG estático (incorporar los documentos, obtener los k mejores resultados y entregárselos al LLM) maneja bien las preguntas simples y autónomas basadas en conocimientos internos estables. Sin embargo, cuando una pregunta requiere múltiples pasos, es sensible al tiempo o necesita tanto documentos internos como datos en tiempo real, el flujo estático comienza a generar alucinaciones fiables. Un sistema así podría recomendar un procedimiento clínico basado en guías internas obsoletas, aunque una búsqueda rápida de medio minuto en la web habría revelado los cambios.

Mejores embeddings no solucionarán ese problema. Lo que realmente ayuda es permitir que el LLM decida cuándo y dónde obtener la información.

Los problemas de “obtener una vez, responder siempre”

Imagínese a un bibliotecario que solo puede responder yendo a un único estante, siempre el mismo, antes incluso de que usted termine de formular su pregunta.

Si preguntas “¿Cuáles son los efectos secundarios de la metformina?”, la respuesta está disponible al instante. Ahora pregunta algo más complicado: ¿qué interacciones medicamentosas ha señalado recientemente la FDA para la metformina junto con los nuevos fármacos GLP-1, y el protocolo actual para pacientes las abarca? Para ello se necesitan documentos internos y datos regulatorios actualizados, además de un análisis de la brecha entre ellos. El RAG estático obtiene la información una sola vez de una fuente, y el LLM simplemente resume lo que recibe.

Esto no es hipotético. En una evaluación interna de un proceso de atención médica [1], el RAG estático alcanzó un 89% de relevancia en las respuestas a preguntas simples, pero bajó al 61% cuando era necesario hacer referencias cruzadas entre documentos clínicos internos y pautas de tratamiento externas. Las evaluaciones básicas no lo detectaron porque las respuestas parecían correctas; en realidad estaban desactualizadas.

La solución es fácil de enunciar pero complicada de implementar: la recuperación de información se convierte en una decisión en lugar de un paso concreto.

Qué significa el RAG agente

En el RAG agente, el LLM no realiza la recuperación de información en un momento predeterminado. Lo trata como una herramienta, similar a una calculadora o a un ejecutor de código, y la utiliza cuando considera que la evidencia es insuficiente. De esto surgen tres comportamientos que un flujo estático no puede generar.

  • Recuperación condicional. Si el contexto ya responde a la pregunta, el agente omite por completo la recuperación de información.
  • Recuperación multi-paso. El agente obtiene algo, lo lee, se da cuenta de que falta algún dato y vuelve a buscarlo, adaptando cada intento según lo encontrado en el anterior. Así es como las personas realizan investigaciones.
  • Enrutamiento de fuentes. El agente elige entre un almacén de vectores, una búsqueda en la web o una base de datos estructurada según la pregunta. “¿Qué dice nuestra política?” se dirige al almacén de vectores, mientras que “¿Qué anunció la FDA la semana pasada?” se dirige a la web.
  • Ninguna de estas ideas es nueva. El artículo ReAct de Yao y colaboradores (2022) describió el patrón de intercalar pasos de razonamiento con llamadas a herramientas, y el RAG agente es ReAct aplicado a la recuperación de información [2].

    El cambio reciente está en las herramientas. LangGraph finalmente hace prácticos los bucles con estado y ramificaciones. Tavily ofrece una API de búsqueda en la web diseñada para agentes que devuelve contenido listo para ser procesado por modelos de lenguaje grande, y afirma manejar más de 100 millones de solicitudes al mes con una latencia de búsqueda p50 de 180 ms [3]. Los detalles se encuentran en la publicación Tavily 101.

    Tres componentes dinámicos

    Imagínese a un detective en lugar de a un bibliotecario. Un bibliotecario busca lo que se le pide. Un detective lee lo que está disponible, determina qué falta, va a buscarlo, vuelve a leer y continúa hasta que puede defender una conclusión. El ciclo consta de tres componentes.

    1. El router responde sobre qué tipo de búsqueda necesita la consulta. El LLM analiza la pregunta y decide si el contexto existente es suficiente, si se necesitan documentos, si se necesita la web, o ambos.
    2. Las herramientas de búsqueda son las fuentes de evidencia. Dos herramientas son el mínimo necesario: una que busca en un almacén vectorial de documentos internos y otra que busca en la web información actualizada. Cada una es una función ordinaria que el LLM invoca con una cadena de consulta.
    3. La verificación de fundamentación pregunta si la evidencia es suficiente. Antes de escribir la respuesta final, el agente evalúa si lo que ha recuperado es suficiente y relevante; de no ser así, vuelve a buscar. Los pipelines estáticos no pueden expresar este bucle.

    Una implementación funcional

    Aquí hay un agente híbrido en LangGraph que alterna entre un almacén de vectores Qdrant y la búsqueda web Tavily. Elige la herramienta adecuada, puede utilizar ambas en secuencia y volverá a buscar cuando el primer resultado no sea suficiente para fundamentar la respuesta. La parte relacionada con Qdrant sigue la guía de integración de LangChain.

    from typing import Annotated, Literal
    from langgraph.graph import StateGraph, START, END
    from langgraph.prebuilt import ToolNode
    from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
    from langchain_core.tools import tool
    from langchain_openai import ChatOpenAI
    from langchain_qdrant import QdrantVectorStore
    from langchain_openai import OpenAIEmbeddings
    from tavily import TavilyClient
    from qdrant_client import QdrantClient
    import operator
    from typing import TypedDict, Sequence
    
    # --- State definition ---
    class AgentState(TypedDict):
        messages: Annotated[Sequence, operator.add]
        retrieval_count: int  # guard against infinite loops
    
    # --- Tool 1: Vector store retrieval (internal docs) ---
    qdrant_client = QdrantClient(url="http://localhost:6333")
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
    vector_store = QdrantVectorStore(
        client=qdrant_client,
        collection_name="internal_docs",
        embedding=embeddings,
    )
    @tool
    def retrieve_from_docs(query: str) -> str:
        """
        Search internal document knowledge base.
        Use for questions about internal policies, protocols, or proprietary knowledge.
        """
        results = vector_store.similarity_search(query, k=4)
        if not results:
            return "No relevant internal documents found for this query."
        return "\n\n---\n\n".join([
            f"[Source: {doc.metadata.get('source', 'internal')}]\n{doc.page_content}"
            for doc in results
        ])
    
    # --- Tool 2: Web retrieval (live data) ---
    tavily = TavilyClient(api_key="YOUR_TAVILY_API_KEY")
    @tool
    def retrieve_from_web(query: str) -> str:
        """
        Search the live web for current information.
        Use for recent news, regulatory updates, or anything that may have changed
        since the internal knowledge base was last updated.
        """
        results = tavily.search(
            query=query,
            max_results=5,
            search_depth="advanced",  # deep retrieval for better accuracy
        )
        if not results.get("results"):
            return "No relevant web results found."
        return "\n\n---\n\n".join([
            f"[Source: {r['url']}]\n{r['content']}"
            for r in results["results"]
        ])
    
    # --- Agent node ---
    tools = [retrieve_from_docs, retrieve_from_web]
    tool_node = ToolNode(tools)
    llm = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
    SYSTEM_PROMPT = """You are a research assistant with access to two retrieval tools:
    - retrieve_from_docs: internal document knowledge base (policies, protocols, proprietary data)
    - retrieve_from_web: live web search (recent news, regulatory updates, real-time information)
    For any question:
    1. Decide whether you need to retrieve information or can answer from context.
    2. If retrieval is needed, choose the right source - or call both if the question spans internal and live knowledge.
    3. After retrieving, check if the evidence is sufficient. If not, retrieve again with a refined query.
    4. Always cite your sources in the final answer.
    Do not hallucinate. If you cannot find sufficient evidence, say so explicitly."""
    def call_agent(state: AgentState) -> AgentState:
        messages = [SystemMessage(content=SYSTEM_PROMPT)] + list(state["messages"])
        response = llm.invoke(messages)
        return {
            "messages": [response],
            "retrieval_count": state.get("retrieval_count", 0)
        }
    def update_retrieval_count(state: AgentState) -> AgentState:
        return {
            "messages": [],
            "retrieval_count": state.get("retrieval_count", 0) + 1
        }
    
    # --- Routing logic ---
    MAX_RETRIEVALS = 3  # prevent infinite loops
    def should_continue(state: AgentState) -> Literal["tools", "end"]:
        last_message = state["messages"][-1]
        # If the LLM called a tool and we haven't hit the loop guard, continue
        if hasattr(last_message, "tool_calls") and last_message.tool_calls:
            if state.get("retrieval_count", 0) < MAX_RETRIEVALS:
                return "tools"
        return "end"
    
    # --- Build the graph ---
    graph_builder = StateGraph(AgentState)
    graph_builder.add_node("agent", call_agent)
    graph_builder.add_node("tools", tool_node)
    graph_builder.add_node("count_retrieval", update_retrieval_count)
    graph_builder.add_edge(START, "agent")
    graph_builder.add_conditional_edges(
        "agent",
        should_continue,
        {"tools": "tools", "end": END}
    )
    graph_builder.add_edge("tools", "count_retrieval")
    graph_builder.add_edge("count_retrieval", "agent")
    agent = graph_builder.compile()
    
    # --- Run it ---
    if __name__ == "__main__":
        result = agent.invoke({
            "messages": [
                HumanMessage(content=(
                    "What does our current patient protocol say about GLP-1 usage, "
                    "and are there any FDA safety updates issued in 2025 we should be aware of?"
                ))
            ],
            "retrieval_count": 0,
        })
        print(result["messages"][-1].content)
    

    Algunos detalles merecen atención.

    La protección MAX_RETRIEVALS en la lógica de enrutamiento no es algo secundario. Sin ella, los agentes pueden realizar hasta 15 llamadas de recuperación por una sola pregunta ambigua, desperdiciando recursos y paciencia. Normalmente, tres recuperaciones son suficientes. Si no lo son, la pregunta es demasiado amplia o las herramientas devuelven información inútil.

    El campo retrieval_count en el estado es lo que permite interrumpir el bucle. El StateGraph de LangGraph lo transporta en cada transición de nodo, por lo que persiste a lo largo de toda la conversación y no solo de un ciclo individual.

    El prompt del sistema también tiene gran importancia. Indicarle al modelo cuándo usar cada herramienta, y no solo que existen herramientas, diferencia a un agente que toma decisiones razonables de uno que utiliza retrieve_from_web para todo por pereza.

    Cuando el RAG con agentes empeora las cosas

    No reemplaza al RAG estático. Lo sustituye para ciertos tipos de problemas, pero genera efectos negativos en cuatro casos.

    • La latencia es la principal limitación. Cada iteración del bucle implica una llamada adicional al modelo más una llamada a la herramienta. Para un asistente básico de preguntas y respuestas donde la latencia p95 debe mantenerse por debajo de un segundo, un agente que pueda realizar dos o tres ciclos no es una opción viable. Un recuperador estático bien ajustado es más rápido y suficiente.
    • La base de conocimientos es estable y autónoma. Si todo lo que preguntan los usuarios puede responderse con confianza desde el almacén de vectores, estás añadiendo complejidad sin motivo. Mide primero el pipeline estático con RAGAS (artículo académico). Si la recuperación de contexto supera el 85% y la fidelidad de las respuestas supera el 90%, el envoltorio agente representa una sobrecarga innecesaria.
    • No se puede confiar en las elecciones de herramientas del modelo. Con una instrucción de sistema bien redactada, GPT-4o y Claude Sonnet logran seleccionar correctamente entre la recuperación de documentos y la de información web en aproximadamente el 88% de los casos, según las mismas mediciones internas. El 12% restante genera errores en las fuentes que son más difíciles de depurar que las simples alucinaciones, ya que la respuesta existe, pero en el lugar equivocado. Con un modelo menos potente, es necesario seleccionar las herramientas antes de utilizarlo.
    • No se puede confiar en los datos en tiempo real. La recuperación de información de la web añade una superficie de ataque activa. Tavily filtra las inyecciones de instrucciones dentro de su API, pero ningún filtro es perfecto, por lo que todo lo que haga un agente basado en contenido web debe considerarse potencialmente hostil.

    Opte por RAG agente cuando el conocimiento proviene de varias fuentes y algunas de ellas cambian. Comience con RAG estático y actualícelo cuando las puntuaciones de RAGAS revelen un fallo específico que este diseño pueda resolver.

    Por dónde empezar

    Paso más pequeño, de menos de cinco minutos: añada una herramienta Tavily a un agente LangChain existente con from langchain_community.tools.tavily_search import TavilySearchResults. Todavía no necesita LangGraph. Exponga la herramienta, escriba un prompt del sistema que indique cuándo llamarla, ejecute cinco consultas de prueba y observe qué selecciona.

    Nivel siguiente, aproximadamente una tarde: dirija el ejemplo de LangGraph anterior hacia su propio almacén de vectores, manteniendo ambas herramientas. Deje de lado el contador de recuperación al principio para ver con qué frecuencia el agente repite el proceso, y luego ándelo añadiendo. La diferencia le enseñará mucho.

    Avanzado: insertar un paso de evaluación después de cada resultado de la herramienta y antes del siguiente turno del agente. Una llamada a un LLM más pequeño y económico responde sí o no a la pregunta de si el contexto recuperado es suficiente, y solo entonces el modelo principal elabora su respuesta. Ese paso eleva el nivel de recuperación de contexto de “suficientemente bueno en pruebas” a “confiable en producción”.

    Los errores en la ruta de las herramientas, que representan el 12% adicional, son la causa de la mayor parte del tiempo dedicado a la depuración en producción. Instrumentenlos temprano para que el nodo de evaluación los detecte antes que los usuarios.

    Fuentes

    [1] Pruebas internas en una pipeline RAG para el sector sanitario utilizando RAGAS 0.1.x con un conjunto seleccionado de 200 consultas clínicas. No se trata de un estudio publicado.

    [2] Yao et al. (2022), ReAct: Synergizing Reasoning and Acting in Language Models, arXiv:2210.03629.

    [3] Blog de Tavily, Tavily 101, con las cifras de latencia y volumen indicadas en la página del producto en mayo de 2026.