10 min de lecture

Benchmark : grepai vs grep sur Claude Code

La recherche sémantique fait-elle vraiment économiser des tokens ? J'ai testé grepai sur la codebase de Excalidraw avec Claude Code. Résultat : -27% sur la facture API.

La recherche sémantique fait-elle vraiment économiser des tokens ? J'ai testé grepai sur la codebase de Excalidraw avec Claude Code. Résultat : -27% sur la facture API.
Mode de lecture :

-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étriqueBaselinegrepaiΔ
Subagents lancés50-100%
Appels d’outils13962-55%
input_tokens51 1471 326-97%
cache_read_input_tokens5 973 1617 775 888+30%
cache_creation_input_tokens563 883162 289-71%
output_tokens476347-27%

Coût facturé (tarifs Claude Opus 4.5)

Type de tokenBaselinegrepaiΔ
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

TypeDescriptionTarif (Opus 4.5)
input_tokensNouveaux tokens à traiter (pas en cache)$5.00 / M
cache_read_input_tokensTokens déjà en cache, réutilisés$0.50 / M (90% réduction)
cache_creation_input_tokensTokens mis en cache pour la première fois$6.25 / M (25% surcoût)
output_tokensTokens 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

OutilBaselinegrepai
Bash (dont grepai)419
Grep3720
Glob130
Read4330
Task (subagents)50

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étriqueRé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 :


Sources et références

Documentation technique

Benchmarks externes

Articles liés


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.

Back to Blog

Comments (0)

Loading comments...

Leave a Comment