15 KiB
UDB DataSource Settings (⚙️) + konfigurierbare RAG-Limits
Implementierungsstand 2026-05-17: S0–S11 abgeschlossen. Wichtige Abweichung vom ursprünglichen Plan: das "lazy-fill defaults beim ersten Bootstrap" wurde nicht im Walker implementiert (würde die Caller-Override-Semantik aushebeln und liess Tests rot werden). Stattdessen:
_ragLimitsexponiert zwei klare APIs —getStoredOverrides()(nur explizite Overrides, für Walker) undgetRagLimits()(Overrides + Defaults gemerged, für API/Cost-Estimate). Das Modal zeigt fehlende Werte als Placeholder aus dem Cost-Estimate-Endpoint an, damit der User immer eine konkrete Zahl sieht — ohne die Datenbank-Zeile dafür anfassen zu müssen. Siehe CHANGELOG 2026-05-17 für die Detail-Liste.Update 2026-05-28: Minimale
ragLimits-Vererbung implementiert. Das UI speichert Limits auf der Connection-Root-DataSource (path/), aber die Walker iterieren über Kind-DataSources (z.B.sharepointFolder), diesettings.ragLimits = nullhaben. Fix:_loadRagEnabledDataSourcesinsubConnectorIngestConsumer.pykopiert jetztragLimitsvom Root auf Kinder ohne eigene Werte._finalizeResultin allen 4 Walkern zeigt effektive Limits statt hartkodierter Defaults.
Beschreibung und Kontext
Heute sind die RAG-Walker-Limits (maxBytes, maxItems, maxFileSize, maxDepth, maxWorkspaces, maxListsPerWorkspace, maxTasks) als Modul-Konstanten in den subConnectorSync*.py-Dateien hartkodiert. Ein User mit z. B. einem 500 MB grossen SharePoint-Folder erhält nach 200 MB ein stilles Stoppen (stoppedAtLimit=maxBytes) und kann das ohne Code-Änderung nicht anpassen. Heute (2026-05-17) sichtbar geworden auf einem Basecamp-Folder mit ~731 Dateien, von denen nur 25 indexiert wurden, bevor der maxBytes-Default zugriff.
Gleichzeitig fehlt für Feature-Instanz-DataSources (FeatureDataSource) bisher überhaupt ein UI, an dem der Owner Settings anpassen könnte. UserConnections haben den Wizard und das Edit-Modal auf der Connections-Seite – für Feature-Daten gibt es keinen analogen Ort.
Beide Lücken lassen sich mit derselben Architektur schliessen: ein generisches Settings-Icon (⚙️) pro Node in der UDB, das ein typ- und scope-abhängiges Modal öffnet. Initial wird darin RAG-Limit-Werte und Connection-Master-Switch gepflegt, später kommen weitere Einstellungen (z. B. Custom-Neutralization-Regeln, Re-Sync-Cadence, Polling-Strategie) ohne neue Icons dazu.
Wichtiger Architektur-Constraint:
- Keine Icon-Inflation in der UDB. Bereits vorhanden: Scope-Toggle, Neutralize-Toggle, RAG-Index-Toggle (🧠), Chat (💬). Wir fügen genau ein zusätzliches Icon (⚙️) hinzu – nicht ein Icon pro Setting.
Fokus und kritische Details
- UDB ist die einzige Stelle, wo Settings pro Datenobjekt gemanagt werden. Andere Pfade (Wizard, Connection-Edit-Modal) editieren nur die Connection-Ebene, nie einzelne DataSources.
- Speicherort = Quelle der Wahrheit. Keine Override-Schichten, keine Resolver-Logik. Was im UI angezeigt wird, ist exakt das, was im Walker greift. Die Werte werden bei der DataSource-Erstellung mit sinnvollen Defaults vorbefüllt (kopiert aus globalen Konstanten); danach kann der User sie einfach editieren. Vorteil: nachvollziehbar, debuggbar, keine versteckten Vererbungen.
- Walker müssen ihre Default-Konstanten zentralisieren und beim Lesen einer DataSource entweder den dort gespeicherten Wert nehmen oder, falls noch nicht gesetzt, den zentralen Default ausliefern und gleichzeitig auf der DataSource persistieren (lazy initialization → ab dann sind die Werte sichtbar im UI).
- Settings-Werte landen in einer JSON-Spalte auf der DataSource bzw. FeatureDataSource (
settings: Optional[Dict[str, Any]]) – damit ist die Erweiterbarkeit garantiert, ohne neue Spalten/Migrationen pro neuem Setting. - DataSource vs. FeatureDataSource: gleiche Spalte (
settings), gleiches UI, gleiches API-Pattern (PATCH /api/datasources/{id}/settingsundPATCH /api/feature-data-sources/{id}/settings). - Audit-Log-Pflicht: jede Änderung an
settingsschreibt einenAuditCategory.PERMISSION-Eintrag (auch wenn es nur ein numerischer Limit-Wert ist – RAG-Ingest-Grenze ist eine Compliance-relevante Entscheidung). - Defaults bleiben konservativ (200 MB / 10'000 Items / 25 MB / Tiefe 8). Anheben braucht User-Aktion → keine versehentlichen Kostenexplosionen.
- Connection-Master-Switch im Modal: Im Settings-Modal einer DataSource ist auch der Connection-weite Master-Switch
knowledgeIngestionEnabled("Knowledge database active") togglebar – damit ist das Modal die zentrale Stelle für alle relevanten Settings, ohne zwischen Seiten wechseln zu müssen. Dieser Toggle wirkt auf alle DataSources derselben Connection (es ist explizit als "Connection-Setting" gekennzeichnet, nicht als DataSource-Setting). - Kosten-Indikator statt Hard-Cap: Keine harten Obergrenzen. Stattdessen zeigt das Modal eine indikative (nicht-verbindliche) Realtime-Kostenschätzung: basierend auf erwarteter Item-Zahl, Token-Schätzung pro Item und aktuellen Embedding-Preisen. Klar als "indikativ" gekennzeichnet, der User trägt die Verantwortung.
Ziel und Nicht-Ziele
- Ziel:
- Ein Settings-Icon ⚙️ pro Tree-Node in der UDB-Sidebar (
SourcesTab.tsx), das ein Modal öffnet. - Im Modal: drei klar abgegrenzte Sektionen:
- Connection (Master-Switch
knowledgeIngestionEnabled, gemeinsame Mail-/ClickUp-Preferences ausknowledgePreferences). - DataSource RAG-Limits (
maxBytes,maxFileSize,maxItems,maxDepth). - Kostenschätzung (indikativer Wert mit Hinweis).
- Connection (Master-Switch
- Backend:
DataSource.settingsundFeatureDataSource.settingsals JSON-Spalte. Endpunkte zum Lesen/Patchen. - Walker (
subConnectorSyncSharepoint.py,subConnectorSyncKdrive.py,subConnectorSyncGdrive.py,subConnectorSyncClickup.py) lesen Limits direkt ausDataSource.settings.ragLimits, fallen auf zentrale Defaults zurück, wenn (noch) leer. - Partial-Banner auf
RagInventoryPagezeigt zusätzlich Hint: "Limit kann pro DataSource in der UDB unter ⚙️ angehoben werden." - Owner-Kontrolle: für UserConnection-DataSources nur Owner; für FeatureDataSource Owner oder
workspace-admin.
- Ein Settings-Icon ⚙️ pro Tree-Node in der UDB-Sidebar (
- Nicht-Ziel:
- Mandate-weite Defaults / Override-Schichten / Resolver-Layer.
- Hard-Caps (User/Admin trägt Verantwortung).
Settings-Vererbung im Tree (Parent-Folder → Children) – aktuell wirkt eine Änderung nur auf die konkrete DataSource.Update 2026-05-28: Minimale Vererbung implementiert:ragLimitsauf der Connection-Root-DataSource (path/) werden an Kind-DataSources vererbt, die keine eigenen Werte haben. Dies behebt den Bug, dass im UI gesetzte Limits vom Walker ignoriert wurden (weil das UI auf dem Root speichert, der Walker aber Kind-Records liest). Volle Parent→Child-Vererbung im Tree bleibt Nicht-Ziel.- Eigene Settings für Mail-Connectors (
Outlook,Gmail) auf DataSource-Ebene – die haben keine Folder-Hierarchie. Die Mail-Preferences bleiben Connection-weit und werden im Modal in der "Connection"-Sektion editiert.
Architektur-Skizze
Daten-Schicht
DataSource und FeatureDataSource bekommen je eine neue Spalte:
settings: Optional[Dict[str, Any]] = Field(
default=None,
description="DataSource-scoped settings (JSON). Currently used keys: ragLimits.",
json_schema_extra={"frontend_type": "json", "frontend_readonly": True, "frontend_required": False},
)
JSON-Schema-Konvention:
{
"ragLimits": {
"maxBytes": 524288000,
"maxFileSize": 52428800,
"maxItems": 20000,
"maxDepth": 12
}
}
Keine Resolver-Schichten: was in settings.ragLimits steht, gilt. Wenn der Key fehlt, nimmt der Walker den zentralen Default aus _ragLimits.RAG_LIMITS_DEFAULT und schreibt ihn beim nächsten Bootstrap einmalig in die DataSource zurück (lazy fill), damit der User die Werte auch ohne vorherigen Sync schon im UI sieht und editieren kann.
Backend-API
PATCH /api/datasources/{id}/settings { settings: { ragLimits: {...} } }
PATCH /api/feature-data-sources/{id}/settings { settings: { ragLimits: {...} } }
GET /api/datasources/{id}/cost-estimate → { estimatedTokens, estimatedChf, basis: {...} }
/cost-estimate: schätzt anhand Item-Count (sofern bekannt aus letztem Sync) × tokens-per-item-heuristik × Embedding-Preis. Liefert auch die Annahmen (basis), damit der User die Plausibilität prüfen kann.
Walker-Refactor
Heute hartkodiert in jedem Walker:
MAX_BYTES_DEFAULT = 200 * 1024 * 1024
MAX_ITEMS_DEFAULT = 10_000
Wird zu:
# platform-core/modules/serviceCenter/services/serviceKnowledge/_ragLimits.py
RAG_LIMITS_DEFAULT = {
"maxBytes": 200 * 1024 * 1024,
"maxFileSize": 25 * 1024 * 1024,
"maxItems": 10_000,
"maxDepth": 8,
}
def getRagLimits(dataSource: Dict[str, Any]) -> Dict[str, int]:
"""Read limits from DataSource.settings.ragLimits, fall back to defaults
for missing keys. Pure read; lazy persist is the caller's responsibility."""
stored = (dataSource.get("settings") or {}).get("ragLimits") or {}
return {**RAG_LIMITS_DEFAULT, **stored}
def lazyFillRagLimits(rootIf, dataSourceId: str, dataSource: Dict[str, Any]) -> None:
"""Persist defaults to settings if not yet present, so the UI shows them."""
Jeder Walker holt seine Limits beim Eintritt in _bootstrap* einmal und gibt sie an _walkFolder() / _finalizeResult() weiter – die Konstante existiert nur noch im neuen Modul als Default-Fallback.
Frontend-UI
In SourcesTab.tsx (Tree-Node-Render), zwischen Brain-Icon (🧠) und Chat-Icon (💬):
<button
onClick={async (e) => {
e.stopPropagation();
const dsId = ds?.id ?? await onEnsureDs(node);
if (dsId) openSettingsModal(dsId, scope);
}}
title={t('Einstellungen')}
style={{ ... }}
>
⚙️
</button>
Settings-Modal (DataSourceSettingsModal.tsx):
- Sektion "Connection" (oben, mit Connection-Label und Authority-Icon):
- Toggle
knowledgeIngestionEnabled(Master-Switch). Hinweis: wirkt auf alle DataSources dieser Connection. - Optional, wenn relevant:
mailContentDepth,mailIndexAttachments,filesIndexBinaries,clickupScope,clickupIndexAttachments,maxAgeDays(ausknowledgePreferences). - PATCH →
/api/connections/{id}/knowledge-consentbzw./api/connections/{id}/knowledge-preferences.
- Toggle
- Sektion "RAG-Limits" (nur wenn DataSource den Walker-Typ unterstützt):
- Felder
maxBytes,maxFileSize,maxItems,maxDepthmit lesbaren Units (MB, Anzahl). - PATCH →
/api/datasources/{id}/settings.
- Felder
- Sektion "Kostenschätzung" (read-only):
- "Indikative Kosten: ~X CHF pro Voll-Sync" (mit Basis-Tooltip).
- GET →
/api/datasources/{id}/cost-estimatebeim Modal-Öffnen.
Auf der RagInventoryPage-Partial-Banner-Komponente:
<span>... Limit {l} erreicht. Weitere Dateien wurden NICHT indexiert. {' '}
<a onClick={() => openSettingsModalForConn(conn)}>Limit anpassen</a> oder
DataSource enger eingrenzen, dann erneut starten.</span>
Erfolgskriterien
- Auf einer SharePoint-DataSource mit > 200 MB kann der Owner über das ⚙️-Icon
maxBytesauf z. B. 1 GB anheben, einen Re-Index starten und sieht im Inventory > 200 MB indexierte Bytes ohnestoppedAtLimit-Banner. - Auf einer FeatureDataSource ist das gleiche Settings-Modal verfügbar und speichert sauber in
FeatureDataSource.settings. - Wenn
settings.ragLimitsleer ist, ist das Verhalten der Walker bitidentisch zur Vor-Plan-Version (keine Regression). - Der Connection-Master-Switch im Modal und der Toggle auf der Connections-Page und auf der RagInventoryPage zeigen immer denselben Wert (alle drei rufen
/knowledge-consent). - Audit-Log enthält pro Settings-Change einen
PERMISSION-Eintrag mitdataSourceId,userId,mandateId,oldSettings,newSettings. - Keine zusätzlichen Icons in der UDB ausser dem einen ⚙️.
- Kostenschätzung wird als "indikativ, nicht verbindlich" gekennzeichnet und zeigt die zugrunde liegenden Annahmen.
Schritte
- S1 — Datenmodell:
settings: JSONanDataSourceundFeatureDataSourceergänzen (platform-core/modules/datamodels/datamodelDataSource.py,datamodelFeatureDataSource.py); SQL-Migration inplatform-core/scripts/script_db_migrate_datasource_settings.py. - S2 — Zentralisierte Defaults:
platform-core/modules/serviceCenter/services/serviceKnowledge/_ragLimits.pyneu mitRAG_LIMITS_DEFAULT,getRagLimits(),lazyFillRagLimits(). - S3 — Walker-Refactor:
subConnectorSyncSharepoint.py,subConnectorSyncKdrive.py,subConnectorSyncGdrive.py,subConnectorSyncClickup.pylesen Limits viagetRagLimits()und schreiben Defaults vialazyFillRagLimits()zurück. - S4 — Backend-Endpunkte:
PATCH /api/datasources/{id}/settings,PATCH /api/feature-data-sources/{id}/settings,GET /api/datasources/{id}/cost-estimateinrouteDataSources.py(undrouteFeatureDataSources.py, falls noch nicht da). RBAC: Owner für DataSource, Owner +workspace-adminfür FeatureDataSource. - S5 — Frontend ⚙️-Button in
SourcesTab.tsx(Tree-Row, zwischen 🧠 und 💬), Opacity-Logik: voll sichtbar, wennsettingsbefüllt sind, sonst gedimmt. - S6 — Frontend
DataSourceSettingsModal.tsx: drei Sektionen (Connection / RAG-Limits / Kostenschätzung), per-Feld-Validation, Speichern via API. - S7 —
RagInventoryPage.tsxPartial-Banner: Link "Limit anpassen" öffnet Modal für die betroffene DataSource (Heuristik: DS, diestoppedAtLimitausgelöst hat – bei mehreren: die mit den meisten verarbeiteten Bytes). - S8 — Audit-Logging in den neuen Settings-Endpunkten.
- S9 — Cost-Estimate-Engine:
platform-core/modules/serviceCenter/services/serviceKnowledge/_costEstimate.pymit Heuristik (Items × Tokens × Embedding-Preis) und Basis-Annahmen-Output. - S10 — Tests: Unit-Tests für
getRagLimits()/lazyFillRagLimits(), API-Tests für PATCH-Endpunkte (RBAC), Cost-Estimate-Unit-Test, Frontend-Smoke-Test (Modal öffnet, speichert, refetch, Cost-Anzeige). - S11 — Doku:
wiki/b-reference/platform/rag-pipeline.mdAbschnitt "Limits & Settings" ergänzen, plus Eintrag inwiki/TOPICS.md. CHANGELOG-Zeile.
Offene Fragen / Decisions
- F1 – Override-Logik? → Entschieden: KEINE Override-Schichten. Die DataSource speichert ihre eigenen Werte direkt; Walker liest sie 1:1. Lazy-Fill mit zentralen Defaults beim ersten Sync, damit das UI immer einen sinnvollen Startwert zeigt. Nachvollziehbar, debuggbar, keine versteckten Vererbungen.
- F2 – RBAC? → Entschieden: Owner für UserConnection-DataSources, Owner oder
workspace-adminfür FeatureDataSource (analog Scope-/Neutralize-Toggle). - F3 – Hard-Cap pro Mandat? → Entschieden: NEIN. Stattdessen indikative Realtime-Kostenschätzung im Modal. User/Admin trägt Verantwortung.
- F4 – Mail/ClickUp-Preferences im Modal? → Entschieden: JA. Connection-weite Preferences werden in der Sektion "Connection" des Modals editiert – das macht das Modal zur einzigen Stelle für alle relevanten Settings, ohne Seitenwechsel.