ToolOps : le service mesh des outils IA

En une phrase

ToolOps est aux outils IA ce qu'un service mesh est aux microservices : un SDK de middleware indépendant de tout framework, qui dote n'importe quelle fonction Python de cache, de résilience et d'observabilité — sans toucher à votre logique métier.

Quand vous construisez des agents IA, chaque appel externe — vers un LLM, une API, une base de données — est un appel d'outil. En production, ces appels sont coûteux, instables et lents. ToolOps se place entre votre agent et ses outils comme un service mesh se place entre des microservices, et absorbe cette complexité derrière une interface à décorateurs, choisie délibérément pour offrir la plus fine surface d'intégration possible.

1. Philosophie

L'analogie qui décrit le mieux ToolOps est celle du service mesh. De la même manière qu'un service mesh — pensez à Istio ou Linkerd — s'intercale entre les microservices pour gérer de façon transparente les réessais, les délais d'attente et l'ouverture des disjoncteurs, ToolOps s'intercale entre votre agent IA et ses outils. Le code applicatif ignore tout de la couche d'infrastructure qui le porte.

ToolOps est aux outils IA ce qu'un service mesh est aux microservices.
Le postulat de conception

Cette séparation des responsabilités est délibérée. L'auteur d'un outil doit se concentrer sur ce que fait cet outil, pas sur les complexités de systèmes distribués qu'impose son appel fiable à l'échelle. ToolOps absorbe cette complexité derrière l'interface à décorateurs.

2. Le mur de la production

Tout développeur d'agents heurte le même mur au passage de la démo à la production. Les symptômes sont prévisibles : des factures d'API qui montent plus vite que l'usage, des agents qui tombent au troisième réessai, des files de requêtes qui s'engorgent à la moindre concurrence réelle, et des workflows totalement opaques quand ils échouent. ToolOps traite chacun de ces goulots au niveau de la couche d'infrastructure.

Problème → impact métier → avec ToolOps

  • Appels d'API redondants. Coûts multipliés par 10 → 100 appels deviennent 1 appel réel et 99 succès de cache.
  • Requêtes proches formulées autrement. Tokens LLM gaspillés → la correspondance sémantique renvoie le même résultat en cache.
  • Instabilité des API. Agents qui tombent et boucles de réessai → disjoncteur et réessai automatique.
  • Pics de concurrence. Ruée simultanée → la coalescence des requêtes la réduit à un seul appel réel.
  • Exposition de données sensibles. Jetons et données personnelles dans les clés de cache ou les logs → hachage SHA-256 et masquage automatique.
  • Aucune observabilité. Exploitation à l'aveugle → logs JSON structurés et traces OpenTelemetry.

Cache sémantique

Les appels quasi identiques sont servis par le cache — l'agent qui reformule une requête cesse de coûter de l'argent.

Disjoncteurs

Une dépendance défaillante fait ouvrir le circuit et se dégrade proprement au lieu de bloquer toute l'exécution.

Coalescence des requêtes

Les appels concurrents au même outil fusionnent en une seule requête en amont, diffusée à tous les appelants.

Observabilité

Chaque appel émet une télémétrie structurée : succès, échecs, réessais, changements d'état du disjoncteur.

3. Installation

ToolOps est disponible sur PyPI. Depuis la v1.0.0, le paquet est livré tout compris : une seule installation embarque tous les backends de cache — Memory, File, SQLite, Postgres, MySQL/MariaDB, Valkey/Redis, Semantic — ainsi que les pilotes de bases de données, les bibliothèques d'embeddings et d'intégration OpenAI, et la prise en charge de l'observabilité OpenTelemetry/Prometheus. Aucun extra optionnel n'est nécessaire.

Installer et vérifier Shell
# One command installs everything — backends, drivers, telemetry
pip install toolops

# Verify the installation
toolops doctor
Windows Shell
:: One command installs everything
pip install toolops

:: Using the launcher
py -m pip install toolops

Il est fortement recommandé d'installer ToolOps dans un environnement virtuel afin d'éviter les conflits de dépendances.

Mise en place d'un environnement virtuel Shell
# Create and activate (.venv)
python -m venv .venv
source .venv/bin/activate  # Linux/macOS
# .venv\Scripts\Activate.ps1 # Windows (PowerShell)

# Install and verify
pip install toolops
toolops doctor

