← Alle Artikel
Zuletzt aktualisiert: 2026-03-30

KI-Chatbot auf Ihrer Website einrichten (trainiert auf Ihre Inhalte)

Website-Chatbot mit RAG, OpenAI API und Embeddings bauen. Vom Scraping bis zum Chat-Widget.

Kurzfassung (TL;DR)

Ein KI-Chatbot, der auf den eigenen Website-Inhalten trainiert ist, lässt sich mit Retrieval-Augmented Generation (RAG) umsetzen: Website-Texte scrapen, Vektor-Embeddings erzeugen, in einer Vektordatenbank wie ChromaDB speichern und über die OpenAI-API als Kontext bei Nutzeranfragen mitgeben. Fertiglösungen wie Crisp, Tidio oder Chatbase bieten eine schnelle Alternative ohne Programmieraufwand. Eine eigene RAG-Lösung kostet im Betrieb etwa 5–50 € pro Monat je nach Traffic. Die DSGVO-Konformität muss vor dem Einsatz sichergestellt werden.

Voraussetzungen

Chatbot-Optionen im Überblick

Fertiglösungen

LösungVorteileNachteileStartpreis
CrispLive-Chat + KI-Bot kombiniert, saubere OberflächeKI-Features erst ab höheren TarifenGratis / 25 $/Monat
TidioDrag-and-Drop-Flowbuilder, Shopify-IntegrationKI-Antworten im Gratis-Plan begrenztGratis / 29 $/Monat
ChatbaseDokumente/URLs hochladen, sofort RAG-ChatbotWeniger Kontrolle über Retrieval-LogikGratis / 19 $/Monat
Eigener RAG-BotVolle Kontrolle, eigene Daten, kein Vendor-Lock-inErfordert Entwicklung und Wartung~5 $/Monat (API-Kosten)

Wer die volle Kontrolle über Retrieval und Antwortgenerierung haben möchte, baut eine eigene RAG-Pipeline. Die folgende Anleitung zeigt Schritt für Schritt, wie das geht.

Schritt 1: Website-Inhalte extrahieren

Zuerst sammeln wir die Textinhalte der Website-Seiten. Dieses Skript ruft eine Liste von URLs ab und extrahiert den bereinigten Text.

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import json
import time

def website_scrapen(basis_url, pfade):
    """Textinhalte von einer Liste von URL-Pfaden extrahieren."""
    dokumente = []
    
    for pfad in pfade:
        url = urljoin(basis_url, pfad)
        try:
            antwort = requests.get(url, timeout=10)
            antwort.raise_for_status()
            soup = BeautifulSoup(antwort.text, "html.parser")
            
            # Script, Style, Nav, Footer entfernen
            for tag in soup(["script", "style", "nav", "footer", "header"]):
                tag.decompose()
            
            titel = soup.title.string.strip() if soup.title else pfad
            hauptbereich = soup.find("main") or soup.find("article") or soup.body
            text = hauptbereich.get_text(separator="\n", strip=True) if hauptbereich else ""
            
            if len(text) > 50:  # Fast leere Seiten überspringen
                dokumente.append({
                    "url": url,
                    "titel": titel,
                    "inhalt": text[:8000]
                })
                print(f"Gescrapt: {titel} ({len(text)} Zeichen)")
            
            time.sleep(0.5)  # Server nicht überlasten
        except Exception as e:
            print(f"Fehler beim Scraping von {url}: {e}")
    
    return dokumente

# Verwendung
seiten = ["/", "/ueber-uns", "/leistungen", "/faq", "/preise", "/kontakt"]
doks = website_scrapen("https://example.com", seiten)

with open("gescrapte_inhalte.json", "w") as f:
    json.dump(doks, f, indent=2, ensure_ascii=False)

print(f"{len(doks)} Seiten erfolgreich extrahiert.")

Schritt 2: Embeddings erstellen mit ChromaDB

Im nächsten Schritt teilen wir die Texte in Abschnitte auf, erzeugen Vektor-Embeddings über OpenAI und speichern sie in ChromaDB für schnelle Ähnlichkeitssuche.

import chromadb
from openai import OpenAI
import json

client = OpenAI()  # Nutzt die Umgebungsvariable OPENAI_API_KEY
chroma = chromadb.PersistentClient(path="./chroma_db")
collection = chroma.get_or_create_collection(
    name="website_inhalte",
    metadata={"hnsw:space": "cosine"}
)

