Files
ObsiGate/docs/features/ai-tools-mcp.md
T
bruno f02174af57
CI / lint (push) Successful in 1m33s
CI / security (push) Successful in 1m0s
CI / test (push) Successful in 2m57s
CI / build (push) Successful in 56s
CI / e2e (push) Successful in 10m48s
fix(ai): mode agent utilise les outils natifs au lieu du bloc obsigate-action (BUG-053)
2026-09-17 07:30:31 -04:00

9.8 KiB

#79 — Assistant IA — Outils (function calling) & serveur MCP

Statut : ✅ Livré — Phase 0 + A2 + B1/B2/B3/B4/B5/B6/B7 + C + D + E + F + G livrés (2026-09-11) Effort : 10-15 jours | Impact : 🟡 Références : Roadmap · Guide d'architecture IA

  • Description : Transformer l'assistant BooksLM (protocole texte obsigate-action limité à create_file/create_directory) en un agent capable de lire, chercher, lister, ouvrir et modifier, via function calling natif, puis exposer ObsiGate à des clients MCP externes (Claude Desktop, Cursor…). Les deux fronts consomment une couche d'outils partagée — source unique de vérité.
  • Documentation : AI_ARCHITECTURE_GUIDE.md (architecture, catalogue d'outils, sécurité, phases).

A. Fondations — couche d'outils partagée (2-3 jours) — ✅ livré (2026-09-11)

  • A1. Créer backend/tools/ : context.py (ToolContext : user, allowed_vaults, mode, confirmed), registry.py (décorateur @tool + schéma JSON), schemas.py, service.py, audit.py
  • A2. Extraire la logique métier des routes de backend/main.py en fonctions de service réutilisables (les routes deviennent des wrappers) — backend/services/ (errors.py, paths.py, vaults.py, files.py, search.py) ; routes /api/vaults, /api/browse, /api/file/{vault}/raw, /api/search, /api/tags et outils list_vaults/list_directory/read_file/search_fulltext/list_tags délèguent à la même couche. Les ServiceError sont mappées vers HTTPException (routes) et ToolError (outils).
  • A3. Contexte de permissions : chaque outil applique check_vault_access + resolve_safe_path
  • A4. Audit : journalisation JSONL de chaque appel d'outil (qui, quoi, vault, résultat) — action ai_tool_call, arguments sensibles résumés
  • A5. Tests unitaires du registry + services (sans IA) — tests/test_tools.py (30 tests)

B. Function calling in-app (3-4 jours) — ✅ livré (2026-09-11)

  • B1. Abstraction tool-calling provider-agnostique : backend/ai_chat.py (chat_completion, ToolCall, LLMResponse) — OpenAI-compat (tools/tool_choice, parsing tool_calls) + Gemini (functionDeclarations/functionCall)
  • B2. Agent loop backend/agent/loop.py : boucle tool→résultat→tool, limite d'itérations (10), truncation des résultats ; endpoint opt-in POST /api/ai/bookslm/agent (events SSE tool/message/confirmation)
  • B3. Fallback : retry sans tools si le provider rejette les tools (400/404/422) → chat simple ; protocole texte obsigate-action conservé côté frontend pour le chat classique uniquement (BUG-053 : en mode agent, le prompt impose les outils natifs et interdit les blocs obsigate-action)
  • B4. SSE réellement streaming — ai_chat.stream_completion (_openai_stream + _gemini_stream) alimente /api/ai/bookslm/chat token par token ; le middleware GZip laisse passer les endpoints SSE BooksLM.
  • B5. Confirmations UI : toggle « mode agent » (front → /agent), événements tool/confirmation, carte Apply + aperçu diff (LCS) pour les mutations, reprise confirm/confirm_messages côté backend. S'active dès que la phase D enregistre des outils write.
  • B6. Outils de navigation in-app : open_file, reveal_in_tree (événement obsigate:open-file) — livré via les liens cliquables de l'assistant (#80, ai-assistant-ux.md)
  • B7. Tests : agent loop LLM mocké (tests/test_agent_loop.py), providers (tests/test_ai_chat.py), endpoint (tests/test_bookslm.py)

C. Catalogue d'outils — lecture & recherche (1-2 jours) — ✅ livré (2026-09-11)

  • C1. Vaults/navigation : list_vaults, list_directory, list_all_files — list_all_files ajouté via backend/services/vaults.py
  • C2. Lecture : read_file, read_file_raw, get_backlinks, list_backups, diff_backup, get_graph — services backend/services/backups.py (backups/diff) et graph.py ; read_file_raw redacte les secrets
  • C3. Recherche : search_fulltext, search_advanced, search_paths, list_tags, suggest_tags, list_recent — extensions backend/services/search.py (advanced_search_vaults, search_paths) et recent.py ; filtrage systématique par permissions vault
  • Routes /api/recent, /api/vault/{vault}/files, /api/file/{vault}/backups, /api/file/{vault}/diff, /api/graph/{vault}, /api/tree-search, /api/search/advanced refactorées en wrappers des services ; tests tests/test_tools.py (+17)

D. Catalogue d'outils — mutations (2 jours) — ✅ livré (2026-09-11)

  • D1. create_file, create_directory (migration du protocole texte existant)
  • D2. edit_file, append_to_file, rename_file, rename_directory, move_path
  • D3. replace_in_files (⚠️ confirmation two-step + backup auto)
  • D4. delete_file, delete_directory, restore_backup (⚠️ confirmation two-step + backup auto, toggle par vault)
  • Détail : service backend/services/mutations.py (source unique : anti path-traversal, garde lecture seule, backup auto via backend/services/backups.create_backup) ; les routes REST correspondantes délèguent à cette couche. Risques : WRITE (create/edit/append/restore) et DANGEROUS (rename/move/replace/delete) → confirmation two-step automatique par le registry. Toggle par vault aiDestructiveTools (défaut activé) pour désactiver les outils DANGEROUS. Tests : tests/test_tools_mutations.py (40 tests).

E. Serveur MCP (2-3 jours) — ✅ livré (2026-09-11)

  • E1. backend/mcp/server.py (SDK MCP Python mcp==1.9.4) enregistrant les outils depuis le registry
  • E2. Mapping des primitives : Tools (lecture + propose_*/apply_*), Resources (vault://<name>, vault://<name>/<path>), Prompts (summarize-directory, generate-note, find-related)
  • E3. Transport Streamable HTTP : route /mcp (matching exact + sous-chemins), auth Authorization: Bearer <JWT> → get_current_user. stdio optionnel plus tard
  • E4. Confirmations two-step propose_*/apply_* avec token JWT signé (usage unique, TTL OBSIGATE_MCP_CONFIRMATION_TTL, défaut 300 s) + blacklist de JTI persistée (data/mcp_used_tokens.json, anti-rejeu)
  • E5. Toggle par vault aiDestructiveTools appliqué à la proposition et à l'application
  • E6. Tests : tests/test_mcp.py (13 tests) — handshake initialize, tools/list/tools/call, resources, prompts, permissions par vault, anti-rejeu du token
  • Détail : backend/mcp/confirmations.py (jetons signés + anti-rejeu) ; backend/mcp/server.py (Server lowlevel + StreamableHTTPSessionManager en json_response=True, McpMount pour matcher /mcp exact, démarrage paresseux du manager pour les tests). Dépendances ajoutées : mcp==1.9.4, sse-starlette==2.1.3 (compatibles avec FastAPI 0.110 / starlette 0.37).

F. Durcissement & documentation (1 jour) — ✅ livré (2026-09-11)

  • F1. Rate limiting par token/outil + quotas — backend/tools/ratelimit.py (fenêtre glissante par identité et par outil ; identité = JTI du jeton (_token_jti, ajouté par backend/auth/middleware.py) sinon id/username). Env : OBSIGATE_TOOL_RATE_LIMIT (60), OBSIGATE_TOOL_RATE_LIMIT_PER_TOOL, OBSIGATE_TOOL_RATE_WINDOW (60 s) ; erreur ToolRateLimitError (rate_limited). Quotas BOOKSLM_MAX_* : BOOKSLM_MAX_TOOL_CALLS (25) plafonne les appels d'outils par run d'agent (backend/agent/loop.py, stopped="quota_exceeded") et BOOKSLM_MAX_TOOL_READ_BYTES (200000) plafonne read_file.
  • F2. Redaction des secrets avant retour au LLM — backend/tools/redaction.py (redact_payload, récursif) appliqué à tout résultat d'outil dans call_tool (couvre les diffs, extraits de recherche et lectures non pré-redactées).
  • F3. Documentation OpenAPI + guide MCP — backend/openapi_docs.py : tag MCP, règle /mcp, injection du path /mcp (Streamable HTTP, JSON-RPC) dans le schéma ; nouveau docs/MCP_GUIDE.md (endpoint, auth, config Claude Desktop / Cursor, tools/resources/prompts, sécurité, variables, dépannage).
  • F4. Tests E2E de bout en bout — tests/test_ai_e2e.py : agent in-app read→confirmation→write, quota d'outils, rate limiting, redaction, et flux MCP complet read → propose_edit_file → apply_edit_file → resources/read.

G. Sélection fournisseur/modèle par défaut — ✅ livré

  • G1. Persistance ai_default_provider + ai_default_models dans data/config.json (_DEFAULT_CONFIG)
  • G2. Lecture + rechargement à chaud dans backend/ai.py (get_default_provider, reload_ai_config)
  • G3. UI : sélecteurs « Fournisseur par défaut » + « Modèle par défaut » dans #cfg-ai (frontend/index.html, frontend/js/config.js)
  • G4. i18n FR/EN

Décisions

  • ✅ Transport MCP : Streamable HTTP (2026-09-11)
  • ✅ Confirmation MCP : two-step propose/apply (2026-09-11)
  • ✅ Périmètre des mutations externes : toutes autorisées (create/edit/rename/move/delete) — encadrées par confirmation + backup auto + audit + toggle par vault (2026-09-11)
  • ✅ Confirmation d'un lot d'appels (BUG-050) : quand un tour contient plusieurs appels d'outils et qu'un seul est mutateur, les appels non atteints reçoivent un résultat deferred pour préserver la validité du protocole tool-calling ; ils sont réémis après confirmation (2026-09-16)
  • Détail et justification dans le guide §8.