Skip to content

PaulArgoud/mxchat-promptcache

Repository files navigation

MXChat Prompt Cache

Latest release License: GPL-2.0+ PHP WordPress CI

Plugin WordPress qui active automatiquement le prompt caching Anthropic sur les appels API du plugin MXChat Basic, sans modifier ses fichiers.

Stable tag : 0.8.0 — PHP 7.4+ — WordPress 5.8+

Pourquoi

MXChat Basic envoie à chaque requête un gros prompt system et l'historique complet de la conversation à l'API Anthropic. Sans cache, chaque token est facturé au plein tarif et la latence first-token augmente avec la longueur du contexte.

Le prompt caching d'Anthropic réutilise les préfixes identiques entre requêtes :

  • ~90 % de réduction du coût des tokens d'entrée cachés (~0.1× le tarif d'input)
  • TTFT sensiblement réduit quand le préfixe vient du cache
  • Zéro modification de MXChat — tout passe par le filtre http_request_args de WordPress

Prérequis MXChat (important)

Sur MXChat Basic 3.2.8, le chat principal utilise curl_exec directement pour le streaming (fonction mxchat_generate_response_claude_stream, curl_exec ligne 8628 de includes/class-mxchat-integrator.php) et bypasse l'API HTTP WordPress. Ce code path n'est pas interceptable par http_request_args, donc le plugin ne peut rien faire dessus tant que le streaming est actif.

Action requise : désactiver le mode streaming dans les réglages MXChat (MxChat → Settings). Le chat tombe alors sur la branche fallback mxchat_generate_response_claude (ligne 9286), qui utilise wp_remote_post et est pleinement intercepté.

Trade-off : perte de l'effet "machine à écrire" côté UX vs. ~85-95 % de réduction du coût input une fois le cache chaud (typiquement 3-5 requêtes). Mesuré en conditions réelles sur Haiku 4.5 : 49 % de hit rate dès la 3e requête, convergence vers 90 %+ ensuite.

Une issue/PR upstream serait envisageable pour exposer un filtre mxchat_pre_claude_stream_payload juste avant le curl_setopt(CURLOPT_POSTFIELDS, ...) ligne 8547 — ce qui permettrait de cacher même avec streaming ON. Patch de quelques lignes côté MXChat.

Ce qui est cacheable même avec streaming ON

Source Méthode Cacheable ?
Chat principal (streaming) curl_exec ✗ bypasse WP HTTP API
Fallback non-streamé wp_remote_post
Content Generator (admin) wp_remote_post
Test "Test API" admin wp_remote_post ✓ (one-shot)
Intent classification wp_remote_post ✓ techniquement, mais prompt trop court → sous le seuil min

Fonctionnement

Le plugin hooke deux filtres WordPress :

Filtre Rôle
http_request_args Injection — détecte les requêtes POST vers api.anthropic.com/v1/messages, injecte les cache_control et le header beta TTL 1h. Anthropic uniquement.
http_response Mesure — lit les tokens cachés sur tous les providers supportés (Anthropic + cache automatique OpenAI / OpenRouter / xAI / DeepSeek / Gemini) et accumule les stats par modèle

Injection vs mesure. Seul Anthropic exige (et accepte) des cache_control explicites : c'est le seul provider où le plugin injecte. OpenAI, OpenRouter, xAI, DeepSeek et Gemini mettent en cache automatiquement côté serveur ; le plugin ne touche pas à leurs requêtes mais mesure les tokens cachés qu'ils renvoient, pour offrir un tableau de bord d'économies unifié.

Stratégie de breakpoints (4 max imposés par Anthropic)

# Cible Rôle Conditions
1 Dernier outil de tools[] Cache les définitions d'outils (très stable) tools présents + taille >= seuil
2 Dernier bloc de system Cache le prompt système Taille >= seuil
3 Avant-dernier message user Point de lecture au tour suivant >= 3 messages dans l'historique
4 Dernier message user Point d'écriture du cache courant idem

Le couple (3, 4) est "rolling" : le breakpoint d'écriture du tour N devient le breakpoint de lecture du tour N+1. Le hit rate reste stable même quand la conversation s'allonge.

Note (vérifié dans MXChat Basic 3.2.8) : le chat n'envoie pas de tools[] à Anthropic — le RAG est injecté comme dernier message user, pas via tool-calling. Le breakpoint #1 est donc défensif : il ne se déclenchera que si un add-on (ex. MCP) ajoute des outils. Le system reste stable d'un tour à l'autre (donc cacheable), ce qui valide la stratégie « rolling ».

Seuils minimum par modèle