def text_aufteilen(text, chunk_groesse=500, ueberlappung=50):
    """Text in überlappende Abschnitte aufteilen."""
    woerter = text.split()
    abschnitte = []
    for i in range(0, len(woerter), chunk_groesse - ueberlappung):
        abschnitt = " ".join(woerter[i:i + chunk_groesse])
        if len(abschnitt) > 20:
            abschnitte.append(abschnitt)
    return abschnitte

def index_aufbauen(quelldatei="gescrapte_inhalte.json"):
    with open(quelldatei) as f:
        dokumente = json.load(f)
    
    alle_abschnitte = []
    alle_ids = []
    alle_metadaten = []
    
    for dok in dokumente:
        abschnitte = text_aufteilen(dok["inhalt"])
        for i, abschnitt in enumerate(abschnitte):
            abschnitt_id = f"{dok['url']}#teil{i}"
            alle_abschnitte.append(abschnitt)
            alle_ids.append(abschnitt_id)
            alle_metadaten.append({
                "url": dok["url"],
                "titel": dok["titel"]
            })
    
    # Batch-Embedding und Einfügen
    batch_groesse = 100
    for i in range(0, len(alle_abschnitte), batch_groesse):
        batch = alle_abschnitte[i:i + batch_groesse]
        ids = alle_ids[i:i + batch_groesse]
        meta = alle_metadaten[i:i + batch_groesse]
        
        antwort = client.embeddings.create(
            model="text-embedding-3-small",
            input=batch
        )
        embeddings = [e.embedding for e in antwort.data]
        
        collection.upsert(
            ids=ids,
            documents=batch,
            embeddings=embeddings,
            metadatas=meta
        )
        print(f"Indiziert: {i + len(batch)}/{len(alle_abschnitte)} Abschnitte")
    
    print(f"Index fertig: {len(alle_abschnitte)} Abschnitte aus {len(dokumente)} Seiten.")

index_aufbauen()

Schritt 3: RAG-Chatbot-Backend aufbauen

Diese Flask-API nimmt eine Nutzerfrage entgegen, sucht relevante Abschnitte aus ChromaDB und übergibt sie als Kontext an die OpenAI-Chat-API.

from flask import Flask, request, jsonify
from flask_cors import CORS
from openai import OpenAI
import chromadb

app = Flask(__name__)
CORS(app, origins=["https://ihredomain.de"])

oai = OpenAI()
chroma = chromadb.PersistentClient(path="./chroma_db")
collection = chroma.get_collection("website_inhalte")

SYSTEM_PROMPT = """Du bist der virtuelle Assistent von [Firmenname].
Beantworte Fragen AUSSCHLIEßLICH auf Basis des bereitgestellten Kontexts.
Wenn der Kontext die Antwort nicht enthält, sage: "Dazu habe ich leider
keine Information. Bitte kontaktieren Sie uns unter support@example.de."
Sei präzise, freundlich und korrekt. Nenne wenn möglich die Quellseite."""

def kontext_abrufen(anfrage, n_ergebnisse=5):
    """Die relevantesten Abschnitte für eine Anfrage finden."""
    anfrage_embedding = oai.embeddings.create(
        model="text-embedding-3-small",
        input=[anfrage]
    ).data[0].embedding
    
    ergebnisse = collection.query(
        query_embeddings=[anfrage_embedding],
        n_results=n_ergebnisse
    )
    
    kontext_teile = []
    quellen = set()
    for dok, meta in zip(ergebnisse["documents"][0], ergebnisse["metadatas"][0]):
        kontext_teile.append(f"[Quelle: {meta['titel']}]\n{dok}")
        quellen.add(meta["url"])
    
    return "\n\n---\n\n".join(kontext_teile), list(quellen)

@app.route("/api/chat", methods=["POST"])
def chat():
    daten = request.json
    nachricht = daten.get("message", "").strip()
    
    if not nachricht or len(nachricht) > 1000:
        return jsonify({"error": "Ungültige Nachricht"}), 400
    
    kontext, quellen = kontext_abrufen(nachricht)
    
    antwort = oai.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": f"Kontext:\n{kontext}\n\nFrage: {nachricht}"}
        ],
        max_tokens=500,
        temperature=0.3
    )
    
    antwort_text = antwort.choices[0].message.content
    return jsonify({"answer": antwort_text, "sources": quellen})

