Zum Inhalt springen

Knowledge base (RAG)

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

The knowledge base (KB) is server-side, group-scoped retrieval-augmented generation (RAG). When an assistant-class chat runs for a user whose group has a KB, the server transparently lets the model search that KB and grounds the answer in the retrieved passages — returning structured citations the client can render as sources. The model never receives a wider scope than the caller is entitled to.

A KB is modelled as a server plugin (Kind = "mcp_server", config {"type":"knowledge_base",…}) and is switched on for a group through the existing group-plugin allowlist. The feature is behaviour-neutral until you opt in: with no vector DB configured the tool is never injected, the ingestion worker idles, and the admin KB pages show a “vector DB required” notice.

The KB is guarded by two independent gates — both must be satisfied:

  1. License entitlement — the Knowledge license feature must be granted. Without it the chat path simply runs normal (ungrounded) chat with no error, and the ingestion REST API returns 403 feature_not_licensed.
  2. Vector DB configuredKnowledge:ConnectionString must point at a pgvector database. Without it the KB is operationally disabled (the ingestion API returns 503 knowledge_disabled).

KB content lives in its own PostgreSQL database (the pia-vectordb container, running the pgvector extension), separate from the main application database.

StoreContextHolds
Main DBPiaDbContextUsers, groups, the plugin catalog + group allowlist, token usage
Vector DBKnowledgeDbContextDocuments, chunks + embeddings, the embedding-token ledger

The two stores share no foreign keys — the vector DB references the KB catalog by PluginId value only. The embedding dimension is fixed at compile time (one vector(N) column + one HNSW index); startup validates the configured dimension against it and disables the KB on mismatch, because changing it requires a migration and a full re-embed.

Ingestion is asynchronous. A submitted document is hashed (SHA-256, for idempotent de-duplication), persisted as Pending, and drained by a background worker:

Pending → extract text → semantic double-pass chunk → (contextual retrieval) → embed → store chunks → Ready
  • Chunking uses a semantic double-pass merging splitter (ported from the n8n recipe); the chunker and the embedding model are instance-level settings, never per-KB.
  • Contextual retrieval (optional, on by default) prepends a one-sentence, LLM-generated description of each chunk before embedding (context + delimiter + raw chunk), which measurably improves recall. The raw chunk is what gets stored and cited. Contextualization needs its own chat provider; if none is configured it is auto-disabled at startup with a warning rather than failing ingestion.
  • Resilience: the embedding and contextualizer HTTP calls retry on rate-limit/transient errors and time out; a per-chunk contextualization failure degrades gracefully (the raw chunk is still embedded) instead of failing the whole document; and a worker that crashes mid-document resets stranded Processing rows to Pending on its next start.

A document that cannot be processed is marked Failed with an error message, visible in the admin UI.

Retrieval is hybrid and always scoped in SQL to the caller’s allowed KB ids:

  1. Semantic candidates via pgvector cosine distance over an HNSW index.
  2. Lexical candidates via PostgreSQL full-text search over the raw chunk.
  3. The two lists are fused with Reciprocal Rank Fusion (RRF) and re-ranked (identity re-ranker in v1, behind a swappable seam).

The store never returns a chunk outside the allowed set — the scope predicate is re-applied even on the final re-fetch (defense in depth).

Retrieval is exposed to the model as a single search_knowledge_base tool, injected only when the mode is assistant-class (Assistant / Research — never Optimize or voice) and the user’s group enables a KB. KnowledgeChatOrchestrator runs the loop server-side:

  • KB tool calls are resolved on the server and the conversation loops with the results appended (up to an iteration cap).
  • Any client-declared tools the request carried are passed straight back to the client to execute. If a turn mixes a client tool call with a KB call, the KB call is resolved first and the client call deferred to the next round, so a KB lookup is never silently dropped.
  • The KB tool and its call/result messages are stripped from everything the client sees; the retrieved passages are returned as structured knowledge_citations, never inlined as prose.

When the client requests stream:true, the tool-resolution rounds run buffered (a tool call can’t be resolved before its arguments are complete), then the final grounded answer is streamed as Server-Sent Events. A look-ahead guard guarantees no tool-call bytes leak mid-stream; citations arrive as a trailing knowledge_citations event immediately before data: [DONE].

The number of search_knowledge_base calls resolved during a turn is recorded on that request’s token-usage row, so KB activity surfaces alongside model spend in the KB column of the admin token-usage view (/admin/token-usage). This is distinct from the embedding-token ledger below, which tracks ingestion-time spend.

Per-group quotas (GroupSettings.Quotas, edited in the group editor) bound KB usage:

QuotaEnforced
KnowledgeBasesWhen enabling KBs for a group
KnowledgeDocuments / KnowledgeStorageBytesPer KB at ingestion → 409
MonthlyEmbeddingTokensPer group, against a dedicated embedding-token ledger

The effective doc/byte limit for a KB is the most permissive among the groups that enable it (else the instance baseline). For monthly tokens, ingestion pre-checks an estimate against that most-permissive limit and the worker records the actual provider token count afterward; a KB shared across groups attributes its spend to a single deterministic owner (the lowest group id) so usage is never double-counted.

Tenant scoping is server-resolved: the set of allowed KB ids comes from the authenticated user → group → group-plugin allowlist. The model only ever supplies the query text and topK; it cannot widen the scope. Documentation: see Configuration → Knowledge base and the admin setup guide.