Anthropic ignore silencieusement cache_control quand le préfixe est trop court. Le minimum dépend du modèle ET de sa version (ce n'est pas constant par famille). Le plugin lit le champ model de chaque requête et applique le seuil approprié (source : docs Anthropic « prompt caching », vérifié 2026-06-09) :

Modèle Tokens min ~ Caractères
Fable 5 / Mythos 5 512 2 050
Opus 4.8 / 4.1 / 4.0 1 024 4 000
Sonnet 4.6 / 4.5 / 4 / 3.x 1 024 4 000
Opus 4.7 / Mythos Preview / Haiku 3.x 2 048 8 200
Opus 4.6 / 4.5 / Haiku 4.5 4 096 16 400
Modèle inconnu (défaut) 4 096 16 400

⚠️ Le minimum change par version : Opus 4.8 = 1 024 mais Opus 4.6 = 4 096 ; Sonnet 4.6 = 1 024. La taille est mesurée en caractères (mb_strlen, pas en octets) pour rester cohérent en UTF-8 multioctet (arabe/hébreu RTL, CJK). Les modèles récents (Opus 4.7+, Fable, Mythos) utilisent un tokenizer plus dense → le seuil en caractères est légèrement conservateur. Un seuil trop bas étant sans surcoût (Anthropic ignore simplement cache_control), le plugin reste conservateur côté bas en cas de doute.

Installation

cd wp-content/plugins/
git clone https://github.com/PaulArgoud/mxchat-promptcache.git
wp plugin activate mxchat-promptcache

Ou : déposer mxchat-promptcache.php dans wp-content/plugins/mxchat-promptcache/ et activer depuis l'admin WordPress.

Aucune configuration requise. Vérifier après quelques requêtes :

wp mxchat-pc stats

Configuration

Une seule constante, à définir dans wp-config.php avant le chargement du plugin :

// Désactive la TTL 1h (utilise la TTL standard 5min, pas de header beta)
define('MXCHAT_PC_EXTENDED_TTL', false);

À utiliser si ton compte Anthropic rejette le header beta extended-cache-ttl-2025-04-11, ou pour des raisons d'économie : la prime d'écriture est de ×2 en TTL 1 h contre ×1,25 en 5 min. La TTL 1 h (défaut) protège les conversations avec pauses > 5 min (évite de réécrire tout l'historique au retour de l'utilisateur) ; pour du chat très rapide (tours < 5 min), la TTL 5 min suffit et coûte moins cher. Réglage fin possible par requête via le filtre mxchat_pc_ephemeral_control.

Filters d'extensibilité

Quatre hooks WordPress permettent d'ajuster le comportement sans forker :

// Désactiver l'injection pour une requête spécifique
add_filter('mxchat_pc_should_inject', function ($should, $payload, $args, $url) {
    if (($payload['metadata']['user_id'] ?? '') === 'no-cache-user') {
        return false;
    }
    return $should;
}, 10, 4);

// Override du seuil minimum (par modèle ou par requête)
add_filter('mxchat_pc_min_chars', function ($min, $model, $payload) {
    return $model === 'claude-haiku-4-5' ? 12000 : $min;
}, 10, 3);

// Override de la valeur cache_control (ex: TTL custom)
add_filter('mxchat_pc_ephemeral_control', function ($control) {
    return ['type' => 'ephemeral']; // force TTL 5min
});

// Nombre min de messages avant de cacher l'historique (défaut 3).
// Baisser à 1 pour cacher dès le 1er tour (sessions longues) ; garder >= 3
// pour ne pas payer la prime d'écriture sur les chats one-shot.
add_filter('mxchat_pc_min_messages', function ($min, $payload) {
    return 1;
}, 10, 2);

Utilisation (WP-CLI)

Les stats couvrent tous les providers supportés (mesure), pas seulement Anthropic. La fenêtre « 24 h » est une vraie fenêtre glissante (buckets horaires, recalculés à chaque lecture — pas un compteur figé). --by-model ventile par modèle (sur les 24 h et sur le cumulatif avec --total), et un avertissement s'affiche si le cache est écrit mais peu relu (préfixe instable / trafic faible).

Pas de terminal ? Un widget de tableau de bord WordPress (Admin → Tableau de bord) affiche le taux de hit et les tokens économisés sur 24 h glissantes + depuis l'installation, pour les utilisateurs manage_options.

Lecture du taux de hit : il est pondéré par les tokens (lus du cache / total input), pas par requête. Les petits appels utilitaires non cachables (ex. classification d'intention, max_tokens: 20) ne le diluent donc que marginalement (quelques points). C'est volontaire : tenter de les exclure depuis la réponse serait une heuristique fragile, et la métrique pondérée est déjà robuste.

# Hit rate global sur 24h glissantes (tous providers)
wp mxchat-pc stats

# Ventilation par modèle Anthropic
wp mxchat-pc stats --by-model

# Cumulatif depuis l'installation + 24h glissantes
wp mxchat-pc stats --total

# Détails de la dernière requête (breakpoints, modèle, usage)
wp mxchat-pc debug

# Reset des compteurs 24h + debug
wp mxchat-pc reset

# Reset complet (24h + cumulatif + debug)
wp mxchat-pc reset --total

Exemple de sortie stats --total --by-model :

[Cumulatif (depuis installation)]
  Requêtes              : 12480
  Tokens lus du cache   : 98 421 100
  Tokens écrits cache   : 4 312 800
  Tokens entrée bruts   : 8 920 400
  Taux de hit (cache)   : 88.0 %
  Depuis                : 2026-04-01 10:00:00

--- Détail par modèle (cumulatif) ---

[claude-sonnet-4-6]
  Requêtes              : 9 020
  ...

[Dernières 24 h (glissant)]
  Requêtes              : 142
  Tokens lus du cache   : 1 245 300
  ...

--- Détail par modèle (24 h) ---

[claude-sonnet-4-6]
  Requêtes              : 98
  ...

Limites connues

  • Streaming MXChat non interceptable. Voir la section Prérequis MXChat ci-dessus. Solution actuelle : désactiver le streaming. Solution propre : PR upstream pour exposer un filtre avant le curl_setopt (confirmé toujours absent sur MXChat 3.2.8 : aucun cache_control natif, aucun filtre sur le corps de requête).
  • OpenRouter : injection impossible par construction. Sur le chemin OpenRouter, MXChat concatène le contexte RAG dans le message system ($system_prompt_instructions . " " . $relevant_content). Le contenu volatil se retrouve en tête de préfixe, ce qui défait le prefix-match du cache. Injecter cache_control ne ferait que payer des écritures jamais relues — le plugin mesure donc OpenRouter (cache automatique) mais n'y injecte rien.
  • Mesure multi-provider : non-streamé uniquement. Les stats des providers à cache automatique (OpenAI / OpenRouter / xAI / DeepSeek / Gemini) ne couvrent que les réponses non-streamées (wp_remote_post). Leurs réponses streamées (cURL) ne sont pas vues.
  • Compteurs « best-effort » sous forte concurrence. Les stats sont accumulées par read-modify-write (transient + option) à chaque réponse, sans verrou ; des requêtes simultanées peuvent provoquer un léger sous-comptage (stat uniquement, sans impact sur le cache). Un object cache persistant garde ces écritures en mémoire plutôt qu'en base.
  • Stabilité de l'historique. Les breakpoints sur les messages supposent que MXChat envoie un historique stable d'un appel à l'autre. Reformater ou tronquer l'historique entre les tours invalide le cache.
  • System prompt dynamique. get_system_instructions() applique {visitor_name}, le filtre mxchat_system_instructions et do_shortcode(). Un shortcode ou un filtre injectant une donnée volatile (heure, stock…) dans le system prompt invalide le cache à chaque requête. wp mxchat-pc stats signale ce cas (« Écritures de cache > lectures »).
  • Idempotence. Si une future version de MXChat ajoute nativement des cache_control sur l'historique, le plugin détecte leur présence et ne touche pas aux messages (mais continue à cacher tools et system).

Désinstallation

  • Désactivation : sans effet de bord — le plugin n'enregistre que des filtres WordPress, qui cessent simplement d'être appliqués. Les statistiques sont conservées (comportement WordPress attendu : la désactivation ne supprime pas les données).
  • Suppression : uninstall.php supprime toutes les données persistées — l'option mxchat_pc_store (et les anciennes clés mxchat_pc_stats_total / transients pour les installs mises à jour), avec prise en charge multisite. Aucune table custom, aucun cron, aucun fichier sur disque : rien d'orphelin après désinstallation.

Compatibilité

  • WordPress 5.8+
  • PHP 7.4 → 8.3 (CI testée sur 7.4 / 8.0 / 8.1 / 8.2 / 8.3)
  • MXChat Basic (toutes versions — le plugin n'introspecte pas MXChat)
  • Tous les modèles Anthropic supportant le prompt caching (GA sur l'ensemble du catalogue actuel)

Internationalisation

Le plugin est prêt pour la traduction (Text Domain: mxchat-promptcache, Domain Path: /languages). Les chaînes user-facing (WP-CLI) sont wrappées dans __(). Le fichier .pot peut être généré via :

wp i18n make-pot . languages/mxchat-promptcache.pot

Changelog

Voir CHANGELOG.md.

Licence

GPL-2.0-or-later

Auteur

Paul Argoud

About

Plugin WordPress qui active le prompt caching Anthropic sur les appels API de MXChat Basic, sans modifier ses fichiers.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages