Guide des fonctionnalités
Guide des fonctionnalités avec internals, flux de données et références file:line. Pour chaque fonctionnalité : comment elle fonctionne, comment l'utiliser, comment l'étendre.
Guide des fonctionnalités avec internals, flux de données et références file:line. Pour chaque fonctionnalité : comment elle fonctionne, comment l'utiliser, comment l'étendre.
RAG hybride (Vector + PostgreSQL tsvector, RRF)
La recherche hybride est répartie sur deux processus : le collector ne possède que la composante vectorielle (LanceDB/Qdrant) ; le serveur possède la FTS (PostgreSQL tsvector) et le RRF qui fusionne les deux.
Flux (packages/server/src/services/hybridSearchService.ts:104) :
- L'agent invoque la skill
rag_search(agent/builtinSkills.ts:27) avecquery,workspaceId,archiveId. hybridSearch- Guard D-01 : compareEMBEDDING_MODELavec lesembeddingModeldesDocumentdu workspace ; en cas de mismatch → saute le leg vector (FTS-only, pas d'erreur).Promise.all([vectorSearchViaCollector(), ftsSearch()])- vector viaPOST /api/ingest/querydu collector ; FTS viaprisma.$queryRawsurdocument_chunks.searchVector(index GIN,ts_rank).- RRF fusion (k=60) : pour chaque chunk
score += 1/(60+rank+1);sourcesSet distinguevector/fts/both. Tiebreaker déterministe : score DESC → documentId ASC → chunkIndex ASC. rag_searchformate les chunks avec[Source: name (match: semantic+keyword|vector|fts, score: ...)]et retourneSourceCitation[].- Fallback archive D-07 : si 0 résultat et
archiveIdprésent, relance surarchive:${archiveId}et retaguesource: "archive". - Citations propagées au SSE
citations(routes/chat.ts:508).
Metadata filtering (v0.22) : rag_search accepte les filtres documentTypes (pdf/md/txt/csv/docx/xlsx) et dateFrom/dateTo (ISO ; la limite date-only inclut la journée entière). Les filtres traversent les deux legs de la recherche hybride : le leg vector arrive au collector comme pré-filtres sur les métadonnées d'ingest avec timestamp (prédicats JSONB pgvector + must-clause payload Qdrant ; LanceDB et Chroma ignorent les nouvelles clés avec un warn loggé), le leg FTS gagne des prédicats SQL paramétrés sur la table documents. Un backstop server-side post-retrieval contre la table documents autoritative garantit la justesse du filtre sur chaque provider. Les documents ingérés à partir de maintenant sont toujours filtrables ; les vecteurs legacy (pre-stamping) sont exclus tant qu'un filtre est actif et retrouvent leur filtrabilité via re-embed admin - aucun schema change, aucun re-embed des documents existants, comportement sans filtre inchangé.
Citations grounded (v0.22.0) : après la déduplication, un filtre de grounding plafonne les citations par document aux top-2 par relevance score et élimine les citations de chunks dont le texte ne partage pas de chevauchement lexical significatif avec la réponse finale (gate conservatif - pages wiki/archive, résultats web/memory, citations sans chunk text et réponses très courtes sont toujours conservées). Le disclaimer "No supporting evidence" est jugé sur l'ensemble filtré (chemins streaming et non-streaming).
Chunking (collector/services/chunker.ts:16) : RecursiveCharacterTextSplitter, chunkSize=1000, overlap=200, séparateurs ["\n\n","\n",". "," ",""]. Metadata : documentId, paragraph, charStart.
Embedding (collector/services/embeddings.ts) : LocalEmbeddingProvider (Xenova all-MiniLM-L6-v2, 384-dim, air-gap) par défaut ; OpenAIEmbeddingProvider (text-embedding-3-small, 1536-dim). Singleton caché par providerType:modelName.
Vector store (collector/services/vectorStore.ts) : LanceDBProvider (défaut, chemin ${STORAGE_PATH}/vectors/lancedb, table par-workspace ws_<name>_<shortId>) ; QdrantProvider (placeholder enterprise).
FTS pass-through : le collector retourne chunks[].chunkText au serveur, qui écrit DocumentChunk avec searchVector = to_tsvector('simple', text). Le collector n'ouvre jamais Prisma.
Étendre : nouveau provider d'embedding → implémente EmbeddingProvider + factory getEmbeddingProvider + dimensions dans KNOWN_MODEL_DIMS. Tuning RRF → RRF_K dans hybridSearchService.ts:15. Changement de modèle d'embedding → reindex via Settings → Maintenance (POST /api/system/reindex-documents).
Agent Orchestrator (ReAct)
packages/server/src/agent/orchestrator.ts - entrée runAgent (non-stream, :78) et runAgentStreaming (:416). Pattern ReAct single-call : un seul `streamLLM` par itération est la decision call - streamResult.toolCall détermine la branche tool-vs-réponse.
Boucle :
- Setup : charge
WorkspaceAgentConfig, parseenabledSkills, résout le template (resolveSystemPrompt), ajoute en têteuser.customInstructions. - Résout le provider :
resolveProviderConfig(providerId, model)→ fallbackbuildFallbackConfig. - Template de contraintes :
localLLMOnly(bloque les providers non-ollama),hybridSearchForced,citationRequired. - Si
ragContext(widget) → l'injecte dans le system prompt, retirerag_searchdesactiveSkills. - Plan mode (
agentConfig.planMode) : phase planning synchrone → SSEplanevent → injection dans le prompt d'exécution. - ReAct
while(true):- Watchdog wallclock + token budget.
streamLLMavec buffered replay D-02 : itération tool-call → jette le buffer ; final-answer → rejoue les tokens vers leonTokenréel (progressive DLP flush).!toolCall→ essaieresolveImplicitToolCall(recovery XML-ish tag) → final answer, break.- Skill introuvable → circuit breaker BOT-03 : trip à 3 consécutifs / 5 au total →
abortReason: "unknown_tool_breaker". - Loop detection, per-skill timeout,
truncateToolOutput, context cap.
finally: libère le slot de concurrence + persisteWorkspaceTokenUsagesur tous les chemins de sortie.
Skills builtin (agent/builtinSkills.ts) : rag_search, workspace_memory (KV sur SystemConfig avec clé ws_memory:<ws>:<key>), document_temp_process (upload ad-hoc vers le collector), wiki_query (FTS archive_pages + fallback vectoriel + BFS wikilinks, max 10 pages 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.
Sélection de modèle + fallback : priorité per-chat → workspace default → global default → ENV (providerService.resolveProviderConfig). deriveCapabilities tag local-only/fastest/smartest/reasoning. Polling 30s refreshModels → Provider.isAvailable. Frontend fallback 3-tier dans useChat.ts avec toast Undo. Serveur fallback buildFallbackConfig (orchestrator.ts:879). SSE done inclut modelUsed, providerUsed.
Étendre : nouvelle skill → registerSkill({...}) dans builtinSkills.ts. Modèle per-chat → PATCH /workspaces/:ws/chats/:chatId/model. Plan mode → active WorkspaceAgentConfig.planMode. Tuning budget → env AGENT_* (voir 03 - Configuration).
Chat streaming (SSE)
POST /api/workspaces/:id/chat/stream → SSE. Événements : token, status, citations ({ sources }), done ({ chatId, messageId, modelUsed, providerUsed, mcpSources }), error, plan, wiki_edit. Frontend : useChat.ts (@microsoft/fetch-event-source), abort via AbortController. Slash /model, palette Cmd+K, comparaison Cmd+Shift+M. Persistance du modèle effectif via loadChat PATCH (stale default skipped, voir frontend/CLAUDE.md).
Documents, ingestion, OCR
Ingestion (collector/routes/ingest.ts:219) : multer (100 MB, allowlist MIME) → parseFile → chunkText → embed → vectorStore.addDocuments → notifyServerStatus (PUT /api/documents/:id/status avec X-Collector-Secret). La réponse inclut chunks[].chunkText pour la FTS côté serveur.
Parser (collector/services/parser.ts) : PDF (pdf-parse, <100 char → OCR skip D-04, vision est server-side), DOCX (officeparser → fallback mammoth), PPTX (officeparser), XLSX (node-xlsx), TXT/MD/CSV (UTF-8), YouTube (youtube-transcript-plus, 422 si pas de transcript). ocrMode (auto/vision/skip) dans IngestUploadBodySchema.
OCR vision (server-side) (services/ragOcrService.ts:59) : pdfjs-dist charge le PDF (cap 50 pages) → rendu PNG → modèle vision Ollama → strip grounding tags → concaténation. Job service ocrJobService.ts avec workflow approve/reject, qualityScore, credibilityScore. Routes routes/ocr.ts. Frontend SettingsOcr (catalogue de modèles, job list, preview, custom instructions).
DLP filter (services/dlpFilter.ts) : scanContent(text) → {hasMatch, matches, redactedText}. Depuis la v0.22 les patterns sont 10 built-in database-backed (table dlp_patterns, jusqu'à 50 patterns custom avec live test preview) : en plus des 6 originaux (email, credit_card, ssn, api_key sk-..., aws_key AKIA..., private_key PEM) ont été ajoutés 4 patterns EU/IT - it_vat_iva (Partita IVA label-anchored), it_codice_fiscale (16 caractères, uppercase-only), iban (préfixe pays + longueur ≥ 15, ReDoS-safe), eu_phone (téléphone IT/EU, seedé DISABLED pour revue admin). Redaction [REDACTED]. Gate DLP_ENABLED (défaut `"true"` dans CONFIG_DEFAULTS, permissions.ts:173) ; setting DLP_BYPASS_ROLES (tableau JSON de rôles, défaut []) exempte les rôles sélectionnés de chaque scan/redaction avec audit event dlp.bypassed. Toggle admin "Show/Hide DLP texts" pour révéler/re-masquer les textes redacted (admin seulement, session-scoped). Chaque audit event dlp.* taggue la source (source: "chat" | "widget") dérivée du header X-Widget-Id, avec filtres dans le panneau DLP Match History. Fallback au set built-in si la DB est injoignable (cache TTL 5 min) ; le streaming flush token-by-token préserve les patterns built-in. Intégré dans routes/chat.ts (entrée + sortie) ; SSE done → dlp_warning: true ; le widget montre PIIWarningPrompt.
Archives, Wiki, Synthesis
Archive : knowledge base multi-page workspace-scoped, pages markdown avec path slug et wikilink [[Title]]. 14 services archive (archivePageService, archiveGraphService, archiveIndexService, archiveBacklinkService, ...). Routes archive*.ts (20+ fichiers) : 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 (table wiki_pages, workspaceId = archive:<id>). Skill wiki_query (FTS + BFS wikilinks) et wiki_write (preview/dry-run + wiki_edit SSE).
Synthesis (services/synthesisService.ts) : pipeline 5-pass : entity_extraction → summary_generation → candidate_search (BM25 tsvector) → llm_decision (CREATE/UPDATE/SKIP/FLAG_CONTRADICTION) → write_overview. PHI gate : si ArchiveConfig.localLLMOnly=true et provider non-ollama → abort pre-egress. Workflow admin : trigger → preview → approve/reject (routes/synthesis.ts).
Backup
Scheduler Bree (services/backupSchedulerService.ts) : @mintplex-labs/bree, closeWorkerAfterMs:0/timeout:0 (long-running). bootstrapJobs au démarrage (cleanStaleLocks → charge les BackupJob enabled). Mutex DB-level 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 pre-restore (runSafetyBackup).
Encryption (services/encryptionService.ts) : AES-256-GCM. Key : ENCRYPTION_KEY (base64 32 byte) ou scryptSync(JWT_SECRET, salt). Decrypt chain rotation-safe : [current, ...LEGACY_PREVIOUS_ENCRYPTION_KEYS, scryptLegacy?].
Destinations (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, atomicité all-or-nothing). Routes restore.ts + POST /api/backups/trigger (manuel).
Retention (backupRetentionService.ts) : per-job retentionDays (défaut 30) ; cleanup BackupLog success au-delà du cutoff + hard delete. Les erreurs individuelles ne bloquent pas.
Push (VAPID Web Push)
routes/push.ts : initVapid lit les env VAPID_* (auto-générées en dev). GET /vapid-key (public), POST/DELETE /subscribe (license push_notifications), POST /test (admin). sendPushNotification(title, body) export pour event-trigger ; supprime les subscription 410 Gone. Frontend : service worker API.
Webhook
services/webhookService.ts : dispatchWebhookEvent(event, payload) fire-and-forget parallèle. deliverWebhook : body {event, timestamp, data}, header X-Webhook-Signature: sha256=HMAC-SHA256(secret, body). Retry 3 (1s/2s/4s) ; 10 échecs consécutifs → auto-disable. Événements : document.processed, chat.completed, user.created, workspace.updated, mcp.installed/uninstalled, ... Déclenchés par eventLogService.logEvent (async post-DB). Routes webhooks.ts (license webhooks).
Cross-link
- Widget embed : 06 - Widget Integration
- MCP : 07 - MCP Marketplace
- Endpoints : 04 - API Reference
- Env/knobs de budget : 03 - Configuration