Torna alla documentazione
Guida 05

Guida alle funzionalità

Guida alle feature con internals, flusso dati e riferimenti file:line. Per ogni feature: come funziona, come si usa, come si estende.

9 min di lettura

Guida alle feature con internals, flusso dati e riferimenti file:line. Per ogni feature: come funziona, come si usa, come si estende.

RAG ibrido (Vector + PostgreSQL tsvector, RRF)

La search ibrida è dislocata su due processi: il collector possiede solo la componente vettoriale (LanceDB/Qdrant); il server possiede la FTS (PostgreSQL tsvector) e l'RRF che fonde entrambe.

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

  1. L'agente invoca la skill rag_search (agent/builtinSkills.ts:27) con query, workspaceId, archiveId.
  2. hybridSearch - Guard D-01: confronta EMBEDDING_MODEL con gli embeddingModel dei Document del workspace; se mismatch → salta il vector leg (FTS-only, no errore).
  3. Promise.all([vectorSearchViaCollector(), ftsSearch()]) - vector via POST /api/ingest/query del collector; FTS via prisma.$queryRaw su document_chunks.searchVector (GIN index, ts_rank).
  4. RRF fusion (k=60): per ogni chunk score += 1/(60+rank+1); sources Set distingue vector/fts/both. Tiebreaker deterministico: score DESC → documentId ASC → chunkIndex ASC.
  5. rag_search formatta i chunk con [Source: name (match: semantic+keyword|vector|fts, score: ...)] e ritorna SourceCitation[].
  6. Fallback archive D-07: se 0 risultati e archiveId presente, rilancia su archive:${archiveId} e ritagga source: "archive".
  7. Citazioni propagate all'SSE citations (routes/chat.ts:508).

