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+
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_argsde WordPress
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.
| 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 |
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é.
| # | 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 messageuser, 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. Lesystemreste stable d'un tour à l'autre (donc cacheable), ce qui valide la stratégie « rolling ».
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 simplementcache_control), le plugin reste conservateur côté bas en cas de doute.
cd wp-content/plugins/
git clone https://github.com/PaulArgoud/mxchat-promptcache.git
wp plugin activate mxchat-promptcacheOu : 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 statsUne 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.
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);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 --totalExemple 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
...
- 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 : aucuncache_controlnatif, 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. Injectercache_controlne 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 filtremxchat_system_instructionsetdo_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 statssignale ce cas (« Écritures de cache > lectures »). - Idempotence. Si une future version de MXChat ajoute nativement des
cache_controlsur l'historique, le plugin détecte leur présence et ne touche pas aux messages (mais continue à cachertoolsetsystem).
- 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.phpsupprime toutes les données persistées — l'optionmxchat_pc_store(et les anciennes clésmxchat_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.
- 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)
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.potVoir CHANGELOG.md.
Paul Argoud