-27% sur la facture API. C’est le résultat concret que j’ai obtenu en remplaçant les recherches grep par de la recherche sémantique dans Claude Code.
Dans mon précédent article, j’avançais des arguments théoriques. Cette fois, j’ai voulu prouver ces gains avec une expérience contrôlée sur Excalidraw, un projet open source de 155 000+ lignes de code.
TL;DR
📌 Les résultats :
- -55% d’appels d’outils (139 → 62)
- -97% d’input tokens (51 147 → 1 326)
- -27,5% sur le coût facturé ($6.78 → $4.92)
📌 Le protocole :
- 5 questions identiques sur Excalidraw (155k+ lignes de code)
- Comparaison Claude Code standard vs Claude Code + grepai
- Métriques extraites directement des logs JSON de Claude Code
Le protocole expérimental
Le terrain de test : Excalidraw
J’ai choisi Excalidraw pour plusieurs raisons :
- Projet réel : 155 000+ lignes de TypeScript, pas un benchmark artificiel
- Monorepo complexe : packages multiples (
@excalidraw/element,@excalidraw/math, etc.) - Open source : n’importe qui peut vérifier
Les 5 questions du benchmark
J’ai formulé 5 questions qu’un développeur pourrait naturellement poser en découvrant une codebase. Chaque question décrit une intention (ce qu’on veut comprendre), pas un nom de fonction qu’on connaîtrait déjà.
| # | Question (EN) |
|---|---|
| 1 | ”Locate the exact mathematical function used to determine if a user’s cursor is hovering inside a ‘diamond’ shape.” |
| 2 | ”Explain how the application calculates the intersection point when an arrow is attached to an ellipse.” |
| 3 | ”Find the algorithm responsible for simplifying or smoothing the points of a ‘freedraw’ line after the user releases the mouse.” |
| 4 | ”Identify the code responsible for snapping dragged elements to the grid.” |
| 5 | ”How does the codebase handle sending an element ‘backward’ in the z-order?” |
Conditions de test
Deux clones du même projet, dans deux répertoires séparés :
Session 1 - Baseline (sans grepai) :
git clone https://github.com/excalidraw/excalidraw.git excalidraw_raw
cd excalidraw_raw
claude # Session fraîche
Session 2 - Avec grepai :
git clone https://github.com/excalidraw/excalidraw.git excalidraw_grepai
cd excalidraw_grepai
grepai init && grepai watch
grepai agent-setup --with-subagent # Configure CLAUDE.md
claude # Session fraîche
Configuration grepai utilisée : Ollama (modèle
nomic-embed-text) + stockage fichier, sur un MacBook Pro M3 Pro. Voir la documentation grepai pour les options disponibles.
Les 5 questions sont posées dans le même ordre, sans reformulation, dans chaque session.
Les résultats
Tokens facturés par l’API
Voici les métriques directement extraites des logs JSON de Claude Code :
| Métrique | Baseline | grepai | Δ |
|---|---|---|---|
| Subagents lancés | 5 | 0 | -100% |
| Appels d’outils | 139 | 62 | -55% |
input_tokens | 51 147 | 1 326 | -97% |
cache_read_input_tokens | 5 973 161 | 7 775 888 | +30% |
cache_creation_input_tokens | 563 883 | 162 289 | -71% |
output_tokens | 476 | 347 | -27% |
Coût facturé (tarifs Claude Opus 4.5)
| Type de token | Baseline | grepai | Δ |
|---|---|---|---|
input_tokens | $0.26 | $0.01 | -97% |
cache_read_input_tokens | $2.99 | $3.89 | +30% |
cache_creation_input_tokens | $3.52 | $1.01 | -71% |
output_tokens | $0.01 | $0.01 | -27% |
| Total | $6.78 | $4.92 | -27,5% |
💡 En résumé : Pour 5 questions d’exploration sur Excalidraw, grepai a économisé $1.86 (de $6.78 à $4.92). Sur une journée de travail intensive avec des dizaines de questions, les économies peuvent atteindre $10-20/jour.
Note sur les tarifs : Ces prix sont ceux de l’API Anthropic en accès direct. En pratique, je recommande d’utiliser le forfait Max de Claude Code ($100/mois ou $200/mois) pour un usage quotidien. Ce benchmark permet surtout de comprendre les mécanismes sous-jacents.
Comprendre le cache de l’API Claude
Avant d’interpréter ces chiffres, il faut comprendre comment Anthropic facture les tokens. Ce n’est pas aussi simple qu’un tarif unique, et c’est précisément ce mécanisme qui explique pourquoi grepai économise 27% malgré une hausse du cache read.
L’API Claude utilise un système de cache de prompt qui impacte directement la facturation. Voici comment ça fonctionne :
Les 4 types de tokens
| Type | Description | Tarif (Opus 4.5) |
|---|---|---|
input_tokens | Nouveaux tokens à traiter (pas en cache) | $5.00 / M |
cache_read_input_tokens | Tokens déjà en cache, réutilisés | $0.50 / M (90% réduction) |
cache_creation_input_tokens | Tokens mis en cache pour la première fois | $6.25 / M (25% surcoût) |
output_tokens | Tokens générés par Claude | $25.00 / M |
Cache Read vs Cache Creation : pourquoi ces différences ?
cache_creation_input_tokens (-71% avec grepai) :
Chaque fois que Claude Code lance un subagent (via Task), il crée un nouveau contexte. Ce contexte doit être mis en cache → cache_creation.
- Baseline : 5 subagents lancés = 5 nouveaux contextes = 563 883 tokens créés
- grepai : 0 subagent = pas de nouveau contexte = 162 289 tokens créés
C’est la grosse économie : avec grepai, Claude Code n’a pas besoin de lancer des subagents car il trouve directement les bons fichiers.
cache_read_input_tokens (+30% avec grepai) :
Le cache read est le contexte déjà en cache qui est réutilisé à chaque appel. Il inclut :
- Le system prompt de Claude Code (~15k tokens)
- L’historique de la conversation
- Les résultats des outils précédents
grepai a plus de cache read car les résultats de recherche s’accumulent dans le contexte de la conversation. Mais ce n’est pas grave : à $0.50/M contre $5/M pour les input tokens, c’est 10× moins cher.
Pourquoi -97% input tokens mais seulement -27% sur la facture ?
La différence s’explique par le poids du cache read dans le coût total. Même si les input tokens baissent drastiquement, le cache read (facturé 10× moins cher) représente la majorité des tokens consommés dans les deux cas.
Les input_tokens (fresh) représentent le vrai travail de traitement, c’est-à-dire les tokens que Claude voit pour la première fois à chaque appel.
- Baseline : 51 147 tokens (5 subagents × ~10k tokens chacun)
- grepai : 1 326 tokens (pas de subagent, recherches directes)
Sans subagents, Claude n’a pas besoin de “réapprendre” le contexte à chaque exploration.
Pourquoi grepai évite les subagents ?
C’est le mécanisme clé de l’économie.
Sans grepai, Claude Code lance des subagents Explore pour chercher dans le code :
Question → Task(subagent_type: Explore) → Le subagent fait :
├── Grep("diamond") → 47 fichiers
├── Read(fichier1) → pas le bon
├── Grep("hit test") → 12 fichiers
├── Read(fichier2) → intéressant
└── ... (20-40 appels d'outils)
Chaque subagent a son propre contexte = nouveaux tokens à créer et facturer.
Avec grepai, Claude appelle directement la recherche sémantique :
Question → Bash: grepai search "..." → Résultats pertinents
├── Read(fichier pertinent) → confirmation
└── Réponse
Pas de subagent = pas de nouveau contexte = économie.
Les outils utilisés
| Outil | Baseline | grepai |
|---|---|---|
| Bash (dont grepai) | 41 | 9 |
| Grep | 37 | 20 |
| Glob | 13 | 0 |
| Read | 43 | 30 |
| Task (subagents) | 5 | 0 |
grepai réduit drastiquement les appels Glob. Cet outil permet de chercher des fichiers par pattern (ex: **/*.ts, **/diamond*), mais retourne souvent des dizaines de résultats que Claude doit ensuite filtrer en lisant chaque fichier. Avec la recherche sémantique, Claude obtient directement les fichiers pertinents sans cette phase d’exploration.
Le script d’analyse
J’ai développé un script Python pour extraire les métriques directement des logs Claude Code.
Où sont les logs ?
~/.claude/
├── projects/
│ └── <projet-hash>/
│ ├── <session-uuid>.jsonl # Log principal
│ └── <session-uuid>/
│ └── subagents/ # Logs des subagents
│ ├── agent-xxx.jsonl
│ └── agent-yyy.jsonl
Point critique : les subagents ont leurs propres logs. Le script les inclut automatiquement dans l’analyse.
Le script complet
Voir le script Python (benchmark.py)
#!/usr/bin/env python3
"""
Grepai Benchmark - Claude Code Session Analyzer
Usage:
python3 benchmark.py <UUID_BASELINE> <UUID_GREPAI>
Analyzes billed tokens from the Anthropic API and calculates cost.
Automatically includes subagents.
"""
from __future__ import annotations
import json
import glob
import os
import sys
from pathlib import Path
from typing import Optional
# Claude Opus 4.5 pricing ($/million tokens) - January 2025
# Source: https://www.anthropic.com/pricing
PRICING = {
"input_tokens": 5.0, # $5/M
"cache_read_input_tokens": 0.5, # $0.50/M (0.1× input)
"cache_creation_input_tokens": 6.25, # $6.25/M (1.25× input)
"output_tokens": 25.0, # $25/M
}
def find_session_path(uuid: str) -> str | None:
"""Find the JSONL file for a session by its UUID."""
claude_dir = Path.home() / ".claude"
for jsonl in claude_dir.rglob(f"*{uuid}*.jsonl"):
if "subagents" not in str(jsonl):
return str(jsonl)
return None
def collect_session_files(main_jsonl: str) -> list[str]:
"""Collect main log file + all subagent logs."""
files = [main_jsonl]
session_dir = os.path.dirname(main_jsonl)
uuid = os.path.basename(main_jsonl).replace('.jsonl', '')
subagent_dir = os.path.join(session_dir, uuid, "subagents")
if os.path.exists(subagent_dir):
files.extend(glob.glob(os.path.join(subagent_dir, "*.jsonl")))
return files
def analyze_session(uuid: str) -> dict | None:
"""Full analysis of a session (tokens + tools)."""
main_path = find_session_path(uuid)
if not main_path:
print(f" Session not found: {uuid}")
return None
files = collect_session_files(main_path)
data = {
"uuid": uuid,
"files": len(files),
"subagents": len(files) - 1,
"api_calls": 0,
"tool_calls": 0,
"tokens": {
"input_tokens": 0,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0,
"output_tokens": 0,
},
"tools": {},
}
for filepath in files:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
if not line.strip():
continue
try:
entry = json.loads(line)
msg = entry.get('message', {})
usage = msg.get('usage')
if usage:
data["api_calls"] += 1
for key in data["tokens"]:
data["tokens"][key] += usage.get(key, 0)
content = msg.get('content', [])
if isinstance(content, list):
for c in content:
if isinstance(c, dict) and c.get('type') == 'tool_use':
data["tool_calls"] += 1
tool = c.get('name', 'unknown')
data["tools"][tool] = data["tools"].get(tool, 0) + 1
except json.JSONDecodeError:
pass
return data
def calc_cost(tokens: dict) -> float:
"""Calculate cost in dollars."""
return sum(tokens[k] * PRICING[k] / 1_000_000 for k in PRICING)
def print_comparison(baseline: dict, grepai: dict):
"""Display comparison of two sessions."""
print("\n" + "=" * 75)
print("GREPAI BENCHMARK - RESULTS")
print("=" * 75)
print(f"\n{'Metric':<35} {'Baseline':>15} {'Grepai':>15} {'Δ':>10}")
print("-" * 75)
print(f"{'Subagents':<35} {baseline['subagents']:>15} {grepai['subagents']:>15}")
print(f"{'API Calls':<35} {baseline['api_calls']:>15} {grepai['api_calls']:>15}")
delta_tools = (grepai['tool_calls'] - baseline['tool_calls']) / baseline['tool_calls'] * 100
print(f"{'Tool Calls':<35} {baseline['tool_calls']:>15} {grepai['tool_calls']:>15} {delta_tools:>+9.0f}%")
print("-" * 75)
for key in PRICING:
b, g = baseline['tokens'][key], grepai['tokens'][key]
delta = (g - b) / b * 100 if b > 0 else 0
print(f"{key:<35} {b:>15,} {g:>15,} {delta:>+9.0f}%")
total_b = sum(baseline['tokens'].values())
total_g = sum(grepai['tokens'].values())
delta_total = (total_g - total_b) / total_b * 100
print("-" * 75)
print(f"{'TOTAL TOKENS':<35} {total_b:>15,} {total_g:>15,} {delta_total:>+9.0f}%")
print("\n" + "=" * 75)
print("BILLED COST (Claude Opus 4.5 pricing)")
print("=" * 75)
cost_b = calc_cost(baseline['tokens'])
cost_g = calc_cost(grepai['tokens'])
delta_cost = (cost_g - cost_b) / cost_b * 100
print(f"\n{'Baseline':<35} ${cost_b:>14.4f}")
print(f"{'Grepai':<35} ${cost_g:>14.4f}")
print("-" * 55)
print(f"{'SAVINGS':<35} {delta_cost:>+14.1f}%")
def main():
if len(sys.argv) < 3:
print(__doc__)
sys.exit(1)
uuid_baseline = sys.argv[1]
uuid_grepai = sys.argv[2]
print("Analyzing sessions...")
baseline = analyze_session(uuid_baseline)
grepai = analyze_session(uuid_grepai)
if not baseline or not grepai:
print("\nUnable to analyze sessions.")
sys.exit(1)
print_comparison(baseline, grepai)
if __name__ == "__main__":
main()
Usage
python3 benchmark.py <UUID_BASELINE> <UUID_GREPAI>
Les UUIDs se trouvent avec ls ~/.claude/projects/*/ ou dans le prompt Claude Code.
Limites et honnêteté
Variabilité du comportement de Claude
Claude Code peut emprunter des chemins différents pour la même question. Parfois il lance des subagents, parfois non. Dans ce benchmark :
- Baseline : Claude a lancé 5 subagents
- grepai : Claude n’a lancé aucun subagent
J’ai répété l’expérience 5 fois avec des résultats quasi identiques. Le comportement est reproductible : sans grepai, Claude lance systématiquement des subagents pour explorer le code. Avec grepai, il obtient les réponses directement et n’en a plus besoin.
Ce que ce benchmark mesure
- ✅ Les tokens réellement facturés par l’API
- ✅ Le coût financier exact
- ✅ Le nombre d’appels d’outils
Ce que ce benchmark ne mesure pas
- La qualité des réponses (les deux approches trouvent les bonnes réponses)
- Le temps réel (variable selon la charge serveur)
- La reproductibilité (le comportement de Claude varie)
Conclusion
Sur ce benchmark avec Excalidraw :
| Métrique | Réduction |
|---|---|
| Appels d’outils | -55% |
| Input tokens (fresh) | -97% |
| Cache creation | -71% |
| Coût facturé | -27,5% (-$1.86) |
Le mécanisme principal : la recherche sémantique trouve les bons fichiers du premier coup. Au lieu de tâtonner avec des patterns regex qui retournent des dizaines de résultats à filtrer, grepai comprend l’intention de la question et pointe directement vers le code pertinent. Résultat : moins d’itérations, moins de lectures inutiles, et Claude n’a pas besoin de déléguer l’exploration à des subagents coûteux.
Ces résultats sont cohérents avec les benchmarks externes :
- Morph : 26% de tours en moins, 39% de tokens en moins
- mgrep : 2× moins de tokens
- Claude Context (Zilliz) : 40% de réduction
Sources et références
Documentation technique
- Anthropic Pricing : Tarifs officiels de l’API Claude
- Claude Prompt Caching : Fonctionnement du cache
- grepai Documentation : Guide complet de l’outil
- Excalidraw : Projet utilisé pour le benchmark (MIT License)
Benchmarks externes
- Morph Benchmarks : Recherche sémantique vs grep
- mgrep par Mixedbread AI : Alternative sémantique à grep
- Claude Context par Zilliz : Réduction de contexte pour agents
Articles liés
- La recherche sémantique au service des agents IA : Article précédent sur grepai
Envie de tester ? grepai est open source et s’installe en quelques minutes. La documentation inclut un guide de démarrage rapide pour Claude Code.
Loading comments...