功能指南,包含内部实现、数据流和 file:line 引用。针对每项功能:如何工作、如何使用、如何扩展。
混合 RAG(Vector + PostgreSQL tsvector、RRF)
混合搜索分布在两个进程中:collector 只拥有向量组件(LanceDB/Qdrant);服务器拥有 FTS(PostgreSQL tsvector)以及融合两者的 RRF。
流程(packages/server/src/services/hybridSearchService.ts:104):
- agent 调用 skill
rag_search(agent/builtinSkills.ts:27),参数为query、workspaceId、archiveId。 hybridSearch- Guard D-01:将EMBEDDING_MODEL与 workspace 中Document的embeddingModel进行比较;如果不匹配 → 跳过 vector leg(FTS-only,无错误)。Promise.all([vectorSearchViaCollector(), ftsSearch()])- vector 通过 collector 的POST /api/ingest/query;FTS 通过对document_chunks.searchVector执行prisma.$queryRaw(GIN 索引、ts_rank)。- RRF fusion(k=60):每个 chunk
score += 1/(60+rank+1);sourcesSet 区分vector/fts/both。确定性 tiebreaker:score DESC → documentId ASC → chunkIndex ASC。 rag_search将 chunk 格式化为[Source: name (match: semantic+keyword|vector|fts, score: ...)]并返回SourceCitation[]。- Fallback archive D-07:如果 0 结果且存在
archiveId,则在archive:${archiveId}上重试并重新打上source: "archive"标签。 - 引用传播到 SSE
citations(routes/chat.ts:508)。
Metadata filtering(v0.22):rag_search 接受过滤器 documentTypes(pdf/md/txt/csv/docx/xlsx)和 dateFrom/dateTo(ISO;仅日期限制包含一整天)。过滤器贯穿混合搜索的两个 leg:vector leg 以带时间戳的 ingest 元数据上的预过滤器形式到达 collector(pgvector JSONB 谓词 + Qdrant must-clause payload;LanceDB 和 Chroma 忽略新键并记录 warn 日志),FTS leg 获得针对 documents 表的参数化 SQL 谓词。retrieval 之后针对权威 documents 表的服务器端 backstop 保证每个 provider 上过滤器的正确性。从现在起 ingest 的文档始终可过滤;legacy 向量(pre-stamping)在过滤器激活期间被排除,并通过 admin re-embed 恢复可过滤性 - 无 schema change、不重新 embed 现有文档、无过滤器行为不变。
Grounded citations(v0.22.0):去重之后,grounding 过滤器按 relevance score 将每个文档的引用限制为 top-2,并删除文本与最终答案没有显著词汇重叠的 chunk 引用(保守 gate - wiki/archive 页面、web/memory 结果、无 chunk text 的引用以及非常简短的回答始终保留)。"No supporting evidence" 免责声明在过滤后的集合上判断(streaming 和非 streaming 路径)。
Chunking(collector/services/chunker.ts:16):RecursiveCharacterTextSplitter、chunkSize=1000、overlap=200、分隔符 ["\n\n","\n",". "," ",""]。Metadata:documentId、paragraph、charStart。
Embedding(collector/services/embeddings.ts):默认 LocalEmbeddingProvider(Xenova all-MiniLM-L6-v2,384-dim,air-gap);OpenAIEmbeddingProvider(text-embedding-3-small,1536-dim)。按 providerType:modelName 缓存的 singleton。
Vector store(collector/services/vectorStore.ts):LanceDBProvider(默认,路径 ${STORAGE_PATH}/vectors/lancedb,按 workspace 的表 ws_<name>_<shortId>);QdrantProvider(enterprise 占位)。
FTS pass-through:collector 将 chunks[].chunkText 返回给服务器,服务器写入 DocumentChunk 并设置 searchVector = to_tsvector('simple', text)。collector 从不打开 Prisma。
扩展:新 embedding provider → 实现 EmbeddingProvider + factory getEmbeddingProvider + 在 KNOWN_MODEL_DIMS 中添加维度。RRF 调优 → RRF_K(hybridSearchService.ts:15)。更换 embedding 模型 → 通过 Settings → Maintenance 重新索引(POST /api/system/reindex-documents)。
Agent Orchestrator(ReAct)
packages/server/src/agent/orchestrator.ts - 入口 runAgent(non-stream,:78)和 runAgentStreaming(:416)。ReAct single-call 模式:每次迭代一个 `streamLLM` 就是 decision call - streamResult.toolCall 决定 tool-vs-回答分支。
循环:
- Setup:加载
WorkspaceAgentConfig、解析enabledSkills、解析模板(resolveSystemPrompt)、前置user.customInstructions。 - 解析 provider:
resolveProviderConfig(providerId, model)→ fallbackbuildFallbackConfig。 - constraint 模板:
localLLMOnly(阻止非 ollama provider)、hybridSearchForced、citationRequired。 - 如果存在
ragContext(widget)→ 注入 system prompt,从activeSkills中移除rag_search。 - Plan mode(
agentConfig.planMode):同步 planning 阶段 → SSEplanevent → 注入到执行 prompt。 - ReAct
while(true):- Watchdog wallclock + token budget。
streamLLM带 buffered replay D-02:tool-call 迭代 → 丢弃 buffer;final-answer → 将 token replay 到真实的onToken(progressive DLP flush)。!toolCall→ 尝试resolveImplicitToolCall(XML-ish recovery tag)→ final answer,break。- Skill 未找到 → circuit breaker BOT-03:连续 3 次 / 累计 5 次后触发 →
abortReason: "unknown_tool_breaker"。 - Loop detection、per-skill timeout、
truncateToolOutput、context cap。
finally:释放并发槽 + 在所有退出路径上持久化WorkspaceTokenUsage。
Builtin skills(agent/builtinSkills.ts):rag_search、workspace_memory(基于 SystemConfig 的 KV,键为 ws_memory:<ws>:<key>)、document_temp_process(向 collector 临时上传)、wiki_query(FTS archive_pages + 向量 fallback + BFS wikilinks,最多 10 页 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 保留 sysMsg + pinned user msg + last N。
模型选择 + fallback:优先级 per-chat → workspace default → global default → ENV(providerService.resolveProviderConfig)。deriveCapabilities 打标签 local-only/fastest/smartest/reasoning。30s polling refreshModels → Provider.isAvailable。前端 3-tier fallback 在 useChat.ts 中带 toast Undo。服务器 fallback buildFallbackConfig(orchestrator.ts:879)。SSE done 包含 modelUsed、providerUsed。
扩展:新 skill → 在 builtinSkills.ts 中 registerSkill({...})。per-chat 模型 → PATCH /workspaces/:ws/chats/:chatId/model。Plan mode → 启用 WorkspaceAgentConfig.planMode。Budget 调优 → env AGENT_*(见 03 - Configuration)。
聊天流式(SSE)
POST /api/workspaces/:id/chat/stream → SSE。事件:token、status、citations({ sources })、done({ chatId, messageId, modelUsed, providerUsed, mcpSources })、error、plan、wiki_edit。前端:useChat.ts(@microsoft/fetch-event-source),通过 AbortController 中止。Slash /model、palette Cmd+K、对比 Cmd+Shift+M。有效模型的持久化通过 loadChat PATCH(stale default skipped,见 frontend/CLAUDE.md)。
文档、摄取、OCR
摄取(collector/routes/ingest.ts:219):multer(100 MB,MIME allowlist)→ parseFile → chunkText → embed → vectorStore.addDocuments → notifyServerStatus(PUT /api/documents/:id/status,带 X-Collector-Secret)。响应包含 chunks[].chunkText 供服务器端 FTS 使用。
Parser(collector/services/parser.ts):PDF(pdf-parse,<100 字符 → OCR skip D-04,vision 在服务器端)、DOCX(officeparser → mammoth fallback)、PPTX(officeparser)、XLSX(node-xlsx)、TXT/MD/CSV(UTF-8)、YouTube(youtube-transcript-plus,无 transcript 时 422)。ocrMode(auto/vision/skip)在 IngestUploadBodySchema 中。
Vision OCR(服务器端)(services/ragOcrService.ts:59):pdfjs-dist 加载 PDF(上限 50 页)→ 渲染 PNG → Ollama vision 模型 → strip grounding tags → 拼接。Job service ocrJobService.ts 带 approve/reject 工作流、qualityScore、credibilityScore。Routes routes/ocr.ts。前端 SettingsOcr(模型目录、job list、preview、custom instructions)。
DLP filter(services/dlpFilter.ts):scanContent(text) → {hasMatch, matches, redactedText}。从 v0.22 起 pattern 为 10 个内置 database-backed(表 dlp_patterns,最多 50 个带 live test preview 的自定义 pattern):在 6 个原始 pattern(email、credit_card、ssn、api_key sk-...、aws_key AKIA...、private_key PEM)之外新增了 4 个 EU/IT pattern - it_vat_iva(Partita IVA label-anchored)、it_codice_fiscale(16 字符,uppercase-only)、iban(国家前缀 + 长度 ≥ 15,ReDoS-safe)、eu_phone(IT/EU 电话,seed 为 DISABLED 待 admin review)。Redaction [REDACTED]。Gate DLP_ENABLED(CONFIG_DEFAULTS 中默认 `"true"`,permissions.ts:173);setting DLP_BYPASS_ROLES(角色 JSON 数组,默认 [])使选定角色豁免所有 scan/redaction,并记录 audit event dlp.bypassed。Admin toggle "Show/Hide DLP texts" 用于显示/重新隐藏 redact 的文本(仅 admin、session-scoped)。每个 audit event dlp.* 打上来源标签(source: "chat" | "widget",从 X-Widget-Id header 推导),并在 DLP Match History 面板中提供过滤器。DB 不可达时回退到内置集合(cache TTL 5 min);token-by-token 的 streaming flush 保留内置 pattern。集成在 routes/chat.ts(输入 + 输出);SSE done → dlp_warning: true;widget 显示 PIIWarningPrompt。
归档、Wiki、Synthesis
Archive:workspace-scoped 的多页 knowledge base,markdown 页面带 path slug 和 wikilink [[Title]]。14 个 archive service(archivePageService、archiveGraphService、archiveIndexService、archiveBacklinkService 等)。Routes archive*.ts(20+ 文件):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(表 wiki_pages,workspaceId = archive:<id>)。Skill wiki_query(FTS + BFS wikilinks)和 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:如果 ArchiveConfig.localLLMOnly=true 且 provider 非 ollama → pre-egress abort。Admin 工作流:trigger → preview → approve/reject(routes/synthesis.ts)。
Backup
Bree scheduler(services/backupSchedulerService.ts):@mintplex-labs/bree、closeWorkerAfterMs:0/timeout:0(long-running)。启动时 bootstrapJobs(cleanStaleLocks → 加载启用的 BackupJob)。通过 BackupLog.status="running" 实现 DB-level mutex;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)。Restore 前的 safety backup(runSafetyBackup)。
Encryption(services/encryptionService.ts):AES-256-GCM。Key:ENCRYPTION_KEY(base64 32 字节)或 scryptSync(JWT_SECRET, salt)。Decrypt chain 轮换安全:[current, ...LEGACY_PREVIOUS_ENCRYPTION_KEYS, scryptLegacy?]。
Destinations(services/backup/providers/):LocalProvider、S3Provider、S3CompatibleProvider、DropboxProvider、GoogleDriveProvider、FtpProvider、SftpProvider、EmailProvider(接口 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 原子性)。Routes restore.ts + POST /api/backups/trigger(手动)。
Retention(backupRetentionService.ts):per-job retentionDays(默认 30);清理超过 cutoff 的 BackupLog success + hard delete。单个错误不阻塞。
Push(VAPID Web Push)
routes/push.ts:initVapid 读取 VAPID_* env(dev 中自动生成)。GET /vapid-key(公开)、POST/DELETE /subscribe(license push_notifications)、POST /test(admin)。sendPushNotification(title, body) 导出供 event-trigger 使用;移除 410 Gone 的 subscription。前端:service worker API。
Webhook
services/webhookService.ts:dispatchWebhookEvent(event, payload) fire-and-forget 并行。deliverWebhook:body {event, timestamp, data},header X-Webhook-Signature: sha256=HMAC-SHA256(secret, body)。Retry 3(1s/2s/4s);连续 10 次失败 → auto-disable。事件:document.processed、chat.completed、user.created、workspace.updated、mcp.installed/uninstalled 等。由 eventLogService.logEvent 触发(async post-DB)。Routes webhooks.ts(license webhooks)。
交叉链接
- Widget embed:06 - Widget Integration
- MCP:07 - MCP Marketplace
- Endpoint:04 - API Reference
- Env/budget 旋钮:03 - Configuration