Python & AI Tutorials Logo
LangChain & LangGraph

10. Intelligenteres Retrieval: Filter, Schwellenwerte und MMR

In Kapitel 9 haben wir ein einfaches RAG-System aufgebaut. Es war eine Pipeline, die Dokumente in Chunks aufteilte, sie in ChromaDB einbettete und – wenn eine Nutzerfrage eintraf – relevante Chunks abrief und in den Prompt einbezog. Dadurch konnte das LLM Fragen zu Informationen beantworten, mit denen es nie trainiert wurde, etwa interne Unternehmensdokumente und Produkthandbücher. Alles schien einwandfrei zu funktionieren.

Doch sobald Sie eine größere Vielfalt an Fragen stellen, treten schnell Schwächen zutage. Sie fragen nach der Rückerstattungsrichtlinie für Verbraucher, und Inhalte aus dem Mitarbeitershop mischen sich darunter, oder Sie fragen nach etwas, das in keinem Dokument steht, und das LLM erfindet eine plausibel klingende Antwort, oder Sie erhöhen die Anzahl der Suchergebnisse, und die Antwortqualität sinkt sogar.

In diesem Kapitel gehen wir diese drei Probleme nacheinander an. Wir verwenden Metadaten-Filterung, um einzuschränken, welche Dokumente durchsucht werden, Schwellenwerte für Ähnlichkeitswerte, um Ergebnisse auszuschließen, die nichts mit der Frage zu tun haben, und K- und MMR-Tuning, um die Menge und Vielfalt der an das LLM übergebenen Chunks zu verbessern. Es werden keine neuen Tools benötigt. Wir verfeinern die Konfiguration und Verwendung von similarity_search() und dem Chroma-Vector-Store, die Sie bereits kennen.

10.1) Was stimmt mit unserem RAG nicht?

In Kapitel 9 haben wir nur zwei Dokumente in den Vector Store gelegt – eine Rückerstattungsrichtlinie und eine Versandrichtlinie – und jedes Dokument behandelte ein eigenes Thema. Wir haben außerdem nur Fragen getestet, bei denen Antworten in den Dokumenten entweder klar vorhanden oder klar nicht vorhanden waren. Diesmal erstellen wir ein realistischeres Szenario. Wir fügen dem Vector Store einen Leitfaden für den Mitarbeitershop hinzu. Auch dieses Dokument enthält rückerstattungsbezogene Inhalte, ist aber für Mitarbeiter gedacht, nicht für allgemeine Verbraucher. Anschließend stellen wir verschiedene Fragen und sehen, welche Probleme auftreten.

Datenvorbereitung: Hinzufügen des Mitarbeitershop-Leitfadens

Hier sind die beiden Dokumente aus Kapitel 9 zur Referenz.

data/docs/refund_policy.md:

markdown
# Refund Policy
 
**Effective Date**: January 1, 2026
 
## Standard Returns
 
All physical products may be returned within 30 days of purchase for a full refund.
The original receipt or order confirmation email is required. Items must be in their
original packaging and unused condition.
 
After 30 days, returns are accepted for store credit only. Store credit does not expire.
 
## Digital Products
 
Digital products (software licenses, e-books, online courses) are non-refundable
once the download or access link has been activated. If you experience technical
issues preventing access, contact support within 7 days for a replacement or refund.
 
## Defective Items
 
Defective items may be returned at any time for a full refund or replacement.
Please include a description of the defect. Shipping costs for defective returns
are covered by the company.
 
## Subscription Services
 
Monthly subscriptions may be cancelled at any time. Refunds are prorated based on
the remaining days in the billing cycle. Annual subscriptions may be refunded in full
within the first 14 days. After 14 days, no refund is available but access continues until the end of the billing period.

data/docs/shipping_info.md:

markdown
# Shipping Information
 
## Domestic Shipping
 
Standard shipping (5-7 business days): Free on orders over $50, otherwise $5.99.
Express shipping (2-3 business days): $12.99.
Overnight shipping (next business day): $24.99.
 
