Agentic RAG: Dem LLM die Entscheidung überlegen, wann und wo Daten abgerufen werden sollen.
Warum statische RAG bei mehrstufigen und zeitkritischen Fragen versagt und wie ein LangGraph-Agent zwischen Qdrant und Web-Suchen navigiert, inklusive Schutzmaßnahmen und Einschränkungen.
Quellen: Your RAG Pipeline Is Lying to You. The Problem Is Not the Embeddings — Agentic RAG
„RAG ist im Grunde ein bereits gelöstes Problem.“ Dieser Satz wird bei Teambesprechungen geäußert, und die ehrliche Antwort lautet: Es hängt davon ab, was man unter Informationsabruf versteht. Jeder, der bereits seit längerer Zeit RAG-Pipelines in der Praxis eingesetzt hat – sei es für Zusammenfassungen von Krankenakten, Workflows vom Natursprache- ins SQL-Format oder mehragentige Forschungssysteme – stößt in der Regel auf denselben Grenzbereich.
Der Grenzbereich liegen nicht in den Embeddings. Er liegt in der Annahme, dass der Informationsabruf nur einmal, an einer festgelegten Stelle, anhand einer einzigen Quelle erfolgt.
Static RAG (Einspeisen der Dokumente, Abrufen der Top-k-Ergebnisse, Übergeben an den LLM) bewältigt einfache, eigenständige Fragen mit stabilem internen Wissen gut. Sobald eine Frage mehrstufig wird, zeitkritisch ist oder sowohl interne Dokumente als auch aktuelle Daten erfordert, beginnt der statische Ablauf, zuverlässige Fehlinformationen zu erzeugen. Ein solcher Ablauf kann auf der Grundlage veralteter interner Richtlinien eine klinische Vorgehensweise empfehlen, obwohl eine schnelle halbstündige Suche im Internet die Änderung bereits aufgezeigt hätte.
Bessere Embeddings werden das nicht beheben. Hilfreich ist es, dem LLM zu ermöglichen, selbst zu entscheiden, wann und wo Daten abgerufen werden sollen.
Das Problem mit „einmal abrufen, immer antworten“
Stellen Sie sich einen Bibliothekar vor, der erst nachdem Sie Ihren Satz beendet haben, nur zu einem einzigen Regal gehen kann – immer derselben Stelle –, um eine Antwort zu geben.
Fragen Sie „Welche Nebenwirkungen hat Metformin?“ – und das System liefert sofort eine Antwort. Fragen Sie nun etwas Schwierigeres: Welche Wechselwirkungen mit anderen Medikamenten hat die FDA kürzlich für Metformin in Kombination mit neueren GLP-1-Medikamenten hervorgehoben, und deckt das aktuelle Patientenprotokoll diese ab? Dafür sind interne Dokumente sowie aktuelle regulatorische Daten erforderlich, zusammen mit einer Analyse der Lücken dazwischen. Statisches RAG zieht einmalig Daten aus einer Quelle ab, während das LLM die erhaltenen Informationen nur oberflächlich verarbeitet.
Das ist keine Hypothese. In einer internen Bewertung eines Gesundheitsversorgungsprozesses [1] erreichte statisches RAG bei stabilen Fragen eine Antwortrelevanz von 89 %, fiel aber auf 61 %, wenn eine Frage die Abgleichung interner klinischer Dokumente mit externen Behandlungsleitlinien erforderte. Grundlegende Bewertungen übersehen dies, weil die Antworten plausibel klingen – sie sind einfach veraltet.
Die Lösung lässt sich leicht beschreiben, ist aber schwierig umzusetzen: Die Informationsbeschaffung wird zu einer Entscheidung statt zu einem Schritt.
Was agentic RAG bedeutet
In agentic RAG beschafft das LLM die Informationen nicht an einem vorgegebenen Punkt. Es betrachtet die Informationsbeschaffung als Werkzeug, ähnlich wie einen Taschenrechner oder einen Codeausführer, und nutzt es, wenn es die vorliegenden Beweise für unzureichend hält. Daraus ergeben sich drei Verhaltensweisen, die ein statischer Ablauf nicht ermöglichen kann.
- Bedingte Informationsbeschaffung. Wenn der Kontext bereits die Frage beantwortet, überspringt der Agent die Informationsbeschaffung ganz.
- Mehrschrittige Informationsbeschaffung. Der Agent holt etwas ab, liest es, erkennt, dass ein Teil fehlt, und geht erneut danach suchen, wobei jeder Schritt von den Ergebnissen des vorherigen beeinflusst wird. So recherchieren Menschen auch.
Keine dieser Ideen ist neu. Der Artikel „ReAct“ von Yao und Kollegen (2022) beschreibt das Muster des Abwechselns von Schlussfolgerungs Schritten mit Toolaufrufen, und agierendes RAG ist ReAct in Kombination mit der Informationsabrufung [2].
Die jüngste Veränderung liegt in den Werkzeugen. LangGraph macht endlich stateful Schleifen mit Verzweigungen praktikabel. Tavily bietet eine für Agenten konzipierte Websuche-API, die Inhalt bereitstellt, der für Large Language Models geeignet ist, und gibt an, mehr als 100 Millionen Anfragen pro Monat mit einer p50-Suchzeit von 180 ms bewältigen zu können [3]. Weitere Details finden Sie im Beitrag „Tavily 101“.
Drei bewegliche Komponenten
Stellen Sie sich einen Detektiv statt einen Bibliothekar vor. Eine Bibliothekarin holt auf Anweisung etwas. Ein Detektiv liest, was vorhanden ist, ermittelt, was fehlt, holt es, liest erneut und setzt so fort, bis eine Schlussfolgerung vertretbar ist. Der Kreislauf besteht aus drei Komponenten.
- Der Router bestimmt, welche Art der Abrufung eine Anfrage erfordert. Das LLM betrachtet die Frage und entscheidet, ob der vorhandene Kontext ausreicht, ob Dokumente benötigt werden, ob das Internet benötigt wird oder beides.
- Die Abrufwerkzeuge sind die Quellen für Beweise. Zwei Werkzeuge sind das absolute Minimum: eines durchsucht einen Vektorlagerraum mit internen Dokumenten, das andere durchsucht das Internet nach aktuellen Informationen. Jedes ist eine gewöhnliche Funktion, die das LLM mit einer Abfragesuche aufruft.
Eine funktionierende Implementierung
Hier ist ein hybrider Agent in LangGraph, der zwischen einem Qdrant-Vektorlagern und der Tavily-Websuche wechselt. Er wählt das passende Werkzeug, kann beide nacheinander aufrufen und holt erneut Daten, wenn das erste Ergebnis nicht ausreicht, um die Antwort zu stützen. Der Qdrant-Teil folgt dem LangChain-Integrationstool.
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)
Einige Details verdienen Aufmerksamkeit.
Der Schutzmechanismus MAX_RETRIEVALS in der Routing-Logik ist keine bloße Dekoration. Ohne ihn können Agenten bei einer unklaren Frage in insgesamt 15 Abfragen geraten, wodurch Tokens und Geduld verschwendet werden. In der Regel reichen drei Abfragen aus. Falls nicht, ist die Frage zu allgemein oder die Tools liefern unbrauchbare Ergebnisse.
Das Feld retrieval_count im Zustand sorgt dafür, dass der Schleifenaufruf beendet werden kann. LangGraphs StateGraph überträgt diesen Wert bei jeder Knotenübergang, sodass er während des gesamten Gesprächsverlaufs erhalten bleibt und nicht nur innerhalb eines einzigen Zyklus.
Auch der Systemprompt hat große Bedeutung. Dem Modell mitzuteilen, wann welche Tools verwendet werden sollen – und nicht nur, dass Tools vorhanden sind – unterscheidet einen Agenten, der vernünftig routet, von einem, der aus Faulheit für alles retrieve_from_web aufruft.
Wenn agierende RAG-Systeme die Situation verschlimmern
Es ersetzt den statischen RAG nicht. Es ersetzt ihn nur bei bestimmten Problemformen und führt in vier Fällen zu negativen Auswirkungen.
- Die Latenz ist die Hauptbeschränkung. Jeder Durchlauf durch die Schleife kostet einen zusätzlichen Modellaufruf sowie einen Toolaufruf. Für einen einfachen Q&A-Assistenten, bei dem die p95-Latenz unter einer Sekunde bleiben muss, ist ein Agent, der zwei oder drei Mal durchlaufen muss, keine gute Lösung. Ein gut abgestimmter statischer Retriever ist schneller und ausreichend.
- Die Wissensbasis ist stabil und selbstständig. Wenn alle von den Nutzern gestellten Fragen zuverlässig aus dem Vektorlagern beantwortet werden können, fügt man unnötig Komplexität hinzu. Messen Sie zunächst den statischen Ablauf mit RAGAS (Paper). Wenn die Kontextwiederholungsrate über 85 % und die Genauigkeit der Antworten über 90 % liegt, ist der agentebasierte Wrapper überflüssig.
Wählen Sie agierendes RAG, wenn das Wissen aus mehreren Quellen stammt und einige davon sich ändern. Beginnen Sie mit statischem RAG und aktualisieren Sie es, wenn die RAGAS-Werte einen spezifischen Fehler aufzeigen, den dieses Design behebt.
Wo anzufangen
Kleinster Schritt, unter fünf Minuten: Fügen Sie mit from langchain_community.tools.tavily_search import TavilySearchResults ein Tavily-Tool zu einem vorhandenen LangChain-Agenten hinzu. LangGraph ist noch nicht notwendig. Machen Sie das Tool zugänglich, schreiben Sie einen Systemprompt, der angibt, wann es aufgerufen werden soll, führen Sie fünf Testabfragen durch und beobachten Sie, welche Ergebnisse ausgewählt werden.
Nächster Schritt, etwa eine Nachmittagsstunde: Weisen Sie das oben gezeigte LangGraph-Beispiel auf Ihren eigenen Vektor-Speicher hin und behalten Sie beide Tools bei. Lassen Sie den Abrufzähler zunächst aus, um zu sehen, wie oft der Agent wiederholt abruft, fügen Sie ihn anschließend hinzu. Der Unterschied lehrt viel.
Fortgeschritten: Fügen Sie nach jedem Tool-Ergebnis und vor dem nächsten Schritt des Agents einen Bewertungsschritt ein. Ein kleinerer, günstigerer LLM-Aufruf beantwortet mit ja oder nein, ob der abgerufte Kontext ausreicht, und erst danach erstellt das Hauptmodell seine Antwort. Dadurch wird die Kontextnutzung von „in Tests ausreichend“ auf „in der Produktion zuverlässig“ erhoben.
Tool-Routing-Fehler, die 12 % mehr auftreten, sind der Hauptgrund für die meisten Debugging-Aufwände in der Produktion. Instrumentieren Sie diese frühzeitig, damit der Bewertungsknoten sie vor den Nutzern erkennt.
Quellen
[1] Interne Bewertungen an einem Gesundheits-RAG-Pipeline-System unter Verwendung von RAGAS 0,1.x anhand einer sorgfältig ausgewählten Sammlung von 200 klinischen Abfragen. Keine veröffentlichte Studie.
[2] Yao et al. (2022), ReAct: Synergizing Reasoning and Acting in Language Models, arXiv:2210.03629.
[3] Tavily-Blog, Tavily 101, mit den Angaben zu Latenz und Datenmenge wie auf der Produktseite im Mai 2026 angegeben.
Verwandte Artikel
- Agentic Retrieval und ausreichender Kontext: Wenn RAG nicht genug weiß — Standard-RAG antwortet oft zu früh. Lernen Sie, Suchanfragen zu planen, Quellen zu routen, Beweise zu bewerten und erneut zu suchen, bis der Kontext ausreichend ist – mit einem Beispiel von Acme.
- Chroma Context-1: Ein 20B-Such-Sub-Agent für günstigeres, präziseres Agentic RAG — Wie Chromas 20B Context-1 als reiner Such-Sub-Agent fungiert, der seinen eigenen Kontext reduziert, wie er trainiert wurde, was ohne seine Ergänzungen fehlt und wo seine Grenzen liegen.