8.0 KiB
#92 — Assistant IA — Écosystème d'outils : feuille de route technique
Statut : ✅ livré (phase 2, version 2.10.0) — phase 1 livrée dans #91 Effort estimé : 3-5 jours pour la phase 2 | Impact : 🟠 Références : Roadmap · Outils & MCP #79 · Fenêtre de discussion #91 · Changelog
1. Ce qui existe déjà (phase 1, #91)
| Élément | État |
|---|---|
Registre d'outils typé (@tool, Pydantic, risque, scope, rate-limit, audit) |
✅ backend/tools/registry.py |
| Boucle agent multi-tours + confirmation en deux étapes | ✅ backend/agent/loop.py |
| Section « N étapes » Notion-style avec libellés humains + événements SSE en direct | ✅ backend/tools/labels.py |
Recherche web auto-hébergée (web_search via SearXNG) |
✅ backend/tools/web.py |
Lecture d'une page publique (fetch_url, garde SSRF, HTML→texte) |
✅ backend/tools/web.py |
| Outils vault (lecture, recherche, navigation, mutations confirmées) | ✅ backend/tools/service.py (26 outils) |
2. Frameworks d'agents — décision
ObsiGate ne migre pas vers un framework externe : la boucle maison
(run_agent) est déjà testée, typée, auditable et intégrée au flux SSE
(critique pour la section « steps » en direct). Les candidats restent suivis :
| Framework | Point fort | Verdict pour ObsiGate |
|---|---|---|
| Pydantic AI | validation type-safe, cœur léger | inspirant : la validation existe déjà via Pydantic dans registry.call_tool |
| LangChain / LangGraph | écosystème, workflows complexes | dépendance lourde ; à reconsidérer seulement si multi-agents |
| LlamaIndex | RAG clé en main | la recherche locale (TF-IDF + embeddings, #70) couvre déjà le besoin |
| SmolAgents | agents qui écrivent du code | risque d'exécution : hors périmètre |
| LightAgent | ultra-léger, mémoire | redondant avec la mémoire de session déjà fournie |
Règle : tout nouvel outil = @tool dans backend/tools/ + un libellé dans
labels.py + deux clés i18n (ai.step.<key> FR/EN) + un test. Aucune
réécriture de la boucle n'est nécessaire.
3. Phase 2 — catégories à implémenter
Livré (2.10.0, #92). Récapitulatif des décisions finales :
| Catégorie | Décision livrée |
|---|---|
| Recherche web étendue | Tavily, Brave, SerpAPI, Exa à clé (OBSIGATE_*_API_KEY), essayés avant SearXNG ; ordre via OBSIGATE_WEB_PROVIDERS |
| Lecture de pages | fetch_url(render=True) → worker Playwright isolé (backend/tools/webrender.py), dépendance optionnelle + erreur explicite |
| Crawl multi-pages | crawl_site (WRITE + confirmation) : BFS httpx borné (≤ 20 pages, même hôte, SSRF sur chaque URL) → condensé Markdown dans le vault. Scrapy écarté (dépendance lourde inutile à cette échelle) |
| Sources connectées | Gitea + GitHub (git_list_repos, git_search_issues, git_get_file) via env/Infisical ; drives cloud (Drive/OneDrive) orientés serveur MCP externe (#79). #103 (2.11.0) : les clés (URL Gitea, tokens Gitea/GitHub, clés Tavily/Brave/SerpAPI/Exa) se saisissent aussi dans la page Configurations — backend/tools/secrets.py, valeur stockée prioritaire sur l'env |
| Production de documents | create_xlsx, create_docx, create_csv, create_pdf — WRITE + confirmation, écrit via save_raw_file(allow_docs=True) (path safety + backup) |
| Transverse | Cache SQLite (webcache.py, TTL OBSIGATE_WEB_CACHE_TTL), retry backoff maison (OBSIGATE_WEB_RETRY), secrets par env (Infisical-compatible) |
3.1 Recherche web étendue (web_search)
- Fallback sans clé — ✅ livré (BUG-051) : chaîne de fournisseurs dans
backend/tools/web.py— SearXNG auto-hébergé (OBSIGATE_SEARXNG_URL) puis, si aucun résultat, DuckDuckGo (html.duckduckgo.com/html/) puis Bing (www.bing.com/search). Le premier fournisseur non vide est retenu et exposé (provider) ; replis désactivables viaOBSIGATE_WEB_FALLBACK=0. - Fournisseurs optionnels (clé dans Infisical, jamais en dur) : Tavily
(résultats orientés agents), Brave Search API, SerpAPI (Google), Exa.
Interface unifiée type
anysearchpour un sélecteur de fournisseur unique. - Paramètres déjà exposés :
category(general/news/it/science),language,page,max_results.
3.2 Lecture de pages (fetch_url)
- Pages statiques : couvert (httpx + extraction texte maison).
- Pages dynamiques (SPA/React) :
playwright(async) en option, exécuté dans un worker isolé (jamais dans le process web) — images Docker lourdes à prévoir. - Crawl multi-pages :
scrapyuniquement en tâche de fond, jamais déclenché par le modèle sans confirmation (WRITE/confirm). - Alternative légère de parsing :
lxmloubeautifulsoup4si l'extraction maison devient insuffisante (aujourd'hui volontairement sans dépendance).
3.3 Sources connectées (« Searched connected sources »)
Chaque intégration reste derrière le registre (risque, scope, rate-limit, audit) et jamais avec un token en dur :
- Gitea / GitHub :
httpxdirect (déjà utilisé) ougitea-sdk/PyGithub. Priorité haute : ObsiGate est hébergé sur Gitea (issues, PR, commits). - Google Drive / Gmail / Calendar :
google-api-python-client(+PyDrive4pour les tâches simples). OAuth2 viaauthlib. - OneDrive / SharePoint :
onedrive-personal-sdk(async, Microsoft Graph). - Notion / Slack / Jira / Confluence / Salesforce : SDK officiels ou
httpx+ OAuth2 générique. - Voie recommandée : privilégier le serveur MCP externe (#79) pour les services tiers — l'attaque surface reste hors du cœur d'ObsiGate.
3.4 Fichiers et production de documents
- Conversion de formats, tableurs (
openpyxl), documents (python-docx,pypdf), graphiques (matplotlib) : outils WRITE (confirmation obligatoire), exécutés hors requête web si lourd (tâche de fond + SSE). - Pièces jointes : déjà couvert par l'upload vault +
python-multipart.
3.5 Raisonnement interne (« thought », « planned the task »)
- Livré : la note intermédiaire du modèle devient une étape visible.
- Extension possible : exposer les itérations de la boucle (
iterations) comme étapes de planification quand un outil de plan est ajouté. - Garantie de réponse finale — ✅ livré (BUG-052) : à l'épuisement du budget
d'itérations ou du quota d'appels d'outils,
_finalize_answerdéclenche un dernier appel LLM sans outil (instruction de synthèse) ; un repli déterministe liste les sources si cet appel échoue. Une recherche web ne peut plus se terminer sur une conversation sans texte.
4. Transverse — à faire avec la phase 2
| Sujet | Bibliothèque / approche | Où |
|---|---|---|
| Réessais avec backoff | tenacity (ou boucle maison) |
appels réseau des outils |
| Cache des résultats web | table SQLite dédiée + TTL (OBSIGATE_WEB_CACHE_TTL) |
backend/tools/web.py |
| Rate limiting | déjà en place (backend/tools/ratelimit.py) |
registre |
| Secrets | Infisical / variables d'environnement | jamais en dur |
| Async | httpx (async) pour ne pas bloquer la boucle |
nouveaux outils réseau |
| Fallback | chaque outil externe doit échouer proprement (ToolError + message) |
registre |
| Observabilité | audit déjà en place (backend/tools/audit.py) |
registre |
5. Points d'attention
- Sécurité d'abord : tout outil réseau passe par la garde SSRF (
_assert_public_http_url), des limites de taille, et n'est jamais exposé sans rate-limit. - Coût : chaque outil supplémentaire augmente le prompt système (schémas).
Mesurer (
/api/ai/status) et exposer les outils par lots si besoin. - Testabilité : les outils réseau se testent avec
httpxmocké (voirtests/test_web_tools.py), jamais contre Internet en CI. - Une seule source d'affichage : ne pas dupliquer les étapes côté frontend —
le libellé vient du backend (
labels.py), la traduction du locale.