18 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
- Un assistant in-app capable de lire, chercher, lister, ouvrir et modifier, via function calling natif.
- Un serveur MCP exposant ObsiGate aux clients externes (Claude Desktop, Cursor…).
- 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_PROMPTbackend/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-94DEFAULT_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
- Pas de tool calling : parsing regex fragile, pas de résultats structurés, pas de multi-étapes.
- 2 actions seulement (
create_file,create_directory) ; ni lecture active, ni recherche, ni édition, ni suppression exposées au modèle. - SSE :
non réellement streaming→ corrigé (B4) :ai_chat.stream_completionalimente/api/ai/bookslm/chattoken par token ; le tool-calling (/agent) reste non-streaming (les appels d'outils exigent la réponse complète). PROVIDERSchargé 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) :
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
├── files.py # read_raw_file, read_file_text (redaction + quota)
└── search.py # search_vaults (pagination), list_tags
Les routes /api/vaults, /api/browse/{vault}, /api/file/{vault}/raw, /api/search et
/api/tags en sont de simples wrappers, tout comme les outils list_vaults, list_directory,
read_file, search_fulltext et list_tags. 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__.pysuivi —.gitignoreexclut_*.py). La façadeapi.pyjoue 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) ;/agentreste 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
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 :
check_vault_access(vault, user)—backend/auth/middleware.py:87_resolve_safe_path(vault_root, path)—backend/main.py:867- Confirmation utilisateur pour
risk in (write, dangerous)(UI in-app : carte Apply ; MCP : two-steppropose/apply). - Redaction des secrets avant tout envoi au LLM —
redact_file_content(). - Journalisation dans l'audit (
data/audit.log). - 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 + unconfirmation_tokensigné (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 unpropose→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 à jourPROVIDERSen 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 parsaveAIKeys()(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
/mcpréutilise l'auth, le multi-utilisateur et l'accès distant.stdioimposerait 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 éditeurbackend/services/— logique métier partagée (vaults, files, search) consommée par les routes et les outilsbackend/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/bookslm.py,backend/bookslm_routes.py— assistant contextuel (+ endpoint/agent)frontend/js/ai.js,frontend/js/bookslm.js— UI IAbackend/auth/middleware.py— permissionsbackend/secret_redactor.py— redactiondocs/ROADMAP.md— suivi des phases