Zurück zur Dokumentation
Guide 05

Funktionsübersicht

Übersicht der Features mit Internals, Datenfluss und file:line-Referenzen. Für jedes Feature: wie es funktioniert, wie man es nutzt, wie man es erweitert.

7 Min. Lesezeit

Übersicht der Features mit Internals, Datenfluss und file:line-Referenzen. Für jedes Feature: wie es funktioniert, wie man es nutzt, wie man es erweitert.

Hybrides RAG (Vector + PostgreSQL tsvector, RRF)

Die hybride Suche ist auf zwei Prozesse verteilt: Der Collector besitzt nur die Vektorkomponente (LanceDB/Qdrant); der Server besitzt die FTS (PostgreSQL tsvector) und das RRF, das beide fusioniert.

Ablauf (packages/server/src/services/hybridSearchService.ts:104):

  1. Der Agent ruft die Skill rag_search (agent/builtinSkills.ts:27) mit query, workspaceId, archiveId auf.
  2. hybridSearch - Guard D-01: vergleicht EMBEDDING_MODEL mit den embeddingModel der Document des Workspace; bei Mismatch → überspringt den Vector-Leg (FTS-only, kein Fehler).
  3. Promise.all([vectorSearchViaCollector(), ftsSearch()]) - Vector via POST /api/ingest/query des Collectors; FTS via prisma.$queryRaw auf document_chunks.searchVector (GIN-Index, ts_rank).
  4. RRF-Fusion (k=60): pro Chunk score += 1/(60+rank+1); das sources-Set unterscheidet vector/fts/both. Deterministischer Tiebreaker: score DESC → documentId ASC → chunkIndex ASC.
  5. rag_search formatiert die Chunks mit [Source: name (match: semantic+keyword|vector|fts, score: ...)] und gibt SourceCitation[] zurück.
  6. Archive-Fallback D-07: bei 0 Treffern und vorhandener archiveId erneut auf archive:${archiveId} und taggt source: "archive" um.
  7. Zitationen werden an das SSE citations weitergegeben (routes/chat.ts:508).

Metadata filtering (v0.22): rag_search akzeptiert Filter documentTypes (pdf/md/txt/csv/docx/xlsx) und dateFrom/dateTo (ISO; das reine Datumslimit umfasst den ganzen Tag). Die Filter durchlaufen beide Legs der hybriden Suche: Der Vector-Leg erreicht den Collector als Pre-Filter auf den Ingest-Metadaten mit Timestamp (JSONB-Prädikate pgvector + Must-Clause-Payload Qdrant; LanceDB und Chroma ignorieren die neuen Keys mit geloggtem Warn), der FTS-Leg erhält parametrisierte SQL-Prädikate auf der Tabelle documents. Ein serverseitiger Backstop nach der Retrieval gegen die autoritative Tabelle documents garantiert die Korrektheit des Filters bei jedem Provider. Ab jetzt ingestierte Dokumente sind immer filterbar; Legacy-Vektoren (Pre-Stamping) werden ausgeschlossen, solange ein Filter aktiv ist, und erlangen ihre Filterbarkeit per Admin-Re-Embed zurück - keine Schema-Änderung, kein Re-Embed bestehender Dokumente, Verhalten ohne Filter unverändert.

Grounded Citations (v0.22.0): Nach der Deduplizierung begrenzt ein Grounding-Filter die Zitationen pro Dokument auf die Top-2 nach Relevance Score und entfernt Zitationen von Chunks, deren Text keine signifikante lexikalische Überlappung mit der finalen Antwort teilt (konservatives Gate - Wiki-/Archive-Seiten, Web-/Memory-Ergebnisse, Zitationen ohne Chunk-Text und sehr kurze Antworten bleiben immer erhalten). Der Hinweis "No supporting evidence" wird am gefilterten Set beurteilt (Streaming- und Non-Streaming-Pfade).

Chunking (collector/services/chunker.ts:16): RecursiveCharacterTextSplitter, chunkSize=1000, overlap=200, Separatoren ["\n\n","\n",". "," ",""]. Metadata: documentId, paragraph, charStart.

