Files
ObsiGate/docs/AI_ARCHITECTURE_GUIDE.md
T
bruno 55696bfb31
CI / lint (push) Successful in 57s
CI / security (push) Successful in 39s
CI / test (push) Successful in 1m13s
CI / build (push) Successful in 36s
CI / e2e (push) Successful in 10m13s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
feat(ai): phase B function calling in-app (agent loop + endpoint /agent)
- backend/ai_chat.py: chat_completion provider-agnostique (OpenAI-compat tools/tool_calls + Gemini functionDeclarations/functionCall), retry sans tools si rejete
- backend/agent/loop.py: run_agent multi-etapes (limite 10, truncation, confirmation two-step), LLM injectable
- endpoint opt-in POST /api/ai/bookslm/agent (events SSE tool/message/confirmation), extraction _resolve_system_prompt
- tests: test_ai_chat.py, test_agent_loop.py + 3 tests endpoint (728 passed au total)
- ROADMAP B1/B2/B3/B7 livres ; B4/B5/B6 restants
2026-09-11 12:45:16 -04:00

16 KiB

ObsiGate — Guide d'architecture IA

Statut : document de conception (référence pour l'implémentation) Dernière mise à jour : 2026-09-11 Portée : sous-systèmes IA d'ObsiGate, couche d'outils (function calling), serveur MCP, sélection du fournisseur/modèle par défaut.


1. Vue d'ensemble

ObsiGate possède aujourd'hui deux sous-systèmes IA qui partagent la même couche de fournisseurs (backend/ai.py) :

Sous-système Rôle Backend Frontend
Barre d'outils IA de l'éditeur 16 transformations de texte statiques (améliorer, traduire, résumer…) backend/ai.py, backend/ai_routes.py frontend/js/ai.js
Assistant BooksLM Chat contextuel (dossier / documents / général) backend/bookslm.py, backend/bookslm_routes.py frontend/js/bookslm.js

Constat clé : il n'existe aucun tool calling / function calling natif ni MCP. La seule « action » de l'assistant repose sur un protocole texte maison (obsigate-action) limité à create_file et create_directory.

Objectif cible

  1. Un assistant in-app capable de lire, chercher, lister, ouvrir et modifier, via function calling natif.
  2. Un serveur MCP exposant ObsiGate aux clients externes (Claude Desktop, Cursor…).
  3. Les deux fronts consomment la même couche d'outils — source unique de vérité.
                    ┌──────────────────────────────┐
                    │   Couche d'outils partagée    │
                    │  registry + services + perms  │
                    └───────┬───────────────┬───────┘
                            │               │
              ┌─────────────▼─────┐   ┌─────▼──────────────────┐
              │ Assistant in-app  │   │ Serveur MCP (externe)  │
              │ function calling  │   │ Claude Desktop, Cursor │
              │ + confirmations UI│   │ tools + resources      │
              └───────────────────┘   └────────────────────────┘

2. État actuel (références de code)

2.1 Barre d'outils IA de l'éditeur

  • ai_complete() — backend/ai.py:175
  • 16 endpoints REST POST /api/ai/* — backend/ai_routes.py:135+
  • Requête AIRequest (text, instruction, target_lang, tone, provider, model) — backend/ai_routes.py:100
  • Statut GET /api/ai/status — backend/ai_routes.py:34

2.2 Assistant BooksLM

  • Collecte du contexte : collect_directory_context() backend/bookslm.py:69, collect_files_context() backend/bookslm.py:209
  • Arborescence : _build_directory_tree() backend/bookslm.py:276
  • Limites : BOOKSLM_MAX_FILES=200, BOOKSLM_MAX_TOTAL_CHARS=200000, BOOKSLM_MAX_FILE_CHARS=30000 — backend/bookslm.py:21
  • Cache 5 min indexé par mtime — backend/bookslm.py:26, :111
  • Prompts système : build_system_prompt() backend/bookslm.py:295, build_general_system_prompt() backend/bookslm.py:380
  • Protocole d'action : GENERAL_SYSTEM_PROMPT backend/bookslm.py:353-377
  • Routes : POST /api/ai/bookslm/context, POST /api/ai/bookslm/chat — backend/bookslm_routes.py:96,118
  • Extraction/application d'action côté client : _extractActions() frontend/js/bookslm.js:502, _applyAction() frontend/js/bookslm.js:550

2.3 Couche fournisseurs

  • PROVIDERS (dict module, chargé une fois à l'import) — backend/ai.py:37-94
  • DEFAULT_PROVIDER — backend/ai.py:96
  • Appel OpenAI-compatible : _call_deepseek_openrouter() — backend/ai.py:115
  • Appel Gemini : _call_gemini() — backend/ai.py:156
  • Clés stockées dans data/api_keys.json, fallback .env — get_ai_key() backend/ai.py:30
  • Surcharge de modèle par requête : _handle() backend/ai_routes.py:114, bookslm_routes.py:194

2.4 Sécurité existante à réutiliser

Mécanisme Référence
Auth JWT require_auth backend/auth/middleware.py:69
Admin require_admin backend/auth/middleware.py:80
Accès vault check_vault_access backend/auth/middleware.py:87, require_vault_access :101
Anti path-traversal _resolve_safe_path backend/main.py:867
Redaction secrets backend/secret_redactor.py (redact_file_content)
Audit data/audit.log

2.5 Limites de l'existant

  1. Pas de tool calling : parsing regex fragile, pas de résultats structurés, pas de multi-étapes.
  2. 2 actions seulement (create_file, create_directory) ; ni lecture active, ni recherche, ni édition, ni suppression exposées au modèle.
  3. SSE non réellement streaming : la réponse complète est envoyée en un seul événement — backend/bookslm_routes.py:211.
  4. PROVIDERS chargé une seule fois : les modèles par défaut ne sont modifiables que par variables d'environnement (pas de persistance UI).

3. Architecture cible

3.1 Couche 1 — Capacités (services backend)

Extraire la logique métier des routes de backend/main.py vers des fonctions réutilisables (services). Les routes REST, l'agent in-app et le serveur MCP appellent ces mêmes services.

Nouveau module backend/tools/ :

backend/tools/
├── api.py         # façade publique (enregistre les outils, réexporte l'API)
├── context.py     # ToolContext (user, allowed_vaults, mode, confirmed)
├── registry.py    # décorateur @tool(...) + schémas JSON
├── schemas.py     # modèles Pydantic entrée/sortie
├── service.py     # implémentations (wrappers des services métier)
└── audit.py       # journalisation des appels d'outils

ObsiGate utilise des namespace packages implicites (aucun __init__.py suivi — .gitignore exclut _*.py). La façade api.py joue le rôle de point d'entrée et déclenche l'enregistrement des outils.

3.2 Couche 2 — Registry d'outils

Chaque outil déclare : name, description, parameters (JSON Schema), risk (read | write | dangerous), requires_confirmation (bool), scopes (in_app, mcp).

3.3 Couche 3 — Agent loop (in-app)

Nouveau backend/agent/loop.py :

boucle (max N itérations):
    réponse = LLM(messages, tools)
    si tool_calls:
        pour chaque appel:
            vérifier permissions + confirmation
            exécuter via la couche d'outils (ToolContext)
            réinjecter le résultat comme message "tool"
        continuer
    sinon:
        renvoyer le texte final
  • Fallback protocole texte (obsigate-action) si le modèle ne supporte pas les tools.
  • SSE réellement streaming.

3.4 Couche 4 — Serveur MCP

Nouveau backend/mcp/server.py basé sur le SDK MCP Python :

  • Tools : enregistrés depuis le registry (mutations + recherche).
  • Resources : vaults/fichiers en lecture (vault://name/path.md).
  • Prompts : templates (résumer un dossier, générer une note…).
  • Transports : stdio (local) et HTTP/SSE (distant, auth JWT).

4. Catalogue des outils

Légende : R = lecture (auto), M = mutation (confirmation), N = navigation (in-app uniquement).

Vaults & navigation

Outil Type Paramètres Endpoint source
list_vaults R — GET /api/vaults (main.py:1267)
list_directory R vault, path GET /api/browse/{vault} (main.py:1503)
list_all_files R vault GET /api/vault/{vault}/files (main.py:3883)
open_file N vault, path événement front obsigate:open-file
reveal_in_tree N vault, path UI sidebar

Lecture de contenu

Outil Type Paramètres Endpoint source
read_file R vault, path GET /api/file/{vault} (main.py:2722)
read_file_raw R vault, path GET /api/file/{vault}/raw (main.py:1589)
get_backlinks R vault, path GET /api/file/{vault}/backlinks (main.py:2683)
list_backups R vault, path GET /api/file/{vault}/backups (main.py:2476)
diff_backup R vault, path GET /api/file/{vault}/diff (main.py:2513)
get_graph R vault GET /api/graph/{vault} (main.py:3390)

Recherche

Outil Type Paramètres Endpoint source
search_fulltext R q, vault, tag, limit GET /api/search (main.py:3097)
search_advanced R q, vault, filtres, regex… GET /api/search/advanced (main.py:3195)
search_paths R q, vault GET /api/tree-search (main.py:3151)
list_tags R vault GET /api/tags (main.py:3137)
suggest_tags R q GET /api/tags/suggest (main.py:3352)
list_recent R vault GET /api/recent (main.py:1301)

Création / modification

Outil Type Paramètres Endpoint source
create_file M vault, path, content POST /api/file/{vault} (main.py:2134)
create_directory M vault, path POST /api/directory/{vault} (main.py:1921)
edit_file M vault, path, content PUT /api/file/{vault}/save (main.py:1796)
append_to_file M vault, path, content dérivé de save
rename_file M vault, path, new_name PATCH /api/file/{vault} (main.py:2207)
rename_directory M vault, path, new_name PATCH /api/directory/{vault} (main.py:1995)
move_path M vault, src, dst POST /api/move/{vault} (main.py:2289)
replace_in_files M ⚠️ vault, find, replace, filtres POST /api/search/replace (main.py:3241)

Suppression (confirmation renforcée)

Outil Type Paramètres Endpoint source
delete_file M ⚠️ vault, path DELETE /api/file/{vault} (main.py:1851)
delete_directory M ⚠️ vault, path, recursive DELETE /api/directory/{vault} (main.py:2068)
restore_backup M vault, path POST /api/file/{vault}/restore (main.py:2601)

Partage (optionnel, admin)

Outil Type Paramètres Endpoint source
create_share M vault, path, options POST /api/share/{vault} (main.py:4719)
list_shares R — —

5. Sécurité & permissions

Tout outil reçoit un ToolContext et applique systématiquement :

  1. check_vault_access(vault, user) — backend/auth/middleware.py:87
  2. _resolve_safe_path(vault_root, path) — backend/main.py:867
  3. Confirmation utilisateur pour risk in (write, dangerous) (UI in-app : carte Apply ; MCP : two-step propose/apply).
  4. Redaction des secrets avant tout envoi au LLM — redact_file_content().
  5. Journalisation dans l'audit (data/audit.log).
  6. Rate limiting par token/outil ; quotas réutilisant BOOKSLM_MAX_*.

Confirmations MCP — décision : two-step propose/apply

MCP n'a pas de bouton « Apply ». Mécanisme retenu :

  • Two-step (canonique) : propose_* (non destructif) renvoie un aperçu/diff + un confirmation_token signé (usage unique, TTL) ; apply_* exige ce token pour exécuter. Universel (tout client), anti-rejeu/anti-TOCTOU, et unifie in-app et MCP (la carte Apply de l'UI est un propose→apply).
  • Élicitation (optionnelle, plus tard) : quand le client l'annonce dans ses capabilities, afficher la confirmation inline. Fallback two-step sinon.

Les outils destructifs (delete_*, rename_*, move_path, replace_in_files) sont autorisés (ObsiGate + MCP = outil de gestion des vaults pour des agents), mais encadrés :

  • Confirmation two-step obligatoire (propose_* → apply_*, token signé).
  • Backup automatique avant toute opération destructive (mécanisme existant, POST /api/file/{vault}/restore).
  • Audit systématique (data/audit.log).
  • Toggle par vault pour désactiver les outils destructifs (défaut : activés).

6. Sélection du fournisseur et des modèles par défaut

Pour répondre au besoin de cibler un fournisseur/modèle sans dépendre uniquement des variables d'environnement, la configuration expose deux paramètres persistés dans data/config.json :

Clé Type Défaut Rôle
ai_default_provider str deepseek Fournisseur utilisé quand aucun override n'est fourni
ai_default_models dict {} Modèle par défaut par fournisseur (ex. {"deepseek": "deepseek-chat"})
  • Lecture : backend/ai.py (_read_app_config, get_default_provider, _load_provider_keys).
  • Écriture : POST /api/config (admin) — clés ajoutées à _DEFAULT_CONFIG (backend/main.py:4270).
  • Rechargement à chaud : reload_ai_config() met à jour PROVIDERS en place (les imports existants restent valides).
  • UI : section « Clés API Intelligence Artificielle » (frontend/index.html #cfg-ai), sélecteurs « Fournisseur par défaut » + « Modèle par défaut », sauvegardés par saveAIKeys() (frontend/js/config.js).

Précédence de résolution du modèle : override par requête > ai_default_models[provider] > variable d'environnement *_MODEL > défaut codé en dur.

Précédence du fournisseur : override par requête > ai_default_provider > env AI_DEFAULT_PROVIDER > deepseek.


7. Plan par phases

Phase Contenu Livrable
0 — Fondations backend/tools/ (registry, context, service, audit) + extraction des services métier + tests unitaires Couche d'outils testable sans IA
1 — Function calling in-app Abstraction tool-calling multi-provider, agent loop, confirmations UI, SSE réel, outils de navigation Assistant qui lit/cherche/lit/ouvre/modifie avec confirmation
2 — Serveur MCP backend/mcp/server.py (tools + resources + prompts), Streamable HTTP (/mcp, auth JWT), confirmation two-step ObsiGate accessible comme serveur MCP (local + distant, multi-utilisateur)
3 — Durcissement Rate limiting, quotas, redaction, tests, doc OpenAPI/MCP Observabilité et sécurité complètes

Voir docs/ROADMAP.md (item dédié) pour le détail des activités.


8. Décisions

# Décision Statut
1 Transport MCP : Streamable HTTP (/mcp dans FastAPI existant). stdio optionnel plus tard pour clients locaux sans MCP distant. ✅ Décidé (2026-09-11)
2 Confirmation MCP : two-step propose/apply (token signé). Élicitation en bonus quand le client l'annonce. ✅ Décidé (2026-09-11)
3 Périmètre des mutations externes : toutes autorisées via MCP — create, edit, rename, move, delete (ObsiGate + MCP = outil de gestion vault↔agents). Encadrées par confirmation two-step + backup auto + audit + toggle par vault. ✅ Décidé (2026-09-11)
4 Provider tool-calling : DeepSeek par défaut → adressé par la sélection du fournisseur/modèle par défaut (§6). ✅ Résolu

Justification

  • HTTP : ObsiGate est déjà un serveur web avec JWT et permissions par vault ; exposer /mcp réutilise l'auth, le multi-utilisateur et l'accès distant. stdio imposerait un process séparé dupliquant l'infra pour un usage local mono-utilisateur.
  • Two-step : universel (aucune dépendance aux capacités du client), permet un diff avant application, token signé à usage unique (anti-rejeu), et cohérent avec le flux in-app existant.
  • Mutations complètes : l'objectif est de faire d'ObsiGate + MCP la couche de gestion entre les vaults et des agents ; restreindre delete/rename/move limiterait fortement les cas d'usage. La sécurité repose sur la confirmation, le backup automatique, l'audit et le toggle par vault plutôt que sur une interdiction par défaut.

9. Références

  • backend/ai.py, backend/ai_routes.py — couche fournisseurs + actions éditeur
  • backend/ai_chat.py — chat completion provider-agnostique avec tool calling (OpenAI-compat + Gemini)
  • backend/agent/loop.py — agent loop in-app (multi-étapes, LLM injectable)
  • backend/bookslm.py, backend/bookslm_routes.py — assistant contextuel (+ endpoint /agent)
  • frontend/js/ai.js, frontend/js/bookslm.js — UI IA
  • backend/auth/middleware.py — permissions
  • backend/secret_redactor.py — redaction
  • docs/ROADMAP.md — suivi des phases