341 lines
10 KiB
Markdown
Executable File
341 lines
10 KiB
Markdown
Executable File
# Brain-Bot
|
||
|
||
Persönlicher KI-Assistent und RAG-System in Go. Speichert Wissen in einer Qdrant-Vektordatenbank und beantwortet Fragen über ein lokales LLM. Primäres Interface: Discord-Bot. Läuft als systemd-Dienst auf einem Home-Server.
|
||
|
||
## Features
|
||
|
||
- **Discord-Bot** mit Slash-Commands und @Mention
|
||
- **RAG** über Markdown-Notizen, Emails, URLs, PDFs und RSS-Artikel
|
||
- **Email-Management**: IMAP IDLE, Triage (wichtig/unwichtig via LLM), Archiv-Cleanup
|
||
- **Task-Verwaltung** mit Fälligkeit und Priorität
|
||
- **Morgen-Briefing** täglich um 8:00 (Tasks + ungelesene Emails)
|
||
- **Core Memory**: persistente Nutzerfakten, automatisch in jeden LLM-Prompt eingebaut
|
||
- **RSS-Watcher**: automatisches Importieren von Feed-Artikeln
|
||
- **User-Permissions**: optionale Einschränkung auf bestimmte Discord-User-IDs
|
||
|
||
---
|
||
|
||
## Voraussetzungen
|
||
|
||
| Dienst | Adresse | Zweck |
|
||
|--------|---------|-------|
|
||
| Qdrant | `192.168.1.4:6334` (gRPC) | Vektordatenbank |
|
||
| LocalAI | `192.168.1.118:8080` | Embeddings + Chat (OpenAI-kompatibel) |
|
||
| IMAP-Server | konfigurierbar | Email-Abruf (STARTTLS oder TLS) |
|
||
| Discord | Bot-Token | Primäres Interface |
|
||
|
||
---
|
||
|
||
## Schnellstart
|
||
|
||
```bash
|
||
# Einmalig: config.yml anlegen
|
||
cp config.yml.example config.yml # Credentials eintragen
|
||
|
||
# Bot starten
|
||
go run ./cmd/discord/
|
||
|
||
# Oder: CLI-Tools
|
||
go run ./cmd/ask/ "Was sind meine TODOs?"
|
||
go run ./cmd/ingest/ # Markdown aus brain_root importieren
|
||
```
|
||
|
||
---
|
||
|
||
## Discord-Commands
|
||
|
||
### Wissen abfragen
|
||
|
||
| Command | Beschreibung |
|
||
|---------|-------------|
|
||
| `/ask <frage>` | Wissensdatenbank abfragen (mit Gesprächsgedächtnis) |
|
||
| `/research <frage>` | Alias für `/ask` |
|
||
| `/asknobrain <frage>` | Direkt ans LLM, kein RAG |
|
||
| `/clear` | Gesprächsverlauf dieses Channels löschen |
|
||
|
||
### Wissen speichern
|
||
|
||
| Command | Beschreibung |
|
||
|---------|-------------|
|
||
| `/memory store <text>` | Text direkt in Wissensdatenbank speichern |
|
||
| `/memory ingest` | Alle Markdown-Dateien aus `brain_root` importieren |
|
||
| `/memory url <url>` | Webseite fetchen und importieren |
|
||
| `/memory profile <text>` | Fakt zum Kerngedächtnis hinzufügen |
|
||
| `/memory profile-show` | Kerngedächtnis anzeigen |
|
||
| `/remember <text>` | Alias für `/memory store` |
|
||
| `/ingest` | Alias für `/memory ingest` |
|
||
| *(PDF-Anhang)* | PDF an @Bot schicken → automatisch importiert |
|
||
|
||
### Wissensdatenbank verwalten
|
||
|
||
| Command | Beschreibung |
|
||
|---------|-------------|
|
||
| `/knowledge list` | Alle gespeicherten Quellen auflisten |
|
||
| `/knowledge delete <source>` | Quelle und alle ihre Chunks löschen |
|
||
|
||
### Tasks
|
||
|
||
| Command | Beschreibung |
|
||
|---------|-------------|
|
||
| `/task add <text> [--due YYYY-MM-DD] [--priority hoch\|mittel\|niedrig]` | Task anlegen |
|
||
| `/task list` | Alle offenen Tasks anzeigen |
|
||
| `/task done <id>` | Task als erledigt markieren |
|
||
| `/task delete <id>` | Task löschen |
|
||
|
||
### Email
|
||
|
||
| Command | Beschreibung |
|
||
|---------|-------------|
|
||
| `/email summary` | Letzte Emails zusammenfassen |
|
||
| `/email unread` | Ungelesene Emails zusammenfassen |
|
||
| `/email remind` | Termine und Deadlines aus Emails extrahieren |
|
||
| `/email ingest [ordner]` | Emails eines IMAP-Ordners in Qdrant importieren |
|
||
| `/email move <ordner>` | Emails interaktiv in Archivordner verschieben |
|
||
| `/email triage` | Letzte 10 Emails als wichtig/unwichtig klassifizieren |
|
||
|
||
### Sonstiges
|
||
|
||
| Command | Beschreibung |
|
||
|---------|-------------|
|
||
| `/status` | Verbindungen prüfen, offene Tasks zählen |
|
||
|
||
### @Mention
|
||
|
||
```
|
||
@Brain <frage>
|
||
@Brain task add <text> [--due YYYY-MM-DD] [--priority hoch]
|
||
@Brain task list / done <id> / delete <id>
|
||
@Brain email summary / unread / remind / ingest [ordner] / move <ordner>
|
||
@Brain remember <text>
|
||
@Brain clear
|
||
```
|
||
|
||
PDF-Datei an @Brain anhängen → wird automatisch importiert.
|
||
|
||
---
|
||
|
||
## Konfiguration (`config.yml`)
|
||
|
||
```yaml
|
||
# Qdrant-Vektordatenbank
|
||
qdrant:
|
||
host: "192.168.1.4"
|
||
port: "6334"
|
||
api_key: "geheimespasswort"
|
||
collection: "jacek-brain"
|
||
|
||
# Embedding-Modell (LocalAI)
|
||
embedding:
|
||
url: "http://192.168.1.118:8080/v1"
|
||
model: "qwen3-embedding-4b"
|
||
dimensions: 2560 # muss exakt zum Modell passen
|
||
|
||
# Chat-Modell (LocalAI)
|
||
chat:
|
||
url: "http://192.168.1.118:8080/v1"
|
||
model: "Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF"
|
||
|
||
# Discord-Bot
|
||
discord:
|
||
token: "Bot-Token"
|
||
guild_id: "" # leer = global (bis 1h Verzögerung); Guild-ID = sofort
|
||
allowed_users: # optional: leer = alle erlaubt
|
||
- "123456789012345678" # Discord User-ID
|
||
|
||
# Wissensbasis-Verzeichnis (Markdown-Dateien)
|
||
brain_root: "/mnt/c/Users/jacek/AI_Brain"
|
||
top_k: 5 # Anzahl der Suchergebnisse pro Anfrage
|
||
score_threshold: 0.55 # Minimale Relevanz (0.0–1.0)
|
||
|
||
# Task-Speicher
|
||
tasks:
|
||
store_path: "./tasks.json"
|
||
|
||
# Daemon-Einstellungen
|
||
daemon:
|
||
channel_id: "1234567890" # Discord-Channel für proaktive Nachrichten
|
||
task_reminder_hour: 8 # Morgen-Briefing Uhrzeit (0-23, Standard: 8)
|
||
cleanup_hour: 2 # Archiv-Aufräumen Uhrzeit (0-23, Standard: 2)
|
||
ingest_hour: 23 # Email-Ingest Uhrzeit (0-23, Standard: 23)
|
||
|
||
# RSS-Feeds (optional)
|
||
rss_feeds:
|
||
- url: "https://example.com/feed.xml"
|
||
interval_hours: 24
|
||
- url: "https://heise.de/rss/heise.rdf"
|
||
interval_hours: 6
|
||
|
||
# Email — einzelner Account (Legacy)
|
||
email:
|
||
host: "imap.strato.de"
|
||
port: 143
|
||
user: "user@example.de"
|
||
password: "passwort"
|
||
starttls: true # oder tls: true für Port 993
|
||
folder: "INBOX"
|
||
processed_folder: "" # nach Zusammenfassung verschieben (leer = deaktiviert)
|
||
model: "" # eigenes LLM-Modell für Email-Analyse (leer = chat.model)
|
||
triage_important_folder: "Wichtig"
|
||
triage_unimportant_folder: "Unwichtig"
|
||
archive_folders:
|
||
- name: "Archiv"
|
||
imap_folder: "Archiv"
|
||
retention_days: 0 # 0 = dauerhaft behalten
|
||
- name: "5Jahre"
|
||
imap_folder: "5Jahre"
|
||
retention_days: 1825
|
||
- name: "2Jahre"
|
||
imap_folder: "2Jahre"
|
||
retention_days: 730
|
||
|
||
# Email — mehrere Accounts (hat Vorrang vor email:)
|
||
email_accounts:
|
||
- name: "Privat"
|
||
host: "imap.strato.de"
|
||
port: 143
|
||
starttls: true
|
||
user: "privat@example.de"
|
||
password: "passwort"
|
||
processed_folder: "Processed"
|
||
triage_important_folder: "Wichtig"
|
||
triage_unimportant_folder: "Unwichtig"
|
||
archive_folders:
|
||
- name: "Archiv"
|
||
imap_folder: "Archiv"
|
||
retention_days: 0
|
||
- name: "Arbeit"
|
||
host: "imap.firma.de"
|
||
port: 993
|
||
tls: true
|
||
user: "jacek@firma.de"
|
||
password: "passwort"
|
||
```
|
||
|
||
> **Wichtig:** Wenn `embedding.model` oder `dimensions` geändert wird, muss die Qdrant-Collection neu erstellt werden (im Dashboard löschen, dann `/memory ingest` erneut ausführen).
|
||
|
||
---
|
||
|
||
## Deployment
|
||
|
||
```bash
|
||
# deploy.env anlegen (einmalig)
|
||
cp deploy.env.example deploy.env
|
||
# Credentials eintragen: DEPLOY_HOST, DEPLOY_USER, DEPLOY_PASS, DEPLOY_DIR, SERVICE_NAME
|
||
|
||
# Deploy
|
||
bash deploy.sh # build + scp + systemctl restart
|
||
```
|
||
|
||
Das Script baut das Linux-Binary, überträgt es per `sshpass`/`scp` und startet den systemd-Service neu.
|
||
|
||
```bash
|
||
# Systemd-Service (einmalig auf dem Server einrichten)
|
||
sudo systemctl unmask brain-bot # falls masked
|
||
sudo systemctl enable brain-bot
|
||
```
|
||
|
||
---
|
||
|
||
## Entwicklung
|
||
|
||
```bash
|
||
# Tests ausführen
|
||
go test ./...
|
||
|
||
# Bot lokal starten
|
||
go run ./cmd/discord/
|
||
|
||
# Debug-Logging (LLM-Prompts + Antworten)
|
||
DEBUG=1 go run ./cmd/discord/
|
||
|
||
# Abhängigkeiten aufräumen
|
||
go mod tidy
|
||
```
|
||
|
||
### Projektstruktur
|
||
|
||
```
|
||
cmd/
|
||
discord/main.go Discord-Bot + Daemon (primärer Einstiegspunkt)
|
||
ask/main.go CLI: Fragen stellen
|
||
ingest/main.go CLI: Markdown/JSON importieren
|
||
mailtest/main.go CLI: IMAP + LLM testen
|
||
|
||
internal/
|
||
config/
|
||
config.go Konfigurationsstruktur, Client-Factories
|
||
config_test.go Config-Tests
|
||
brain/
|
||
ask.go RAG-Suche + LLM-Antwort (AskQuery, ChatDirect)
|
||
ingest.go Markdown-Import, Chunking, IngestText
|
||
ingest_json.go JSON-Import (Bildbeschreibungen)
|
||
ingest_email.go IMAP-Ordner → Qdrant
|
||
ingest_url.go URL fetchen → Qdrant
|
||
ingest_pdf.go PDF-Text → Qdrant
|
||
knowledge.go ListSources, DeleteBySource
|
||
core_memory.go Kerngedächtnis (core_memory.md)
|
||
agents/
|
||
agent.go Agent-Interface (Request/Response/HistoryMessage)
|
||
actions.go Typsichere Action-Konstanten
|
||
memory/agent.go Memory-Agent (store, ingest, url, profile)
|
||
research/agent.go Research-Agent (RAG-Suche mit History)
|
||
task/
|
||
agent.go Task-Agent (add, list, done, delete)
|
||
store.go Atomarer JSON-Speicher (tasks.json)
|
||
tool/
|
||
agent.go Tool-Dispatcher + ResolveArchiveFolder
|
||
email/
|
||
client.go IMAP-Client (Verbinden, Fetch, Move, Delete)
|
||
summary.go Email-Zusammenfassung + LLM-Triage
|
||
idle.go IMAP IDLE-Watcher (Echtzeit-Benachrichtigung)
|
||
rss/
|
||
watcher.go RSS-Feed-Watcher (gofeed + brain.IngestText)
|
||
triage/
|
||
triage.go RAG-basiertes Triage-Lernen (eigenes Package)
|
||
diag/
|
||
diag.go Verbindungsdiagnose (/status)
|
||
```
|
||
|
||
---
|
||
|
||
## Wie es funktioniert
|
||
|
||
### RAG-Pipeline
|
||
|
||
1. Nutzer stellt Frage via Discord
|
||
2. Frage wird in Vektor umgewandelt (Embedding-Modell)
|
||
3. Qdrant liefert die `top_k` ähnlichsten Chunks (Score ≥ `score_threshold`)
|
||
4. Core Memory + Chunks + Gesprächshistory werden in LLM-Prompt eingefügt
|
||
5. LLM generiert Antwort (Streaming)
|
||
|
||
### Email-Triage
|
||
|
||
1. IMAP IDLE meldet neue Email in Echtzeit
|
||
2. `triageUnread()` klassifiziert jede Email via LLM (wichtig/unwichtig)
|
||
3. Ähnliche frühere Entscheidungen aus Qdrant werden als Few-Shot-Kontext mitgegeben
|
||
4. Jede Entscheidung wird in Qdrant gespeichert (Typ `email_triage`) → Modell lernt
|
||
5. Wichtige Emails bleiben in INBOX, unwichtige werden in `triage_unimportant_folder` verschoben
|
||
|
||
### Core Memory
|
||
|
||
- Datei: `brain_root/core_memory.md`
|
||
- Inhalt: Eine Zeile pro Fakt (Markdown-Liste)
|
||
- Wird bei **jeder** `AskQuery()`-Anfrage automatisch in den System-Prompt eingebaut
|
||
- Kein Neustart nötig (wird bei jedem Call frisch geladen)
|
||
|
||
### Deterministische IDs
|
||
|
||
Alle Qdrant-Punkte haben SHA256-basierte IDs (`source:text`). Derselbe Chunk kann beliebig oft importiert werden — Qdrant überschreibt denselben Punkt (Upsert), keine Duplikate.
|
||
|
||
---
|
||
|
||
## Externe Dienste
|
||
|
||
| Dienst | Standard-Adresse | Protokoll |
|
||
|--------|-----------------|-----------|
|
||
| Qdrant | `192.168.1.4:6334` | gRPC |
|
||
| LocalAI (Embedding) | `192.168.1.118:8080` | HTTP/OpenAI |
|
||
| LocalAI (Chat) | `192.168.1.118:8080` | HTTP/OpenAI (Streaming) |
|
||
| Strato IMAP | `imap.strato.de:143` | STARTTLS |
|
||
| Discord | `discord.com` | WebSocket |
|