Files
ai-agent/README.md
Christoph K. 905981cd1e zwischenstand
2026-03-20 23:24:56 +01:00

341 lines
10 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.01.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 |