zwischenstand
This commit is contained in:
383
README.md
383
README.md
@@ -1,43 +1,340 @@
|
||||
# my-brain-importer
|
||||
|
||||
Persönlicher Wissens-Agent für den AI_Brain. Importiert Markdown-Notizen und Bildbeschreibungen in eine Qdrant-Vektordatenbank und beantwortet Fragen darüber mit einem lokalen LLM.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Go 1.22+
|
||||
- LocalAI läuft auf `embedding.url` mit dem konfigurierten Embedding-Modell geladen
|
||||
- LocalAI läuft auf `chat.url` mit dem konfigurierten Chat-Modell geladen
|
||||
- Qdrant läuft auf dem NAS (Port 6334 gRPC, Port 6333 Dashboard)
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
bash build.sh
|
||||
```
|
||||
|
||||
Erzeugt `bin/ingest`, `bin/ingest.exe`, `bin/ask`, `bin/ask.exe`.
|
||||
|
||||
## Nutzung
|
||||
|
||||
```bash
|
||||
# Markdown-Dateien aus brain_root importieren
|
||||
./bin/ingest
|
||||
|
||||
# Alternatives Verzeichnis angeben
|
||||
./bin/ingest /pfad/zum/verzeichnis
|
||||
|
||||
# Bildbeschreibungen aus JSON importieren
|
||||
./bin/ingest image_descriptions.json
|
||||
|
||||
# Frage stellen
|
||||
./bin/ask "Was sind meine Reisepläne für Norwegen?"
|
||||
./bin/ask "Erzähl mir über Veronica Bellmore"
|
||||
```
|
||||
|
||||
## Brain aktualisieren
|
||||
|
||||
Kein Löschen der Datenbank nötig — einfach `./bin/ingest` erneut ausführen:
|
||||
- Bestehende Chunks → gleiche SHA256-ID → Qdrant überschreibt
|
||||
- Neue Dateien → neue IDs → werden hinzugefügt
|
||||
|
||||
Architektur und Konfiguration: [doc/architecture.md](doc/architecture.md)
|
||||
# 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 |
|
||||
|
||||
Reference in New Issue
Block a user