Embedding (collector/services/embeddings.ts): LocalEmbeddingProvider (Xenova all-MiniLM-L6-v2, 384-dim, Air-Gap) als Standard; OpenAIEmbeddingProvider (text-embedding-3-small, 1536-dim). Singleton gecacht pro providerType:modelName.

Vector Store (collector/services/vectorStore.ts): LanceDBProvider (Standard, Pfad ${STORAGE_PATH}/vectors/lancedb, Tabelle pro Workspace ws_<name>_<shortId>); QdrantProvider (Enterprise-Platzhalter).

FTS Pass-Through: Der Collector gibt chunks[].chunkText an den Server zurück, der DocumentChunk mit searchVector = to_tsvector('simple', text) schreibt. Der Collector öffnet nie Prisma.

Erweitern: neuer Embedding-Provider → implementiere EmbeddingProvider + Factory getEmbeddingProvider + Dimensionen in KNOWN_MODEL_DIMS. RRF-Tuning → RRF_K in hybridSearchService.ts:15. Wechsel des Embedding-Modells → Reindex via Settings → Maintenance (POST /api/system/reindex-documents).

Agent Orchestrator (ReAct)

packages/server/src/agent/orchestrator.ts - Einstieg runAgent (non-stream, :78) und runAgentStreaming (:416). ReAct-Single-Call-Pattern: ein `streamLLM` pro Iteration ist die Decision Call - streamResult.toolCall bestimmt den Tool-vs-Antwort-Zweig.

Loop:

  1. Setup: lädt WorkspaceAgentConfig, parst enabledSkills, löst das Template auf (resolveSystemPrompt), stellt user.customInstructions voran.
  2. Provider auflösen: resolveProviderConfig(providerId, model) → Fallback buildFallbackConfig.
  3. Constraint-Template: localLLMOnly (blockiert Nicht-Ollama-Provider), hybridSearchForced, citationRequired.
  4. Bei ragContext (Widget) → in den System-Prompt injizieren, rag_search aus activeSkills entfernen.
  5. Plan-Modus (agentConfig.planMode): synchrone Planning-Phase → SSE plan-Event → Injektion in den Execute-Prompt.
  6. ReAct while(true):
    • Watchdog Wallclock + Token-Budget.
    • streamLLM mit Buffered Replay D-02: Tool-Call-Iteration → Buffer verwerfen; Final-Answer → Tokens an den echten onToken replayen (progressive DLP-Flush).
    • !toolCall → versuche resolveImplicitToolCall (XML-ish-Recovery-Tag) → Final Answer, break.
    • Skill nicht gefunden → Circuit Breaker BOT-03: Trip bei 3 aufeinanderfolgenden / 5 insgesamt → abortReason: "unknown_tool_breaker".
    • Loop Detection, Per-Skill-Timeout, truncateToolOutput, Context Cap.
  7. finally: Concurrency-Slot freigeben + WorkspaceTokenUsage auf allen Exit-Pfaden persistieren.

Builtin-Skills (agent/builtinSkills.ts): rag_search, workspace_memory (KV auf SystemConfig mit Key ws_memory:<ws>:<key>), document_temp_process (Ad-hoc-Upload an den Collector), wiki_query (FTS archive_pages + Vektor-Fallback + BFS-Wikilinks, max 10 Seiten depth 3), wiki_write (Preview + SSE wiki_edit, RBAC archive:write).

Budget Tracker (services/agentBudgetService.ts:122): Watchdog Wallclock/Token/Context/Loop-Detection/Per-Skill-Timeout/Per-User-Concurrency (CHAT_MAX_CONCURRENT_PER_USER). AbortReason: none|wallclock|token_budget|context_overflow|loop_detected|aborted|done|unknown_tool_breaker. truncateContextToByteBudget keep sysMsg + pinned user msg + last N.

Modellauswahl + Fallback: Priorität Per-Chat → Workspace Default → Global Default → ENV (providerService.resolveProviderConfig). deriveCapabilities taggt local-only/fastest/smartest/reasoning. Polling 30s refreshModels → Provider.isAvailable. Frontend-Fallback 3-Tier in useChat.ts mit Undo-Toast. Server-Fallback buildFallbackConfig (orchestrator.ts:879). SSE done enthält modelUsed, providerUsed.