if __name__ == "__main__":
    app.run(port=5000)

Schritt 4: Prompt Engineering für Website-Chatbots

Der System-Prompt ist entscheidend für die Qualität der Antworten. Hier sind bewährte Ansätze:

Strikt kontextbasierter Prompt

STRIKT_PROMPT = """Du bist der virtuelle Assistent von [Firmenname].
Regeln:
1. Antworte AUSSCHLIEßLICH auf Basis des bereitgestellten Kontexts.
   Erfinde NIEMALS Informationen.
2. Bei Unsicherheit verweise auf den Kundenservice.
3. Halte Antworten auf maximal 3 Sätze, es sei denn, Details
   werden explizit angefragt.
4. Nenne immer die Quellseite.
5. Antworte in der Sprache, in der die Frage gestellt wird.
6. Gehe nicht auf Wettbewerber oder sachfremde Themen ein."""

Vertriebsorientierter Prompt

VERTRIEB_PROMPT = """Du bist ein freundlicher Assistent von [Firmenname].
Dein Ziel ist es, Besuchern beim Finden des passenden Angebots zu helfen.
Regeln:
1. Antworte auf Basis des Kontexts. Wenn die Antwort fehlt,
   biete an, den Kontakt zu einem Mitarbeiter herzustellen.
2. Weise bei passender Gelegenheit dezent auf relevante
   Produkte/Leistungen hin.
3. Nenne bei Preisfragen die Information und schlage eine
   Demo-Buchung vor.
4. Sei warmherzig, prägnant und professionell."""

Schritt 5: Chat-Widget einbinden

Fügen Sie diesen Code-Ausschnitt vor dem schließenden </body>-Tag Ihrer Website ein. Er erzeugt einen schwebenden Chat-Button mit Dialogfenster.

<!-- KI-Chatbot-Widget -->
<style>
  #chatbot-toggle {
    position: fixed; bottom: 24px; right: 24px; z-index: 9999;
    width: 56px; height: 56px; border-radius: 50%; border: none;
    background: #2563eb; color: #fff; font-size: 24px; cursor: pointer;
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  }
  #chatbot-window {
    display: none; position: fixed; bottom: 90px; right: 24px;
    width: 380px; max-height: 520px; z-index: 9999;
    border-radius: 12px; overflow: hidden;
    box-shadow: 0 8px 30px rgba(0,0,0,0.2);
    font-family: -apple-system, BlinkMacSystemFont, sans-serif;
    background: #fff; flex-direction: column;
  }
  #chatbot-window.open { display: flex; }
  #chatbot-header {
    background: #2563eb; color: #fff; padding: 16px;
    font-weight: 600; font-size: 15px;
  }
  #chatbot-messages {
    flex: 1; overflow-y: auto; padding: 16px;
    max-height: 360px; min-height: 200px;
  }
  .cb-msg { margin-bottom: 12px; line-height: 1.5; font-size: 14px; }
  .cb-msg.bot { color: #1e293b; }
  .cb-msg.user { color: #2563eb; text-align: right; }
  #chatbot-input-wrap {
    display: flex; border-top: 1px solid #e2e8f0; padding: 8px;
  }
  #chatbot-input {
    flex: 1; border: none; outline: none; padding: 8px 12px;
    font-size: 14px;
  }
  #chatbot-send {
    background: #2563eb; color: #fff; border: none; padding: 8px 16px;
    border-radius: 6px; cursor: pointer; font-size: 14px;
  }
</style>

<button id="chatbot-toggle" onclick="toggleChat()">ὊC</button>
<div id="chatbot-window">
  <div id="chatbot-header">Haben Sie eine Frage?</div>
  <div id="chatbot-messages">
    <div class="cb-msg bot">Hallo! Wie kann ich Ihnen helfen?</div>
  </div>
  <div id="chatbot-input-wrap">
    <input id="chatbot-input" placeholder="Ihre Frage eingeben..."
           onkeydown="if(event.key==='Enter')nachrichtSenden()" />
    <button id="chatbot-send" onclick="nachrichtSenden()">Senden</button>
  </div>
</div>

