Przegrupowanie w RAG: kodery krzyżowe kontra sędziowie LLM oraz ich opóźnienie
Jak druga warstwa ponownego rankowania poprawia kolejność kandydatów w RAG, porównanie koderów krzyżowych z sędziami opartymi na LLM, koszty opóźnień, sposoby awarii oraz jak je oceniać.
Źródła: Reranking for RAG: Cross-Encoders, LLM Rerankers, and Latency Tradeoffs
Prawidłowy fragment znajdował się w wynikach, po prostu zbyt nisko
Wyobraź sobie system RAG, którego hybrydowe wyszukiwanie w końcu zaczęło poprawnie funkcjonować. Magazyn wektorów jest dostępny, indeks słów kluczowych działa, logika fuzji została napisana, a fragment zawierający odpowiedź pojawia się teraz gdzieś w zestawie kandydatów. Mimo to model nadal generuje przeciętną odpowiedź.
Logi wyjaśniają dlaczego. Programista zapytał, jak naprawić błąd PAYMENTS_API_TIMEOUT w środowisku testowym. Hybrydowe wyszukiwanie zwróciło dwadzieścia kandydatów: ogólny przegląd procesu autoryzacji, przestarzałą notatkę o incydencie z poprzedniego roku, ogólny artykuł na temat logiki ponawiania prób oraz, na ósmej pozycji, fragment podręcznika do środowiska testowego z dokładnym rozwiązaniem. W pole wprowadzania zapytania było miejsce tylko na trzy fragmenty. Prawidłowy znajdował się zbyt nisko, więc model skonstruował swoją odpowiedź na podstawie niewłaściwych danych, ponieważ te właściwe nie pasowały.
Proces wyszukiwania znalazł dowody, ale nie umieścił ich na początku. Głównym celem wyszukiwania jest przywoływanie informacji, natomiast ponowna sortowanie ma na celu zapewnienie dokładności przy ograniczonym budżecie zasobów.
Sama dobra lista kandydatów nie wystarcza. To, co faktycznie odczytuje model, zależy od kolejności tych elementów, a ta kolejność stanowi wąskie gardło.
Co robi etap ponownego sortowania
Pierwsza próba przetwarzania jest zaprojektowana tak, by być szybka i obejmować jak największy zakres danych. Zapytanie skierowane do bazy wektorowej lub indeksu słów kluczowych musi przetworzyć miliony rekordów w ułamku sekundy, dlatego takie systemy polegają na aproksymacjach i uprzednio obliczonych indeksach, celowo pobierając więcej danych, niż jest to konieczne.
Reranking dodaje drugi etap, który wybiera niewielką podgrupę wyników i bada każdy z nich bardziej dokładnie. Jest wolniejszy, ale dokładniejszy, i w ogóle nie dotyka bazy danych. To, co zostało zwrócone po pierwszym przetworzeniu – zazwyczaj dwadzieścia lub pięćdziesiąt najlepszych wyników – jest ponownie oceniane za pomocą bardziej skomplikowanego i precyzyjnego modelu.
Pierwsze przetworzenie generuje kandydatów, a reranking decyduje, które z nich znajdą miejsce w ograniczonym oknie kontekstowym.
Dlaczego pierwsze przetworzenie nie może sprostać zadaniu
Jeśli model embeddingów jest dobry, czy najlepszy fragment nie powinien po prostu znaleźć się na szczycie listy? Nie zawsze, ponieważ proces wyszukiwania w pierwszym etapie jest z natury niespójny.
Ocena słów kluczowych, takia jak BM25, opiera się na dokładnym pokryciu się terminów. Szeroka strona referencyjna, która wspomina kod błędu pięćdziesiąt razy, często przewyższa krótki przewodnik krok po kroku, który wspomina go dwa razy, ponieważ algorytm interpretuje częstotliwość jako stopień istotności.
Wyzysk wektorów ma inne problemy związane z szumem. Embeddingi mierzą semantyczną bliskość, umieszczając koncepcje w wysoko wymiarowym przestrzeni. W przypadku pytań dotyczących rozwiązywania problemów wyświetlają one fragmenty tekstowe związane z wymienionymi systemami. Ogólne przeglądy zazwyczaj znajdują się bardzo blisko konkretnych procedur w tej przestrzeni. Fragment opisujący architekturę fakturowania może mieć wyższą ocenę niż dokładny polecenie uruchamiające ponowne uruchomienie procesu fakturowania. Model wie, że oba tematy dotyczą fakturowania, ale nie potrafi odróżnić instrukcji operacyjnej od teoretycznego opisu.
Hybrydowy wyzysk łączy listy leksykalne i semantyczne w kompleksowy zbiór kandydatów zawierający potrzebne dowody. Zawiera on również duplikaty, przestarzałe dokumenty, ogólne streszczenia oraz fragmenty, które są tylko częściowo istotne.
Wyzysk w pierwszej fazie nigdy nie powinien być traktowany jako ostatnia warstwa sortowania. Działa jak lejek, a ponowne sortowanie stanowi filtr na jego dnie.
Dwa sposoby na ponowną klasyfikację
Gdy już istnieją kandydaci, trzeba je jakś uporządkować. Standardem stały się dwa podejścia. Jedno wykorzystuje cross-encodery – specjalistyczne modele szkolenie w celu oceny stopnia istotności dokumentu w odniesieniu do zapytania. Drugie wykorzystuje duże modele językowe, w których model taki jak Claude lub GPT otrzymuje zapytanie i dokument oraz jest pytany o to, na ile ten dokument jest przydatny.
Oba znacznie poprawiają jakość klasyfikacji w porównaniu z pierwszą próbą. Różnią się czasem reakcji, kosztem, determinizmem oraz wymaganiami inżynieryjnymi.
Cross-encodery
Cross-encoder to model typu transformer, chociaż funkcjonuje inaczej niż modele embeddingowe w bazie danych wektorowych.
Standardowy model embeddingowy przekazuje zapytanie przez sieć, aby uzyskać jeden wektor, a dokument również przez sieć, aby uzyskać inny wektor, następnie porównuje te dwa wektory za pomocą podobieństwa kosinowego. To jest projekt bi-encodera, który jest szybki, ponieważ każdy wektor dokumentu może zostać obliczony z góry.
Cross-encoder nie tworzy oddzielnych wektorów. Łączy zapytanie i dokument w jedną ciąg znaków i przekazuje całość jednocześnie przez sieć. Dzięki temu mechanizm self-attention może na każdej warstwie analizować zarówno słowa zapytania, jak i słowa dokumentu, aby zobaczyć, jak terminy są ze sobą powiązane w kontekście. Wynikiem jest pojedynczy wynik oceny trafności.
Taki projekt doskonale sprawdza się przy dokładnych decyzjach dotyczących trafności. Wyniki są przewidywalne i stabilne, z znacznie mniejszą zmiennością niż w przypadku używania modeli językowych do generowania odpowiedzi, a także w sposób niezawodny faworyzuje konkretne fragmenty tekstu zamiast szerokich streszczeń.
Oto funkcja ponownego rankowania stworzona na bazie hostowanej API cross-encodera.
import cohere
import time
def rerank_cross_encoder(
query: str,
candidates: list[dict],
top_n: int = 5,
model: str = "rerank-v4.0-pro",
) -> list[dict]:
"""
The practical default for second-stage ranking.
Passes the query and candidate texts to a dedicated cross-encoder model.
"""
co = cohere.ClientV2()
# Extract just the text content for the API call
documents = [c["content"] for c in candidates]
resp = co.rerank(
model=model,
query=query,
documents=documents,
top_n=top_n,
)
# Reattach the original metadata and the new score
reranked = []
for r in resp.results:
original_chunk = candidates[r.index]
reranked.append({
**original_chunk,
"rerank_score": r.relevance_score
})
return reranked
W warunkach produkcyjnych cross-encoder nadaje się do dziesięciu do pięćdziesięciu kandydatów. Zazwyczaj jest używany przy wyszukiwaniu dokumentacji dla inżynierów, w bazach wiedzy obsługi klienta oraz w wewnętrznych asystentach.
Głównym kosztem jest szybkość. Cross-encoder nie może przeszukać całej bazy danych. Czeka na pierwszą próbę, otrzymuje kandydatów przez API lub lokalny model, a następnie czeka na oceny, co dodaje krok połączenia sieciowego i obliczeniowy.
LLM rerankers
Alternatywą jest model językowy o zastosowaniu uniwersalnym pełniący rolę sędziego. Sztywny prompt zawiera zapytanie użytkownika oraz jeden fragment kandydata, a model zwraca ocenę.
Systemy do ponownego rankowania LLM dobrze radzą sobie z subtelnościami. Kodownik krzyżowy szuka głębokiego znaczeniowego powiązania, podczas gdy LLM może również sprawdzić, czy procedura jest kompletna, rozważyć sprzeczne dowody oraz odróżnić fragmenty zawierające rzeczywiste kroki od tych, które mają jedynie kontekst. Prompt może poinstruować go, aby karał niejasność lub faworyzował fragmenty z przykładami kodu.
Ten prompt sędziego wykorzystuje klienta Anthropic.
import anthropic
JUDGE_PROMPT = """\
You are a strict relevance judge. Given a user query and a candidate document chunk,
rate how well the chunk answers the query on a scale of 0 to 10.
Respond with ONLY a JSON object in this exact format:
{"score": <int>, "reason": "<one short sentence>"}
Query: {query}
Chunk: {chunk}"""
async def rerank_llm(
query: str,
candidates: list[dict],
top_n: int = 5,
) -> list[dict]:
"""
Expensive special forces. Uses an LLM to reason about nuance and completeness.
"""
client = anthropic.AsyncAnthropic()
scored = []
for c in candidates:
resp = await client.messages.create(
model="claude-opus-4-6",
max_tokens=128,
messages=[{
"role": "user",
"content": JUDGE_PROMPT.format(query=query, chunk=c["content"]),
}],
)
import json
try:
result = json.loads(resp.content[0].text)
scored.append({
**c,
"rerank_score": result["score"],
"reason": result.get("reason", "")
})
except (json.JSONDecodeError, KeyError):
# Fallback if the model fails to follow JSON instructions
scored.append({**c, "rerank_score": 0, "reason": "parse_error"})
# Sort by the LLM-assigned score descending
scored.sort(key=lambda x: x["rerank_score"], reverse=True)
return scored[:top_n]
Od modelu wymaga się podania powodu wraz z oceną. Napisanie najpierw uzasadnienia działa jak metoda łańcucha myśli i stabilizuje ostateczną wartość.
One zarabiają na zapytaniach o wysokiej wartości, w systemach o niskiej przepustowości, gdzie dokładność odpowiedzi usprawiedliwia czekanie, oraz w złożonych korpusach, gdzie istotność zależy od niuansów polityki, a nie tylko od dopasowania tematu.
Koszty są wysokie. Dla każdego kandydującego fragmentu wykonywany jest pełny wywołanie LLM, a nawet gdy wywołania odbywają się jednocześnie, naj wolniejsza odpowiedź determinuje wynik. Koszt jest znacznie wyższy niż w przypadku kodera krzyżowego, a wynik jest mniej deterministyczny.
Jak ponowne sortowanie wpływa na opóźnienie
Dodatkowy etap zmienia sposób działania systemu. Prowadzi to do dodatkowych wywołań modelu, dodatkowej serializacji treści tekstowych oraz dodatkowego czasu potrzebnego na połączenie sieciowe.
Użytkownicy nie przejmują się tym, jak jest organizowana wyszukiwania, ani nie są świadomi metryk odnoszących się do przywoływania informacji. Zwracają uwagę na jakość odpowiedzi oraz czas reakcji. Teoretycznie doskonały narzędzie do ponownego rankowania jest bezużyteczne, jeśli sprawia, że asystent działa zbyt wolno, by można mu było zaufać.
To narzędzie mierzy czas wykonywania funkcji ponownego rankowania.
def rerank_with_timing(
rerank_fn: callable,
query: str,
candidates: list[dict],
top_n: int = 5,
) -> tuple[list[dict], float]:
"""
Measure the exact cost of the reranking stage.
"""
t0 = time.perf_counter()
results = rerank_fn(query, candidates, top_n)
latency_ms = (time.perf_counter() - t0) * 1000
return results, latency_ms
Przesłanie dwudziestu kandydatów do szybkiego API cross-encodera może dodać od 150 do 300 milisekund, co zazwyczaj jest przyzwoitą ceną za lepszy kontekst. Przesłanie pięćdziesięciu kandydatów do oceniającego modelu LLM może dodać od dwóch do czterech sekund. Dodając jeszcze trzy sekundy na generowanie, użytkownik musi czekać siedem sekund, co przekracza akceptowalny czas w interfejsie czatowym.
Liczba ponownie sklasyfikowanych kandydatów wymaga dostosowania. Pierwsza dziesiątka jest szybka, ale może pominąć właściwy fragment, gdy pierwsza próba napotyka trudności. Pierwsza pięćdziesiątka obejmuje niemal wszystko, ale zużywa cały budżet opóźnień. W produkcji dla cross-encodera zazwyczaj optymalna jest pierwsza dwudziestka.
Kiedy ponowna klasyfikacja się opłaca
Należy dodać narzędzie do ponownej klasyfikacji, gdy pierwsza próba wyszukiwania daje wiele szumów, ale zazwyczaj znajduje odpowiednie dowody.
Jeśli ślady wskazują na to, że konkretny fragment dowodowy często pojawia się w kandydatach, ale na dziesiątym lub piętnastym miejscu, ponowna klasyfikacja natychmiast naprawi system. Jest to uzasadnione, gdy szerokie dokumenty regularnie przewyższają precyzyjne procedury, a jakość odpowiedzi w dużej mierze zależy od posiadania trzech najlepszych fragmentów w instrukcji, więc precyzja ma większe znaczenie niż prosty odzysk informacji.
Podręczniki, wewnętrzne asystenci polityki oraz narzędzia do rozwiązywania problemów prawie zawsze wymagają narzędzia do ponownej klasyfikacji. Wybór właściwego polecenia zamiast tego bliskiego błędnemu może zadecydować o tym, czy incydent zostanie rozwiązany, czy przekształci się w przerwę w dostępności.
Gdy ponowna klasyfikacja jest przesadą
Przy bardzo małym korpusie tekstu można jej uniknąć. Baza wiedzy składająca się z pięćdziesięciu stron może zostać w całości włączona i obsłużona za pomocą wyszukiwania wektorowego.
Jest to również bezsensowne, gdy pierwsza próba jest już prawie doskonała. W systemie wymagającym intensywnego wyszukiwania, gdzie BM25 niezawodnie umieszcza odpowiednią referencję API na pierwszym miejscu, kodownik krzyżowy jedynie spowalnia proces, nie zmieniając rezultatu.
Bardzo ograniczony budżet opóźnienia, np. w asystencie głosowym lub w funkcji uzupełniania kodu w czasie rzeczywistym, nie pozostawia miejsca na dodatkową fazę. Optymalizuj pierwszą próbę i zaakceptuj pewien poziom „szumu”. A gdy właściwy fragment zazwyczaj już znajduje się na pierwszym lub drugim miejscu, ranking nie jest tym, co cię powstrzymuje.
Warianty awarii, na które należy zwrócić uwagę
Narzędzia do ponownego rankingu poprawiają kolejność, ale przywołują ze sobą własne problemy. Bądź czujny na nie po wdrożeniu.
Uprzedzenie na rzecz szerokich podsumowań. Niektóre kodowniki krzyżowe są szkoleni na danych, które nagradzają kompleksowe odpowiedzi. Ogólne podsumowanie może osiągnąć lepszy wynik niż krótki, dokładny fragment kodu, ponieważ ma ono większe pokrycie słownictwa z zapytaniem.
Podwójne nagromadzenie danych. Jeśli korpus zawiera trzy wersje jednego przewodnika implementacyjnego, pierwsza próba pobierania prawdopodobnie pobra wszystkie trzy. System ponownego rankowania ocenia każdą z nich jako dobrze odpowiadającą na zapytanie i przyznaje im wysokie oceny. Trzy najwyżej ocenione elementy w oknie kontekstowym zawierają więc te same informacje, co uniemożliwia umieszczenie kolejnego odrębnego dowodu. Taki narzędzie pomocnicze eliminuje prawie identyczne dane.
def dedupe_candidates(
candidates: list[dict],
similarity_threshold: float = 0.85,
) -> list[dict]:
seen_tokens: list[set[str]] = []
deduped = []
for c in candidates:
tokens = set(c["content"].lower().split())
is_dup = False
for s in seen_tokens:
# Calculate simple Jaccard similarity
overlap = len(tokens & s) / max(len(tokens | s), 1)
if overlap >= similarity_threshold:
is_dup = True
break
if not is_dup:
deduped.append(c)
seen_tokens.append(tokens)
return deduped
Faworyzowanie starych dokumentów. Kodownik wielozadaniowy czyta jedynie tekst i nie ma pojęcia, który to rok. Dobrze przygotowana instrukcja z 2022 roku może osiągnąć lepsze wyniki niż pośpiesznie przygotowana wersja z 2026 roku, mimo że starszy dokument jest już całkowicie przestarzały. Metadane dodawane po ponownym rankingu korygują oceny pod kątem świeżości i statusu dokumentu.
from datetime import datetime, timezone
def apply_metadata_boost(
candidates: list[dict],
freshness_halflife_days: int = 90,
) -> list[dict]:
now = datetime.now(timezone.utc)
boosted = []
for c in candidates:
score = c.get("rerank_score", 0.0)
# Hard penalty for superseded documentation
if c.get("status") == "superseded":
score *= 0.4
# Gradual decay for older documents
updated = c.get("updated_at")
if updated:
age_days = (now - updated).days
decay_factor = max(0.5, 1 - age_days / (freshness_halflife_days * 2))
score *= decay_factor
boosted.append({**c, "rerank_score": score})
# Sort again based on the adjusted scores
boosted.sort(key=lambda x: x["rerank_score"], reverse=True)
return boosted
Eksplozja opóźnień. Jakość poprawia się po wdrożeniu, ale od czasu do czasu system zatrzymuje się na pięć sekund. Zazwyczaj dzieje się tak, gdy liczba kandydatów gwałtownie rośnie lub jakość zewnętrznej usługi do ponownego sortowania spada. Zawsze należy zaimplementować mechanizm awaryjny z czasem wygaśnięcia, który omija usługę do ponownego sortowania i zwraca wyniki uzyskane już w pierwszej fazie, gdy druga faza trwa zbyt długo.
Prawidłowa ocena usługi do ponownego sortowania
Zespoły często mierzą niewłaściwe wskaźniki, sprawdzając, czy właściwy fragment tekstu pojawia się wśród dwudziestu najlepszych wyników. Pierwsza faza wyszukiwania już to obejmuje.
Lepie jest zastanowić się, czy usługa do ponownego sortowania podniosła właściwy fragment na wystarczająco wysoką pozycję, aby miał znaczenie. Oznacza to mierzenie trafności wyników na pozycjach 1 i 3, kompletności odpowiedzi oraz skuteczności eliminacji duplikatów, a także monitorowanie kosztów i opóźnień związanych z każdym zapytaniem.
Stwórz narzędzie oceny, które porównuje prostą bazę pierwszego przetwarzania z kodownikiem krzyżowym oraz ponownym rankerem LLM, używając identycznych zbiorów kandydatów.
from dataclasses import dataclass
@dataclass
class RerankEvalCase:
query: str
expected_substring: str
category: str # e.g., "identifier", "procedure", "troubleshooting"
def eval_reranking(
cases: list[RerankEvalCase],
retrieve_fn: callable,
rerank_fns: dict[str, callable | None],
top_n: int = 3,
) -> dict:
"""
Compare multiple reranking strategies against a baseline.
Measures hit rate at top-N and tracks latency overhead.
"""
results = {}
for name, rerank_fn in rerank_fns.items():
hits = 0
total_latency = 0.0
by_type: dict[str, dict] = {}
for case in cases:
# Get the exact same starting candidates for every strategy
candidates = retrieve_fn(case.query)
if rerank_fn is not None:
reranked, lat = rerank_with_timing(
rerank_fn, case.query, candidates, top_n
)
total_latency += lat
else:
# Baseline: just take the top-N from first-pass retrieval
reranked = candidates[:top_n]
# Check if the expected evidence made it into the final prompt window
top_contents = [r["content"] for r in reranked]
found = any(case.expected_substring in c for c in top_contents)
hits += int(found)
# Track metrics by query category
by_type.setdefault(case.category, {"hit": 0, "total": 0})
by_type[case.category]["total"] += 1
by_type[case.category]["hit"] += int(found)
total = len(cases)
results[name] = {
"hit_rate": hits / total if total else 0,
"avg_latency_ms": total_latency / total if total else 0,
"by_type": {
t: {**v, "rate": v["hit"] / v["total"]}
for t, v in by_type.items()
},
}
return results
Zbiór testowy powinien obejmować różne typy zapytań: takie bogate w identyfikatory, gdzie liczy się dokładna sekwencja znaków, takie oparte na procedurach, gdzie ważny jest porządek kroków, oraz takie służące do rozwiązywania problemów, gdzie objawy wskazują na przyczyny podstawowe.
Liczby te pokazują dokładnie, co daje ponowna klasyfikacja. Jeśli kodownik krzyżowy zwiększa wskaźnik trafień w pierwszej trójce z 60% do 85% w ciągu 200 milisekund, należy go zastosować. Jeśli ponowna klasyfikacja za pomocą LLM osiąga 88% w ciągu trzech sekund, kodownik krzyżowy prawdopodobnie pozostanie bez zmian. Ponowna klasyfikacja musi być uzasadniona lepszymi ostatecznymi odpowiedziami w ramach realistycznego budżetu opóźnienia.
Rozsądny standardowy zestaw narzędzi
Niezawodna baza odniesienia dla systemu produkcyjnego opiera się na określonej sekwencji. Dziel dokumenty przy użyciu metody dzielenia uwzględniającej strukturę. Wykorzystaj hybrydowy mechanizm wyszukiwania, aby zebrać od dwudziestu do trzydziestu kandydatów. Usuń duplikaty, by pozbyć się niemal identycznego tekstu. Ocenić pozostałe elementy należy za pomocą kodera krzyżowego w celu określenia ich semantycznej istotności. Zastosuj wzmocnienie metadanych, które karze przestarzałe dokumenty. Następnie weź trzy do pięciu najlepszych fragmentów z ostatecznej uporządkowanej listy i stwórz odpowiedni prompt.
def two_stage_retrieve(
query: str,
retrieve_fn: callable,
top_k: int = 20,
top_n: int = 5,
) -> tuple[list[dict], dict]:
"""
The complete production pipeline for second-stage ranking.
"""
t0 = time.perf_counter()
# Stage 1: Fast hybrid retrieval
candidates = retrieve_fn(query)[:top_k]
# Clean up the candidate pool
candidates = dedupe_candidates(candidates)
# Stage 2: Cross-encoder rerank
# We score slightly more than top_n to allow metadata boosts to reorder the edges
score_limit = min(top_n * 2, len(candidates))
reranked = rerank_cross_encoder(query, candidates, top_n=score_limit)
# Apply business logic for freshness and status
final = apply_metadata_boost(reranked)[:top_n]
latency = (time.perf_counter() - t0) * 1000
trace = {
"query": query,
"first_pass_count": len(candidates),
"post_rerank_count": len(reranked),
"final_count": len(final),
"latency_ms": latency,
}
return final, trace
Standardowe rozwiązanie działa, ponieważ każde narzędzie wykonuje zadanie, do którego jest przeznaczone. Proces pobierania zajmuje się odzyskiwaniem odpowiednich kandydatów, ponowne sortowanie reguluje ich kolejność, a kodery krzyżowe zapewniają najlepszy stosunek jakości do opóźnienia w tej drugiej fazie.
Jedna uwaga: narzędzie do ponownego sortowania nie powinno być stosowane wyłącznie w celu naprawy uszkodzonych danych. Przy złym dzieleniu na fragmenty będzie jedynie dokładnie sortować te błędne fragmenty. W przypadku braku metadanych nie może odgadnąć, który dokument jest przestarzały. Najpierw należy naprawić proces pobierania i wydobywania danych.
Co nadal pozostaje nierozwiązane w przypadku ponownego sortowania
Narzędzie do ponownego sortowania zapewnia, że najsilniejsze dowody z uzyskanego zbioru trafiają do modelu. Nie może jednak podpowiedzieć, czy wyszukiwanie wektorowe w ogóle było właściwym rozwiązaniem. Weźmy na przykład pytanie o to, ilu użytkowników jest aktywnych w określonym regionie: kierowanie go do indeksu dokumentacji jest błędem, ponieważ ta liczba znajduje się w bazie danych SQL.
Jakość wydobywania informacji ma różne poziomy. Pierwszy etap polega na znalezieniu tekstu, a drugi na jego uporządkowaniu, jednak przed tymi krokami podejmuje się decyzję o tym, gdzie szukać. Kierowanie żądaniem pomiędzy wyszukiwaniem wektorowym, zapytaniami SQL oraz żywym wyszukiwaniem w sieci, w oparciu o intencję użytkownika, stanowi warstwę znajdującą się powyżej wszystkich powyższych elementów.