## International Shipping
 
International orders are shipped via tracked airmail. Delivery times vary by
destination, typically 10-21 business days. International shipping costs are
calculated at checkout based on weight and destination.
 
Customs duties and import taxes are the responsibility of the buyer and are not included in the shipping cost.
 
## Order Tracking
 
All orders include a tracking number sent via email within 24 hours of shipment.
Track your order through the tracking link in your email or through the carrier's website.
 
## Lost or Damaged Packages
 
If your package is lost or arrives damaged, contact support within 48 hours.
We will ship a replacement at no additional cost. For damaged items, please
provide photos of the damage and packaging.

Fügen Sie hier den Mitarbeitershop-Leitfaden hinzu.

Erstellen Sie data/docs/employee_store.md:

markdown
# Employee Store Guide
 
## Eligibility and Benefits
 
Employees can purchase company products at a 30% discount through the internal employee store.
The monthly purchase limit is $500, and payment can be made via payroll deduction or benefit points.
 
## Ordering and Shipping
 
Employee store orders are placed through the internal portal, and delivery is only available to the company address.
Orders are delivered within 3-5 business days, and shipping is free.
 
## Refund Policy
 
Refunds are available within 7 days of purchase for unopened items only.
Cash refunds are not available; refunds are credited as benefit points.
After opening, only exchanges are allowed, limited to one exchange per identical product.
 
## Contact
 
For employee store inquiries, please contact HR at hr@acme.com.

Das Verzeichnis data/docs/ enthält jetzt drei Dateien: refund_policy.md, shipping_info.md, employee_store.md. Führen Sie das Ingestion-Skript aus Kapitel 9 erneut aus, um den Vector Store neu aufzubauen.

python
# ingest.py — dieselbe Ingestion-Pipeline wie in Kapitel 9
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
 
loader = DirectoryLoader(
    "data/docs/", glob="**/*.md",
    loader_cls=TextLoader, loader_kwargs={"encoding": "utf-8"},
)
documents = loader.load()
print(f"Loaded {len(documents)} documents")
 
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=80,
    separators=["\n## ", "\n\n", "\n", " ", ""],
)
chunks = text_splitter.split_documents(documents)
print(f"Created {len(chunks)} chunks")
 
embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")
 
vector_store = Chroma.from_documents(
    documents=chunks,
    embedding=embedding_model,
    persist_directory="data/chroma_db",
    collection_name="company_docs",
)
print(f"Stored {len(chunks)} chunks in ChromaDB")

Ausgabe:

Loaded 3 documents
Created 12 chunks
Stored 12 chunks in ChromaDB

Problem 1: Irrelevante Dokumente mischen sich in die Suchergebnisse

Suchen wir nach den Rückerstattungsbedingungen.

python
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
 
embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = Chroma(
    persist_directory="data/chroma_db",
    collection_name="company_docs",
    embedding_function=embedding_model,
)
 
results = vector_store.similarity_search("What are the refund conditions?", k=3)
 
for i, doc in enumerate(results):
    source = doc.metadata["source"]
    print(f"Result {i+1} [{source}] {doc.page_content[:80]}...")

Ausgabe:

Result 1 [data/docs/refund_policy.md] ## Standard Returns
All physical products may be returned within 30 days of pur...
 
Result 2 [data/docs/employee_store.md] ## Refund Policy
Refunds are available within 7 days of purchase for unopened i...
 
Result 3 [data/docs/refund_policy.md] ## Subscription Services
Monthly subscriptions may be cancelled at any time. Re...

Betrachten Sie Result 2. Ein Kunde hat nach den Rückerstattungsbedingungen gefragt, und die Rückerstattungsrichtlinie des Mitarbeitershops ist in den Ergebnissen enthalten. Die Verbraucher-Rückerstattungsrichtlinie erlaubt vollständige Rückerstattungen innerhalb von 30 Tagen, aber der Mitarbeitershop erlaubt Rückerstattungen nur innerhalb von 7 Tagen für ungeöffnete Artikel, wobei die Rückerstattungen als Benefit-Punkte gutgeschrieben werden. Wenn beide Richtlinien gemeinsam an das LLM übergeben werden, könnten dem Kunden Rückerstattungsbedingungen genannt werden, die nur für Mitarbeiter gelten.

