Strona główna / Artykuły

Agentic RAG: Pozwalanie modelowi językowemu na decydowanie, kiedy i gdzie pobierać dane.

Dlaczego statyczny RAG zawodzi przy pytaniami wieloetapowych i wymagających szybkiej odpowiedzi, oraz jak agent LangGraph kieruje zapytania pomiędzy Qdrant a wyszukiwarką internetową, z zasadami bezpieczeństwa i ograniczeniami.

Wydawca: REACTAPP.TOP9 min czytania2167 słów

Źródła: Your RAG Pipeline Is Lying to You. The Problem Is Not the Embeddings — Agentic RAG

„RAG to w większości problem już rozwiązany”. Te słowa padają podczas ocen zespołów, a szczera odpowiedź brzmi: zależy to od tego, co rozumie się przez wyszukiwanie informacji. Każdy, kto przez jakiś czas używał pipeline’ów RAG w warunkach produkcyjnych – do tworzenia streszczeń dokumentów medycznych, przekształcania tekstu naturalnego na SQL czy w systemach badawczych z udziałem wielu agentów – zazwyczaj napotyka ten sam limit.

Tym limitem nie są embeddingi. Jest to założenie, że wyszukiwanie odbywa się tylko raz, na określonym etapie, w odniesieniu do jednego źródła.

Staticzny RAG (wgranie dokumentów, pobranie najważniejszych wyników typu top-k i przekazanie ich modelowi LLM) dobrze radzi sobie z prostymi, samodzielnymi pytaniami opartymi na stabilnej wiedzy wewnętrznej. Gdy pytanie staje się złożone, wymagające szybkiego działania lub potrzebujące zarówno dokumentów wewnętrznych, jak i aktualnych danych, taki statyczny proces zaczyna generować błędne odpowiedzi. Taki system może zalecić procedurę kliniczną na podstawie przestarzałych wytycznych wewnętrznych, mimo że krótkie poszukiwanie w internecie trwałoby zaledwie pół minuty i ujawniłoby aktualne zmiany.

Lepsze embeddingi tego nie naprawią. Pomaga natomiast umożliwienie modelowi LLM decydowania, kiedy i skąd pobrać informacje.

Problemy z zasadą „pobierz raz, odpowiadaj zawsze”

Wyobraźmy sobie bibliotekarza, który może odpowiadać tylko po pójściu na jedną, tę samą półkę, zanim skończysz swoje zdanie.

Zadaj pytanie „Jakie są skutki uboczne metforminy?”, a system dostarczy odpowiedź. Teraz zapytaj o coś trudniejszego: jakie interakcje lekowe zostały ostatnio wskazane przez FDA w związku z metforminą w połączeniu z nowszymi lekami typu GLP-1, i czy obecny protokół leczenia pacjentów je obejmuje? Do tego potrzebne są wewnętrzne dokumenty oraz aktualne dane regulacyjne, a także analiza różnic pomiędzy nimi. Statyczny RAG pobiera dane tylko raz z jednego źródła, a model językowy jedynie przybliżenie tego, co otrzymał.

To nie jest hipoteza. W wewnętrznej ocenie procesu opieki zdrowotnej [1] statyczny RAG osiągnął 89% trafności odpowiedzi na proste pytania, ale spadł do 61%, gdy pytanie wymagało porównania wewnętrznych dokumentów klinicznych z zewnętrznymi wytycznymi leczenia. Proste oceny je przeoczyły, ponieważ odpowiedzi brzmiały poprawnie – były po prostu przestarzałe.

Rozwiązanie jest proste do sformułowania, ale trudne do zaimplementowania: pozyskiwanie informacji staje się decyzją, a nie konkretnym krokiem.

Co oznacza agentic RAG

