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.
Ź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.
Ż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.
- 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.
- 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ń.
- 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.