Design wechseln

RAG Query-Routing in der Praxis: Multi-Vektorspeicher und intelligente Retrieval-Verteilung

Easton editorial illustration: LOGIC 路由开关, SEMANTIC 路由开关, 三座向量库, RRF 合并托盘

Drei Uhr nachts – im Produktivsystem blinken wieder die Alarme.

Auf dem Dashboard schießt die Antwortzeit für „Q3-Umsatz Ostchina“ auf 12 Sekunden. In der Demo lief alles glatt – warum bricht es live ein? Schlimmer: Die Logs zeigen die volle Multi-Hop-Reasoning-Pipeline – nur für eine einfache Faktenabfrage.

„Mit Kanonen auf Spatzen schießen“, sagte ein Kollege trocken.

Klar wurde: Das Problem ist nicht die Retrieval-Genauigkeit, sondern die Retrieval-Strategie. Klassisches RAG verhält sich wie ein „blinder Retriever“: immer derselbe Ablauf – Vektorsuche plus LLM-Generierung. Einfache Fragen werden überfrachtet, komplexe unterversorgt.

Dieser Artikel zeigt, wie Sie Ihrem RAG einen „Verkehrsregler“ geben – einen Query-Router. Er erkennt Merkmale der Anfrage und leitet sie auf die passende Strecke: Schnellpfad für Fakten, Tiefgang für Reasoning, Multi-Source für vollständige Antworten.

Ehrlich: Diese Lösung hat das Projekt gerettet.

1. Warum Query-Routing? – Vom blinden Retrieval zur intelligenten Verteilung

Zuerst die Stolpersteine aus der Praxis.

Letztes Jahr bauten wir ein E-Commerce-Support-RAG. Vier Wissensbereiche: Produkte, Rückgabe, Logistik, Marketing. Tests grün – nach dem Go-live verdoppelten sich Beschwerden.

In den Logs ein Klassiker: „Wie lange dauert die Erstattung?“ – Antworten zu Versandzeiten und Aktionen. Nicht falsch, aber unfokussiert; zu viel Rauschen verwirrt Nutzer.

Wissensinterferenz ist der erste Schmerz: Alles in einem Vektorspeicher → gemischte Treffer. Frage zu Szenario A, „ähnlicher“ Inhalt aus B.

Der zweite Schmerz ist subtiler: Antworteffizienz.

„Wie hoch war der Q3-Umsatz in Ostchina?“ – eine Faktenfrage. Reichen DB oder Keyword-Suche. Stattdessen: Embedding, Cosinus, Top-K, LLM – 1–2 Sekunden und hoher Aufwand.

Der dritte Schmerz: Intent-Fehlklassifikation.

„Video mit kürzester Wiedergabedauer“ – SelfQueryRetriever erkennt „Wiedergabedauer“ oft nicht als Metadatenfilter. „Hat der Streik den Kurs beeinflusst?“ braucht Multi-Hop – ein Vektorretriever reicht nicht.

Sie brauchen einen Router, der die Lage „liest“ und den passenden Pfad wählt – wie in einem Restaurant: Theke für Schnellgerichte, Küche für Aufwand, Auslieferung für Kanäle.

2. Kernarchitektur – Drei-Schichten-Modell

Kernidee: Schichten, klare Rollen.

Nutzeranfrage

┌─────────────────────────────────┐
│  Routing: Szenario-Klassifikation │
│  (LLM / Semantic Router)         │
│  → python_docs / js_docs / go_docs│
└─────────────────────────────────┘

┌─────────────────────────────────┐
│  Retrieval: Szenario-Vektorspeicher │
│  Chroma(python) / Chroma(js)...  │
│  → top-k Chunks                  │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│  Merge: RRF-Algorithmus          │
│  RRF(d) = Σ 1/(k + rank(d))      │
│  → finale Antwort                │
└─────────────────────────────────┘

Antwort generieren

Die Routing-Schicht ist das „Gehirn“: Pfad wählen – LLM-Logik oder Semantic Router (Details folgen).