Problem 2: Ergebnisse werden zurückgegeben, selbst wenn keine relevanten Inhalte existieren

Fragen wir nun nach etwas, das nirgendwo in unseren Dokumenten vorkommt. Wir verwenden similarity_search_with_score(), das wir in Kapitel 9 gelernt haben, um auch die Distanzwerte zu sehen. In ChromaDB bedeuten niedrigere Distanzwerte eine höhere Ähnlichkeit.

python
results = vector_store.similarity_search_with_score(
    "What is the hiring process at this company?", k=3
)
 
for doc, score in results:
    source = doc.metadata["source"]
    print(f"[dist={score:.4f}] [{source}] {doc.page_content[:80]}...")

Ausgabe:

[dist=1.4648] [data/docs/employee_store.md] ## Refund Policy
Refunds are available within 7 days of purchase for unopened i...
 
[dist=1.4699] [data/docs/employee_store.md] ## Eligibility and Benefits
Employees can purchase company products at a 30% di...
 
[dist=1.4743] [data/docs/refund_policy.md] ## Subscription Services
Monthly subscriptions may be cancelled at any time. Re...

Es gibt nirgendwo Informationen über den Einstellungsprozess. Die Distanzwerte liegen alle über 1,4 und zeigen damit eine sehr geringe Ähnlichkeit, dennoch hat similarity_search_with_score() drei Chunks zurückgegeben. Sehen wir uns an, welche Antwort die RAG-Chain produziert, wenn diese Chunks an sie übergeben werden.

python
from rag_chain import build_rag_chain  # RAG-Chain aus Kapitel 9
 
chain = build_rag_chain()
answer = chain.invoke("What is the hiring process at this company?")
print(answer)

Ausgabe:

I don't have enough information to answer that question.

Das LLM antwortete, dass es nicht genügend Informationen habe, um die Frage zu beantworten. Das liegt daran, dass wir die Anweisung „sage, dass du nicht genügend Informationen hast" in den System-Prompt von build_rag_chain() aufgenommen haben. Allerdings kann das LLM diese Einschätzung nicht immer treffen. Wenn die abgerufenen Chunks Phrasen enthalten, die mit der Frage zusammenzuhängen scheinen, kann das LLM auf Basis dieses Inhalts eine falsche Antwort generieren.

Problem 3: Hilft mehr Suchergebnisse immer?

Sie denken vielleicht „wäre mehr Kontext nicht besser?". Wenn Sie k von 3 auf 10 erhöhen, steigt zwar die Wahrscheinlichkeit, dass benötigte Chunks enthalten sind. Aber gleichzeitig kommen auch mehr irrelevante Chunks hinzu. Da das LLM all diese Chunks als Kontext erhält und daraus Antworten generiert, können unnötige oder falsche Informationen in der Antwort landen. Mehr Kontext bedeutet nicht zwangsläufig bessere Antworten.

Zudem werden alle abgerufenen Chunks als Tokens an das LLM übergeben. Mit wachsendem k steigen die Kosten der API-Aufrufe und die Antwortzeiten werden langsamer.

Wir haben drei Probleme identifiziert. Lösen wir sie nun nacheinander.

10.2) Metadaten-Filterung: Eingrenzung des Suchraums

Als wir in Problem 1 nach den Rückerstattungsbedingungen suchten, erschienen sowohl die Kunden-Rückerstattungsrichtlinie als auch die Rückerstattungsrichtlinie des Mitarbeitershops gemeinsam. Das geschah, weil wir similarity_search() nicht mitgeteilt haben, in welchen Dokumenten gesucht werden soll.

