wiki/c-work/2-build/2026-05-udb-datasource-settings.md
2026-05-18 00:26:13 +02:00

14 KiB
Raw Blame History

UDB DataSource Settings (⚙️) + konfigurierbare RAG-Limits

Implementierungsstand 2026-05-17: S0S11 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:

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, estimatedUsd, 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:

# 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 (💬):

<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 USD pro Voll-Sync" (mit Basis-Tooltip).
    • GET → /api/datasources/{id}/cost-estimate beim 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 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.