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

204 lines
7.0 KiB
Go
Executable File
Raw Permalink 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.
// config.go Konfiguration, Clients und gemeinsame Verbindungen
package config
import (
"fmt"
"log"
"os"
openai "github.com/sashabaranov/go-openai"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"gopkg.in/yaml.v3"
)
// ArchiveFolder beschreibt einen IMAP-Archivordner mit optionaler Aufbewahrungsdauer.
type ArchiveFolder struct {
Name string `yaml:"name"` // Anzeigename (z.B. "5Jahre")
IMAPFolder string `yaml:"imap_folder"` // Echter IMAP-Ordnername (z.B. "5Jahre")
RetentionDays int `yaml:"retention_days"` // 0 = dauerhaft behalten
}
// EmailAccount beschreibt einen einzelnen IMAP-Account.
type EmailAccount struct {
Name string `yaml:"name"`
Host string `yaml:"host"`
Port int `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
TLS bool `yaml:"tls"`
StartTLS bool `yaml:"starttls"`
Folder string `yaml:"folder"`
ProcessedFolder string `yaml:"processed_folder"`
Model string `yaml:"model"`
ArchiveFolders []ArchiveFolder `yaml:"archive_folders"`
TriageImportantFolder string `yaml:"triage_important_folder"` // Ordner für wichtige Emails (leer = in INBOX lassen)
TriageUnimportantFolder string `yaml:"triage_unimportant_folder"` // Ordner für unwichtige Emails (leer = kein Triage)
}
type Config struct {
Qdrant struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
APIKey string `yaml:"api_key"`
Collection string `yaml:"collection"`
} `yaml:"qdrant"`
Embedding struct {
URL string `yaml:"url"`
Model string `yaml:"model"`
Dimensions uint64 `yaml:"dimensions"`
} `yaml:"embedding"`
Chat struct {
URL string `yaml:"url"`
Model string `yaml:"model"`
} `yaml:"chat"`
Discord struct {
Token string `yaml:"token"`
GuildID string `yaml:"guild_id"`
AllowedUsers []string `yaml:"allowed_users"` // Wenn gesetzt, dürfen nur diese User-IDs den Bot nutzen
} `yaml:"discord"`
// Email ist der Legacy-Block für einen einzelnen Account.
Email struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
TLS bool `yaml:"tls"`
StartTLS bool `yaml:"starttls"`
Folder string `yaml:"folder"`
ProcessedFolder string `yaml:"processed_folder"`
Model string `yaml:"model"`
ArchiveFolders []ArchiveFolder `yaml:"archive_folders"`
TriageImportantFolder string `yaml:"triage_important_folder"`
TriageUnimportantFolder string `yaml:"triage_unimportant_folder"`
} `yaml:"email"`
// EmailAccounts ermöglicht mehrere IMAP-Accounts. Hat Vorrang vor email:.
EmailAccounts []EmailAccount `yaml:"email_accounts"`
Tasks struct {
StorePath string `yaml:"store_path"`
} `yaml:"tasks"`
Daemon struct {
ChannelID string `yaml:"channel_id"`
EmailIntervalMin int `yaml:"email_interval_min"`
TaskReminderHour int `yaml:"task_reminder_hour"`
CleanupHour int `yaml:"cleanup_hour"` // Uhrzeit für tägliches Archiv-Aufräumen (Standard: 2)
IngestHour int `yaml:"ingest_hour"` // Uhrzeit für nächtlichen Email-Ingest (Standard: 23, 0 = deaktiviert)
} `yaml:"daemon"`
BrainRoot string `yaml:"brain_root"`
TopK uint64 `yaml:"top_k"`
ScoreThreshold float32 `yaml:"score_threshold"`
// RSSFeeds definiert RSS-Feeds die automatisch überwacht werden.
RSSFeeds []RSSFeed `yaml:"rss_feeds"`
}
// RSSFeed beschreibt einen RSS-Feed mit Polling-Intervall.
type RSSFeed struct {
URL string `yaml:"url"`
IntervalHours int `yaml:"interval_hours"` // 0 = Standard 24h
}
var Cfg Config
// AllEmailAccounts gibt alle konfigurierten Email-Accounts zurück.
// Wenn email_accounts konfiguriert ist, hat das Vorrang vor dem Legacy-email:-Block.
func AllEmailAccounts() []EmailAccount {
if len(Cfg.EmailAccounts) > 0 {
return Cfg.EmailAccounts
}
if Cfg.Email.Host == "" {
return nil
}
return []EmailAccount{{
Name: "Email",
Host: Cfg.Email.Host,
Port: Cfg.Email.Port,
User: Cfg.Email.User,
Password: Cfg.Email.Password,
TLS: Cfg.Email.TLS,
StartTLS: Cfg.Email.StartTLS,
Folder: Cfg.Email.Folder,
ProcessedFolder: Cfg.Email.ProcessedFolder,
Model: Cfg.Email.Model,
ArchiveFolders: Cfg.Email.ArchiveFolders,
TriageImportantFolder: Cfg.Email.TriageImportantFolder,
TriageUnimportantFolder: Cfg.Email.TriageUnimportantFolder,
}}
}
// NewQdrantConn öffnet eine gRPC-Verbindung zur Qdrant-Instanz.
// Der Aufrufer ist verantwortlich für conn.Close().
func NewQdrantConn() *grpc.ClientConn {
conn, err := grpc.Dial(
fmt.Sprintf("%s:%s", Cfg.Qdrant.Host, Cfg.Qdrant.Port),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalf("❌ Qdrant Verbindung fehlgeschlagen: %v", err)
}
return conn
}
// NewEmbeddingClient erstellt einen Client für LocalAI (Embeddings).
func NewEmbeddingClient() *openai.Client {
c := openai.DefaultConfig("localai")
c.BaseURL = Cfg.Embedding.URL
return openai.NewClientWithConfig(c)
}
// NewChatClient erstellt einen Client für Chat-Completion (LocalAI).
func NewChatClient() *openai.Client {
c := openai.DefaultConfig("localai")
c.BaseURL = Cfg.Chat.URL
return openai.NewClientWithConfig(c)
}
// LoadConfig liest config.yml aus dem aktuellen Verzeichnis und validiert Pflichtfelder.
func LoadConfig() {
data, err := os.ReadFile("config.yml")
if err != nil {
log.Fatalf("❌ config.yml nicht gefunden: %v\n Lege config.yml im selben Verzeichnis an.", err)
}
if err := yaml.Unmarshal(data, &Cfg); err != nil {
log.Fatalf("❌ config.yml ungültig: %v", err)
}
validateConfig()
}
// validateConfig prüft Pflichtfelder und gibt früh eine klare Fehlermeldung.
func validateConfig() {
var errs []string
if Cfg.Qdrant.Host == "" {
errs = append(errs, "qdrant.host fehlt")
}
if Cfg.Qdrant.Port == "" {
errs = append(errs, "qdrant.port fehlt")
}
if Cfg.Embedding.URL == "" {
errs = append(errs, "embedding.url fehlt")
}
if Cfg.Embedding.Model == "" {
errs = append(errs, "embedding.model fehlt")
}
if Cfg.Chat.URL == "" {
errs = append(errs, "chat.url fehlt")
}
if Cfg.Chat.Model == "" {
errs = append(errs, "chat.model fehlt")
}
if len(errs) > 0 {
for _, e := range errs {
log.Printf("❌ config.yml: %s", e)
}
log.Fatal("❌ Konfiguration unvollständig Bot wird nicht gestartet.")
}
}