ti-pote/docs/architecture.md
ordinarthur 4ac7bd16d2 add doc
2026-04-01 17:10:18 +02:00

22 KiB

Architecture Logicielle

Principes directeurs

Ti-Pote suit une architecture hexagonale (Ports & Adapters). L'objectif est de découpler totalement la logique métier des détails d'implémentation (bases de données, APIs tierces, protocoles de communication). Chaque brique peut être remplacée sans impacter le reste du système.

Pourquoi l'architecture hexagonale ?

Le projet intègre de nombreux services externes (STT, TTS, LLM, Google Calendar, SMTP…) qui sont susceptibles de changer. En isolant chaque intégration derrière un port (interface TypeScript), on peut swapper un provider sans toucher au code métier. Par exemple, passer de Deepgram à Whisper pour le STT ne modifie que l'adaptateur, pas le service de conversation.

Vue d'ensemble

Architecture globale (robot + cloud)

┌──────────────────────────────────────────────────────────────────────────────┐
│                              ROBOT PHYSIQUE                                  │
│                                                                              │
│  ┌──────────────┐     UART      ┌──────────────────────────┐                │
│  │  ESP32-S3    │ ◄────────────►│  robot-client (Pi Zero)  │                │
│  │  • Audio I2S │               │  • WebSocket cloud       │                │
│  │  • Servos    │               │  • Wake word             │                │
│  │  • LEDs      │               │  • Domotique (local)     │                │
│  │  • Capteurs  │               │  • WiFi/BLE setup        │                │
│  └──────────────┘               └────────────┬─────────────┘                │
│                                              │                               │
└──────────────────────────────────────────────┼───────────────────────────────┘
                                               │ WebSocket (WSS)
                                               ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│                           BACKEND CLOUD (NestJS)                             │
│                                                                              │
│  Adaptateurs entrants → Core (services) → Adaptateurs sortants              │
│  (WebSocket, REST)      (Conversation,    (STT, TTS, LLM,                   │
│                          Calendar, etc.)   Storage, etc.)                    │
└──────────────────────────────────────────────────────────────────────────────┘

Note : Le robot-client gère la domotique en local (Hue, Home Assistant, MQTT) pour la latence et la résilience. Les commandes domotiques sont décidées par le LLM côté cloud, puis transmises au robot-client qui les exécute sur le réseau local. Voir home-automation.md et robot-client.md pour le détail.