Die Retrieval-Schicht sind die „Hände“: pro Szenario ein Index (Python-, JS-, Go-Docs). Routing entscheidet wo, Retrieval sucht.

Die Merge-Schicht ist der „Schiedsrichter“: bei Multi-Szenario-Ergebnissen sortiert RRF (Reciprocal Rank Fusion).

LangChain EnsembleRetriever in Kurzform:

from langchain.retrievers import EnsembleRetriever
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings

python_store = Chroma(
    persist_directory="./chroma_python",
    embedding_function=OpenAIEmbeddings()
)
python_retriever = python_store.as_retriever(search_kwargs={"k": 5})

js_store = Chroma(
    persist_directory="./chroma_js",
    embedding_function=OpenAIEmbeddings()
)
js_retriever = js_store.as_retriever(search_kwargs={"k": 5})

ensemble_retriever = EnsembleRetriever(
    retrievers=[python_retriever, js_retriever],
    c=60
)

docs = ensemble_retriever.invoke("Wie behandelt man asynchrone Callbacks?")
print(f"Gefunden: {len(docs)} Chunks")

EnsembleRetriever ruft beide Retriever parallel auf und merged per RRF. c=60 ist ein bewährter Kompromiss – zu groß glättet, zu klein betont Spitzenränge.

Im Test stieg die Genauigkeit von 72 % auf 92 % – auf Kosten etwas Latenz durch parallele Abfragen.

3. Drei Routing-Strategien – Logisch, semantisch, Metadaten

3.1 Logisches Routing: LLM als Dispatcher

Direkter Ansatz: LLM analysiert Intent und wählt die Datenquelle.

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_deepseek import ChatDeepSeek

system_prompt = """Du bist ein Routing-Experte für Programmierfragen.
Leite nach Sprache zur Datenquelle:

- Python → python_docs
- JavaScript → js_docs
- Go → golang_docs
- unklar → general_docs

Nur den Datenquellennamen zurückgeben."""

prompt = ChatPromptTemplate.from_messages([
    ("system", system_prompt),
    ("human", "{query}")
])

llm = ChatDeepSeek(model="deepseek-chat", temperature=0)
router_chain = prompt | llm | StrOutputParser()

query = "Wie implementiert man in Python einen asynchronen Crawler?"
datasource = router_chain.invoke({"query": query})
print(f"Routing: {datasource}")  # python_docs

Flexibel für komplexe Intents („Python vs. Go Concurrency“ → mehrere Quellen + Ensemble). Nachteil: jeder Routingschritt kostet ~0,5–1 s LLM-Latenz und API-Gebühren.

3.2 Semantisches Routing: Vektoren statt LLM

Für Tempo: Semantic Router – wie unscharfes if/else mit Beispiel-Utterances pro Route.

from semantic_router import Route, RouteLayer
from semantic_router.encoders import OpenAIEncoder

python_route = Route(
    name="python_docs",
    utterances=[
        "Dateien in Python lesen",
        "Python-Dekoratoren",
        "Asynchrones Programmieren in Python",
        "List Comprehension in Python",
    ]
)

js_route = Route(
    name="js_docs",
    utterances=[
        "JavaScript async Callbacks",
        "DOM in JS manipulieren",
        "Node.js Event Loop",
        "Promise vs. async/await in JS",
    ]
)

route_layer = RouteLayer(
    encoder=OpenAIEncoder(),
    routes=[python_route, js_route]
)

query = "Wie nutzt man Python-Generatoren?"
result = route_layer(query)
print(f"Routing: {result.name}")  # python_docs

3–5× schneller als LLM-Routing: Embedding ~100 ms vs. LLM 500 ms+. Grenze: Routen müssen vordefiniert sein; unbekannte Fragetypen → None. Ideal bei stabilen Abfragemustern.

3.3 Metadaten-Routing: SelfQueryRetriever

Bei reichen Metadaten (Kategorie, Sprache, Datum) filtert SelfQueryRetriever präzise:

from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain.chains.query_constructor.base import AttributeInfo
from langchain_openai import ChatOpenAI

metadata_field_info = [
    AttributeInfo(
        name="category",
        description="tutorial, api, guide, troubleshooting",
        type="string"
    ),
    AttributeInfo(
        name="language",
        description="python, javascript, golang",
        type="string"
    ),
    AttributeInfo(
        name="date",
        description="Veröffentlichungsdatum",
        type="date"
    )
]

llm = ChatOpenAI(model="gpt-4", temperature=0)
retriever = SelfQueryRetriever.from_llm(
    llm=llm,
    vectorstore=vectorstore,
    document_contents="Programmier-Dokumentation",
    metadata_field_info=metadata_field_info,
    verbose=True
)

query = "Aktuelle Python-Tutorials"
docs = retriever.invoke(query)
# z. B. category == "tutorial" AND language == "python", sortiert nach date

Stark bei sauberen Tags – ohne Metadatenqualität wenig Nutzen.

4. Multi-Vektorspeicher – EnsembleRetriever im Detail

Manche Fragen betreffen mehrere Domänen („Python vs. JavaScript Async vergleichen“) – dann parallel suchen und mergen.

RRF-Prinzip

RRF(d) = Σ 1/(k + rank(d))
  • d: Dokument
  • rank(d): Rang in einem Retriever (ab 1)
  • k: Glättung, typisch 60

Beispiel: Retriever A Rang 2 → 1/62; B Rang 5 → 1/65; Summe sortiert alle Dokumente.

RRF nutzt Ränge, nicht rohe Scores – wichtig, wenn Vektor-Cosinus und BM25 unterschiedliche Skalen haben.

Dense + Sparse

from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import Chroma

bm25_retriever = BM25Retriever.from_texts(documents_text_list, k=5)
vector_retriever = Chroma.from_texts(
    documents_text_list,
    embedding=OpenAIEmbeddings()
).as_retriever(search_kwargs={"k": 5})

ensemble = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6],
    c=60
)

docs = ensemble.invoke("LangChain Agent Tool Calling")

Gewichte nach Abfrageart: semantisch höher Vektor (0,6–0,7), Keyword höher BM25 (0,5–0,6), sonst 0,5/0,5.

Qualität mit TruLens

from trulens_eval import Feedback, TruChain
from trulens_eval.feedback.provider.openai import OpenAI

provider = OpenAI()
relevance_feedback = Feedback(
    provider.relevance, name="Answer Relevance"
).on_input_output()
context_relevance_feedback = Feedback(
    provider.context_relevance, name="Context Relevance"
).on_input().on(context)

tru_recorder = TruChain(
    chain=ensemble_retriever_chain,
    feedbacks=[relevance_feedback, context_relevance_feedback],
    feedback_mode="with_chain"
)

with tru_recorder as recording:
    response = ensemble_retriever_chain.invoke({"query": test_query})

Einzelner Vektorretriever: Kontext-Relevanz ~0,72; mit Ensemble ~0,91.

5. Performance und Best Practices

Vier Varianten auf 500 Anfragen (vier Szenarien):

StrategieØ AntwortzeitGenauigkeitEinsatz
Kein Routing (ein Store)1,2 s72 %Ein Domäne
Logisch (LLM)1,8 s85 %Multi-Domäne, komplex
Semantisch0,5 s88 %Feste Fragetypen
Ensemble RRF1,0 s92 %Hybrid, Multi-Source

Semantisch 3–4× schneller als logisch (Embedding ~100 ms vs. LLM ~800 ms). Ensemble höchste Genauigkeit, etwas mehr Ressourcen. Ohne Routing paradox oft langsamer: mehr Rauschen belastet die Generierung.

