Files
ObsiGate/docs/AI_ARCHITECTURE_GUIDE.md
T
bruno 705f755b6b
CI / lint (push) Successful in 2m2s
CI / security (push) Successful in 1m25s
CI / test (push) Successful in 4m13s
CI / build (push) Successful in 1m16s
CI / e2e (push) Successful in 11m56s
docs: guides d'utilisation, capture reelle et README ameliores
2026-09-22 22:40:51 -04:00

23 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. → résolu (phases B/C/D) : tool calling natif, catalogue lecture/recherche + mutations.
  2. 2 actions seulement (create_file, create_directory) ; ni lecture active, ni recherche, ni édition, ni suppression exposées au modèle. → résolu (phases C/D) : catalogue complet (lecture, recherche, mutations avec confirmation).
  3. SSE : non réellement streaming → corrigé (B4) : ai_chat.stream_completion alimente /api/ai/bookslm/chat token par token ; le tool-calling (/agent) reste non-streaming (les appels d'outils exigent la réponse complète).
  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/services/ (A2 lecture/recherche + C + D mutations) :

backend/services/
├── errors.py      # ServiceError (code + status HTTP)
├── paths.py       # resolve_safe_path (anti path-traversal, source unique)
├── vaults.py      # list_accessible_vaults, browse_directory, get_vault_root, list_all_files
├── files.py       # read_raw_file, read_file_text (redaction + quota)
├── mutations.py   # create/edit/append/rename/move/delete/restore/replace (D)
├── search.py      # search_vaults (pagination), list_tags, advanced_search_vaults, search_paths
├── backups.py     # get_backup_dir, create_backup, list_backup_files, diff_backup
├── graph.py       # get_graph (nodes/edges, wikilinks)
└── recent.py      # list_recent, humanize_mtime

Les routes /api/vaults, /api/browse/{vault}, /api/file/{vault}/raw, /api/search, /api/tags, /api/recent, /api/vault/{vault}/files, /api/file/{vault}/backups, /api/file/{vault}/diff, /api/graph/{vault}, /api/tree-search, /api/search/advanced (phase C) ainsi que /api/file/{vault} (POST/PATCH/DELETE), /api/file/{vault}/save, /api/file/{vault}/restore, /api/directory/{vault} (POST/PATCH/DELETE), /api/move/{vault} et /api/search/replace (phase D) en sont de simples wrappers, tout comme les outils correspondants du catalogue. Un ServiceError est traduit en HTTPException (handler global de backend/main.py) ou en ToolError (backend/tools/registry.py).

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 sur /chat (ai_chat.stream_completion) ; /agent reste buffered (les appels d'outils exigent la réponse complète avant exécution). Confirmations UI en deux temps (confirmation → confirm/confirm_messages).

3.4 Couche 4 — Serveur MCP (livré, phase E)

backend/mcp/server.py s'appuie sur le SDK MCP Python (mcp==1.9.4) :

  • Tools : enregistrés depuis le registry (scope mcp). Les outils read sont exposés directement ; les outils write/dangerous le sont via la paire propose_<tool> / apply_<tool> (jeton signé, usage unique, TTL).
  • Resources : vault://<name> (vaults accessibles) et vault://<name>/<path> (fichiers, lecture seule, secrets redactés).
  • Prompts : summarize-directory, generate-note, find-related.
  • Transport : Streamable HTTP (/mcp, SDK StreamableHTTPSessionManager en json_response=True), auth Authorization: Bearer <JWT> → get_current_user. stdio optionnel plus tard.
  • Confirmations (E4) : backend/mcp/confirmations.py — jeton JWT (type=mcp_confirmation) contenant outil + arguments + utilisateur ; blacklist de JTI persistée pour l'anti-rejeu.
  • Le manager de session est démarré paresseusement à la première requête, pour fonctionner aussi bien sous uvicorn que sous le client de test (sans lifespan ASGI).

4. Catalogue des outils

Inventaire au registre — 28 outils enregistrés (backend/tools/service.py, backend/tools/web.py), vérifiable par docker exec obsigate-test python -c "from backend.tools import api; from backend.tools.registry import _REGISTRY; print(len(_REGISTRY))". Les tables ci-dessous reflètent le code, pas l'intention : ce qui n'est pas listé n'est pas exposé au modèle.

Légende : R = lecture (appel automatique), M = mutation (confirmation two-step obligatoire), D = dangereux (confirmation + réglage par vault aiDestructiveTools). « Étape (UI) » = libellé affiché dans la section « N étapes » de l'assistant (backend/tools/labels.py → clés ai.step.* FR/EN).

Vaults & navigation

Outil Type Paramètres Étape (UI)
list_vaults R — Liste des vaults consultée
list_directory R vault, path Répertoire exploré : {chemin}
list_all_files R vault, dir, limit, recursive Fichiers listés

Lecture de contenu

Outil Type Paramètres Étape (UI)
read_file R vault, path Fichier lu : {chemin}
read_file_raw R vault, path Fichier lu : {chemin}
get_backlinks R vault, path Backlinks analysés
list_backups R vault, path Sauvegardes consultées
diff_backup R vault, path, version, compare_with Comparaison de sauvegarde : {chemin}
get_graph R vault, path, depth, scope, tag Graphe du vault consulté

Recherche

Outil Type Paramètres Étape (UI)
search_fulltext R q, vault, tag, limit Recherche dans le vault : {q}
search_advanced R q, vault, tag, limit, offset, sort, case_sensitive, whole_word, regex, include_paths, exclude_paths, created, modified, size Recherche dans le vault : {q}
search_paths R q, vault Chemins recherchés : {q}
list_tags R vault Tags consultés
suggest_tags R q, vault, limit Tags suggérés pour {q}
list_recent R vault, limit, mode Fichiers récents consultés

Web (in-app uniquement, jamais exposé en MCP)

Outil Type Paramètres Étape (UI)
web_search R query, max_results, category, language, page Recherche sur le web : {query}
fetch_url R url Page web consultée : {url}

web_search interroge l'instance SearXNG auto-hébergée (OBSIGATE_SEARXNG_URL, search.dracodev.net par défaut — aucune clé API) ; fetch_url extrait le texte d'une page publique après garde SSRF (URL et redirections reverrouillées hop par hop, plafond de taille).

Création / modification (confirmation)

Outil Type Paramètres Étape (UI)
create_file M vault, path, content Fichier proposé : {chemin}
create_directory M vault, path Dossier proposé : {chemin}
edit_file M vault, path, content Fichier modifié : {chemin}
append_to_file M vault, path, content Fichier complété : {chemin}
restore_backup M vault, path, version Sauvegarde restaurée : {chemin}

Suppression & opérations destructives (confirmation + toggle par vault)

Outil Type Paramètres Étape (UI)
rename_file D vault, path, new_name Fichier renommé : {chemin}
rename_directory D vault, path, new_name Dossier renommé : {chemin}
move_path D vault, source_path, destination_dir Élément déplacé : {chemin}
replace_in_files D find, replace, vault, case_sensitive, whole_word, regex, include_paths, exclude_paths, replace_all, dry_run Remplacements : {motif}
delete_file D vault, path Fichier supprimé : {chemin}
delete_directory D vault, path, recursive Dossier supprimé : {chemin}

Prévu, non implémenté

Navigation front (open_file, reveal_in_tree — réalisés côté UI par les liens cliquables de l'assistant, pas comme outils du registry), partage (create_share, list_shares) et sources connectées (Gitea, Drive, courriel… : voir #92).


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() puis backend/tools/redaction.py sur tout résultat d'outil (diffs, extraits de recherche).
  5. Journalisation dans l'audit (data/audit.log).
  6. Rate limiting par jeton/outil (backend/tools/ratelimit.py : OBSIGATE_TOOL_RATE_LIMIT, OBSIGATE_TOOL_RATE_LIMIT_PER_TOOL, OBSIGATE_TOOL_RATE_WINDOW) et quotas réutilisant BOOKSLM_MAX_* (BOOKSLM_MAX_TOOL_CALLS par run d'agent, BOOKSLM_MAX_TOOL_READ_BYTES pour read_file).

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, cartes dépliables par fournisseur — #104), 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.


6bis. Skills, commandes, vision & capacités (#81)

  • Skills & commandes / — backend/skills.py définit les skills intégrés (id, icône, description, prompt) et les métadonnées des commandes admin. Les skills utilisateur sont persistés par utilisateur dans data/skills.json. Endpoints : GET/POST /api/ai/skills, DELETE /api/ai/skills/{id}. Le champ skill d'une requête chat/agent injecte le prompt du skill dans le system prompt (_resolve_system_prompt). Les commandes admin (/help, /providers, /provider, /model, /keys) sont exécutées côté client.
  • Contexte ad-hoc @ — champs extra_files / extra_directories sur les requêtes context/chat/agent ; collecte et fusion par collect_adhoc_context() + merge_contexts() (backend/bookslm.py).
  • Vision — les messages peuvent porter un contenu multimodal (tableau OpenAI text + image_url). backend/ai_chat.py convertit les data URLs en inlineData Gemini (_content_to_gemini_parts) ; l'OpenAI-compatible passe le tableau tel quel. Les images viennent d'un copier-coller (base64) ou d'un fichier de vault (data URL chargée par load_vault_image_data_url). Garde-fou : _validate_vision_support rejette (400) une requête d'image si le modèle n'est pas vision.
  • Capacités — table statique curée backend/model_capabilities.py (8 flags : chat, embeddings, rerank, images, video, audio_speech, audio_transcription, vision). Exposée par GET /api/ai/model-capabilities et par le champ capabilities de GET /api/config/ai-models ; affichée dans le picker de l'assistant et le panneau de configuration.

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 (backend/tools/ratelimit.py), quotas BOOKSLM_MAX_*, redaction systématique des résultats (backend/tools/redaction.py), doc OpenAPI (tag/path MCP) + guide MCP, tests E2E 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/services/ — logique métier partagée (vaults, files, search, backups, graph, recent) consommée par les routes et les outils
  • backend/ai_chat.py — chat completion provider-agnostique avec tool calling et streaming (OpenAI-compat + Gemini)
  • backend/agent/loop.py — agent loop in-app (multi-étapes, LLM injectable)
  • backend/mcp/server.py — serveur MCP (Streamable HTTP /mcp, tools/resources/prompts)
  • backend/mcp/confirmations.py — jetons de confirmation signés (two-step, anti-rejeu)
  • backend/tools/ratelimit.py — rate limiting par jeton/outil (phase F)
  • backend/tools/redaction.py — redaction récursive des résultats d'outils (phase F)
  • docs/GUIDES/MCP.md — guide d'installation et d'utilisation des clients MCP
  • 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