Immer wieder stoße ich im Internet auf das Angebot, mir eine ‚eigene KI‘ zu programmieren. Dahinter verbergen sich allerdings höchst unterschiedliche Dinge. In den meisten Fällen wird nicht tatsächlich ein KI-Modell programmiert. Stattdessen werden bestimmte Einstellungen zum Chatten festgelegt (z.B. ‚Gib möglichst kurze Antworten‘ oder ‚Sprich mich mit Du und Nele an‘) und/oder bestimmte Quellen festgelegt, auf die sich ein genutzter Chatbot in der Antwort beziehen soll.
Der Fachbegriff für letzteres lautet: Retrieval Augmented Generation, abgekürzt RAG. Solch eine RAG-Herangehensweise ermöglicht es, einem KI-Modell eine bestimmte Datenbasis zur Verfügung zu stellen, auf die sich der Chatbot in seinen Antworten dann vorrangig beziehen soll.
Ich habe RAG bereits vielfach auf meinem eigenen KI-Server ausprobiert. Meistens habe ich das aber auf einzelne Inhalte (z.B. auf einen bestimmten Blogbeitrag oder ein Transkript) bezogen, nicht auf umfangreichere Quellen wie z.B. alle Beiträge eines bestimmten Blogs. Genau dafür suchte ich jetzt nach einer Lösung. In diesem Zusammenhang wollte ich zugleich eine Lösung entwickeln, die auch andere möglichst ohne Kosten und umfangreiche technische Vorkenntnisse für sich nutzen können und die mit Open Source Modellen sowie unabhängig von proprietären KI-Plattformen funktioniert. Das Vorhaben ist somit – neben dem praktischen Nutzen – vor allem auch ein Projekt für mehr digitale Mündigkeit. Mein Vorgehen habe ich als Schritt-für Schritt Anleitung aufgeschrieben.
Das klingt für dich interessant? Dann lies weiter und probiere die Anleitung sehr gerne für dich aus. Vielleicht hast du ja auch Lust, sie weiter zu verbessern!
Grundsätzliche Schritte
Unser Ziel ist ein RAG-Chatbot für Blog-Websites im Terminal deines Rechners (= dem schwarzen Fenster, in das sich Befehle eintippen lassen). Du kannst den Chatbot auch dann bauen, wenn du bisher noch nicht mit dem Terminal gearbeitet hast.
Wir gehen in mehreren Schritten vor: Nach ein paar Vorbereitungen nutzen wir zuerst ein Programm, mit dem wir alle Beiträge von den von uns gewünschten Blogs herunterladen. Dabei überprüfen wir natürlich zunächst, ob die Blog-Inhaber*innen solch ein Scraping untersagen. Wenn nein, dann versuchen wir die Beiträge entweder über die REST-API (das funktioniert oft bei WordPress-Blogs; es ist ein maschinenlesbarer Zugang zu den Daten der Website) oder ansonsten über den RSS-Feed oder die Sitemap zu erhalten. Wir müssen dazu lediglich die URL (= die Internetadresse) der gewünschten Blogs eingegeben.
Aus den eingesammelten Beiträgen gestalten wir dann – mit einem weiteren Open Source Programm – eine Vektor-Datenbank. Das ist eine komprimierte und maschinenlesbare Variante der Inhalte, die als Datenbasis von KI-Sprachmodellen genutzt werden kann. Diese Datenbank verbinden wir mit dem Open Source KI-Modell Mistral.
Im letzten Schritt nutzen wir dann ein weiteres Programm – den eigentlichen Chatbot – um in die Kommunikation über die Beiträge in den Blogs zu gehen. Das funktioniert dann weiterhin über das Terminal, aber du wirst sehen, dass du im Ergebnis dann einen Bot gestaltet hast, der ähnlich wie andere KI-Chatbots menschliche Sprache auswerten und mit passenden Reaktionen – in unserem Fall mit den Inhalten basierend auf den eingepflegten Blogbeiträgen – antworten kann.
Die folgende Anleitung stellt dir den Code für das ‚Beitragseinsammel-Programm‘ und das ‚Chatbot-Programm‘ zum direkten Weiternutzen zur Verfügung. Ich empfehle dir, erst einmal damit zu arbeiten. Später kannst du aber natürlich diese Programme auch anpassen und deinen Chatbot so noch sehr viel spezifischer für deine Bedürfnisse gestalten.
Wozu das Ganze?
Ich finde dieses Projekt aus mehreren Gründen ziemlich cool:
- Es ist ein offenes Projekt, das dir die Potenziale von Open Source Software zeigt und dir eine Alternative zu den proprietären KI-Plattformen ermöglicht.
- Es ist ein Projekt für mehr digitale Mündigkeit. Du verstehst und kannst erkunden, wie KI-Chatbots unter der Haube funktionieren.
- Du bekommst eine sehr nützliche Anwendung, mit der du Online-Inhalte aus von dir ausgewählten Blogs über eine Chatoberfläche in einem vernetzten Sinne ‚lesen‘ kannst.
Schritt-für-Schritt Anleitung
Insgesamt wirst du für die folgenden Schritte wahrscheinlich ungefähr eine Stunde Zeit brauchen. Du kannst jederzeit zwischendrin aufhören und wieder neu einsteigen.
(Einschränkend möchte ich anmerken, dass ich die Schritte auf meinem Linux-Laptop durchgeführt habe. Ich gebe auch die entsprechenden Befehle für Windows und Mac an, aber habe diese nicht selbst getestet. Entstanden ist die Anleitung basierend auf meiner Ideen dazu, allgemeiner Internet-Recherche und Interaktion mit KI-Chatbots)
Schritt 1: Vorbereitungen
Um solch einen RAG-Chatbot für Blog-Websites im Terminal selbst zu basteln, sind zunächst drei Vorbereitungen nötig:
- Du solltest grundlegende Befehle im Terminal kennen.
- Du solltest dir Python installieren.
- Du solltest dir einen Mistral-API-Key besorgen.
1. Grundlegende Befehle im Terminal kennen
Zunächst zu den grundlegenden Befehlen im Terminal: Diese unterscheiden sich, je nachdem, ob du Linux, Windows oder einen Mac nutzt. Du findest das Terminal mit diesen Eingaben – oder auch einfach über eine Programmsuche:
Linux:
- Tastenkombination
Strg + Alt + Toder - Suche nach „Terminal/ Konsole“ in deinen Anwendungen
Windows:
- Tastenkombination
Windows-Taste + R, danncmdeingeben und Enter drücken oder - Suche nach „Eingabeaufforderung“ bzw. „PowerShell“ im Startmenü
Mac:
- Tastenkombination
Cmd + Leertaste, dann „Terminal“ eingeben oder - Gehe zu Programme → Dienstprogramme → Terminal
Es öffnet sich dann ein kleines Konsolen-Fenster, über das du an deinen Rechner bestimmte Befehle senden kannst.
(Ich habe die Terminal-Nutzung in den letzten Monaten übrigens sehr viel in Interaktion mit KI-Chatbots gelernt. Vor allem die Anthropic-Modelle funktionieren dazu aus meiner Sicht gut. Du kannst dann einfach im KI-Chat fragen, was der richtige Befehl für das Terminal ist, diesen dann im Zweifel noch einmal im Internet gegenchecken, dann eingeben – und wenn du dann einen irritierenden Output bekommst oder irgend etwas nicht funktioniert, diesen wiederum kopieren, mit dem KI-Chatbot teilen und dir erklären lassen.)
Hier kommt eine kleine Basis-Übung, um das Prinzip des Terminals zu verstehen.
Du brauchst dazu diese Befehle:
Linux/Mac:
mkdir ~/Verzeichnis– um ein Verzeichnis zu erstellencd ~/Verzeichnis– um in das Verzeichnis zu wechselnnano Dateiname– um eine Datei zu erstellen und/oder zu öffnen- Nach dem Eintragen dann
Strg+OundEnterundStrg+X, um das Programm zu speichern und zu schließen
Windows (PowerShell):
mkdir ~/Verzeichnis– um ein Verzeichnis zu erstellencd ~/Verzeichnis– um in das Verzeichnis zu wechselnnotepad Dateiname.txt– um eine Datei zu erstellen und/oder zu öffnen- Nach dem Eintragen über Datei → Speichern und dann Fenster schließen
Windows (CMD):
mkdir %USERPROFILE%\Verzeichnis– um ein Verzeichnis zu erstellencd %USERPROFILE%\Verzeichnis– um in das Verzeichnis zu wechselnnotepad Dateiname.txt– um eine Datei zu erstellen und/oder zu öffnen
Mit diesen Befehlen kannst du die Terminal-Nutzung testen:
Für Linux/Mac gib im Terminal ein:
mkdir ~/mein-testverzeichnisDann drücke Enter.
cd ~/mein-testverzeichnisDann drücke Enter.
nano testdatei.txtDann drücke Enter.
Für Windows (PowerShell) gib im Terminal ein:
mkdir ~/mein-testverzeichnisDann drücke Enter.
cd ~/mein-testverzeichnisDann drücke Enter.
notepad testdatei.txtDann drücke Enter.
Für Windows (CMD) gib im Terminal ein:
mkdir %USERPROFILE%\mein-testverzeichnisDann drücke Enter.
cd %USERPROFILE%\mein-testverzeichnisDann drücke Enter.
notepad testdatei.txtDann drücke Enter.
Es öffnet sich ein Eingabefeld. Bei Linux/Mac ist das direkt im Terminal (nano-Editor), bei Windows öffnet sich der Notepad-Editor. Schreibe irgend etwas rein. Zum Beispiel:
Hallo, ich erstelle eine Datei mithilfe des Terminals :-)
Bei Linux/Mac: Dann Strg+O und Enter und Strg+X
Bei Windows: Speichere über Datei → Speichern und schließe das Notepad-Fenster
Nach dem Schließen gelangst du wieder zum ursprünglichen Terminal-Fenster zurück. Dass deine Befehle erfolgreich waren, siehst du, wenn du deine Ordnerverwaltung öffnest und darin tatsächlich den erstellten Ordner ‚mein-testverzeichnis‘ mit der Datei testdatei.txt und deinem Inhalt darin findest.
(Ich fand das ziemlich großartig, als ich das zum ersten Mal gemacht habe. Wenn es auch dein erstes Mal ist, dann kannst du dich erst einmal freuen, dass du jetzt einen ziemlich coolen Weg kennst, um mit deinem Gerät zu kommunizieren! Wir werden das im Folgenden nutzen!)
2. Python installieren
Mit diesem Basiswissen können wir uns dann direkt an die Installation von Python machen. Das ist eine Programmiersprache, in der die Programme geschrieben sein werden, mit denen wir arbeiten.
Ob Python bei dir ohnehin schon installiert ist, kannst du überprüfen mit diesem Befehl im Terminal:
python --versionoder
python3 --versionWenn dann eine Versionsnummer zurückkommt (z.B. Python 3.13.11), ist Python installiert.
Wenn nicht, dann kannst du es folgendermaßen installieren:
Linux (Ubuntu/Debian):
sudo apt update
sudo apt install python3 python3-pipMac:
brew install python3(Falls Homebrew nicht installiert ist, folge dieser Anleitung.)
Windows:
- Gehe auf https://www.python.org/downloads/
- Lade die neueste Version herunter
- Führe den Installer aus und setze unbedingt den Haken bei „Add Python to PATH“
- Klicke auf „Install Now“
3. Mistral API-Key besorgen
Als letztes benötigst du noch einen Mistral API Key. Mistral ist ein europäisches Open Source KI-Modell. Wir nutzen im Folgenden Mistral-small, was in einem begrenzten, aber für den persönlichen Gebrauch erfahrungsgemäß mehr als ausreichendem Umfang für Entwickler*innen kostenfrei zur Verfügung steht.
(Mit dieser sehr umfangreichen, kostenfreien Entwicklungsmöglichkeit mit der Mistral-API verfolgt das Unternehmen übrigens eine aus meiner Sicht sehr stimmige Open Source Strategie: Je mehr Menschen mit der Mistral-API erkunden und ausprobieren, desto mehr Ideen und Produkte werden auf dieser Basis auch entwickelt, von denen einige dann später einen kostenpflichtigen Plan in Anspruch nehmen werden.)
Um dir einen Mistral-API-Key zu besorgen, öffnest du diese Website, registrierst dich, gehst auf API und erstellst dir einen API-Key und speicherst ihn ab. Du kannst ihn z.B. „Blog-Chatbot“ nennen. Den API Key speicherst du dir ab, so dass du ihn später nutzen kannst.
Wichtig: Du solltest den API-Key nicht öffentlich teilen!
Schritt 2: Projekt aufsetzen
In diesem Schritt sind wir weiter vorbereitend tätig, aber du kannst direkt deine Learnings aus Schritt 1 anwenden. Wir benötigen nämlich eine Projektumgebung (= einen Ordner auf deinem Rechner, in dem du deinen KI-Chatbot und die Inhalte speicherst). In diesem Ordner speicherst du dann deinen Mistral-API-Key und die weiteren, benötigten Programme.
1. Projektverzeichnis erstellen
Zunächst erstellen wir die Projektumgebung:
Linux/Mac:
mkdir ~/blog-chatbot-projekt
cd ~/blog-chatbot-projektWindows (PowerShell):
mkdir ~/blog-chatbot-projekt
cd ~/blog-chatbot-projektWindows (CMD):
mkdir %USERPROFILE%\blog-chatbot-projekt
cd %USERPROFILE%\blog-chatbot-projekt2. Virtuelle Umgebung erstellen
Dann erstellen wir eine virtuelle Umgebung. Das ist sinnvoll, weil damit alle installierten Programme nur für dieses Projekt gelten und nicht mit anderen Python-Projekten auf deinem Rechner in Konflikt kommen können.
Linux/Mac:
python3 -m venv venv
source venv/bin/activateWindows (PowerShell):
python -m venv venv
.\venv\Scripts\Activate.ps1(Falls du eine Fehlermeldung bekommst, führe einmalig aus: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser)
Windows (CMD):
python -m venv venv
venv\Scripts\activate.batNach der Aktivierung siehst du (venv) am Anfang deiner Kommandozeile – das zeigt, dass die virtuelle Umgebung aktiv ist.
3. API-Key speichern
Jetzt speichern wir deinen Mistral-API-Key in einer Datei, die wir so erstellen, wie du es eingangs mit der Testdatei gelernt hast:
Linux/Mac:
nano .envWindows:
notepad .envTrage dann folgendes ein (ersetze YOUR_API_KEY durch deinen tatsächlichen Key, den du dir auf der Mistral-Website erstellt hast):
MISTRAL_API_KEY=dein_api_key_hierBei Linux/Mac: Speichere mit Strg+O, Enter, Strg+X
Bei Windows: Speichere über Datei → Speichern und schließe Notepad
4. Benötigte Programme installieren
Jetzt installieren wir die Programme, die wir für unseren Chatbot brauchen:
- chromadb – Die Vektor-Datenbank, in der die Blog-Beiträge gespeichert werden
- sentence-transformers – Für die semantische Suche und Einbettung der Texte
- requests – Um Webseiten abzurufen
- mistralai – Das Programm, um mit der Mistral-API zu kommunizieren
- python-dotenv – Um den API-Key aus der .env-Datei zu laden
- feedparser – Um RSS-Feeds zu lesen
- beautifulsoup4 – Um HTML-Inhalte zu verarbeiten
Bei all diesen Programmen handelt es sich um Open Source Programme. Die Installation funktioniert über eine Befehlseingabe zur Installation (und etwas Zeit).
Für alle Betriebssysteme (wenn virtuelle Umgebung aktiviert ist) gib diesen Befehl im Terminal ein:
pip install chromadb sentence-transformers requests mistralai python-dotenv feedparser beautifulsoup4Dieser Installationsvorgang kann einige Minuten dauern. Lass das Terminal einfach arbeiten, bis du wieder die ursprüngliche Eingabeaufforderung siehst.
Schritt 3: Blogs einsammeln und Vektordatenbank erstellen
Mit der eingerichteten Basis aus Schritt 2 können wir jetzt das Blog-Einsammel-Programm erstellen und ausführen, so dass damit unsere Vektor-Datenbank erstellt wird. Ich nenne dieses Programm im Folgenden ‚Verwaltungsprogramm‘.
1. Das Verwaltungsprogramm erstellen
Du gehst wie folgt vor:
Linux/Mac:
nano blog_verwalten.pyWindows:
notepad blog_verwalten.pyDann trägst du diesen Inhalt ein:
import requests
import chromadb
from chromadb.utils import embedding_functions
from bs4 import BeautifulSoup
import feedparser
from urllib.parse import urlparse
from urllib.robotparser import RobotFileParser
# Vektordatenbank-Setup
client = chromadb.PersistentClient(path="./chroma_db")
embedding_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="paraphrase-multilingual-MiniLM-L12-v2"
)
collection = client.get_or_create_collection(
name="blog_posts",
embedding_function=embedding_fn
)
def ist_gueltige_wahl(eingabe, optionen):
"""Prüft ob Eingabe eine gültige Menü-Option ist"""
return eingabe in optionen
def blog_existiert(blog_url):
"""Prüft ob Blog bereits in Datenbank ist"""
alle_daten = collection.get()
for meta in alle_daten['metadatas']:
if meta['blog_url'] == blog_url:
return True
return False
def pruefe_robots_txt(blog_url):
"""Prüft, ob Scraping erlaubt ist"""
print("[1/5] Prüfe robots.txt... ", end="", flush=True)
try:
rp = RobotFileParser()
rp.set_url(f"https://{blog_url}/robots.txt")
rp.read()
if rp.can_fetch("*", f"https://{blog_url}/"):
print("✅ Scraping erlaubt")
return True
else:
print("❌ Blog erlaubt kein automatisches Auslesen")
print("\n💡 Respektiere die Vorgaben des Blogs!")
print(" Alternative: XML-Export hochladen (falls du Zugriff hast)\n")
return False
except:
# Keine robots.txt = erlaubt
print("✅ Keine robots.txt (= erlaubt)")
return True
def hole_via_wordpress_api(blog_url):
"""Versucht Beiträge über WordPress REST API zu holen"""
print("[2/5] WordPress REST API... ", end="", flush=True)
alle_posts = []
seite = 1
try:
while True:
url = f"https://{blog_url}/wp-json/wp/v2/posts?per_page=100&page={seite}"
response = requests.get(url, timeout=10)
if response.status_code != 200 or not response.json():
break
alle_posts.extend(response.json())
seite += 1
if alle_posts:
print(f"✅ {len(alle_posts)} Beiträge gefunden")
return [(p['title']['rendered'],
BeautifulSoup(p['content']['rendered'], 'html.parser').get_text(),
p['link'])
for p in alle_posts]
else:
print("❌ nicht verfügbar")
return []
except:
print("❌ nicht verfügbar")
return []
def hole_via_rss(blog_url):
"""Versucht Beiträge über RSS-Feed zu holen"""
print("[3/5] RSS-Feed Standard... ", end="", flush=True)
feed_urls = [
f"https://{blog_url}/feed/",
f"https://{blog_url}/rss/",
f"https://{blog_url}/feed.xml",
f"https://{blog_url}/rss.xml"
]
for feed_url in feed_urls:
try:
feed = feedparser.parse(feed_url)
if feed.entries:
print(f"✅ {len(feed.entries)} Beiträge gefunden")
return [(e.title,
BeautifulSoup(e.get('content', [{}])[0].get('value', e.get('summary', '')), 'html.parser').get_text(),
e.link)
for e in feed.entries]
except:
continue
print("❌ nicht verfügbar")
return []
def hole_via_rss_pagination(blog_url):
"""Versucht RSS mit Pagination"""
print("[4/5] RSS mit Pagination... ", end="", flush=True)
alle_posts = []
seite = 2 # Seite 1 hatten wir schon
try:
while seite <= 50: # Max 50 Seiten
feed_url = f"https://{blog_url}/feed/?paged={seite}"
feed = feedparser.parse(feed_url)
if not feed.entries:
break
alle_posts.extend(feed.entries)
seite += 1
if alle_posts:
print(f"✅ weitere {len(alle_posts)} Beiträge")
return [(e.title,
BeautifulSoup(e.get('content', [{}])[0].get('value', e.get('summary', '')), 'html.parser').get_text(),
e.link)
for e in alle_posts]
else:
print("❌ nicht verfügbar")
return []
except:
print("❌ nicht verfügbar")
return []
def blog_hinzufuegen(blog_url):
"""Holt alle Beiträge von einem Blog"""
# Prüfen ob Blog schon existiert
if blog_existiert(blog_url):
print(f"\n⚠️ Blog {blog_url} ist bereits gespeichert!")
print("💡 Tipp: Erst löschen (Option 3), dann neu hinzufügen.\n")
return
print(f"\n📥 Versuche Beiträge von {blog_url} zu holen...\n")
# 1. robots.txt prüfen
if not pruefe_robots_txt(blog_url):
return
# 2-4. Verschiedene Methoden probieren
alle_beitraege = []
beitraege = hole_via_wordpress_api(blog_url)
if beitraege:
alle_beitraege.extend(beitraege)
else:
beitraege = hole_via_rss(blog_url)
if beitraege:
alle_beitraege.extend(beitraege)
# Versuche noch mehr zu holen
mehr = hole_via_rss_pagination(blog_url)
if mehr:
alle_beitraege.extend(mehr)
print(f"[5/5] Zusammenfassung... ", end="", flush=True)
if not alle_beitraege:
print("❌")
print("\n❌ Keine Beiträge gefunden!\n")
print("Mögliche Gründe:")
print("• API/Feed sind deaktiviert")
print("• URL ist nicht erreichbar")
print("• Blog nutzt ungewöhnliche Struktur")
print("\n💡 Alternative: XML-Export hochladen\n")
return
print(f"✅ {len(alle_beitraege)} Beiträge insgesamt")
print("\n💾 Speichere in Datenbank...")
for i, (titel, inhalt, link) in enumerate(alle_beitraege):
collection.add(
documents=[inhalt],
metadatas=[{
"title": titel,
"link": link,
"blog_url": blog_url
}],
ids=[f"{blog_url}_{i}"]
)
if (i+1) % 50 == 0:
print(f" {i+1} Beiträge verarbeitet...")
print(f"✓ Fertig! {len(alle_beitraege)} Beiträge von {blog_url} gespeichert.\n")
def blogs_anzeigen():
"""Zeigt alle gespeicherten Blogs"""
alle_daten = collection.get()
if not alle_daten['metadatas']:
print("\n❌ Noch keine Blogs gespeichert!\n")
return
blogs = {}
for meta in alle_daten['metadatas']:
blog = meta['blog_url']
blogs[blog] = blogs.get(blog, 0) + 1
print(f"\n📚 Gespeicherte Blogs ({len(blogs)}):")
for blog, anzahl in sorted(blogs.items()):
print(f" • {blog} ({anzahl} Beiträge)")
print()
def blog_loeschen(blog_url):
"""Löscht alle Beiträge eines Blogs"""
alle_daten = collection.get()
ids_to_delete = [
alle_daten['ids'][i]
for i, meta in enumerate(alle_daten['metadatas'])
if meta['blog_url'] == blog_url
]
if not ids_to_delete:
print(f"\n❌ Blog {blog_url} nicht gefunden!\n")
return
collection.delete(ids=ids_to_delete)
print(f"\n✓ {len(ids_to_delete)} Beiträge von {blog_url} gelöscht.\n")
def menu():
while True:
print("=" * 50)
print("BLOG-CHATBOT VERWALTUNG")
print("=" * 50)
print("1 - Blog hinzufügen")
print("2 - Blogs anzeigen")
print("3 - Blog löschen")
print("0 - Beenden")
print("=" * 50)
wahl = input("\nDeine Wahl: ").strip()
if not ist_gueltige_wahl(wahl, ['0', '1', '2', '3']):
print("\n❌ Ungültige Eingabe! Bitte wähle eine der Optionen (0-3).\n")
continue
if wahl == "1":
url = input("Blog-URL (z.B. ebildungslabor.de): ").strip()
blog_hinzufuegen(url)
elif wahl == "2":
blogs_anzeigen()
elif wahl == "3":
url = input("Welchen Blog löschen? ").strip()
blog_loeschen(url)
elif wahl == "0":
print("\nTschüss! 👋\n")
break
if __name__ == "__main__":
menu()Bei Linux/Mac: Speichere mit Strg+O, Enter, Strg+X
Bei Windows: Speichere über Datei → Speichern und schließe Notepad
2. Programm ausführen
Jetzt kannst du das Programm starten:
Für alle Betriebssysteme (virtuelle Umgebung muss aktiv sein):
python blog_verwalten.pyMit der Eingabe dieses Befehls braucht es nur ein paar Sekunden bis das Verwaltungsprogramm im Terminal aktiv wird. Du wirst dann gefragt, was du machen willst. Am sinnvollsten ist es, hier jetzt erst einmal Blogs hinzuzufügen (Option 1).
Wenn du diese Option auswählst, wirst du aufgefordert, die URL des gewünschten Blogs einzugeben, von dem die Inhalte geholt und gespeichert werden sollen.
Manchmal wirst du die Meldung bekommen, dass Scraping bei diesem Blog nicht erlaubt ist. Das solltest du respektieren.
Du kannst so viele Blogs einsammeln, wie du möchtest. Ich würde an deiner Stelle erst einmal mit 3-5 Blogs beginnen.
Insgesamt ist dieser Schritt eher langweilig, weil du nicht siehst, was im Hintergrund passiert. Wenn du neugierig bist, kannst du aber über den Datei-Manager deine angelegte Projektumgebung öffnen. Darin siehst du den Ordner chroma_db mit einer SQLite-Datenbank und einem Ordner mit mehreren Vektoren, was aber für dich alles nicht direkt lesbar und verwendbar ist. Ich fand es trotzdem spannend, mir mal direkt anzuschauen, wie so etwas aussieht.
Schritt 4: Deinen Chat-Bot gestalten
Die angelegte Vektor-Datenbank und der eingetragene Mistral-API-Key sind nun die Basis, mit der du deinen Blog-Chatbot erstellst. Grundsätzlich lässt sich dieser sehr unterschiedlich gestalten. Im Bot-Programm, das ich dir im folgenden vorschlage, wirst du beim Start des Chats immer erst gefragt, auf welche Blogs du dich bei deiner Frage beziehen willst. Dann kannst du einen oder mehrere der eingespeicherten Blogs auswählen, dann eine Frage stellen und daraufhin dann eine möglichst hilfreiche Antwort erhalten. Mit dem Befehl ‚quellen‘ erreichst du, dass die Links zu den durchsuchten Beiträgen angezeigt werden. Mit dem Befehl ‚mehr‘ werden nicht nur 3, sondern 10 Beiträge für die Antwort verwendet. (Du kannst diese Zahlen natürlich auch hochsetzen, dann kostet dich das mehr Tokens. Für mich passt es so ganz gut).
In dem Chatbot-Programm ist auch ein Systemprompt enthalten, den du ebenfalls ändern kannst, wenn du das möchtest. Aber auch hier ist es wahrscheinlich ganz sinnvoll, erst einmal diese Vorlage auszuprobieren. Ich habe eine ganze Weile experimentiert und war dann mit der jetzt entstandenen Version recht zufrieden!
So gehst du vor, um das Chat-Programm einzutragen:
Linux/Mac:
nano chatbot.pyWindows:
notepad chatbot.pyDu trägst diesen Inhalt ein:
import os
from dotenv import load_dotenv
import chromadb
from chromadb.utils import embedding_functions
from mistralai import Mistral
# Lade API-Key
load_dotenv()
api_key = os.getenv("MISTRAL_API_KEY")
client_mistral = Mistral(api_key=api_key)
# Lade Vektordatenbank
client_chroma = chromadb.PersistentClient(path="./chroma_db")
embedding_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="paraphrase-multilingual-MiniLM-L12-v2"
)
collection = client_chroma.get_collection(
name="blog_posts",
embedding_function=embedding_fn
)
# Speichere letzte Quellen und Frage
letzte_quellen = []
letzte_frage = ""
def hole_verfuegbare_blogs():
"""Zeigt alle gespeicherten Blogs"""
alle_daten = collection.get()
if not alle_daten['metadatas']:
return []
blogs = set(meta['blog_url'] for meta in alle_daten['metadatas'])
return sorted(list(blogs))
def blog_auswahl():
"""Lässt Nutzer*in Blogs auswählen"""
blogs = hole_verfuegbare_blogs()
if not blogs:
print("\n❌ Keine Blogs gespeichert!")
print("💡 Führe erst 'python blog_verwalten.py' aus.\n")
return None
print("\n📚 Verfügbare Blogs:")
print("0 - Alle Blogs durchsuchen")
for i, blog in enumerate(blogs, 1):
print(f"{i} - {blog}")
while True:
wahl = input("\nWelche Blogs durchsuchen? (Zahl oder mehrere mit Komma, z.B. 1,3): ").strip()
if wahl == "0":
return None # None = alle Blogs
try:
nummern = [int(n.strip()) for n in wahl.split(",")]
ausgewaehlte = [blogs[n-1] for n in nummern if 1 <= n <= len(blogs)]
if ausgewaehlte:
return ausgewaehlte
else:
print("❌ Ungültige Auswahl!")
except:
print("❌ Ungültige Eingabe!")
def extrahiere_keywords(frage):
"""Extrahiert wichtige Schlüsselwörter aus der Frage"""
stopwords = ['was', 'ist', 'sind', 'der', 'die', 'das', 'ein', 'eine', 'mit', 'zu', 'im', 'in',
'von', 'für', 'und', 'oder', 'wie', 'wo', 'wann', 'warum', 'welche', 'welcher',
'gibt', 'es', 'dazu', 'darüber', 'findest', 'du', 'infos', 'informationen',
'schreibt', 'blog', 'beitrag', 'dem', 'den', 'des', 'an', 'auf', 'bei',
'nach', 'über', 'aus', 'hier', 'da', 'haben', 'hat', 'wird', 'werden', 'kann',
'können', 'soll', 'sollen', 'muss', 'müssen']
woerter = frage.lower().split()
keywords = [w.strip('?.,!') for w in woerter if w.lower() not in stopwords]
return keywords
def keyword_suche(keywords, blog_filter=None):
"""Sucht direkt nach Keywords in Titeln und Texten"""
alle_daten = collection.get()
treffer = []
for i, meta in enumerate(alle_daten['metadatas']):
# Blog-Filter anwenden
if blog_filter and meta['blog_url'] not in blog_filter:
continue
titel_lower = meta['title'].lower()
text_lower = alle_daten['documents'][i].lower()
# Zähle Keyword-Matches
titel_matches = sum(1 for kw in keywords if kw in titel_lower)
text_matches = sum(1 for kw in keywords if kw in text_lower)
if titel_matches > 0 or text_matches > 1: # Mind. 1 im Titel ODER 2 im Text
treffer.append({
'id': alle_daten['ids'][i],
'doc': alle_daten['documents'][i],
'meta': meta,
'score': titel_matches * 3 + text_matches
})
# Nach Score sortieren
treffer.sort(key=lambda x: x['score'], reverse=True)
return treffer
def hybrid_suche(frage, blog_filter=None, anzahl_beitraege=3):
"""Kombiniert Keyword-First mit semantischer Suche"""
# 1. Keywords extrahieren
keywords = extrahiere_keywords(frage)
# 2. Keyword-Suche
keyword_treffer = keyword_suche(keywords, blog_filter)
# 3. Semantische Vektorsuche
if blog_filter:
vektor_results = collection.query(
query_texts=[frage],
n_results=anzahl_beitraege * 2,
where={"blog_url": {"$in": blog_filter}}
)
else:
vektor_results = collection.query(
query_texts=[frage],
n_results=anzahl_beitraege * 2
)
# 4. Kombiniere: Keyword-Treffer zuerst, dann Vektor-Results
finale_results = []
verwendete_ids = set()
# Keyword-Treffer haben Priorität
for treffer in keyword_treffer[:anzahl_beitraege]:
finale_results.append({
'doc': treffer['doc'],
'meta': treffer['meta'],
'ist_keyword_match': True
})
verwendete_ids.add(treffer['id'])
# Fülle auf mit Vektor-Results
if vektor_results and vektor_results['documents'][0]:
for i in range(len(vektor_results['documents'][0])):
if len(finale_results) >= anzahl_beitraege:
break
meta = vektor_results['metadatas'][0][i]
# Prüfe ob schon dabei
if any(r['meta']['link'] == meta['link'] for r in finale_results):
continue
finale_results.append({
'doc': vektor_results['documents'][0][i],
'meta': meta,
'ist_keyword_match': False
})
if not finale_results:
return None
return {
'documents': [[r['doc'] for r in finale_results]],
'metadatas': [[r['meta'] for r in finale_results]],
'keyword_flags': [r['ist_keyword_match'] for r in finale_results]
}
def chat(frage, blog_filter=None, konversation_history=None, anzahl_beitraege=3):
"""Beantwortet Frage basierend auf Blog-Inhalten"""
global letzte_quellen, letzte_frage
if konversation_history is None:
konversation_history = []
# Frage speichern
letzte_frage = frage
# Hybrid-Suche
results = hybrid_suche(frage, blog_filter, anzahl_beitraege)
if not results or not results['documents'][0]:
return "❌ Keine passenden Beiträge gefunden!", konversation_history
# Quellen speichern
letzte_quellen = []
for i, meta in enumerate(results['metadatas'][0], 1):
marker = "🎯" if results['keyword_flags'][i-1] else ""
letzte_quellen.append(f" {marker}{i}. {meta['title']}\n {meta['link']}")
# Kontext zusammenbauen
kontext_teile = []
for i, doc in enumerate(results['documents'][0], 1):
meta = results['metadatas'][0][i-1]
kontext_teile.append(f"[Beitrag {i}: {meta['title']}]\n{doc[:2000]}")
kontext = "\n\n---\n\n".join(kontext_teile)
# System-Prompt
system_prompt = """Du bist ein hilfreicher Assistent, der auf Basis von Blogbeiträgen antwortet.
REGELN:
1. Nutze ALLE Informationen aus den gegebenen Texten
2. Lies die Texte VOLLSTÄNDIG und GENAU
3. Wenn ein Text die Antwort enthält, NUTZE sie
4. Antworte natürlich und gesprächig
5. KEINE Quellenangaben in der Antwort (nur auf explizite Nachfrage)
6. Wenn du etwas nicht weißt: Sage es ehrlich"""
# Konversationshistorie aufbauen
messages = [{"role": "system", "content": system_prompt}]
# Bisherige Konversation
for msg in konversation_history:
messages.append(msg)
# Aktuelle Frage mit Kontext
user_message = f"""VERFÜGBARE BLOGBEITRÄGE:
{kontext}
---
FRAGE: {frage}"""
messages.append({"role": "user", "content": user_message})
# API-Aufruf
response = client_mistral.chat.complete(
model="mistral-small-latest",
messages=messages,
temperature=0.3
)
antwort = response.choices[0].message.content
# Konversation aktualisieren
konversation_history.append({"role": "user", "content": frage})
konversation_history.append({"role": "assistant", "content": antwort})
return antwort, konversation_history
def zeige_quellen():
"""Zeigt die durchsuchten Beiträge"""
global letzte_quellen
if not letzte_quellen:
print("\n❌ Keine Quellen verfügbar (stelle erst eine Frage).\n")
return
print("\n📖 Diese Beiträge wurden als relevant erachtet und durchsucht:")
print(" (🎯 = Keyword-Match im Titel oder Text)\n")
for quelle in letzte_quellen:
print(quelle)
print()
def main():
global letzte_frage
print("\n" + "=" * 50)
print("BLOG-CHATBOT")
print("=" * 50)
# Blog-Auswahl
blog_filter = blog_auswahl()
if blog_filter is None:
print("\n🔍 Durchsuche: ALLE Blogs")
else:
print(f"\n🔍 Durchsuche: {', '.join(blog_filter)}")
print("\n💬 Stelle deine Fragen!")
print("\n📋 Befehle:")
print(" • 'mehr' → Durchsucht 10 statt 3 Beiträge zur letzten Frage")
print(" • 'quellen' → Zeigt die durchsuchten Beiträge")
print(" • 'wechseln' → Ändert die Blog-Auswahl")
print(" • 'exit' → Beendet das Programm\n")
# Konversationshistorie
konversation = []
# Chat-Loop
while True:
eingabe = input("❓ ")
if eingabe.lower() in ['exit', 'quit', 'tschüss', 'bye']:
print("\nBis bald! 👋\n")
break
if eingabe.lower() == 'quellen':
zeige_quellen()
continue
if eingabe.lower() == 'mehr':
if not letzte_frage:
print("\n❌ Stelle erst eine Frage!\n")
continue
print(f"\n🔎 Durchsuche nun 10 Beiträge zur Frage: '{letzte_frage}'\n")
antwort, konversation = chat(letzte_frage, blog_filter, konversation, anzahl_beitraege=10)
print(f"\n💡 {antwort}\n")
continue
if eingabe.lower() == 'wechseln':
blog_filter = blog_auswahl()
konversation = []
letzte_frage = ""
if blog_filter is None:
print("\n🔍 Durchsuche: ALLE Blogs\n")
else:
print(f"\n🔍 Durchsuche: {', '.join(blog_filter)}\n")
continue
if not eingabe.strip():
continue
antwort, konversation = chat(eingabe, blog_filter, konversation, anzahl_beitraege=3)
print(f"\n💡 {antwort}\n")
if __name__ == "__main__":
main()Bei Linux/Mac: Speichere mit Strg+O, Enter, Strg+X
Bei Windows: Speichere über Datei → Speichern und schließe Notepad
Damit hast du es geschafft und kannst deinen Chatbot jetzt nutzen.
Schritt 5: Deinen Chatbot nutzen!
Mit diesem Befehl startest du deinen Chatbot:
Für alle Betriebssysteme (virtuelle Umgebung muss aktiv sein):
python chatbot.pyEs braucht wahrscheinlich ein paar Sekunden. Wie oben beschrieben stellt der Chatbot dir dann in der oben geteilten Version des Bot-Programms zunächst die Frage, welche eingespeicherten Blogs du als Basis nutzen willst. Dann kannst du deine Frage eingeben. Wenn du noch tiefer recherchieren willst, dann gib mehr ein. Wenn du andere Blogs als Grundlage nehmen willst, dann nutze den Befehl wechseln. Mit quellen siehst du, welche Beiträge der Bot durchsucht hat.
Hier ist ein exemplarischer Chatverlauf, bei dem ich den Bot auf die eBildungslabor-Beiträge als RAG fokussiert habe:
❓ Wie definiert Nele Kreativität?
💡 Nele definiert Kreativität im pädagogischen Kontext als die Fähigkeit, Neues denken zu können. Dabei geht es nicht primär um künstlerisches Tätigsein, sondern darum, über das Bestehende hinauszudenken und innovative Lösungen zu finden. Diese Definition steht im Rahmen der 4K-Kompetenzen, die auch Kommunikation, Kollaboration und kritisches Denken umfassen. Kreativität wird hier also als eine Schlüsselkompetenz für den digitalen Wandel und die aktive Gestaltung unserer Gesellschaft verstanden.
❓ Was schreibt Nele zu H5P?
💡 Nele schreibt über H5P, dass es ein großartiges und offenes Tool zur Gestaltung von interaktiven Online-Materialien zum Lernen und Lehren ist. Sie schätzt besonders, dass es immer wieder neue Inhaltstypen gibt, die viel Raum für eine gezielte Nutzung im jeweiligen pädagogischen Setting bieten. Nele erwähnt, dass viele ihrer Lieblings-Inhaltstypen von Oliver, einem freiberuflichen Programmierer für H5P, entwickelt wurden.
Sie stellt verschiedene Anwendungen und Möglichkeiten von H5P vor, wie den Phrase-Randomizer, der für kreative Ideenentwicklung genutzt werden kann, oder die Game Map, mit der interaktive Karten gestaltet werden können. Nele beschreibt auch, wie sie H5P genutzt hat, um aus Fotokollen interaktive Lerninhalte zu erstellen, indem sie den Inhaltstyp „Image Hotspots“ verwendet hat. Dabei werden Hintergrundbilder mit zusätzlichen Informationen und Erläuterungen versehen, um so einen vertiefenden Lerninhalt zu schaffen.
Insgesamt zeigt Nele auf, wie vielseitig und nützlich H5P für die Gestaltung interaktiver und kreativer Bildungsmaterialien sein kann.
❓ quellen
📖 Diese Beiträge wurden als relevant erachtet und durchsucht:
(🎯 = Keyword-Match im Titel oder Text)
🎯1. Praxis-Tipp: Inspirationsduschen mit H5P
https://ebildungslabor.de/blog/praxis-tipp-inspirationsduschen-mit-h5p/
🎯2. H5P-Hacking: Mit der Game Map interaktive Online-Karten gestalten
https://ebildungslabor.de/blog/h5p-hacking-mit-der-game-map-interaktive-online-karten-gestalten/
🎯3. Schnelle Idee für H5P: Image Hotspots statt ‚Fotokoll‘
https://ebildungslabor.de/blog/schnelle-idee-fuer-h5p-image-hotspots-statt-fotokoll/
Ich finde: Ziemlich gut und nützlich!
Mit exit kommst du aus dem Bot wieder raus und kannst dich dann daran machen, z.B. die Bot-Datei zu überarbeiten. (Du weißt ja jetzt, wie es geht: nano chatbot.py (Linux/Mac) bzw. notepad chatbot.py (Windows) eingeben und Änderungen vornehmen.)
Wenn du das Terminal geschlossen hast und irgendwann später den Bot wieder öffnen willst, dann öffnest du wie in Schritt 2 beschrieben mit dem cd Befehl wieder das Verzeichnis, aktivierst anschließend die virtuelle Umgebung und startest dann das Botprogramm (mit dem Befehl: python chatbot.py)
Fazit
Ich habe mir erste Blogs eingespeichert und mit dem entwickelten Bot eine Weile gechattet. Komplexere Anfragen (Wie definiert Melanie im Vergleich zu Nele den Begriff Kreativität?) sind mit diesem Basis-Setup nicht möglich. Um sich aber einen Überblick über Blog-Inhalte zu verschaffen und zusammenfassende Verbindungen zu nutzen sowie Hinweise auf weiterführende Quellen zu erhalten, finde ich diese selbst gebastelte Anwendung richtig hilfreich. Ich kann dir das Nachbasteln somit sehr empfehlen.
Viel Freude beim Erkunden und Ausprobieren!
Bonus: Einen RAG-Chatbot online bringen!
So wie du einen RAG-Chatbot in deinem Terminal gestalten kannst, kannst du solch eine Anwendung natürlich auch auf einen Online-Server übertragen und dann anderen öffentlich zur Verfügung stellen. Das wird im Internet ja zuhauf angeboten. Man kann es sich eben aber auch selbst gestalten.
Ich habe das mit einer leicht angepassten Version des obigen Bot-Programms ausprobiert, in dem ich darin nur die Inhalte von meiner eigenen Website eingespeist habe und dazu einen Bot erstellt habe. Der Server kostet ca. 4 Euro / Monat. Ich lasse ihn auf eine Subdomain meiner Website verweisen. Ich habe außerdem (um meine Mistral-Tokens nicht alle direkt aufzubrauchen) eine Begrenzung auf 25 Fragen bei der öffentlichen Nutzung eingestellt. Als Interface habe ich die Open Source Software Streamlit genutzt (anstelle des Terminals auf dem jeweils eigenen Rechner). Das Ergebnis kannst du auf bot.ebildungslabor.de ausprobieren.
(Wenn wir mit einer lokalen und damit dezentralen Nutzung vorankommen, so wie ich es in meinem obigen Blogbeitrag beschrieben habe, dann sind solche Website-Bots bald überflüssig. Schließlich ist es ja viel sinnvoller, sich selbst genau die Anwendung mit den Inhalten, Einstellungen und Systemprompts zu bauen, die man gerade braucht!)
Beitrag weiternutzen und teilen
Dieser Beitrag steht unter der Lizenz CC BY 4.0 und kann somit gerne offen weitergenutzt und geteilt werden. Hier kannst du dir den Beitragslink und/oder den Lizenzhinweis kopieren. Wenn du den Beitragslink in das Suchfeld im Fediverse (z.B. bei Mastodon) eingibst, wird er dir dort angezeigt und du kannst ihn kommentieren.