Architecture du backend cloud

                        ┌─────────────────────────────────────────────┐
                        │              ADAPTATEURS ENTRANTS           │
                        │                                             │
                        │  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
                        │  │WebSocket │  │ REST API │  │ Web App  │  │
                        │  │(Robot)   │  │(Config)  │  │(React)   │  │
                        │  └────┬─────┘  └────┬─────┘  └────┬─────┘  │
                        └───────┼──────────────┼─────────────┼────────┘
                                │              │             │
                        ┌───────▼──────────────▼─────────────▼────────┐
                        │                                             │
                        │              PORTS ENTRANTS                 │
                        │         (Interfaces TypeScript)             │
                        │                                             │
                        │  IConversationPort   IConfigPort            │
                        │  IAuthPort           IDevicePort            │
                        │                                             │
                        ├─────────────────────────────────────────────┤
                        │                                             │
                        │              CORE (DOMAINE)                 │
                        │                                             │
                        │  ┌───────────────┐  ┌───────────────┐      │
                        │  │ Conversation   │  │ Calendar      │      │
                        │  │ Service        │  │ Service       │      │
                        │  └───────────────┘  └───────────────┘      │
                        │  ┌───────────────┐  ┌───────────────┐      │
                        │  │ Mail          │  │ Timer/Alarm   │      │
                        │  │ Service       │  │ Service       │      │
                        │  └───────────────┘  └───────────────┘      │
                        │  ┌───────────────┐  ┌───────────────┐      │
                        │  │ Memory        │  │ WebSearch     │      │
                        │  │ Service       │  │ Service       │      │
                        │  └───────────────┘  └───────────────┘      │
                        │  ┌───────────────┐  ┌───────────────┐      │
                        │  │ User          │  │ Device        │      │
                        │  │ Service       │  │ Service       │      │
                        │  └───────────────┘  └───────────────┘      │
                        │  ┌───────────────┐                         │
                        │  │ Context       │                         │
                        │  │ Builder       │                         │
                        │  └───────────────┘                         │
                        │                                             │
                        ├─────────────────────────────────────────────┤
                        │                                             │
                        │              PORTS SORTANTS                 │
                        │         (Interfaces TypeScript)             │
                        │                                             │
                        │  ISTTPort        ITTSPort                   │
                        │  ILLMPort        ICalendarPort              │
                        │  IMailPort       ISearchPort                │
                        │  IStoragePort    IVectorStorePort           │
                        │  ICachePort      INotificationPort          │
                        │                                             │
                        ├─────────────────────────────────────────────┤
                        │              ADAPTATEURS SORTANTS           │
                        │                                             │
                        │  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
                        │  │Deepgram  │  │ElevenLabs│  │OpenAI    │  │
                        │  │(STT)     │  │(TTS)     │  │(LLM)     │  │
                        │  └──────────┘  └──────────┘  └──────────┘  │
                        │  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
                        │  │Google    │  │SMTP      │  │PostgreSQL│  │
                        │  │Calendar  │  │(Mail)    │  │(Storage) │  │
                        │  └──────────┘  └──────────┘  └──────────┘  │
                        │  ┌──────────┐  ┌──────────┐               │
                        │  │Redis     │  │pgvector  │               │
                        │  │(Cache)   │  │(Vectors) │               │
                        │  └──────────┘  └──────────┘               │
                        └─────────────────────────────────────────────┘

Services du Core

ConversationService

Le service central qui orchestre chaque échange vocal. Responsabilités :

  • Recevoir l'audio streamé depuis le robot via WebSocket
  • Déléguer la transcription au port STT
  • Construire le contexte (via ContextBuilder) en injectant la mémoire pertinente
  • Envoyer le prompt au LLM avec les définitions de functions/tools
  • Interpréter la réponse du LLM : réponse directe ou function call
  • Si function call → exécuter via le service concerné → renvoyer le résultat au LLM
  • Envoyer la réponse texte au TTS et streamer l'audio de retour
Audio entrant (robot)
    │
    ▼
┌──────────┐    texte    ┌──────────────┐    prompt    ┌─────────┐
│   STT    │ ──────────► │ Context      │ ───────────► │  LLM    │
│          │             │ Builder      │              │         │
└──────────┘             └──────────────┘              └────┬────┘
                                                           │
                              ┌─────────────────────────────┤
                              │                             │
                         function call                 réponse directe
                              │                             │
                              ▼                             ▼
                    ┌──────────────┐               ┌──────────┐
                    │ Service      │               │   TTS    │
                    │ métier       │               │          │
                    │ (Calendar,   │               └────┬─────┘
                    │  Mail, etc.) │                    │
                    └──────┬───────┘                    ▼
                           │                    Audio sortant (robot)
                           ▼
                    Résultat → LLM → TTS → Audio

ContextBuilder

Service dédié à la construction du prompt envoyé au LLM. Il assemble :

  1. System prompt — Personnalité de Ti-Pote, instructions, capacités disponibles
  2. Profil utilisateur — Faits connus sur l'utilisateur (préférences, contacts fréquents…)
  3. Souvenirs pertinents — Récupérés par recherche sémantique dans pgvector
  4. Historique de session — Les N derniers messages de la conversation active (depuis Redis)
  5. Définitions de tools — Les functions que le LLM peut appeler
  6. Message courant — La transcription de ce que l'utilisateur vient de dire

Le ContextBuilder respecte un budget de tokens configurable. Si le contexte dépasse le budget, il priorise : message courant > historique récent > profil > souvenirs, et tronque les éléments les moins prioritaires.

CalendarService