Metadaten-Filterung hängt jedem Chunk Attribute wie Kategorie, Quelle und Veröffentlichungsjahr an und filtert dann die Chunks anhand dieser Attribute, bevor die Ähnlichkeitssuche ausgeführt wird. Nur Chunks, die den Bedingungen entsprechen, durchlaufen die Ähnlichkeitsberechnung. Sie erfüllt eine ähnliche Rolle wie die WHERE-Klausel in SQL.

10.2.1) Dokumentinhalt vs. Dokument-Metadaten

Das Document-Objekt, das wir in Kapitel 9 kennengelernt haben, enthält zwei Dinge:

  • page_content: Der Text selbst. Er wird in einen Vektor eingebettet und ist das, worauf die Ähnlichkeitssuche operiert.
  • metadata: Ein Dictionary, das Attribute wie Quelle und Kategorie enthält. Diese Werte werden nicht eingebettet.

Die Ähnlichkeitssuche operiert auf page_content, während die Metadaten-Filterung auf den metadata-Informationen operiert.

10.2.2) Neuaufbau des Vector Stores: Hinzufügen von Metadaten

Wir fügen ein category-Attribut zum Filtern hinzu und bauen den Vector Store neu auf. Wir müssen lediglich Code zur Zuweisung von Metadaten zum ingest.py aus 10.1 hinzufügen.

python
# ingest_with_metadata.py
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
 
# Schritt 1: Dokumente laden (wie in 10.1)
loader = DirectoryLoader(
    "data/docs/", glob="**/*.md",
    loader_cls=TextLoader, loader_kwargs={"encoding": "utf-8"},
)
documents = loader.load()
print(f"Loaded {len(documents)} documents")
 
# Schritt 2: [NEU] Kategorie-Metadaten basierend auf dem Dateinamen zuweisen
CATEGORY_MAP = {
    "refund_policy.md": "customer",
    "shipping_info.md": "customer",
    "employee_store.md": "employee",
}
for doc in documents:
    filename = doc.metadata["source"].split("/")[-1]
    doc.metadata["category"] = CATEGORY_MAP.get(filename, "unknown")
 
# Schritt 3: In Chunks aufteilen (wie in 10.1)
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=80,
    separators=["\n## ", "\n\n", "\n", " ", ""],
)
chunks = text_splitter.split_documents(documents)
print(f"Created {len(chunks)} chunks")
 
# Schritt 4: Vector Store aufbauen (wie in 10.1)
embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")
 
vector_store = Chroma.from_documents(
    documents=chunks,
    embedding=embedding_model,
    persist_directory="data/chroma_db",
    collection_name="company_docs",
)
print(f"Stored {len(chunks)} chunks in ChromaDB")

Ausgabe:

Loaded 3 documents
Created 12 chunks
Stored 12 chunks in ChromaDB

Der einzige Unterschied zum ursprünglichen ingest.py ist das Hinzufügen von category-Metadaten zu jedem Dokument.

10.2.3) Filter auf die Suche anwenden

Wir können nun den Parameter filter von similarity_search() verwenden, um einzuschränken, welche Dokumente durchsucht werden. Wir suchen mit derselben Abfrage wie in Problem 1, fügen aber einen Filter hinzu, um nur kundenorientierte Dokumente zu durchsuchen.

python
results = vector_store.similarity_search(
    "What are the refund conditions?",
    k=3,
    filter={"category": "customer"},
)
 
for i, doc in enumerate(results):
    source = doc.metadata["source"]
    category = doc.metadata["category"]
    print(f"Result {i+1} [{category}] [{source}] {doc.page_content[:80]}...")

Ausgabe:

Result 1 [customer] [data/docs/refund_policy.md] ## Standard Returns
All physical products may be returned within 30 days of pur...
 
Result 2 [customer] [data/docs/refund_policy.md] ## Subscription Services
Monthly subscriptions may be cancelled at any time. Re...
 
Result 3 [customer] [data/docs/refund_policy.md] ## Digital Products
Digital products (software licenses, e-books, online course...

Die Rückerstattungsrichtlinie des Mitarbeitershops, die sich in Problem 1 untergemischt hatte, ist nicht mehr enthalten. Das liegt daran, dass wir festgelegt haben, dass nur Chunks mit category gleich "customer" durchsucht werden sollen.

Mehrere Bedingungen kombinieren

Das obige Beispiel verwendete eine einzelne Bedingung (category gleich "customer"). Wenn mehrere Bedingungen gleichzeitig angewendet werden müssen, können Sie sie mit logischen Operatoren wie $and und $or kombinieren.

python
# $and: nur Chunks, die ALLE Bedingungen erfüllen
filter={
    "$and": [
        {"category": "customer"},
        {"source": "data/docs/refund_policy.md"},
    ]
}
 
# $or: Chunks, die IRGENDEINE Bedingung erfüllen
filter={
    "$or": [
        {"source": "data/docs/refund_policy.md"},
        {"source": "data/docs/shipping_info.md"},
    ]
}

Weitere Operatoren sind $ne (ungleich), $gt (größer als) und $lt (kleiner als). Die vollständige Liste der Operatoren finden Sie in der ChromaDB-Dokumentation.

10.3) Ähnlichkeitsschwellenwerte: Ausschluss von Ergebnissen mit geringer Relevanz

In Problem 2 wurden, als wir nach dem Einstellungsprozess fragten, Chunks mit Distanzwerten über 1,4 zurückgegeben. In ChromaDB deuten so hohe Distanzwerte auf nahezu keine Relevanz hin. Dennoch wurden sie abgerufen. Das liegt daran, dass similarity_search() immer k Ergebnisse zurückgibt.

Dieses Problem lässt sich durch das Setzen eines Distanz-Schwellenwerts lösen. Ergebnisse, die weiter als der Schwellenwert entfernt sind, werden nicht in den an das LLM übergebenen Kontext aufgenommen.

Welchen Schwellenwert sollten wir also setzen? Vergleichen wir zunächst die Distanzwerte zwischen Fragen, die relevante Inhalte in den Dokumenten haben, und Fragen, die das nicht haben.

10.3.1) Distanzen vergleichen: Fragen mit und ohne relevante Inhalte

python
queries = [
    "Can I cancel a subscription service?",           # relevante Inhalte vorhanden
    "What is the hiring process at this company?",     # keine relevanten Inhalte
]
 
for query in queries:
    print(f"\nQuery: {query}")
    results = vector_store.similarity_search_with_score(query, k=1)
    for doc, score in results:
        print(f"  [dist={score:.4f}] {doc.page_content[:80]}...")

Ausgabe:

Query: Can I cancel a subscription service?
  [dist=0.7511] ## Subscription Services
Monthly subscriptions may be cancelled at any time. Re...
 
Query: What is the hiring process at this company?
  [dist=1.4648] ## Refund Policy
Refunds are available within 7 days of purchase for unopened i...

Fragen mit relevanten Inhalten haben Distanzwerte um 0,75, während Fragen ohne relevante Inhalte Werte über 1,4 haben. Testen Sie mehrere solcher Fragen und wählen Sie dann einen Schwellenwert, der die beiden Fälle klar trennt. Der richtige Schwellenwert kann je nach Embedding-Modell, der Natur Ihrer Dokumente und der Chunk-Größe variieren, daher ist es am besten, ihn durch direktes Testen mit Ihren eigenen Daten zu bestimmen.

10.3.2) Suchergebnisse nach Distanzschwellenwert filtern

Sobald ein Schwellenwert gesetzt ist, bauen wir eine Funktion, die Ergebnisse herausfiltert, die den Schwellenwert überschreiten. Nur Chunks, die den Filter überstehen, werden in den an das LLM übergebenen Kontext aufgenommen. Wenn keine Chunks unter dem Schwellenwert verbleiben, überspringen wir den LLM-Aufruf vollständig und antworten mit „I don't have enough information to answer that question."

python
from langchain_openai import ChatOpenAI
 
llm = ChatOpenAI(model="gpt-5-mini")
 
def retrieve_or_abstain(query: str, max_distance: float = 1.0, k: int = 3):
    """Gibt nur Chunks unter dem Distanzschwellenwert zurück. Gibt None zurück, wenn keiner besteht."""
    scored = vector_store.similarity_search_with_score(query, k=k)
    good = [doc for doc, dist in scored if dist <= max_distance]
    return good or None
 
def safe_answer(query: str) -> str:
    docs = retrieve_or_abstain(query)
    if docs is None:
        return "I don't have enough information to answer that question."
 
    context = "\n\n".join(d.page_content for d in docs)
    prompt = (
        "Answer the question using ONLY the context below.\n\n"
        f"Context:\n{context}\n\nQuestion: {query}"
    )
    return llm.invoke(prompt).content

Testen wir mit denselben Fragen wie zuvor.

python
# Frage mit relevanten Inhalten
print(safe_answer("Can I cancel a subscription service?"))
print("---")
# Frage ohne relevante Inhalte
print(safe_answer("What is the hiring process at this company?"))

Ausgabe:

Yes. Monthly subscriptions may be cancelled at any time, and refunds are prorated
based on the remaining days in the billing cycle. Annual subscriptions may be refunded
in full within the first 14 days...
---
I don't have enough information to answer that question.

10.4) K und MMR: Steuerung der Menge und Vielfalt von Suchergebnissen

In diesem Abschnitt vergleichen wir direkt, wie sich Suchergebnisse mit unterschiedlichen k-Werten verändern, und lernen die MMR-Suche kennen, die die Vielfalt der Ergebnisse verbessert.

10.4.1) Was passiert, wenn Sie K erhöhen?

In Problem 3 sagten wir, dass das Erhöhen von k auch mehr irrelevante Chunks mit sich bringt. Überprüfen wir das. Wir suchen mit k=10 und untersuchen die Distanzwerte jedes Chunks.

python
results = vector_store.similarity_search_with_score(
    "What are the refund conditions?",
    k=10,
    filter={"category": "customer"},
)
 
for i, (doc, dist) in enumerate(results, 1):
    source = doc.metadata["source"].split("/")[-1]
    print(f"{i:>2}. [dist={dist:.4f}] [{source}] {doc.page_content[:80]}...")

Ausgabe:

 1. [dist=0.7798] [refund_policy.md] ## Standard Returns
**Effective Date**: January 1, 2026
All physical products ma...
 
 2. [dist=0.8203] [refund_policy.md] ## Defective Items
Defective items may be returned at any time for a full refun...
 
 3. [dist=0.9353] [refund_policy.md] ## Subscription Services
Monthly subscriptions may be cancelled at any time. Re...
 
 4. [dist=1.4249] [shipping_info.md] ## Lost or Damaged Packages
If your package is lost or arrives damaged, contact...
 
 5. [dist=1.5516] [shipping_info.md] ## Domestic Shipping
Standard shipping (5-7 business days): Free on orders over...
...

Die obersten 3 Ergebnisse haben Distanzen unter 1,0 und beziehen sich alle auf Rückerstattungen. Ab dem 4. Ergebnis springen die Distanzen über 1,4, und Chunks, die nichts mit den Rückerstattungsbedingungen zu tun haben – etwa Versandinformationen –, tauchen auf. Mit k=10 werden all diese Chunks an das LLM übergeben.

Die Kosten der Erhöhung von k sind wie folgt:

  • Rauschen: Niedriger eingestufte Chunks können vollständig unabhängig von der Frage sein. Wenn solche Chunks in den Prompt aufgenommen werden, kann das LLM unnötige oder falsche Informationen in seine Antwort einbeziehen.
  • Höhere Kosten: Mehr Chunks bedeuten mehr Tokens, die an das LLM gesendet werden, was die Kosten der API-Aufrufe erhöht.
  • Langsamere Antworten: Mehr zu verarbeitende Tokens bedeuten längere Antwortzeiten.