Metadata filtering (v0.22): rag_search accetta filtri documentTypes (pdf/md/txt/csv/docx/xlsx) e dateFrom/dateTo (ISO; il limite solo-data include l'intero giorno). I filtri attraversano entrambe le gambe della search ibrida: il leg vector arriva al collector come pre-filtri sui metadata di ingest con timestamp (predicati JSONB pgvector + must-clause payload Qdrant; LanceDB e Chroma ignorano le nuove chiavi con warn loggato), il leg FTS guadagna predicati SQL parametrizzati sulla tabella documents. Un backstop server-side post-retrieval contro la tabella documents autorevole garantisce la correttezza del filtro su ogni provider. I documenti ingeriti da ora sono sempre filtrabili; i vettori legacy (pre-stamping) sono esclusi mentre un filtro è attivo e riacquistano filtrabilità via re-embed admin - nessuno schema change, nessun re-embed dei documenti esistenti, comportamento senza filtri invariato.

Citazioni grounded (v0.22.0): dopo la deduplicazione, un filtro di grounding cappa le citazioni per documento alle top-2 per relevance score ed elimina le citazioni di chunk il cui testo non condivide overlap lessicale significativo con la risposta finale (gate conservativo - pagine wiki/archive, risultati web/memory, citazioni senza chunk text e risposte molto brevi sono sempre mantenute). Il disclaimer "No supporting evidence" è giudicato sul set filtrato (path streaming e non-streaming).

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

Embedding (collector/services/embeddings.ts): LocalEmbeddingProvider (Xenova all-MiniLM-L6-v2, 384-dim, air-gap) di default; OpenAIEmbeddingProvider (text-embedding-3-small, 1536-dim). Singleton cached per providerType:modelName.

Vector store (collector/services/vectorStore.ts): LanceDBProvider (default, path ${STORAGE_PATH}/vectors/lancedb, tabella per-workspace ws_<name>_<shortId>); QdrantProvider (remoto, VECTOR_DB_PROVIDER=qdrant; default resta lancedb).

FTS pass-through: il collector ritorna chunks[].chunkText al server, che scrive DocumentChunk con searchVector = to_tsvector('simple', text). Il collector non apre mai Prisma.

Estendere: nuovo embedding provider → implementa EmbeddingProvider + factory getEmbeddingProvider + dimensioni in KNOWN_MODEL_DIMS. Tuning RRF → RRF_K in hybridSearchService.ts:15. Cambio modello embedding → reindex via Settings → Maintenance (POST /api/system/reindex-documents).

Agent Orchestrator (ReAct)

packages/server/src/agent/orchestrator.ts - entry runAgent (non-stream, :78) e runAgentStreaming (:416). Pattern ReAct single-call: una `streamLLM` per iterazione è la decision call - streamResult.toolCall determina branch tool-vs-risposta.

Loop:

  1. Setup: carica WorkspaceAgentConfig, parse enabledSkills, risolve template (resolveSystemPrompt), prepend user.customInstructions.
  2. Resolve provider: resolveProviderConfig(providerId, model) → fallback buildFallbackConfig.
  3. Constraint template: localLLMOnly (blocca provider non-ollama), hybridSearchForced, citationRequired.
  4. Se ragContext (widget) → inietta nel system prompt, rimuove rag_search da activeSkills.
  5. Plan mode (agentConfig.planMode): fase planning sincrona → SSE plan event → iniezione nel prompt di execute.
  6. ReAct while(true):
    • Watchdog wallclock + token budget.
    • streamLLM con buffered replay D-02: tool-call iteration → scarta buffer; final-answer → replay token al onToken reale (progressive DLP flush).
    • !toolCall → prova resolveImplicitToolCall (recovery XML-ish tag) → final answer, break.
    • Skill non trovata → circuit breaker BOT-03: trip a 3 consecutivi / 5 totali → abortReason: "unknown_tool_breaker".
    • Loop detection, per-skill timeout, truncateToolOutput, context cap.
  7. finally: rilascia slot concorrenza + persist WorkspaceTokenUsage su tutti gli exit path.

Le skill custom v0.28.1 (/slug) sono prompt-template-only (arbitraggio server-side sugli scope personal/global/workspace) - vedi 15 - Custom Skills.

Skill builtin (agent/builtinSkills.ts): rag_search, workspace_memory (KV su SystemConfig con chiave ws_memory:<ws>:<key>), document_temp_process (upload ad-hoc al collector), wiki_query (FTS archive_pages + fallback vettoriale + BFS wikilinks, max 10 pagine 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.

Selezione modello + 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 in useChat.ts con toast Undo. Server fallback buildFallbackConfig (orchestrator.ts:879). SSE done include modelUsed, providerUsed.

Estendere: nuova skill → registerSkill({...}) in builtinSkills.ts. Per-chat model → PATCH /workspaces/:ws/chats/:chatId/model. Plan mode → abilita WorkspaceAgentConfig.planMode. Tuning budget → env AGENT_* (vedi 03 - Configuration).

Chat streaming (SSE)

POST /api/workspaces/:id/chat/stream → SSE. Eventi: 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, confronto Cmd+Shift+M. Persistenza modello effettivo via loadChat PATCH (stale default skipped, vedi frontend/CLAUDE.md).

Documenti, ingestion, OCR

Ingestion (collector/routes/ingest.ts:219): multer (100 MB, allowlist MIME) → parseFile → chunkText → embed → vectorStore.addDocuments → notifyServerStatus (PUT /api/documents/:id/status con X-Collector-Secret). Risposta include chunks[].chunkText per FTS lato server.

Parser (collector/services/parser.ts): PDF (pdf-parse, <100 char → OCR skip D-04, vision è server-side), DOCX (officeparser → mammoth fallback), PPTX (officeparser), XLSX (node-xlsx), TXT/MD/CSV (UTF-8), YouTube (youtube-transcript-plus, 422 se no transcript). ocrMode (auto/vision/skip) in IngestUploadBodySchema.

OCR vision-model server-side (il nome della cosa: server-side vision-model OCR, F-OCR-1) (services/ragOcrService.ts:59): pdfjs-dist load PDF (cap 50 pagine) → render PNG → Ollama vision model → strip grounding tags → concatena. Braccio aria-chiusa: tesseract.js 7.0.0 in-process (Phase 217, il braccio air-gap - OCR-BENCH) - statement fattuale, mai il claim «no librerie OCR third-party» (F-OCR-2). Job service ocrJobService.ts con workflow approve/reject, qualityScore, credibilityScore. Routes routes/ocr.ts. Frontend SettingsOcr (catalogo modelli, job list, preview, custom instructions).

DLP filter (services/dlpFilter.ts): scanContent(text) → {hasMatch, matches, redactedText}. Dalla v0.22 i pattern sono 10 built-in database-backed (tabella dlp_patterns, fino a 50 pattern custom con live test preview): oltre ai 6 originali (email, credit_card, ssn, api_key sk-..., aws_key AKIA..., private_key PEM) sono stati aggiunti 4 pattern EU/IT - it_vat_iva (Partita IVA label-anchored), it_codice_fiscale (16 caratteri, uppercase-only), iban (prefisso paese + lunghezza ≥ 15, ReDoS-safe), eu_phone (telefono IT/EU, seedato DISABLED per review admin). Redaction [REDACTED]. Gate DLP_ENABLED (default `"true"` in CONFIG_DEFAULTS, permissions.ts:173); setting DLP_BYPASS_ROLES (array JSON di ruoli, default []) esenta i ruoli selezionati da ogni scan/redaction con audit event dlp.bypassed. Toggle admin "Show/Hide DLP texts" per rivelare/ri-nascondere i testi redatti (solo admin, session-scoped). Ogni audit event dlp.* tagga la sorgente (source: "chat" | "widget") derivata dall'header X-Widget-Id, con filtri nel pannello DLP Match History. Fallback al set built-in se il DB è irraggiungibile (cache TTL 5 min); lo streaming flush token-by-token mantiene i pattern built-in. Integrato in routes/chat.ts (input + output); SSE done → dlp_warning: true; widget mostra PIIWarningPrompt. Vedi 16 - Document DLP Pipeline.

Archivi, Wiki, Synthesis

Archive: knowledge base multi-pagina workspace-scoped, pagine markdown con path slug e wikilink [[Title]]. 14 servizi archive (archivePageService, archiveGraphService, archiveIndexService, archiveBacklinkService, ...). Routes archive*.ts (20+ file): 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 (tabella wiki_pages, workspaceId = archive:<id>). Skill wiki_query (FTS + BFS wikilinks) e wiki_write (preview/dry-run + wiki_edit SSE). Vedi 14 - Wiki Knowledge Engine.

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: se ArchiveConfig.localLLMOnly=true e provider non-ollama → abort pre-egress. Workflow admin: trigger → preview → approve/reject (routes/synthesis.ts). La detection delle contraddizioni è la F-SYNTH-1 (il sistema «riunisce i documenti e segnala dove non sono d'accordo»). Vedi 18 - Synthesis Pipeline.

Quota e costi

Quota token per-utente/gruppo con sweep oraria di reset (Phase 207); la quota non impostata è illimitata (fail-open, F-QUOTA-1). Quota costi con motore hold-and-settle (QuotaBalance/QuotaHold) che salda €-in-cents al usage seam + contabilità per-modello con endpoint per-valuta (Phase 203/209). Vedi 17 - Quotas & Costs.

Backup

Scheduler Bree (services/backupSchedulerService.ts): @mintplex-labs/bree, closeWorkerAfterMs:0/timeout:0 (long-running). bootstrapJobs a startup (cleanStaleLocks → carica 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 pre-restore (runSafetyBackup).

Encryption (services/encryptionService.ts): AES-256-GCM. Key: ENCRYPTION_KEY (base64 32 byte) o scryptSync(JWT_SECRET, salt). Decrypt chain rotation-safe: [current, ...LEGACY_PREVIOUS_ENCRYPTION_KEYS, scryptLegacy?].

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

Restore (backupService.ts:617): restoreDatabaseWithPsql (psql --single-transaction --set ON_ERROR_STOP=on, atomicity all-or-nothing). Routes restore.ts + POST /api/backups/trigger (manuale).

Retention (backupRetentionService.ts): per-job retentionDays (default 30); cleanup BackupLog success oltre cutoff + hard delete. Errori individuali non bloccano.

Push (VAPID Web Push)

routes/push.ts: initVapid legge VAPID_* env (auto-generate in dev). GET /vapid-key (pubblico), POST/DELETE /subscribe (license push_notifications), POST /test (admin). sendPushNotification(title, body) export per event-trigger; rimuove subscription 410 Gone. Frontend: service worker API.

Webhook

services/webhookService.ts: dispatchWebhookEvent(event, payload) fire-and-forget parallelo. deliverWebhook: body {event, timestamp, data}, header X-Webhook-Signature: sha256=HMAC-SHA256(secret, body). Retry 3 (1s/2s/4s); 10 fallimenti consecutivi → auto-disable. Eventi: document.processed, chat.completed, user.created, workspace.updated, mcp.installed/uninstalled, ... Triggerati da eventLogService.logEvent (async post-DB). Routes webhooks.ts (license webhooks).

Connettori chat (Enterprise)

Canali in ingresso che collegano Telegram/Discord/Slack/WhatsApp alla stessa knowledge base - binding archivio, messaggi welcome/fallback, limiti di rate e sessione; gestione admin con l'arm EDIT (PUT /api/connectors/:id, F-CONN-2) e webhook di piattaforma su una surface pubblica firmata (no RBAC). Vedi 13 - Connectors.

Provenienza v0.28.1

AffermazioneFonte
Il naming server-side vision-model OCR + il braccio tesseract.js 7.0.0 in-processREADME.md:32 + docs/OCR-BENCH.md:10 (census F-OCR-1/2, row-time)
Le skill custom /slug con scoping server-sidedocs/CUSTOM_SKILLS.md + docs/API.md:358-360 - F-SKILL-1
Hold-and-settle €-cents + fail-openREADME.md:29 + docs/QUOTAS.md - F-QUOTA-1
La F-SYNTH-1 (contraddizioni)docs/SERVICES.md + README.md:33

Riferimento operativo - Simmetric Chat v0.28.1 (docs aggiornate 2026-10-02, census SHA `aca5b8ff`).