Erweitern: neue Skill → registerSkill({...}) in builtinSkills.ts. Per-Chat-Modell → PATCH /workspaces/:ws/chats/:chatId/model. Plan-Modus → WorkspaceAgentConfig.planMode aktivieren. Budget-Tuning → Env AGENT_* (siehe 03 - Configuration).

Chat-Streaming (SSE)

POST /api/workspaces/:id/chat/stream → SSE. Events: token, status, citations ({ sources }), done ({ chatId, messageId, modelUsed, providerUsed, mcpSources }), error, plan, wiki_edit. Frontend: useChat.ts (@microsoft/fetch-event-source), Abbruch via AbortController. Slash /model, Palette Cmd+K, Vergleich Cmd+Shift+M. Persistenz des effektiven Modells via loadChat PATCH (Stale Default übersprungen, siehe frontend/CLAUDE.md).

Dokumente, Ingestion, OCR

Ingestion (collector/routes/ingest.ts:219): multer (100 MB, MIME-Allowlist) → parseFile → chunkText → embed → vectorStore.addDocuments → notifyServerStatus (PUT /api/documents/:id/status mit X-Collector-Secret). Die Antwort enthält chunks[].chunkText für die serverseitige FTS.

Parser (collector/services/parser.ts): PDF (pdf-parse, <100 Zeichen → OCR Skip D-04, Vision ist serverseitig), DOCX (officeparser → Fallback mammoth), PPTX (officeparser), XLSX (node-xlsx), TXT/MD/CSV (UTF-8), YouTube (youtube-transcript-plus, 422 wenn kein Transkript). ocrMode (auto/vision/skip) in IngestUploadBodySchema.

Vision-OCR (serverseitig) (services/ragOcrService.ts:59): pdfjs-dist lädt das PDF (Cap 50 Seiten) → PNG-Rendering → Ollama-Vision-Modell → Grounding-Tags strippen → Verkettung. Job-Service ocrJobService.ts mit Approve/Reject-Workflow, qualityScore, credibilityScore. Routes routes/ocr.ts. Frontend SettingsOcr (Modellkatalog, Job-Liste, Preview, Custom Instructions).

DLP-Filter (services/dlpFilter.ts): scanContent(text) → {hasMatch, matches, redactedText}. Seit v0.22 sind die Patterns 10 Built-in database-backed (Tabelle dlp_patterns, bis zu 50 Custom-Patterns mit Live-Test-Preview): zusätzlich zu den 6 Originalen (email, credit_card, ssn, api_key sk-..., aws_key AKIA..., private_key PEM) kamen 4 EU/IT-Patterns hinzu - it_vat_iva (Partita IVA label-anchored), it_codice_fiscale (16 Zeichen, uppercase-only), iban (Länderpräfix + Länge ≥ 15, ReDoS-safe), eu_phone (IT/EU-Telefon, als DISABLED geseedet für Admin-Review). Redaction [REDACTED]. Gate DLP_ENABLED (Standard `"true"` in CONFIG_DEFAULTS, permissions.ts:173); Setting DLP_BYPASS_ROLES (JSON-Array von Rollen, Standard []) befreit ausgewählte Rollen von jedem Scan/Redaction mit Audit-Event dlp.bypassed. Admin-Toggle "Show/Hide DLP texts" zum Aufdecken/erneuten Maskieren der redigierten Texte (nur Admin, session-scoped). Jedes Audit-Event dlp.* taggt die Quelle (source: "chat" | "widget"), abgeleitet aus dem Header X-Widget-Id, mit Filtern im DLP Match History-Panel. Fallback auf das Built-in-Set, wenn die DB nicht erreichbar ist (Cache TTL 5 min); der Token-für-Token-Streaming-Flush erhält die Built-in-Patterns. Integriert in routes/chat.ts (Eingang + Ausgang); SSE done → dlp_warning: true; das Widget zeigt PIIWarningPrompt.

Archive, Wiki, Synthesis