W agentic RAG model językowy nie pozyskuje informacji za Ciebie w ustalonym momencie. Traktuje to pozyskiwanie jako narzędzie, podobne do kalkulatora lub wykonawcy kodu, i korzysta z niego wtedy, gdy uznaje dowody za niewystarczające. Prowadzi to do trzech zachowań, których nie może zapewnić statyczny proces.

  • Pozyskiwanie informacji warunkowe. Jeśli kontekst już odpowiada na pytanie, agent pomija całkowicie krok pozyskiwania informacji.
  • Pozyskiwanie informacji wieloetapowe. Agent pobiera dane, je czyta, zdaje sobie sprawę, że brakuje jakiejś informacji, i wraca po nią ponownie, przy czym każdy krok jest kierowany tym, co znaleziono poprzednio. W ten sposób ludzie przeprowadzają badania.
  • Ścieżka źródłowa. Agent wybiera między magazynem wektorowym, wyszukiwarką internetową a strukturalną bazą danych w zależności od pytania. Pytanie „Co mówi nasza polityka?” kierowane jest do magazynu wektorowego, natomiast „Co ogłosiło FDA w zeszłym tygodniu?” – do internetu.
  • Żadna z tych koncepcji nie jest nowa. Artykuł ReAct autorstwa Yao i współpracowników (2022) przedstawił wzorzec łączenia kroków rozumowania z wywołaniami narzędzi, a agentic RAG to zastosowanie ReAct do wyszukiwania informacji [2].

    Niedawłą zmianą są narzędzia. LangGraph w końcu umożliwia praktyczne stosowanie pętli z możliwością rozgałęziania. Tavily oferuje API do wyszukiwania w internecie zaprojektowane dla agentów, które zwraca treści gotowe do użycia przez modele językowe, a według jego informacji obsługuje ponad 100 milionów zapytań miesięcznie przy czasie reakcji 180 ms [3]. Szczegóły znajdują się w wpisie Tavily 101.

    Trzy elementy ruchome

    Pomyśl o detektywie zamiast o bibliotekarzu. Bibliotekarz przywołuje informacje na żądanie. Detektyw czyta to, co jest dostępne, ustala, czego brakuje, idzie to zdobyć, ponownie czyta i kontynuuje, aż można będzie przedstawić uzasadniony wniosek. Ten proces składa się z trzech elementów.

    1. Router określa, jaki rodzaj wyszukiwania jest potrzebny w zapytaniu. LLM analizuje pytanie i decyduje, czy wystarczający jest istniejący kontekst, czy potrzebne są dokumenty, czy potrzebna jest sieć internetowa, lub jedno i drugie.
    2. Narzędzia do wyszukiwania to źródła dowodów. Minimalnie potrzebne są dwa narzędzia: jedno do wyszukiwania w bazie wektorowej wewnętrznych dokumentów, drugie do wyszukiwania informacji na żywo w internecie. Każde z nich to zwykła funkcja, którą LLM wywołuje za pomocą ciągu zapytań.
    3. Weryfikacja uzasadnienia polega na sprawdzeniu, czy dostępne dowody są wystarczające. Przed napisaniem ostatecznej odpowiedzi agent ocenia, czy to, co uzyskał, jest wystarczające i istotne; jeśli nie, próbuje ponownie. Statyczne procesy nie mogą odzwierciedlić takiego cyklu.

    Funkcjonalna implementacja

    Oto hybrydowy agent w LangGraph, który przełącza się między magazynem wektorowym Qdrant a wyszukiwarką internetową Tavily. Wybiera odpowiedni narzędzie, może korzystać z obu kolejno i ponownie wyszuka informacje, gdy pierwszy wynik nie dostarcza wystarczających podstaw dla odpowiedzi. Część związana z Qdrant jest zgodna z przewodnikiem integracji 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)
    

    Część szczegółów wymaga uwagi.

    Zabezpieczenie MAX_RETRIEVALS w logice routingu to nie tylko dekoracja. Bez niego agenci mogą wykonać aż 15 prób pobierania informacji na jedno niejednoznaczne pytanie, marnując tym samym zasoby i cierpliwość. Zazwyczaj wystarczą trzy próby. Jeśli to niewystarczające, pytanie jest zbyt ogólne lub narzędzia zwracają bezużyteczne dane.

    Pole retrieval_count w stanie umożliwia przerwanie pętli. Struktura StateGraph z LangGraph przenosi je przy każdej zmianie węzła, dzięki czemu zachowuje się ono przez cały obrót rozmowy, a nie tylko przez jeden cykl.

    Wskazówki systemowe również mają duże znaczenie. Informowanie modelu, kiedy używać poszczególnych narzędzi, a nie tylko o ich istnieniu, odróżnia agenta, który podejmuje rozsądne decyzje, od tego, który z lenistwa używa funkcji retrieve_from_web we wszystkich przypadkach.

    Kiedy RAG z agentem pogarsza sytuację

    Nie zastępuje on statycznego RAG. Zastępuje go w przypadku określonych typów zadań, ale przynosi negatywne skutki w czterech sytuacjach.

    • Głównym ograniczeniem jest opóźnienie. Każdy przepływ przez pętlę wiąże się z dodatkowym wywołaniem modelu oraz narzędzia. W przypadku prostego asystenta do pytań i odpowiedzi, gdzie opóźnienie p95 musi być poniżej sekundy, agent, który może działać dwa lub trzy razy, jest niewystarczający. Dobrze dostrojony statyczny system wyszukiwania jest szybszy i wystarczający.
    • Baza wiedzy jest stabilna i samodzielna. Jeśli na wszystkie pytania użytkowników można pewnie odpowiedzieć z magazynu wektorowego, dodawanie złożoności jest bezcelowe. Najpierw zmierz wydajność statycznego procesu za pomocą RAGAS (artykuł naukowy). Jeśli stopień przywoływania kontekstu przekracza 85%, a wiarygodność odpowiedzi powyżej 90%, to wrapper typu agentic stanowi zbędny obciążenie.
    • Nie można ufać wyborowi narzędzi przez model. Dzięki starannie sformułowanemu promptowi systemowemu GPT-4o i Claude Sonnet prawidłowo wybierają między pobieraniem danych z dokumentów a z sieci w około 88% przypadków, według tych samych wewnętrznych pomiarów. Pozostałe 12% powoduje błędy źródłowe, które trudniej jest naprawić niż zwykłe halucynacje, ponieważ odpowiedź ma sens, tylko znajduje się w niewłaściwym miejscu. W przypadku słabszych modeli konieczne jest wcześniejsze wybranie narzędzi.
    • Danych w czasie rzeczywistym nie można ufać. Pobieranie danych z sieci tworzy dodatkową powierzchnię ataku. Tavily filtrowa pod kątem iniekcji promptów w swojej API, ale żaden filtr nie jest doskonały, więc wszystko, co agent robi na podstawie treści z sieci, powinno traktować te treści jako potencjalnie wrogie.

    Wybierz rozwiązanie typu agentic RAG, gdy wiedza pochodzi z kilku źródeł, a niektóre z nich się zmieniają. Zacznij od wersji statycznej RAG i zaktualizuj ją, gdy wyniki RAGAS ujawnią konkretne problemy, które to rozwiązanie rozwiązuje.

    Od czego zacząć

    Najmniejszy krok, poniżej pięciu minut: dodaj jeden narzędzie Tavily do istniejącego agenta LangChain za pomocą from langchain_community.tools.tavily_search import TavilySearchResults. Na razie nie potrzebujesz LangGraph. Ujawnij to narzędzie, napisz instrukcję systemową określającą, kiedy je wywołać, uruchom pięć zapytań testowych i obserwuj, co zostanie wybrane.

    Kolejny poziom, około jednego popołudnia: skieruj powyższy przykład LangGraph na swój własny magazyn wektorowy, zachowując oba narzędzia. Na początku pomiń licznik pobierania danych, aby zobaczyć, jak często agent się powtarza, a następnie go dodaj. Ta różnica wiele uczy.

    Wersja zaawansowana: umieszczenie kroku oceny po każdym wyniku narzędzia i przed kolejną rundą agenta. Mniejszy, tańszy wywołanie modelu LLM odpowiada „tak” lub „nie” na pytanie, czy uzyskany kontekst jest wystarczający, a dopiero wtedy główny model tworzy swoją odpowiedź. Dzięki temu kroku powrót do kontekstu przechodzi z poziomu „dostatecznie dobry w testach” na poziom „niezawodny w produkcji”.

    Błędy w kierowaniu narzędziami, które stanowią 12% wszystkich problemów, to główny powód poświęcania czasu na debugowanie w środowisku produkcyjnym. Należy je wykrywać wcześnie, aby węzeł oceny mógł je zidentyfikować przed użytkownikami.

    Źródła

    [1] Wewnętrzne testy przeprowadzone na pipeline’u RAG w obszarze opieki zdrowotnej przy użyciu RAGAS 0.1.x na zestawie 200 zapytań klinicznych. Nie jest to publikowane badanie.

    [2] Yao i współpracownicy (2022), ReAct: Synergizing Reasoning and Acting in Language Models, arXiv:2210.03629.

    [3] Blog Tavily, Tavily 101, z danymi dotyczącymi opóźnienia i objętości przedstawionymi na stronie produktu w maju 2026 roku.