Zurück zur Startseite

RAG: smarte Dokumentensuche für Entwickler

Retrieval-Augmented Generation (RAG) kombiniert Suche nach relevanten Dokumentenfragmenten mit LLM-Antwortgenerierung. Der Artikel zerlegt die Phasen: Format-Parsing, semantisches Chunking, hybride Suche (vectors+BM25+RRF), Reranking und Optimierungen für große Datenbanken.

RAG in der Praxis: Code und Datenbanksuchalgorithmen
Advertisement 728x90

Retrieval-Augmented Generation: Intelligente Dokumentensuche für Unternehmen

RAG kombiniert Vektor-Suche mit generativer KI, um präzise Antworten aus privaten Dokumenten-Repositories zu liefern. Das System holt relevante Inhalte aus PDFs, Excel-Dateien, Code und mehr und generiert Erklärungen ausschließlich basierend auf den Quellmaterialien – was die Wahrscheinlichkeit von Halluzinationen deutlich reduziert.

Der Prozess beginnt mit der Vorverarbeitung: Dokumente werden analysiert, in intelligente Abschnitte unterteilt, wobei der Inhaltstyp berücksichtigt wird, und in Embeddings umgewandelt, die in einer Vektordatenbank gespeichert werden.

Textextraktion aus Dateiformaten

Das System unterstützt die Verarbeitung mehrerer Formate ohne Datenverlust. Jedes Format nutzt einen spezialisierten Extraktor.

Google AdInline article slot
SUPPORTED = {
    '.pdf':  lambda f: ''.join(p.extract_text() or '' for p in PyPDF2.PdfReader(f).pages),
    '.docx': lambda f: '\n'.join(p.text for p in Document(f).paragraphs),
    '.pptx': lambda f: '\n'.join(
        shape.text
        for slide in Presentation(f).slides
        for shape in slide.shapes
        if hasattr(shape, "text") and shape.text.strip()
    ),
    '.xlsx': extract_xlsx,
    '.csv':  extract_csv,
    # .txt, .json, .py, .js, etc.
}

Ausgabe ist sauberer, verarbeitbarer Text.

Intelligente Aufteilung nach Inhaltstyp

Text wird nicht nach festen Größen geteilt – stattdessen erfolgt eine semantische Segmentierung. Zuerst wird der Inhaltstyp erkannt:

def detect_content_type(text):
    if re.search(r'<table|</td>|<tr>', text, re.IGNORECASE):
        return 'table'
    tab_lines = sum(1 for line in text.splitlines() if '\t' in line)
    if tab_lines > max(3, len(text.splitlines()) * 0.4):
        return 'table'
    if re.search(r'function\s+\w+\s*\(|\bdef\s+\w+\s*\(|\bclass\s+\w+\b', text):
        return 'code'
    return 'prose'
  • Tabellen: Wiederholte Überschriften in jedem Abschnitt
  • Code: Aufteilung nach Funktionen/Klassen ohne logische Unterbrechung
  • Prosa: Semantische Grenzen mittels Satz-Embeddings

Für Tabellen:

Google AdInline article slot
def split_table_by_rows(text, chunk_size):
    lines = [l for l in text.splitlines() if l.strip()]
    header = lines[0]
    chunks = []
    cur_lines = [header]
    for line in lines[1:]:
        if sum(len(l) for l in cur_lines) + len(line) >= chunk_size:
            chunks.append('\n'.join(cur_lines))
            cur_lines = [header, line]
        else:
            cur_lines.append(line)
    if cur_lines:
        chunks.append('\n'.join(cur_lines))
    return chunks

Die semantische Aufteilung nutzt die Kosinus-Ähnlichkeit zwischen benachbarten Sätzen mit einem adaptiven Schwellwert (25. Perzentil).

Embedding-Erzeugung

Abschnitte werden mit Modellen wie text-embedding-3-small (1536 Dimensionen) vektorisiert. Vektoren und Metadaten werden in der Datenbank gespeichert.

def get_embeddings(texts, batch_size=100):
    embeddings = []
    for i in range(0, len(texts), batch_size):
        resp = requests.post(
            "https://openrouter.ai/api/v1/embeddings",
            headers=HEADERS,
            json={
                "model": "openai/text-embedding-3-small",
                "input": texts[i:i + batch_size],
                "encoding_format": "float"
            },
        ).json()
        embeddings.extend(d["embedding"] for d in resp.get("data", []))
    return np.array(embeddings)

Mehrstufige Suche

Die Abfrage wird in einen Vektor umgewandelt, mithilfe einer LLM in drei Varianten umformuliert und dann über eine hybride Suche verarbeitet.

Google AdInline article slot

Hybride Suche: Vektoren + BM25

  • Vektor-Suche: Kosinus-Ähnlichkeit im Embedding-Raum
  • BM25: TF-IDF mit k1=1.5, b=0.75 für exakte Treffer

Ergebnisse werden mittels RRF fusioniert:

# RRF-Fusion
for rank, idx in enumerate(sorted_by_vector):
    rrf_scores[idx] += 1.0 / (k_rrf + rank)
for rank, idx in enumerate(sorted_by_bm25):
    rrf_scores[idx] += bm25_normalized[idx] * (1.0 / (k_rrf + rank))

Nachrangung und Filter

Die Top-50-Kandidaten werden durch eine LLM bewertet, wobei 5–10 beste ausgewählt werden. Erzwungene Einbeziehung:

  • Zitate in Anführungszeichen
  • Zahlen/IDs (Regex: \b(№|N|number)\s*\d+\b)
quote_matches = re.findall(r'«([^»]+)»|"([^"+])"', query)
for match in quote_matches:
    quote = (match[0] or match[1]).strip()
    if len(quote) > 5:
        for i, chunk in enumerate(chunks_data):
            if quote in chunk['content']:
                forced_indices.append(i)
                break

Leistungssteigerungen

Hierarchische Suche

Zuerst werden die relevantesten Dokumente über Annotationen identifiziert; danach werden die Abschnitte innerhalb dieser Dokumente durchsucht.

doc_summaries = [chunks_data_by_doc[doc_id][0]['content'][:500] for doc_id in all_doc_ids]
doc_embs = get_embeddings(doc_summaries)
sims = np.dot(doc_embs_normalized, query_embedding_normalized)
top_doc_ids = [all_doc_ids[i] for i in np.argsort(sims)[::-1][:5]]
chunks_data = [ch for ch in chunks_data if ch['doc_id'] in top_doc_ids]

Iterative Suche

Bei komplexen Abfragen werden mehrere Durchläufe mit Feedback der LLM zur Feinjustierung der Ergebnisse durchgeführt.

Wichtige Erkenntnisse

  • RAG minimiert Halluzinationen, indem Antworten ausschließlich aus relevanten Abschnitten generiert werden
  • Hybride Suche (Vektoren + BM25 + RRF) steigert die Genauigkeit um 20–30 % gegenüber rein vektorbasierter Suche
  • Semantische Aufteilung bewahrt Kontext bei Tabellen und Code und reduziert Rauschen
  • Hierarchischer Ansatz beschleunigt die Suche in großen Korpora (>10.000 Dokumente)
  • Erzwungene Filter sorgen dafür, dass Fakten mit Zahlen oder zitierten Texten gefunden werden

— Editorial Team

Advertisement 728x90

Weiterlesen