Zum Hauptinhalt springen
Zurück zum Blog

Wie ich meinen eigenen AI-Chatbot baute - Neo4j, RAG und der Wolf

Wolf Eric Lenk
· 20. December 2025 · 4 Min. Lesezeit
Wie ich meinen eigenen AI-Chatbot baute - Neo4j, RAG und der Wolf

Der Wolf ist geboren

Auf meiner Website sitzt unten rechts ein kleiner Wolf. Er sieht harmlos aus, aber hinter ihm steckt mehr als nur ein Button mit GPT-Anbindung. Er kennt mich – meine Services, meine Projekte, meine Geschichte. Und er antwortet, ohne zu halluzinieren.

Wie das funktioniert? Mit einer Graph-Datenbank, RAG (Retrieval Augmented Generation) und einigen Lektionen, die ich gerne früher gewusst hätte.


Warum nicht einfach ChatGPT anbinden?

Klar, man könnte einen LLM einfach als API aufrufen und fertig. Das Problem:

  1. Halluzinationen: GPT erfindet Dinge, die nicht stimmen
  2. Kein Kontext: Das Modell weiß nichts über meine Website
  3. Keine Kontrolle: Was antwortet es auf "Was kostet ein Projekt?"

Die Lösung: RAG (Retrieval Augmented Generation)

Statt dem LLM blind zu vertrauen, geben wir ihm nur die Informationen, die es braucht. Der Bot fragt erst die Datenbank, dann formuliert er die Antwort.


Die Architektur

User-Frage
    ↓
WolfChatController (ASP.NET)
    ↓
Neo4jService → Holt relevanten Kontext aus Graph-DB
    ↓
WolfChatService → Baut Prompt + ruft LLM auf
    ↓
Antwort mit korrekten Informationen

Die Komponenten:

Komponente Aufgabe
Neo4j Graph-Datenbank mit meinem gesamten Website-Wissen
Neo4jService Synct Content → Graph, holt Kontext für Fragen
WolfChatService System-Prompt, LLM-Call, Antwort-Formatierung
wolf-chat.js Frontend Widget mit Alpine.js

Warum eine Graph-Datenbank?

Relationale Datenbanken (SQL) sind top für strukturierte Daten. Aber für Wissensnetze sind sie umständlich.

Beispiel: Welche Skills braucht man für welchen Service?

SQL:

SELECT s.name, sk.name 
FROM services s
JOIN service_skills ss ON s.id = ss.service_id
JOIN skills sk ON ss.skill_id = sk.id
WHERE ... -- 3 Tabellen, 2 Joins

Neo4j (Cypher):

MATCH (s:Service)-[:REQUIRES]->(sk:Skill)
RETURN s.name, sk.name

Ein Graph bildet Beziehungen natürlich ab. Und genau das braucht ein Chatbot, der Zusammenhänge verstehen soll.


Das Knowledge-Schema

Mein Wolf kennt diese Knoten-Typen:

(Person {name, role, contact})
(Service {name, description})
(Project {title, status, tech})
(BlogPost {title, excerpt})
(Feature {name, trigger, instructions})
(Skill) (Technology)

Und diese Beziehungen:

(Person)-[:OFFERS]->(Service)
(Service)-[:USES]->(Technology)
(Project)-[:USES]->(Technology)
(Feature)-[:NAVIGATES_TO]->(Page)

Der Content-Sync

Jedes Mal, wenn die App startet, synct ein ContentSyncService alle Inhalte in die Graph-DB:

public async Task SyncAllContentAsync()
{
    await SyncPersonInfoAsync();
    await SyncServicesAsync();
    await SyncFeaturesAsync();
    
    // Blog-Posts und Projekte aus Markdown-Dateien
    foreach (var post in blogPosts)
        await SyncBlogPostAsync(post);
}

Das bedeutet: Immer aktuell, keine manuelle Pflege.


Der RAG-Flow in Aktion

Wenn ein User fragt: "Was kostet ein Website-Projekt?"

  1. Wolf empfängt Frage (via WolfChatController)
  2. Neo4j-Query: Hole alles über Services, Preise, Prozess
  3. Prompt-Building: System-Prompt + Kontext + User-Frage
  4. LLM-Call: Gemini/GPT generiert Antwort
  5. Antwort geht zurück mit korrekten Infos
var context = await _neo4jService.GetRelevantContextAsync(userQuestion);
var systemPrompt = BuildSystemPrompt(context);
var response = await _llm.GenerateAsync(systemPrompt, userQuestion);

Learnings

1. Immer ALLES laden, nicht filtern

Ursprünglich wollte ich "relevante" Nodes per Keyword-Matching laden. Das führte zu:

  • Lücken im Kontext
  • Inkonsistente Antworten

Lösung: Ich lade einfach alles. Bei meiner Website-Größe (~50 Nodes) ist das kein Problem und verhindert Halluzinationen.

2. System-Prompt ist King

Der wichtigste Code ist nicht der Graph-Query, sondern der System-Prompt:

Du bist der Wolf-Assistent von Lupus Malus Deviant.
Du antwortest NUR basierend auf dem folgenden Kontext.
Wenn du etwas nicht weißt, sag es ehrlich.
Du bist freundlich, direkt und hilfst gerne.

3. Fehlerfall = Ehrlichkeit

Wenn Neo4j nicht erreichbar ist oder das LLM spinnt, antwortet der Wolf:

"Awoo! Da ist was schiefgelaufen. Schreib Eric lieber direkt an..."

Besser als ein kaputter Bot.


Tech-Stack

  • Backend: ASP.NET Core 8
  • Graph-DB: Neo4j (Docker)
  • LLM: Google Gemini (via API)
  • Frontend: Alpine.js, Tailwind CSS
  • Deployment: Docker + GitHub Actions

Fazit

Ein guter Chatbot ist nicht der, der am meisten weiß – sondern der, der nur das sagt, was er wirklich weiß.

Mit Neo4j + RAG habe ich einen Assistenten gebaut, der:

  • ✅ Korrekte Informationen gibt
  • ✅ Meine Website kennt
  • ✅ Nicht halluziniert
  • ✅ Persönlichkeit hat (er ist ein Wolf!)

Hast du Fragen zum Wolf oder willst einen eigenen Chatbot? Schreib mir – ich helfe gerne!

Wolf

Eric Lenk

Web-Entwickler mit Fokus auf ASP.NET, moderne Frontends und KI-Integration. Immer auf der Jagd nach dem nächsten spannenden Projekt.

Achievement!