Gestion des rendez-vous et événements. Expose des méthodes métier (createEvent, listEvents, findFreeSlots…) qui sont mappées comme tools pour le LLM. L'adaptateur sortant implémente l'intégration OAuth2 avec Google Calendar (et Apple Calendar à terme).

MailService

Envoi et lecture d'emails. Supporte SMTP pour l'envoi et IMAP pour la lecture. À terme, intégration WhatsApp et autres messageries.

TimerAlarmService

Gestion des minuteurs et alarmes. Les timers sont stockés en Redis avec un TTL. Quand le timer expire, le robot est notifié via WebSocket pour jouer un son ou annoncer vocalement la fin du timer.

MemoryService

Gère les 3 niveaux de mémoire (session, épisodique, sémantique). Voir memory-system.md pour le détail complet.

WebSearchService

Recherche sur internet à la demande de l'utilisateur. Utilise une API de recherche (SearXNG auto-hébergé ou API tierce) et peut extraire le contenu des pages pour le résumer.

UserService

Gestion des utilisateurs, authentification, préférences. Stockage des credentials chiffrés (AES-256) pour les services tiers.

DeviceService

Gestion des robots (devices). Enregistrement, statut de connexion, configuration (wake word, volume, langue…). Chaque robot maintient une connexion WebSocket persistante identifiée par un device ID.

Structure NestJS proposée

src/
├── main.ts
├── app.module.ts
│
├── core/                          # Domaine métier (aucune dépendance externe)
│   ├── ports/
│   │   ├── inbound/               # Ports entrants (interfaces)
│   │   │   ├── conversation.port.ts
│   │   │   ├── config.port.ts
│   │   │   ├── auth.port.ts
│   │   │   └── device.port.ts
│   │   └── outbound/              # Ports sortants (interfaces)
│   │       ├── stt.port.ts
│   │       ├── tts.port.ts
│   │       ├── llm.port.ts
│   │       ├── calendar.port.ts
│   │       ├── mail.port.ts
│   │       ├── search.port.ts
│   │       ├── storage.port.ts
│   │       ├── vector-store.port.ts
│   │       └── cache.port.ts
│   ├── services/
│   │   ├── conversation.service.ts
│   │   ├── context-builder.service.ts
│   │   ├── calendar.service.ts
│   │   ├── mail.service.ts
│   │   ├── timer-alarm.service.ts
│   │   ├── memory.service.ts
│   │   ├── web-search.service.ts
│   │   ├── user.service.ts
│   │   └── device.service.ts
│   ├── domain/                    # Entités et value objects
│   │   ├── user.entity.ts
│   │   ├── device.entity.ts
│   │   ├── conversation-session.entity.ts
│   │   ├── memory-entry.entity.ts
│   │   └── ...
│   └── tools/                     # Définitions des functions pour le LLM
│       ├── calendar.tools.ts
│       ├── mail.tools.ts
│       ├── timer.tools.ts
│       ├── search.tools.ts
│       └── index.ts
│
├── adapters/
│   ├── inbound/                   # Adaptateurs entrants
│   │   ├── websocket/
│   │   │   └── robot.gateway.ts   # WebSocket Gateway NestJS
│   │   ├── rest/
│   │   │   ├── config.controller.ts
│   │   │   ├── auth.controller.ts
│   │   │   └── device.controller.ts
│   │   └── web/                   # Serveur du frontend
│   │       └── static.module.ts
│   └── outbound/                  # Adaptateurs sortants
│       ├── stt/
│       │   ├── deepgram.adapter.ts
│       │   └── whisper.adapter.ts
│       ├── tts/
│       │   ├── elevenlabs.adapter.ts
│       │   └── azure-tts.adapter.ts
│       ├── llm/
│       │   ├── openai.adapter.ts
│       │   └── anthropic.adapter.ts
│       ├── calendar/
│       │   └── google-calendar.adapter.ts
│       ├── mail/
│       │   └── smtp.adapter.ts
│       ├── search/
│       │   └── searxng.adapter.ts
│       ├── storage/
│       │   └── postgresql.adapter.ts
│       ├── vector-store/
│       │   └── pgvector.adapter.ts
│       └── cache/
│           └── redis.adapter.ts
│
├── config/                        # Configuration applicative
│   ├── database.config.ts
│   ├── redis.config.ts
│   ├── llm.config.ts
│   └── app.config.ts
│
└── shared/                        # Utilitaires partagés
    ├── crypto.util.ts             # Chiffrement AES-256
    ├── token-counter.util.ts      # Comptage de tokens
    └── logger.ts

