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

99 lines
2.6 KiB
Go
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.
// ingest_email.go Importiert Emails aus einem IMAP-Ordner in Qdrant
package brain
import (
"context"
"fmt"
"log/slog"
"strings"
"time"
pb "github.com/qdrant/go-client/qdrant"
"google.golang.org/grpc/metadata"
"my-brain-importer/internal/agents/tool/email"
"my-brain-importer/internal/config"
)
// IngestEmailFolder importiert alle Emails aus einem IMAP-Ordner in Qdrant.
// Gibt Anzahl der importierten Emails zurück.
// maxEmails = 0 bedeutet: alle (bis max. 500).
func IngestEmailFolder(acc config.EmailAccount, folder string, maxEmails uint32) (int, error) {
if maxEmails == 0 {
maxEmails = 500
}
cl, err := email.ConnectAccount(acc)
if err != nil {
return 0, fmt.Errorf("IMAP-Verbindung: %w", err)
}
defer cl.Close()
slog.Info("Email-Ingest: Lade Emails", "account", acc.Name, "folder", folder, "max", maxEmails)
msgs, err := cl.FetchWithBody(folder, maxEmails)
if err != nil {
return 0, fmt.Errorf("Emails laden: %w", err)
}
if len(msgs) == 0 {
return 0, nil
}
ctx := context.Background()
ctx = metadata.AppendToOutgoingContext(ctx, "api-key", config.Cfg.Qdrant.APIKey)
embClient := config.NewEmbeddingClient()
conn := config.NewQdrantConn()
defer conn.Close()
ensureCollection(ctx, pb.NewCollectionsClient(conn))
pointsClient := pb.NewPointsClient(conn)
var chunks []chunk
for _, m := range msgs {
text := formatEmailForIngest(m)
if len(strings.TrimSpace(text)) < 20 {
continue
}
source := fmt.Sprintf("email/%s/%s", folder, m.Date)
chunks = append(chunks, chunk{Text: text, Source: source, Type: "email"})
}
if len(chunks) == 0 {
return 0, nil
}
slog.Info("Email-Ingest: Starte Embedding", "emails", len(msgs), "chunks", len(chunks))
// In Batches von 20 ingesten (Embeddings können langsam sein)
ingested := 0
for i := 0; i < len(chunks); i += 20 {
end := i + 20
if end > len(chunks) {
end = len(chunks)
}
batch := chunks[i:end]
if err := ingestChunks(ctx, embClient, pointsClient, batch); err != nil {
slog.Warn("Email-Ingest Batch-Fehler", "batch_start", i, "fehler", err)
continue
}
ingested += len(batch)
slog.Info("Email-Ingest Fortschritt", "ingested", ingested, "total", len(chunks))
time.Sleep(50 * time.Millisecond)
}
return ingested, nil
}
// formatEmailForIngest formatiert eine Email als durchsuchbaren Text.
func formatEmailForIngest(m email.MessageWithBody) string {
var sb strings.Builder
fmt.Fprintf(&sb, "Betreff: %s\n", m.Subject)
fmt.Fprintf(&sb, "Von: %s\n", m.From)
fmt.Fprintf(&sb, "Datum: %s\n", m.Date)
if m.Body != "" {
sb.WriteString("\n")
sb.WriteString(m.Body)
}
return sb.String()
}