187 lines
14 KiB
Markdown
187 lines
14 KiB
Markdown
<!-- status: build -->
|
||
<!-- started: 2026-05-17 -->
|
||
<!-- completed: 2026-05-17 -->
|
||
<!-- component: gateway | frontend-nyla -->
|
||
|
||
# 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: `_ragLimits` exponiert
|
||
> zwei klare APIs — `getStoredOverrides()` (nur explizite Overrides, für Walker)
|
||
> und `getRagLimits()` (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.
|
||
|
||
## 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}/settings` und `PATCH /api/feature-data-sources/{id}/settings`).
|
||
- Audit-Log-Pflicht: jede Änderung an `settings` schreibt einen `AuditCategory.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:
|
||
1. **Connection** (Master-Switch `knowledgeIngestionEnabled`, gemeinsame Mail-/ClickUp-Preferences aus `knowledgePreferences`).
|
||
2. **DataSource RAG-Limits** (`maxBytes`, `maxFileSize`, `maxItems`, `maxDepth`).
|
||
3. **Kostenschätzung** (indikativer Wert mit Hinweis).
|
||
- Backend: `DataSource.settings` und `FeatureDataSource.settings` als JSON-Spalte. Endpunkte zum Lesen/Patchen.
|
||
- Walker (`subConnectorSyncSharepoint.py`, `subConnectorSyncKdrive.py`, `subConnectorSyncGdrive.py`, `subConnectorSyncClickup.py`) lesen Limits direkt aus `DataSource.settings.ragLimits`, fallen auf zentrale Defaults zurück, wenn (noch) leer.
|
||
- Partial-Banner auf `RagInventoryPage` zeigt 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`.
|
||
- **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.
|
||
- 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:
|
||
```python
|
||
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:
|
||
```json
|
||
{
|
||
"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:
|
||
```python
|
||
MAX_BYTES_DEFAULT = 200 * 1024 * 1024
|
||
MAX_ITEMS_DEFAULT = 10_000
|
||
```
|
||
Wird zu:
|
||
```python
|
||
# gateway/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 (💬):
|
||
```typescript
|
||
<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` (aus `knowledgePreferences`).
|
||
- PATCH → `/api/connections/{id}/knowledge-consent` bzw. `/api/connections/{id}/knowledge-preferences`.
|
||
- **Sektion "RAG-Limits"** (nur wenn DataSource den Walker-Typ unterstützt):
|
||
- Felder `maxBytes`, `maxFileSize`, `maxItems`, `maxDepth` mit lesbaren Units (MB, Anzahl).
|
||
- PATCH → `/api/datasources/{id}/settings`.
|
||
- **Sektion "Kostenschätzung"** (read-only):
|
||
- "Indikative Kosten: ~X CHF pro Voll-Sync" (mit Basis-Tooltip).
|
||
- GET → `/api/datasources/{id}/cost-estimate` beim Modal-Öffnen.
|
||
|
||
Auf der `RagInventoryPage`-Partial-Banner-Komponente:
|
||
```typescript
|
||
<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 `maxBytes` auf z. B. 1 GB anheben, einen Re-Index starten und sieht im Inventory > 200 MB indexierte Bytes ohne `stoppedAtLimit`-Banner.
|
||
- Auf einer FeatureDataSource ist das gleiche Settings-Modal verfügbar und speichert sauber in `FeatureDataSource.settings`.
|
||
- Wenn `settings.ragLimits` leer 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 mit `dataSourceId`, `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: JSON` an `DataSource` und `FeatureDataSource` ergänzen (`gateway/modules/datamodels/datamodelDataSource.py`, `datamodelFeatureDataSource.py`); SQL-Migration in `gateway/scripts/script_db_migrate_datasource_settings.py`.
|
||
- [ ] S2 — Zentralisierte Defaults: `gateway/modules/serviceCenter/services/serviceKnowledge/_ragLimits.py` neu mit `RAG_LIMITS_DEFAULT`, `getRagLimits()`, `lazyFillRagLimits()`.
|
||
- [ ] S3 — Walker-Refactor: `subConnectorSyncSharepoint.py`, `subConnectorSyncKdrive.py`, `subConnectorSyncGdrive.py`, `subConnectorSyncClickup.py` lesen Limits via `getRagLimits()` und schreiben Defaults via `lazyFillRagLimits()` zurück.
|
||
- [ ] S4 — Backend-Endpunkte: `PATCH /api/datasources/{id}/settings`, `PATCH /api/feature-data-sources/{id}/settings`, `GET /api/datasources/{id}/cost-estimate` in `routeDataSources.py` (und `routeFeatureDataSources.py`, falls noch nicht da). RBAC: Owner für DataSource, Owner + `workspace-admin` für FeatureDataSource.
|
||
- [ ] S5 — Frontend ⚙️-Button in `SourcesTab.tsx` (Tree-Row, zwischen 🧠 und 💬), Opacity-Logik: voll sichtbar, wenn `settings` befüllt sind, sonst gedimmt.
|
||
- [ ] S6 — Frontend `DataSourceSettingsModal.tsx`: drei Sektionen (Connection / RAG-Limits / Kostenschätzung), per-Feld-Validation, Speichern via API.
|
||
- [ ] S7 — `RagInventoryPage.tsx` Partial-Banner: Link "Limit anpassen" öffnet Modal für die betroffene DataSource (Heuristik: DS, die `stoppedAtLimit` ausgelöst hat – bei mehreren: die mit den meisten verarbeiteten Bytes).
|
||
- [ ] S8 — Audit-Logging in den neuen Settings-Endpunkten.
|
||
- [ ] S9 — Cost-Estimate-Engine: `gateway/modules/serviceCenter/services/serviceKnowledge/_costEstimate.py` mit 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.md` Abschnitt "Limits & Settings" ergänzen, plus Eintrag in `wiki/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-admin` fü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.
|