Empfehlungen:

  1. Einfache Szenarien → Semantic Router – schnell, günstig.
  2. Multi-Hop / Vergleich → logisches Routing – LLM versteht Cross-Domain besser.
  3. Unsicherer Intent → Ensemble – 3–4 Retriever als Obergrenze.
  4. Reiche Metadaten → SelfQuery – präzise Filter.
  5. Hybrid in Produktion – semantisch zuerst; Confidence < 0,6 → logischer Fallback.

Zusammenfassung

Viele optimieren Embeddings und Prompts und vergessen Query-Routing.

Die Frage „Schnell- oder Tiefgangspfad?“ allein kann Latenz von 1,8 s auf 0,5 s und Genauigkeit von 72 % auf 92 % bewegen.

  • Einfach → semantisch
  • Komplex → logisch
  • Multi-Source → Ensemble + RRF
  • Metadaten → SelfQuery

Unser Produktiv-Setup: semantische Erststufe, Fallback logisch, Multi-Source per Ensemble – Lösungsrate im Support-Bot von 68 % auf 89 %.

Beispielcode: GitHub-Repository. Fragen gern in den Kommentaren.

RAG Query-Routing-System implementieren

Intelligente RAG-Routing-Architektur mit semantischem, logischem Routing und Multi-Source-Retrieval

⏱️ Estimated time: 45 min

  1. 1

    Step 1: Routing-Strategie wählen

    Je nach Szenario: feste Fragetypen → semantisches Routing (~100 ms); mehrere Domänen → logisches Routing (~800 ms); Mischszenarien → Ensemble RRF (bis 92 % Genauigkeit).
  2. 2

    Step 2: Semantisches Routing umsetzen

    Mit semantic-router: Route und RouteLayer definieren, OpenAIEncoder nutzen, Beispiel-Utterances pro Datenquelle hinterlegen – ohne LLM-Aufruf pro Anfrage.
  3. 3

    Step 3: EnsembleRetriever konfigurieren

    Mehrere Retriever (z. B. BM25 + Vektor) mit EnsembleRetriever verbinden, weights und c=60 setzen; Gewichte nach Abfrageart (semantisch vs. Keyword) anpassen.
  4. 4

    Step 4: Retrieval-Qualität messen

    TruLens für Answer Relevance und Context Relevance; vor/nach Ensemble vergleichen – im Test stieg Kontext-Relevanz von 0,72 auf 0,91.

FAQ

Semantisches oder logisches Routing – was ist besser?
Kommt aufs Szenario an. Semantisch ~100 ms, gut bei festen Fragetypen; logisch stärker bei komplexer Intent oder mehreren Domänen. Produktion: Hybrid – semantisch zuerst, bei niedriger Confidence logischer Fallback.
Wie setzt man den RRF-Parameter c im EnsembleRetriever?
c=60 ist ein bewährter Startwert. Größeres c glättet Rankings, kleineres c betont Top-Ränge. Mit 60 beginnen, dann 40–80 feinjustieren.
Was tun bei Routing-Fehlschlag?
Default-Retriever unter Schwellwert; Fallback auf LLM-Logikrouting; oder Ensemble über mehrere Stores mit RRF-Merge, wenn die Intent unsicher ist.
Welche Metadaten braucht SelfQueryRetriever?
Saubere Felder wie category, language, date. Je reicher die Metadaten, desto präziser die Filter. Fehlende oder inkonsistente Tags schwächen die Qualität.
Werden parallele Multi-Retriever zu langsam?
Parallel ist schneller als seriell, kostet aber mehr Ressourcen. 3–4 Retriever sind ein sinnvolles Limit; im Test ~1,0 s bei ~20 % langsamer, aber >20 % höherer Genauigkeit.
Wie stark beeinflusst Routing die RAG-Performance?
Deutlich: ohne Routing 72 % / 1,2 s; semantisch 88 % / 0,5 s; Ensemble RRF 92 % / 1,0 s. Richtige Strategie kann Latenz halbieren und Genauigkeit um rund 20 % steigern.

6 Min. Lesezeit · Veröffentlicht am: 13. Mai 2026 · Aktualisiert am: 14. Juli 2026

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog