Zum Hauptinhalt springen
Lektion 13 von 18 · Embeddings, RAG und semantische Suche

Abruf und Generierung

Lektion 13 von 18 Aktualisiert am 2. November 2026 Entwickler
Auf dieser Seite
  1. Vom Vektor zur Antwort
  2. Aufbau des RAG-Prompts
  3. Generierung der Antwort
  4. Einbettung des Flusses in eine Klasse
  5. Drei Prompt-Varianten nach Anwendungsfall
  6. Kernaussagen

Vom Vektor zur Antwort

Sie wissen, wie man Dokumente indiziert und diejenige abrufen, die einer Frage am nächsten stehen. Es bleibt noch ein Schritt: Die Umwandlung einer Liste von Textabschnitten in eine lesbare Antwort. Dies bedeutet, den abgerufenen Kontext in einen Prompt einzubinden, sodass das LLM eine Antwort auf Basis Ihrer Daten formuliert, anstatt sich auf seine Trainingsdaten zu stützen. Es sind nur wenige Zeilen Code erforderlich, doch dies macht einen entscheidenden Unterschied für die Qualität aus. Eine gut präsentierte Kontextinformation und eine klare Anweisung sind ebenso wichtig wie die Präzision des Abrufs.

Aufbau des RAG-Prompts

Die Struktur des Prompts ist einfach, aber jede Entscheidung hat Konsequenzen. Nummerieren Sie die Abschnitte und kennzeichnen Sie sie mit ihrer Quelle, damit das Modell zutreffend zitieren kann. Trennen Sie sie durch ein sichtbares Delimiter, ---, damit das Modell zwei widersprüchliche Dokumente nicht zu einer einzigen Aussage verschmilzt. Wichtigster Teil ist die Anweisung: Sie muss explizit beschränken, dass die Antwort nur auf dem bereitgestellten Kontext basiert, und dem Modell erlauben, anzugeben, dass es die Information nicht kennt. Ohne diese Erlaubnis füllt ein LLM bei fehlenden Informationen die Lücke mit dem, was plausibel erscheint. Das ist der genaue Mechanismus von Halluzinationen.

def build_rag_prompt(question, retrieved_chunks):
    """Build the RAG prompt with retrieved context."""
    # Format the context
    context_parts = []
    for i, chunk in enumerate(retrieved_chunks):
        source = chunk.get("source", "unknown source")
        context_parts.append(f"[Document {i+1} - {source}]\n{chunk['text']}")

    context = "\n\n---\n\n".join(context_parts)

    prompt = f"""You are an expert assistant. Answer the question using
ONLY the context provided below. If the information is not in the
context, say so clearly.

## Context

{context}

## Question

{question}

## Answer"""

    return prompt

Generierung der Antwort

Der Aufruf zur Generierung ist unkompliziert: Eine Systemnachricht, die die Rolle wiederholt, und eine Benutzernachricht, die den zusammengesetzten Prompt enthält. Achten Sie auf den Parameter temperature, der hier auf 0,1 eingestellt ist. Bei kreativen Texten wünschen Sie Vielfalt; bei RAG das Gegenteil. Sie möchten, dass das Modell so nah wie möglich am Kontext bleibt, und Sie möchten, dass zwei Durchläufe derselben Frage die gleiche Antwort produzieren. Eine hohe Temperatur würde das Modell dazu verleiten, umzuphrasieren, zu extrapolieren und von der Quelle abzuweichen.

from mistralai.client import Mistral

client = Mistral(api_key=api_key)

def generate_answer(question, retrieved_chunks, model="mistral-large-latest"):
    """Generate a RAG answer with Mistral."""
    prompt = build_rag_prompt(question, retrieved_chunks)

    response = client.chat.complete(
        model=model,
        messages=[
            {
                "role": "system",
                "content": "You are a precise assistant that answers based on the provided context."
            },
            {
                "role": "user",
                "content": prompt,
            }
        ],
        temperature=0.1,  # Low temperature for factual answers
    )

    return response.choices[0].message.content

# Example
question = "What are the main steps of RAG?"
results = search_index(question, index, chunks, k=3)
answer = generate_answer(question, results)
print(answer)

Einbettung des Flusses in eine Klasse

Verstreute Funktionen eignen sich für die Erkundung, nicht jedoch für die Bereitstellung. Die Klasse RAGPipeline bündelt den Zustand – die Abschnitte, die Einbettungsmatrix und den FAISS-Index, der einmal beim Instantiieren erstellt wird – und drei Operationen: retrieve kodiert die Frage und abfragt den Index, generate montiert den Prompt und ruft das Modell auf, und ask verknüpft beide. Beachten Sie, was ask zurückgibt: Nicht einen String, sondern ein Wörterbuch, das die Antwort, die Liste der Quellen und die Anzahl der verwendeten Abschnitte enthält. Diese Nachvollziehbarkeit unterscheidet einen überprüfbaren Assistenten von einer Black Box. An einem Tag, an dem ein Nutzer eine Antwort hinterfragt, werden Sie wissen, welche Passagen er verwendet hat.

import numpy as np
import faiss
from mistralai.client import Mistral

client = Mistral(api_key=api_key)

class RAGPipeline:
    """Complete RAG pipeline with Mistral and FAISS."""

    def __init__(self, chunks, embeddings):
        self.chunks = chunks
        self.embeddings = embeddings.astype(np.float32)

        # Create the FAISS index
        dimension = embeddings.shape[1]
        self.index = faiss.IndexFlatL2(dimension)
        self.index.add(self.embeddings)

    def retrieve(self, question, k=3):
        """Step 1: retrieve relevant chunks."""
        response = client.embeddings.create(
            model="mistral-embed",
            inputs=[question],
        )
        query_vector = np.array(
            [response.data[0].embedding], dtype=np.float32
        )

        distances, indices = self.index.search(query_vector, k)

        results = []
        for dist, idx in zip(distances[0], indices[0]):
            if idx >= 0:
                results.append({
                    "text": self.chunks[idx]["text"],
                    "source": self.chunks[idx].get("source", ""),
                    "distance": float(dist),
                })
        return results

    def generate(self, question, context_chunks):
        """Step 2: generate the answer with context."""
        prompt = build_rag_prompt(question, context_chunks)

        response = client.chat.complete(
            model="mistral-large-latest",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.1,
        )
        return response.choices[0].message.content

    def ask(self, question, k=3):
        """Full pipeline: question -> sourced answer."""
        # Retrieval
        context = self.retrieve(question, k=k)

        # Generation
        answer = self.generate(question, context)

        return {
            "question": question,
            "answer": answer,
            "sources": [c["source"] for c in context],
            "n_chunks_used": len(context),
        }

# Usage
rag = RAGPipeline(chunks, embeddings)
result = rag.ask("How does indexing work in FAISS?")

print(f"Question: {result['question']}")
print(f"Answer: {result['answer']}")
print(f"Sources: {result['sources']}")

Drei Prompt-Varianten nach Anwendungsfall

Sie können den Basis-Prompt je nach Erwartung an den Assistenten spezialisieren. Wenn Ihre Nutzer jede Behauptung überprüfen müssen, bitten Sie explizit um Zitate in eckigen Klammern. Das Modell fügt dann jedem Satz ein nummeriertes Dokument hinzu. Wenn die Antwort eine Schnittstelle oder einen Bericht speist, erzwingt ein strukturiertes Format stabile Abschnitte: eine konzise Antwort, Details, Quellen und ein Vertrauensniveau, das widerspiegelt, wie gut der Kontext die Frage abdeckt. Die dritte Variante spielt eine andere Rolle: Sie lehrt das Modell, Unsicherheit aufzubrechen, indem es unterscheidet, was es mit Sicherheit behaupten kann, was im Kontext fehlt und wo man weiter suchen sollte. Dies erzeugt in der Praxis oft die nützlichsten Assistenten, denn „Ich vermute X” schlägt eine selbstbewusste, aber falsche Antwort.

# 1. Prompt with citation instruction
prompt_cite = """Answer by citing source documents in brackets.
Example: "RAG improves accuracy [Document 1]."

Context: {context}
Question: {question}"""

# 2. Prompt with structured format
prompt_structured = """Answer in the following format:

**Answer**: [your concise answer]
**Details**: [detailed explanation]
**Sources**: [list of documents used]
**Confidence**: [high/medium/low based on context coverage]

Context: {context}
Question: {question}"""

# 3. Prompt with missing-information handling
prompt_honest = """If the context does not contain enough information to
answer completely, state clearly:
- What you can assert with certainty
- What is missing from the context
- Suggestions for obtaining the missing information

Context: {context}
Question: {question}"""

Auf der Seite der Aufrufparameter steuern drei Einstellungen das RAG-Verhalten. Eine niedrige Temperatur sichert den faktischen Output, max_tokens verhindert Antworten, die in Essays abdriften, und ein konservatives top_p beschränkt das Stichproben-Vokabular auf die wahrscheinlichsten Fortsetzungen.

# For RAG, use a low temperature
response = client.chat.complete(
    model="mistral-large-latest",
    messages=[{"role": "user", "content": prompt}],
    temperature=0.1,   # Factual, low creativity
    max_tokens=1024,    # Limit answer length
    top_p=0.95,         # Conservative nucleus sampling
)

Kernaussagen

  • Der RAG-Prompt injiziert den abgerufenen Kontext vor der Frage
  • Verwenden Sie eine niedrige Temperatur (0,1–0,3) für faktische Antworten
  • Bitten Sie das Modell, Quellen zu zitieren, um Nachvollziehbarkeit zu gewährleisten
  • Die Klasse RAGPipeline kapselt den gesamten Fluss: Abrufen, Generieren, Fragen
  • Behandeln Sie Fälle, in denen der Kontext nicht über ausreichende Informationen verfügt