Les anciens extras comme toolops[postgres], toolops[semantic] ou toolops[all] restent disponibles sous forme d'alias vides de compatibilité, afin que les scripts de CI et de déploiement existants continuent de fonctionner — les nouvelles installations utiliseront simplement pip install --upgrade toolops.

Les contributeurs ont besoin de deux étapes supplémentaires : un environnement Docker couvrant toute la matrice de backends, et les cibles Make normalisées qui encapsulent les outils de test, de lint et de formatage du projet.

Environnement de développement Docker Contributeurs
# One-command setup with PostgreSQL
docker-compose up -d
docker-compose exec toolops make test
Makefile — commandes normalisées Contributeurs
make test      # Run full test suite with coverage
make lint      # Ruff + Black checks
make format    # Auto-format with Black
make typecheck # mypy strict mode
make coverage  # HTML coverage report
make clean     # Remove all build artifacts

Depuis la v1.0.0, pip install toolops constitue le contrat d'installation complet : le jeu de dépendances par défaut inclut les backends de production pris en charge et les bibliothèques d'intégration, si bien que le cache persistant et le traçage distribué fonctionnent dès le premier jour. Les contributeurs installent depuis requirements.txt, qui ajoute le paquet en mode éditable et la chaîne d'outils de développement.

4. L'architecture

L'architecture compte trois couches : une interface à décorateurs qui se pose sur vos fonctions outils, un pipeline de middlewares composables qui orchestre la résilience et l'observabilité, et un système de backends interchangeables qui gère le stockage et les embeddings. Les trois couches sont entièrement découplées — vous pouvez changer de backend ou réordonner les middlewares sans modifier une seule ligne du code de vos outils.

Un décorateur, pas une réécriture

  • Async / await. Nativement pris en charge, là où @lru_cache ne l'est pas.
  • Cache sémantique. Embeddings vectoriels, là où @lru_cache n'apparie que des clés exactes.
  • Cache distribué. Postgres, SQLite, MySQL, Valkey/Redis, là où @lru_cache ne vit qu'en mémoire.
  • Disjoncteur. Intégré, avec temporisation exponentielle, là où @lru_cache n'en a aucun.
  • Coalescence des requêtes. Diffuse le résultat à tous, là où @lru_cache laisse passer la ruée.
  • Repli stale-if-error. Sert la dernière valeur valide, là où @lru_cache lève une exception.
  • Sécurité. Clés SHA-256 et masquage automatique, nativement — là où @lru_cache n'offre rien.
  • Observabilité. Télémétrie structurée OpenTelemetry et Prometheus, là où @lru_cache n'offre rien.
  • Pensé pour l'IA. Intégrations MCP et frameworks natives, là où @lru_cache reste générique.

4.1 Les décorateurs

ToolOps fournit deux décorateurs qui correspondent proprement aux deux catégories d'opérations d'un outil. La distinction entre lecture et écriture est un concept de premier plan : les lectures sont idempotentes, donc sûres à mettre en cache et à réessayer ; les écritures ne le sont pas — elles reçoivent uniquement les motifs de résilience, jamais de réessai automatique qui pourrait provoquer une double soumission.

Décorateurs — lecture ou écriture Python
from toolops import readonly, cache_manager
from toolops.cache import MemoryCache

cache_manager.register("memory", MemoryCache(), is_default=True)

@readonly(
    cache_backend="memory", cache_ttl=3600,
    retry_count=3, sensitive_params=["api_key", "auth_token"]
)
async def get_market_data(ticker: str, api_key: str) -> dict:
    return await api.fetch(ticker, api_key=api_key)
# Cached, retried, traced — api_key excluded from the cache key
# and masked in logs.

@sideeffect(circuit_breaker=True, timeout=5.0, retry_count=2)
async def execute_trade(order: dict) -> bool:
    return await broker.submit(order)
# No caching — protected by circuit breaker and timeout only.

4.2 Les backends de cache

Enregistrez les backends une fois au démarrage de l'application, puis référencez-les par leur nom dans tous vos décorateurs. Plusieurs backends peuvent coexister — une couche mémoire rapide pour les données chaudes, une couche SQL persistante pour la piste d'audit, une couche Valkey/Redis distribuée pour les déploiements multi-processus, et une couche sémantique pour les charges de TAL. Depuis la v1.0.0, tous les backends ci-dessous sont installés par défaut et partagent des règles de cycle de vie identiques : appeler une opération sur un backend fermé lève un RuntimeError explicite, plutôt que de se reconnecter silencieusement ou d'échouer sur un AttributeError — le même contrat pour Memory, File, SQLite, Postgres, MySQL, Valkey/Redis et Semantic.

Enregistrer les backends Python
from toolops import cache_manager
from toolops.cache import (
    MemoryCache, SQLiteCache, PostgresCache, ValkeyCache, MySQLCache
)

cache_manager.register("memory", MemoryCache(), is_default=True)
cache_manager.register("sqlite", SQLiteCache("toolops_cache.db"))
cache_manager.register("db", PostgresCache("postgresql://user:pass@localhost:5432/mydb"))
cache_manager.register("valkey", ValkeyCache(host="localhost", port=6379))
cache_manager.register("mysql", MySQLCache(dsn="mysql://root:secret@localhost:3306/myapp"))

Sept backends, un seul cycle de vie

  • MemoryCache. Dans le processus — développement, tests, déploiements mono-processus.
  • FileCache. Persistant, sur le système de fichiers — persistance locale légère, sans base de données.
  • SQLiteCache. Persistant, un seul fichier — persistance sans serveur via aiosqlite, avec invalidation par tags.
  • PostgresCache. SQL persistant — piste d'audit complète, partageable entre processus.
  • MySQLCache. SQL persistant — MySQL 8+ et MariaDB 10.5+ via aiomysql, connexion par DSN.
  • ValkeyCache / RedisCache. Distribué, en mémoire — pool de connexions asynchrone complet.
  • SemanticCache. Embeddings vectoriels — appariement par intention pour les pipelines de TAL et de RAG.

4.3 Le pipeline de middlewares

Le décorateur que vous appelez n'est qu'une fine enveloppe : en interne, ToolOps a été refondu en un pipeline composable de middlewares indépendants, chacun responsable d'une seule préoccupation, orchestrés en séquence par un ToolExecutor. Un ToolContext partagé transporte l'état mutable tout au long du pipeline — et le comportement des décorateurs est préservé à 100 %, si bien que le code existant qui utilise @tool, @readonly, @sideeffect ou @stateful en bénéficie sans aucune modification.

Le pipeline par défaut Python
from toolops.middlewares import build_executor, DEFAULT_PIPELINE

executor = build_executor(pipeline=DEFAULT_PIPELINE)
# → [LoggingMiddleware, CacheMiddleware, CircuitBreakerMiddleware,
#    RetryMiddleware, CoalescingMiddleware, FallbackMiddleware]

LoggingMiddleware

Journalisation JSON structurée pour chaque appel d'outil.

CacheMiddleware

Consultation du cache, stale-if-error, écriture en cache.

CircuitBreakerMiddleware

Protection par disjoncteur.

RetryMiddleware

Boucle de réessai avec temporisation exponentielle.

CoalescingMiddleware

Coalescence des requêtes — déduplication des appels concurrents.

FallbackMiddleware

Exécution de repli en cas d'échec.

5. Motifs de résilience

Au-delà du simple bloc try/except, ToolOps met en œuvre trois motifs déterministes empruntés à l'ingénierie des systèmes distribués. Ensemble, ils garantissent qu'un agent ne se retrouve jamais piégé dans une boucle d'échec, n'épuise jamais son budget d'API sur un service dégradé, et ne sert jamais de données périmées quand le service en amont fonctionne.

5.1 Le disjoncteur

Il coupe tous les appels vers un service défaillant au-delà d'un seuil d'échecs configurable. Une fois ouvert, le circuit échoue immédiatement — il rend la main aussitôt au lieu d'attendre l'expiration d'un délai — puis entre dans une fenêtre de rétablissement avant de tenter de rouvrir la connexion. Cela empêche la défaillance d'un seul outil de se propager en panne complète de l'agent.

Disjoncteur Python
@readonly(
    circuit_breaker=True,
    circuit_failure_threshold=5,   # opens after 5 consecutive failures
    circuit_recovery_timeout=60    # retries after 60 seconds
)
async def get_exchange_rates() -> dict:
    return await forex_api.fetch()

5.2 Le repli stale-if-error

Quand un service en amont échoue et qu'aucune donnée fraîche ne peut être récupérée, ToolOps peut se replier automatiquement sur la dernière valeur valide connue du cache — même au-delà de son TTL normal. L'équivalent, en production, de « servir quelque chose d'utile plutôt que de tomber ».

Stale-if-error Python
@readonly(
    cache_ttl=3600,
    stale_if_error=True,
    stale_ttl=86400   # serve stale data for up to 24h on failure
)
async def get_exchange_rates() -> dict:
    return await forex_api.fetch()

5.3 La coalescence des requêtes

Quand plusieurs instances d'agent appellent le même outil simultanément — un schéma courant dans les pipelines multi-agents — ToolOps détecte la requête déjà en vol et met les appelants suivants en attente jusqu'à ce que la première aboutisse. L'unique résultat réel est ensuite diffusé à tous les appelants en attente.

50 → 1

Appels concurrents fusionnés

98%

De consommation de crédits en moins

0

Modification des agents appelants

Sur un banc d'essai comptant 50 appels d'agents concurrents vers le même outil météo, la coalescence des requêtes a ramené les appels d'API en amont de 50 à 1.

6. Le cache sémantique

Les caches traditionnels fonctionnent par égalité stricte des clés. Cela convient aux systèmes déterministes, mais un agent n'en est pas un — une même intention utilisateur ressort sous des dizaines de formulations différentes. ToolOps s'appuie sur des embeddings vectoriels pour saisir le sens d'un appel d'outil, et pas seulement ses arguments littéraux.

Même intention, mots différents Exemple
# Call 1 — cache miss, real API call
query: "What is the status of invoice #442?"

# Call 2 — semantic similarity 0.97 → cache hit
query: "Check the current status for invoice 442"

# Call 3 — semantic similarity 0.94 → cache hit
query: "Invoice 442 — is it paid?"
Configurer le cache sémantique Python
from toolops.cache import SemanticCache, SentenceTransformerEmbedder

embedder = SentenceTransformerEmbedder("all-MiniLM-L6-v2")
semantic = SemanticCache(embedder=embedder, threshold=0.92)
cache_manager.register("semantic", semantic)

@readonly(cache_backend="semantic")
async def ask_agent(query: str) -> str:
    return await llm.complete(query)
# Reduces LLM latency by up to 90% on repeated intent patterns.

Le seuil de similarité — 0,92 ci-dessus — est le principal levier de réglage. Une valeur élevée exige un alignement sémantique plus étroit avant de déclarer un succès ; une valeur basse est plus agressive. La bonne valeur dépend du degré de variation acceptable dans le domaine d'entrée de votre outil : une recherche factuelle tolère un seuil plus élevé qu'une tâche de génération créative.

Note de performance

En v0.2.0, l'éviction de SemanticCache est passée d'opérations de liste en O(n) à un collections.deque en O(1) — garantissant une latence et une empreinte mémoire prévisibles en forte concurrence.

7. Observabilité

Déboguer des workflows d'agents non déterministes exige une instrumentation plus profonde que la journalisation applicative. ToolOps émet une télémétrie structurée à chaque étape du cycle de vie d'un outil — succès, échecs, réessais, changements d'état du disjoncteur — offrant une piste d'audit complète sans instrumentation manuelle.

Traçage et métriques Python
from toolops import configure_opentelemetry, prometheus_metrics

# Configure OpenTelemetry tracing — accepts any standard tracer instance
configure_opentelemetry(tracer)

# Expose Prometheus metrics as a raw text string
metrics_string = prometheus_metrics()

Logs structurés

Chaque succès de cache, échec, défaillance et réessai est émis en JSON exploitable par une machine. Les champs comprennent le nom de l'outil, le backend, la latence, la clé de cache et l'issue — prêts à être ingérés par n'importe quel agrégateur de logs.

OpenTelemetry

Des traces et spans OTEL natifs enveloppent chaque exécution d'outil. Passez n'importe quel tracer standard à configure_opentelemetry(tracer) et visualisez le graphe d'appels complet dans Jaeger, Honeycomb ou Datadog.

Métriques Prometheus

Des jauges en temps réel pour le taux de succès du cache, l'état du circuit (fermé / ouvert / semi-ouvert) et les percentiles de latence des outils. Parmi les métriques clés : toolops_cache_hits_total, toolops_tool_latency_seconds et toolops_circuit_opens_total — prêtes à alimenter règles d'alerte et tableaux de bord.

8. Écosystème et MCP

Les outils ToolOps sont de simples fonctions Python. Ce choix de conception n'a rien d'accidentel — il signifie qu'ils fonctionnent nativement avec tout framework d'agents acceptant des appelables Python, sans code d'adaptation ni configuration propre au framework.

Les intégrations, toutes disponibles aujourd'hui

  • LangChain / LangGraph. Assistant intégré.
  • CrewAI. Assistant intégré.
  • LlamaIndex. Compatibilité générale.
  • Model Context Protocol. Assistant intégré.
  • PydanticAI. Compatibilité générale.
  • AutoGPT et frameworks maison. Tout appelable Python.

L'intégration MCP mérite une mention particulière : un adaptateur intégré expose n'importe quel outil décoré sous forme de définition compatible MCP, sans écrire une ligne de JSON Schema — un outil résilient, de qualité production, devient ainsi immédiatement accessible à Claude Desktop, Cursor ou tout hôte compatible MCP.

Exposer un outil via MCP Python
from toolops.integrations.mcp import MCPIntegration

# get_weather is already decorated with @readonly
definition = MCPIntegration.to_mcp_definition(get_weather)
# → MCP-compatible tool definition, ready for Claude Desktop or Cursor.

9. CLI et exploitation

ToolOps est livré avec un outil en ligne de commande pour inspecter et gérer l'infrastructure des outils en production — conçu pour les équipes d'exploitation et les pipelines de CI, pas seulement pour les développeurs.

Exploiter une application en fonctionnement Shell
# List all available commands
toolops --help

# Check system health and backend readiness
toolops doctor

# View real-time cache statistics
toolops stats --app my_app:setup_toolops

# Print current metrics
toolops metrics --app my_app:setup_toolops

# Inspect a specific cache key
toolops inspect-key --app my_app:setup_toolops

# Clear a specific cache backend
toolops clear postgres --app my_app:setup_toolops

toolops doctor est particulièrement utile dans les pipelines de déploiement : il valide la connectivité des backends, vérifie la disponibilité du modèle d'embeddings et rapporte l'état des disjoncteurs — un contrôle de disponibilité que vous pouvez brancher directement sur votre endpoint de santé.

10. Feuille de route

La v1.0.0 marque la version stable et tout comprise : pipeline de middlewares composable, sécurité durcie, cycle de vie unifié sur tous les backends de cache, et installation en une commande sans aucun extra. Les éléments ci-dessous sont prévus pour les prochaines versions, par ordre de livraison attendue.

La suite

  • Tableau de bord web. Métriques en temps réel, imputation des coûts et taux de succès du cache dans une interface navigateur — sans installer Prometheus ni Grafana.
  • Contrôle budgétaire. Plafonds stricts sur les coûts d'API induits par les outils, à l'heure ou à la journée, configurables par outil et par backend.
  • Serveur MCP natif. Déploiement en un clic des outils ToolOps sous forme d'hôte MCP autonome — sans configuration de Claude Desktop.
  • Middleware de streaming. Prise en charge complète des sorties d'outils en flux dans les pipelines d'agents.
  • Nouveaux backends. Prise en charge de ChromaDB et Pinecone, élargissant les options de cache vectoriel natif. (MariaDB est déjà pris en charge depuis la v1.0.0 via MySQLCache.)

ToolOps est open source sous licence Apache 2.0. Ajoutez une étoile au dépôt, ouvrez une issue ou proposez une pull request — le projet est construit au grand jour, et les priorités de la feuille de route sont dictées par les cas d'usage réels remontés par la communauté.

1

Décorateur à adopter

0

Dépendance à un framework

7

Backends de cache, tout compris

FAQ

Questions fréquentes

ToolOps est indépendant de tout framework. Là où LangChain ou CrewAI proposent une logique de réessai basique, ToolOps apporte des motifs de qualité industrielle — disjoncteurs, coalescence des requêtes, cache sémantique — qui fonctionnent sur n'importe quel outil Python, sans aucun coût de migration.

Des agents en production ?

Essayez-le d'abord sur votre outil le plus capricieux. Issues et retours de terrain bienvenus.

Ajouter une étoile sur GitHub

Contact

Un système à construire ?

Si vous montez un pipeline RAG, un workflow d'agents IA ou une intégration MCP et que vous voulez comparer vos notes, échanger des idées, ou simplement éviter les erreurs que j'ai déjà faites — écrivez-moi.

Ou recevez le Playbook par e-mail — un message quand une nouvelle leçon sort, et rien d'autre.

Pas de bruit, pas de spam. Désinscription à tout moment.