Archive: workspace-scoped Multi-Page-Knowledge-Base, Markdown-Seiten mit Pfad-Slug und Wikilink [[Title]]. 14 Archive-Services (archivePageService, archiveGraphService, archiveIndexService, archiveBacklinkService, ...). Routes archive*.ts (20+ Dateien): CRUD, pages, config, search (hybrid RRF), graph, export, index, import (copy-from-doc), schema templates.

Wiki-Embedding (services/wikiEmbeddingService.ts:8): indexWikiPage → Chunk 800/100 → Collector POST /api/ingest/wiki-pages (Tabelle wiki_pages, workspaceId = archive:<id>). Skill wiki_query (FTS + BFS-Wikilinks) und wiki_write (Preview/Dry-Run + wiki_edit SSE).

Synthesis (services/synthesisService.ts): 5-Pass-Pipeline: entity_extraction → summary_generation → candidate_search (BM25 tsvector) → llm_decision (CREATE/UPDATE/SKIP/FLAG_CONTRADICTION) → write_overview. PHI-Gate: wenn ArchiveConfig.localLLMOnly=true und Provider nicht Ollama → Abbruch vor Egress. Admin-Workflow: Trigger → Preview → Approve/Reject (routes/synthesis.ts).

Backup

Bree-Scheduler (services/backupSchedulerService.ts): @mintplex-labs/bree, closeWorkerAfterMs:0/timeout:0 (long-running). bootstrapJobs beim Start (cleanStaleLocks → lädt BackupJob enabled). DB-Level-Mutex via BackupLog.status="running"; cleanStaleLocks → running >2h = failed. Cron-Helper buildCronExpression(frequency, time, ...).

Pipeline (services/backupService.ts:282): dumpDatabase (pg_dump spawn no-shell) → collectFiles (Storage-Dirs, Path-Traversal-Guard) → createArchive (archiver zlib level 9) → computeChecksum (SHA-256 Streaming) → uploadWithRetry (3 Retry-Backoff) → sendFailureNotification (SMTP). Safety-Backup vor Restore (runSafetyBackup).

Verschlüsselung (services/encryptionService.ts): AES-256-GCM. Key: ENCRYPTION_KEY (base64 32 Byte) oder scryptSync(JWT_SECRET, salt). Decrypt-Chain rotationssicher: [current, ...LEGACY_PREVIOUS_ENCRYPTION_KEYS, scryptLegacy?].

Ziele (services/backup/providers/): LocalProvider, S3Provider, S3CompatibleProvider, DropboxProvider, GoogleDriveProvider, FtpProvider, SftpProvider, EmailProvider (Interface IBackupDestinationProvider). License backup_enabled + max_backup_destinations (Community 1, Enterprise ∞).

Restore (backupService.ts:617): restoreDatabaseWithPsql (psql --single-transaction --set ON_ERROR_STOP=on, All-or-Nothing-Atomarität). Routes restore.ts + POST /api/backups/trigger (manuell).

Retention (backupRetentionService.ts): Per-Job retentionDays (Standard 30); Cleanup BackupLog Success jenseits des Cutoff + Hard Delete. Einzelne Fehler blockieren nicht.

Push (VAPID Web Push)

routes/push.ts: initVapid liest die VAPID_*-Envs (Auto-Generierung in Dev). GET /vapid-key (öffentlich), POST/DELETE /subscribe (License push_notifications), POST /test (Admin). sendPushNotification(title, body) Export für Event-Trigger; entfernt Subscription 410 Gone. Frontend: Service-Worker-API.

Webhook

services/webhookService.ts: dispatchWebhookEvent(event, payload) fire-and-forget parallel. deliverWebhook: Body {event, timestamp, data}, Header X-Webhook-Signature: sha256=HMAC-SHA256(secret, body). Retry 3 (1s/2s/4s); 10 aufeinanderfolgende Fehler → Auto-Disable. Events: document.processed, chat.completed, user.created, workspace.updated, mcp.installed/uninstalled, ... Ausgelöst von eventLogService.logEvent (async post-DB). Routes webhooks.ts (License webhooks).