wiki/b-reference/teams-bot/architecture.md
2026-05-12 15:19:28 +02:00

157 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- status: canonical -->
<!-- lastReviewed: 2026-05-12 -->
<!-- verifiedAgainst: service-teams-browser-bot (audioCaptureProcedure WebRTC-Wrapper Gating 2026-05-12); service-teams-browser-bot (documentation review 2026-02-18); gateway/modules/features/teamsbot/{service,routeFeatureTeamsbot,interfaceFeatureTeamsbot,datamodelTeamsbot}.py + `GET /api/teamsbot/{instanceId}/dashboard/stream` (2026-05-11); gateway/tests/unit/teamsbot/test_directorPrompts.py (Director Prompts 2026-04-24); gateway Voice STT batch + linear16 (2026-05-10); frontend_nyla TeamsbotDashboardView / TeamsbotModulesView / TeamsbotSessionView (IA + SSE 2026-05-11) -->
# Teams Meeting Bot -- Architektur
## Überblick
AI-gesteuerter Meeting-Bot für Microsoft Teams. Tritt Meetings als regulärer Teilnehmer bei (Browser-Automation via Playwright/Chromium), erfasst Live-Transkripte, reagiert per Sprache (TTS) und/oder Chat. Kein Teams-Graph-SDK nötig -- funktioniert mandantenübergreifend ohne Admin-Approval.
## System-Architektur
```
┌────────────┐ SSE ┌──────────────┐ WebSocket ┌─────────────┐
│ Frontend │◄──────────────│ Gateway │◄───────────────│ Bot Service │
│ (Nyla UI) │ │ (AI, TTS, │ HTTP (join/ │ (Playwright │
│ │ │ Sessions) │ leave) │ Chromium) │
└────────────┘ └──────────────┘ └─────────────┘
```
| Verbindung | Protokoll | Zweck |
|------------|----------|-------|
| Gateway ↔ Bot | WebSocket | Echtzeit: Transkripte, Chat, Audio, Status |
| Gateway → Bot | HTTP | Session-Steuerung (join, leave, status) |
| Frontend ← Gateway | SSE | Live-Transkript-Stream für UI; Dashboard-Push (`dashboard/stream`) |
## Nyla UI (MeetingModule-IA)
Die Feature-Oberfläche ist in **fünf Tabs** strukturiert (siehe auch `wiki/c-work/2-build/2026-04-teamsbot-greenfield-ia-and-live-update.md`):
| Tab | Route-Segment | Zweck |
|-----|----------------|--------|
| Dashboard | `dashboard` | KPIs, Modul-Aktivität, Quick-Actions; Daten per **SSE** `GET /api/teamsbot/{instanceId}/dashboard/stream` (Snapshots `sessions` + `modules`, Intervall 3 s bei aktiver eigener Sitzung, sonst 20 s) |
| Assistent | `assistant` | Wizard: Modul wählen/anlegen, Meeting-Link, Join-Modus (Systembot / Gast / Mein Account), optional Sitzungskontext → startet Session und navigiert zur Live-Ansicht |
| Module | `modules` | CRUD `TeamsbotMeetingModule`, aufklappbare Session-Liste pro Modul; **Deep-Link** `?moduleId=<uuid>` klappt das Modul auf und scrollt es ins Sichtfeld |
| Live-Session | `sessions` | Regie-Panel, UDB, Transkript, SSE `sessions/{id}/stream`; **MFA** (`mfaChallenge` / `mfaResolved`) wird hier über dieselbe SSE-Verbindung abgewickelt |
| Einstellungen | `settings` | Bot-Stimme, User-Settings, System-Bots (SysAdmin) |
**Datenmodell:** `TeamsbotMeetingModule` gruppiert Reihen; `TeamsbotSession.moduleId` verknüpft optional (Adhoc ohne Modul). Standard-Meeting-Link und Standard-Bot-Name pro Modul für Prefill im Assistenten.
## Kernfähigkeiten
- **Live Transcription:** Erfasst Untertitel mit Sprecher-Zuordnung, streamt via SSE
- **AI-Analyse:** Transkript-Segmente werden durch AI-Modell (GPT-4o-mini / Claude) analysiert
- **Voice Response:** TTS-Audio wird über den Mikrofon-Kanal ins Meeting gespielt
- **Chat Response:** Bot kann Chat-Nachrichten ins Meeting schreiben
- **Multi-Session:** Mehrere Bot-Instanzen parallel in verschiedenen Meetings
## Use Cases
| UC | Beschreibung |
|----|-------------|
| AI Meeting Assistant | Bot nimmt teil, hört zu, antwortet auf Ansprache ("Hey Nyla, ...") |
| Live Transcription | Echtzeit-Transkript-Stream für Teilnehmer ausserhalb des Meetings |
| Meeting Summary | AI-generierte Zusammenfassung nach Meeting-Ende |
| Multi-Bot | Mehrere parallele Sessions in verschiedenen Meetings |
| Director Prompts | Operator gibt dem laufenden Bot **private** Regieanweisungen (One-Shot oder Persistent), Antwort wird ins Meeting eingespielt |
| Hybrid Agent Escalation | SPEECH_TEAMS-Pfad kann komplexe Anfragen via `needsAgent=true` an den vollen Agent (`agentService.runAgent`) eskalieren |
## Integration mit Gateway
Der Gateway (Feature `teamsbot`) verwaltet Sessions und stellt die AI-Pipeline bereit:
- Session-Lifecycle: erstellen, starten, stoppen
- WebSocket-Verbindung pro Session
- AI-Analyse der Transkript-Segmente via `serviceAi`
- TTS-Generierung für Voice-Responses
- **Dashboard-SSE:** `GET /api/teamsbot/{instanceId}/dashboard/stream` — wiederholte JSON-Events `{ "type": "dashboardState", "sessions": [...], "modules": [...] }` mit gleicher Sichtbarkeit wie `GET /sessions` (eigene Sessions, ausser Platform-Admin sieht alle).
## Schlüssel-Dateien
| Datei / Bereich | Rolle |
|-----------------|-------|
| `gateway/modules/features/teamsbot/` | Gateway-seitiges Feature-Modul (inkl. `dashboard/stream`, Session-SSE, Module-CRUD) |
| `frontend_nyla/src/pages/views/teamsbot/` | Dashboard (SSE), Assistent, Module, Live-Session, Einstellungen |
| `frontend_nyla/src/api/teamsbotApi.ts` | u.a. `createDashboardStream`, `createSessionStream`, Module-API |
| `service-teams-browser-bot/` | Eigenständiger Bot-Service (separates Repository) |
## Regeln / Invarianten
- Bot tritt als **regulärer Web-Teilnehmer** bei (Browser-Automation), nicht via Graph Communications SDK
- Jede Session läuft in einer **eigenen Browser-Instanz** (Isolation)
- Authentifizierter Join (mit Microsoft-Account) oder Anonymous Guest -- je nach Konfiguration
- Gateway ist die **einzige** Schnittstelle für AI-Aufrufe und TTS -- der Bot-Service selbst hat keine AI-Logik
**STT auf dem Gateway:** Meeting-Audio-Chunks (WebSocket `audioChunk`, PCM) werden pro Chunk mit `VoiceObjects.speechToText` transkribiert (Batch `recognize`, gemeinsamer Connector mit CommCoach). Konfiguration u. a. `audioFormat=linear16`, `skipFallbacks=True`; Details und Modell-Defaults: [voice-google.md](../gateway/voice-google.md).
### Audio-Capture: WebRTC-Wrapper-Gating
Der Bot installiert einen `RTCPeerConnection`-Wrapper per `addInitScript` (Browser-Start, vor jeder Teams-Navigation), damit später keine PC unbeobachtet bleibt. **Während Pre-Join, Lobby und SDP-Renegotiation darf der Wrapper aber NICHTS am Audio-Stream anfassen** — kein `clone()`, kein `createMediaStreamSource()`, kein `AudioContext`. Jeder Eingriff in dieser Phase löst in Teams' aktuellem `light-meetings`-Bundle den Renderer-Crash `Cannot read properties of null (reading 'rejectMediaDescriptionsUpdateAsync')` aus und der anonyme Bot landet entweder dauerhaft in der Lobby oder wird wieder zur Pre-Join-Seite geworfen.
Mechanik:
1. Wrapper-Init setzt `window.__audioCaptureEnabled = false` und legt einen `track`-Listener pro PC an, der bei `false` ausschließlich Diagnostik loggt.
2. Erst nach `orchestrator._setState('in_meeting')` ruft `_enableTranscriptCapture``audioCaptureProcedure.startCapture()` auf.
3. `startCapture()` flippt `__audioCaptureEnabled = true` und iteriert `getReceivers()` aller PCs mit `connectionState === 'connected'`. Für jede live Audio-Spur wird `__audioCaptureAttachTrack(pc, track)` ausgeführt (AudioContext + `track.clone()` + `MediaStreamSource` + AudioWorklet/ScriptProcessor → PCM16 16 kHz Mono).
4. Spätere `track`-Events bauen ihren Audio-Graph automatisch, sobald die zugehörige PC `connected` ist.
Es wird **keine Label-Filterung** angewendet — die Tracks haben je nach Session/Layout entweder `mainAudio-<n>` oder UUID-Labels. Der einzige saubere Trigger ist der Bot-State.
Refs: `service-teams-browser-bot/src/bot/audioCaptureProcedure.ts` (`__audioCaptureEnabled`, `__audioCaptureAttachTrack`, `startCapture`); Aufrufer `orchestrator.ts` (`_attemptJoin` STEP 4 → `_setState('in_meeting')``_enableTranscriptCapture`).
## Hybrid-Routing: SPEECH_TEAMS + Agent
Der Teamsbot läuft auf zwei kooperierenden Pfaden:
```mermaid
flowchart LR
Audio["Meeting Audio"] --> STT["STT"]
STT --> ST["SPEECH_TEAMS<br/>(fast, low-latency)"]
ST -->|shouldRespond=true| TTS["TTS + Chat"]
ST -->|needsAgent=true| Agent["agentService.runAgent<br/>(toolSet=core, web=on,<br/>maxRounds=5, maxCostCHF=0.10)"]
Agent -->|FINAL text| TTS
UI["Operator UI<br/>(Regie-Panel + UDB)"] -->|POST directorPrompt| Route["routeFeatureTeamsbot"]
Route -->|submitDirectorPrompt| Svc["TeamsbotService<br/>(_activeServices)"]
Svc -->|asyncio.create_task| Agent
TTS --> Meeting
Svc -->|SSE 'directorPrompt'| UI
```
- **`SPEECH_TEAMS`** bleibt der Default-Pfad mit niedrigster Latenz. Der dazugehörige System-Prompt erlaubt dem Modell explizit, `needsAgent=true` + `agentReason` zu setzen, wenn die Anfrage Web-Recherche, Mail oder Multi-Step-Tools erfordert.
- **Director Prompts** umgehen `SPEECH_TEAMS` komplett und feuern direkt einen `runAgent`-Lauf, dessen `FINAL`-Event-Text wieder über die bestehenden TTS-/Chat-Kanäle ins Meeting geliefert wird.
## Director Prompts (private Operator-Anweisungen)
Operator-Prompts sind **privat** (nur per SSE an den Session-Owner sichtbar) und werden in PostgreSQL gespeichert (`poweron_teamsbot.TeamsbotDirectorPrompt`).
| Modus | Verhalten |
|---|---|
| `oneShot` | Einmaliger Agent-Lauf, danach Status `consumed` |
| `persistent` | Agent-Lauf wird ausgeführt **und** der Text wird als `OPERATOR_DIRECTIVES`-Block in jeden folgenden `SPEECH_TEAMS`-Trigger eingemischt, bis der Operator den Prompt löscht |
| Lifecycle-Status | Bedeutung |
|---|---|
| `queued` | Eingereicht, Agent noch nicht gestartet |
| `running` | Agent läuft |
| `succeeded` | Agent fertig, persistent bleibt aktiv |
| `consumed` | One-Shot abgeschlossen oder persistent gelöscht |
| `failed` | Agent-Lauf fehlgeschlagen, persistent wird automatisch aus aktiven Direktiven entfernt |
**Persistenz beim Reconnect:** Bei jedem WebSocket-Reconnect ruft der Service `interface.getActivePersistentPrompts(sessionId)` auf und füllt `_activePersistentPrompts` neu, damit Direktiven Network-Drops überleben.
**Limits:** `DIRECTOR_PROMPT_TEXT_LIMIT = 8000` Zeichen, `DIRECTOR_PROMPT_FILE_LIMIT = 10` UDB-Dateien (Pflicht-Prüfung in der Route, validiert auch von `TeamsbotDirectorPromptCreateRequest`).
**RBAC:** Routes prüfen `_validateInstanceAccess` + `_validateSessionOwnership`. Ein Nicht-Owner sieht 404, niemals 403, um die Existenz der Session nicht preiszugeben.
**Rate-Limit:** `30/minute` pro Operator (slowapi).
## Schicht-Trennung (Plan #5 abgeschlossen 2026-04-24)
| Verantwortung | Datei |
|---|---|
| Persistenz + Lifecycle | `interfaceFeatureTeamsbot.py` (`createDirectorPrompt`, `getActivePersistentPrompts`, `updateDirectorPrompt`, `deleteDirectorPrompt`) |
| Orchestrierung + Agent-Lauf + SSE | `service.py` (`submitDirectorPrompt`, `_processDirectorPrompt`, `_runAgentForMeeting`, `_buildPersistentDirectorContext`, `removePersistentPrompt`) |
| HTTP + RBAC + Limits | `routeFeatureTeamsbot.py` (POST/GET/DELETE `/sessions/{id}/directorPrompts`) |
| Frontend Regie-Panel + UDB-Sidebar + SSE-Listener | `frontend_nyla/src/pages/views/teamsbot/TeamsbotSessionView.tsx` + `Teamsbot.module.css` |
| Frontend API-Wrapper | `frontend_nyla/src/api/teamsbotApi.ts` (`submitDirectorPrompt`, `listDirectorPrompts`, `deleteDirectorPrompt`) |
| Tests | `gateway/tests/unit/teamsbot/test_directorPrompts.py` (26 Tests, AC 5 + 6 abgedeckt; AC 14 manuell live) |