10.4.2) MMR: Sowohl Relevanz als auch Vielfalt erreichen

In realen Umgebungen ist es mit wachsendem Dokumentbestand üblich, dass mehrere Chunks mit ähnlichem Inhalt entstehen. Die chunk_overlap-Einstellung aus Kapitel 9, die benachbarte Chunks einen Teil ihres Inhalts teilen ließ, ist ebenfalls eine Quelle für Duplikate. In solchen Fällen könnte sogar eine Suche mit k=3 drei nahezu identische Chunks zurückgeben.

MMR (Maximum Marginal Relevance) ist eine Suchmethode, die verhindert, dass Ergebnisse zugunsten desselben Inhalts verzerrt werden. Die Standard-Ähnlichkeitssuche gibt die k Chunks zurück, die der Abfrage am nächsten sind, was dazu führen kann, dass sich ähnliche Chunks an der Spitze ballen. MMR priorisiert Chunks, die sowohl für die Abfrage relevant als auch von den bereits ausgewählten Ergebnissen verschieden sind.

So funktioniert es:

  1. Genau wie bei der Standard-Ähnlichkeitssuche ruft sie zunächst fetch_k Kandidaten-Chunks ab, die der Abfrage am nächsten sind.
  2. Sie wählt den Chunk, der der Abfrage am nächsten ist, als erstes Ergebnis aus.
  3. Aus den verbleibenden Kandidaten wählt sie den nächsten Chunk aus, der für die Abfrage relevant, aber inhaltlich verschieden von den bereits ausgewählten Chunks ist.
  4. Schritt 3 wird wiederholt, bis k Chunks ausgewählt sind.

Das Ergebnis ist eine Menge von Chunks, die Relevanz beibehalten und gleichzeitig überlappende Inhalte vermeiden.

python
results_mmr = vector_store.max_marginal_relevance_search(
    "What are the refund conditions?",
    k=3,
    fetch_k=10,
)
 
for i, doc in enumerate(results_mmr):
    print(f"{i+1}. {doc.page_content[:80]}...")

Ausgabe:

1. ## Standard Returns
All physical products may be returned within 30 days of pur...
 
2. ## Defective Items
Defective items may be returned at any time for a full refun...
 
3. ## Digital Products
Digital products (software licenses, e-books, online course...

Bei den aktuellen Daten gibt es keinen großen Unterschied zur Standardsuche, weil der Datensatz klein ist. Wenn die Dokumente jedoch auf Hunderte oder Tausende anwachsen, ballen sich ähnliche Chunks häufig an der Spitze der Ergebnisse, und genau hier wird MMR sehr nützlich. fetch_k ist die Größe des Kandidatenpools, aus dem MMR auswählt – ein üblicher Ansatz ist es, mit 10–20 zu beginnen.

10.4.3) Wann man mit dem Tuning aufhören sollte

Es gibt mehrere Parameter zum Anpassen: k, fetch_k, Metadaten-Filter, Distanzschwellenwerte und mehr. Diese einfachen Regeln helfen Ihnen, effizient zu tunen:

  1. Bereiten Sie mehrere Fragen vor – einige mit relevanten Inhalten in den Dokumenten und einige ohne.
  2. Führen Sie die Fragen aus und untersuchen Sie direkt die abgerufenen Chunks.
  3. Wenn ein Problem gefunden wird, ändern Sie jeweils nur einen Parameter und testen Sie mit denselben Fragen erneut.

Wenn Fragen mit relevanten Inhalten korrekte Antworten produzieren und Fragen ohne relevante Inhalte zu einer Enthaltung führen, haben Sie eine Grundqualität erreicht. Die Einstellungen von Anfang an perfekt zu treffen, ist nicht einfach. Reagieren Sie auf Probleme, die während der tatsächlichen Nutzung entdeckt werden, und verbessern Sie schrittweise.