Communication Robot ↔ Core

WebSocket (audio bidirectionnel)

Le robot maintient une connexion WebSocket permanente avec le core. Le protocole :

  1. Authentification — À la connexion, le robot envoie un device_token JWT. Le core valide et associe la session au device + user.
  2. Streaming audio entrant — Le robot envoie des chunks audio (PCM 16kHz 16bit mono) en continu pendant que l'utilisateur parle.
  3. Événements de contrôle — Wake word détecté, fin de parole (VAD), interruption utilisateur.
  4. Streaming audio sortant — Le core streame les chunks audio TTS au fur et à mesure de la génération.
  5. Notifications — Timer expiré, rappel de rendez-vous, alertes.
// Messages WebSocket (types)
type RobotMessage =
  | { type: 'audio_chunk'; data: Buffer; sampleRate: number }
  | { type: 'wake_word_detected' }
  | { type: 'speech_end' }       // VAD détecte fin de parole
  | { type: 'user_interrupt' }   // L'utilisateur parle pendant la réponse

type CoreMessage =
  | { type: 'audio_chunk'; data: Buffer }
  | { type: 'response_start' }
  | { type: 'response_end' }
  | { type: 'notification'; payload: NotificationPayload }
  | { type: 'status'; state: 'listening' | 'thinking' | 'speaking' }

REST API (configuration)

Utilisée par l'app web/mobile pour toute la configuration :

  • CRUD utilisateurs et devices
  • Gestion des credentials OAuth (Google, Apple, etc.)
  • Configuration du robot (wake word, volume, voix TTS, modèle LLM…)
  • Consultation de l'historique et de la mémoire
  • Gestion des timers et alarmes

Gestion des erreurs et résilience

  • Circuit breaker sur les appels aux services externes (STT, TTS, LLM) — si un provider tombe, le système peut fallback sur un autre (ex: Deepgram → Whisper local).
  • Retry avec backoff exponentiel sur les appels API.
  • Queue de messages pour les function calls non critiques (ex: envoi d'email) — si ça échoue, on retry sans bloquer la conversation.
  • Mode dégradé offline — le robot peut toujours gérer les timers, alarmes, commandes domotiques basiques, et commandes locales si le core est injoignable.

Robot Client

Le robot-client (apps/robot-client) est l'application Node.js/TypeScript qui tourne sur le Raspberry Pi Zero 2 W. Il orchestre toute la logique locale du robot : communication UART avec l'ESP32-S3, connexion WebSocket vers le backend cloud, détection du wake word, provisioning WiFi (BLE + AP mode), et exécution des commandes domotiques sur le réseau local.

En développement, le robot-client tourne sur le PC du développeur en mode simulateur (ROBOT_MODE=simulator), les modules hardware étant remplacés par des mocks. Le simulateur web (apps/simulator) sert alors de frontend de debug.

Voir robot-client.md pour l'architecture détaillée.

Couche Domotique

L'intégration domotique est gérée localement par le robot-client, pas par le backend cloud. Chaque écosystème (Philips Hue, Home Assistant, MQTT…) est encapsulé dans un bridge qui implémente une interface IAutomationBridge. L'AutomationManager orchestre les bridges et expose une API unifiée.

Le flux : le LLM côté cloud décide d'appeler un tool domotique → le backend transmet la commande au robot-client via WebSocket → le robot-client exécute la commande localement sur le réseau.

Voir home-automation.md pour le détail complet.