<script>
const CHATBOT_API = "https://ihre-api.example.de/api/chat";

function toggleChat() {
  document.getElementById("chatbot-window").classList.toggle("open");
}

async function nachrichtSenden() {
  const eingabe = document.getElementById("chatbot-input");
  const msg = eingabe.value.trim();
  if (!msg) return;
  
  const nachrichten = document.getElementById("chatbot-messages");
  nachrichten.innerHTML += `<div class="cb-msg user">${escapeHtml(msg)}</div>`;
  eingabe.value = "";
  nachrichten.innerHTML += `<div class="cb-msg bot" id="typing">Einen Moment...</div>`;
  nachrichten.scrollTop = nachrichten.scrollHeight;
  
  try {
    const res = await fetch(CHATBOT_API, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ message: msg })
    });
    const data = await res.json();
    document.getElementById("typing").remove();
    nachrichten.innerHTML += `<div class="cb-msg bot">${escapeHtml(data.answer)}</div>`;
  } catch {
    document.getElementById("typing").remove();
    nachrichten.innerHTML += `<div class="cb-msg bot">Entschuldigung, es ist ein Fehler aufgetreten.</div>`;
  }
  nachrichten.scrollTop = nachrichten.scrollHeight;
}

function escapeHtml(str) {
  const d = document.createElement("div");
  d.textContent = str;
  return d.innerHTML;
}
</script>

Schritt 6: Hosting-Optionen

Option A: Serverless (empfohlen bei geringem Traffic)

PlattformKaltstartGratis-KontingentIdeal für
Vercel (Python Functions)~300 ms100k Anfragen/MonatSchnelles Deployment, Next.js-Seiten
Cloudflare Workers~5 ms100k Anfragen/TagNiedrige Latenz, globales Edge-Netzwerk
AWS Lambda~500 ms1 Mio. Anfragen/MonatKomplexe Pipelines, Enterprise

Option B: VPS (empfohlen bei hohem Traffic oder Self-Hosting)

Ein kleiner VPS (2 vCPU, 4 GB RAM) für 5–12 €/Monat bei Hetzner, DigitalOcean oder Contabo reicht für ChromaDB + Flask bei bis zu ~10.000 täglichen Konversationen aus. Für das Deployment empfiehlt sich Docker Compose:

# docker-compose.yml (vereinfacht)
# services:
#   chatbot:
#     build: .
#     ports: ["5000:5000"]
#     volumes: ["./chroma_db:/app/chroma_db"]
#     environment:
#       - OPENAI_API_KEY=${OPENAI_API_KEY}

Kostenkalkulation

Der größte Kostenfaktor ist die OpenAI-API. Hier eine Formel mit Rechenbeispielen:

KomponenteKostenBerechnung
Embedding (Indizierung, einmalig)~0,01 $50 Seiten x 2k Tokens x 0,02 $/1M Tokens
Embedding (pro Anfrage)~0,000002 $~100 Tokens x 0,02 $/1M Tokens
GPT-4o-mini (pro Anfrage)~0,0004 $~2k Input- + 300 Output-Tokens
1.000 Anfragen/Monat~0,40 $Embedding + Completion pro Anfrage
10.000 Anfragen/Monat~4,00 $
100.000 Anfragen/Monat~40,00 $
ChromaDB / Hosting0–12 $/MonatLokal kostenlos, VPS ansonsten

Realistische Gesamtkosten für eine kleine Firmenwebsite: 5–15 $/Monat inklusive Hosting.

DSGVO-Anforderungen

Bei Nutzern aus der EU müssen folgende Punkte beachtet werden:

Grenzen & Wann ein Chatbot NICHT sinnvoll ist

Fehlerbehebung

Chatbot antwortet immer mit "Dazu habe ich keine Information"

Widget wird auf der Seite nicht angezeigt

CORS-Fehler beim API-Aufruf

Hohe Latenz (>5 Sekunden pro Antwort)

Vorbeugung & Best Practices

Experten-Hilfe gebraucht?

Chatbot heute installiert? €99, trainiert auf bis zu 50 Seiten Ihres Inhalts.

Jetzt buchen — €99

100% Geld-zurück-Garantie

HR

Harald Roessler

Infrastructure Engineer mit 20+ Jahren Erfahrung. Gründer der DSNCON GmbH.