Claude CLI - Les Bases

Navigation : Index | Suivant >>

Module : Vibe-Coding / Claude Code / Notebooks CLI
Niveau : Debutant
Duree : 20 min
Prerequis : Claude Code CLI installe, OpenRouter configure

Objectifs d’Apprentissage


Qu’est-ce que Claude CLI ?

Claude CLI (Command Line Interface) est l’outil en ligne de commande officiel de Claude Code. Il permet d’interagir avec les modèles Claude directement depuis un terminal ou un script, sans passer par une interface graphique.

Cas d’usage typiques : - Automatiser des tâches repetitives (generation de code, documentation) - Integrer Claude dans des pipelines CI/CD - Créer des scripts de productivite personnalises - Tester rapidement des prompts sans quitter le terminal

Note : Ce notebook utilise des wrappers Python pour executer les commandes CLI. Vous pouvez aussi executer ces mêmes commandes directement dans un terminal.

1. Configuration et Imports

Commençons par importer les modules necessaires et configurer l’environnement.

Ce notebook n’appelle pas l’exécutable claude directement : il passe par un module helper claude_cli (dans le dossier helpers/) qui encapsule les appels subprocess et normalise leurs sorties. Cette indirection sert deux buts : (1) récupérer proprement stdout, stderr et le code de retour plutôt que d’afficher du texte brut, et (2) offrir un mode simulation (SIMULATION_MODE = True) qui renvoie des réponses pré-enregistrées sans consommer de quota API — utile pour réviser la structure du notebook sans coût. Laissez SIMULATION_MODE = False pour de vrais appels. Le bloc try/except ci-dessous gère le cas où le chemin helpers/ ne serait pas sur sys.path.

# Configuration
import sys
import os

# Ajouter le repertoire helpers au path
sys.path.insert(0, 'helpers')

# Mode simulation : mettre a True si pas de cle API
SIMULATION_MODE = False

# Import des helpers
try:
    from claude_cli import (
        run_claude, 
        check_claude_status, 
        verify_installation,
        get_claude_version,
        print_response
    )
    # Activer le mode simulation si demande
    import claude_cli
    claude_cli.SIMULATION_MODE = SIMULATION_MODE
    print("Helpers charges avec succes")
except ImportError as e:
    print(f"Erreur d'import: {e}")
    print("Assurez-vous d'etre dans le bon repertoire")
Helpers charges avec succes

Lecture. L’import des helpers depuis claude_cli réussit, et le mode SIMULATION_MODE = False indique qu’on s’adresse à la vraie CLI – un mode True aurait fait court-circuiter les appels HTTP pour permettre la rédaction d’un notebook en environnement sans clé API. Le helper print_response encapsule la triple stdout/stderr/code qu’on va voir dans toutes les cellules qui suivent.

Avertissement : dans un environnement de production, la variable SIMULATION_MODE doit rester à False ; à True, les exercices donneraient l’illusion d’un dialogue sans qu’aucun appel ne sorte.

2. Verification de l’Installation

Avant de commencer, verifions que Claude CLI est correctement installe.

La vérification de l’installation est le premier diagnostic systématique car le défaut n°1 de Claude CLI est un binaire absent du PATH (installation globale non terminée, shell non rechargé, préfixe d’installation différent entre macOS/Linux/Windows). Plutôt que de laisser un subprocess échouer sur une FileNotFoundError cryptique plus loin, les helpers verify_installation() et get_claude_version() testent la présence du binaire et retournent un message lisible. Si la vérification échoue, consultez ../docs/INSTALLATION-CLAUDE-CODE.md avant de continuer — toutes les cellules suivantes dépendent d’un CLI fonctionnel.

# Verifier l'installation
if verify_installation():
    print("Claude CLI est installe")
else:
    print("Claude CLI n'est PAS installe")
    print("Consultez le guide d'installation : INSTALLATION-CLAUDE-CODE.md")
Claude CLI est installe

Lecture. Le helper verify_installation() retourne True : la commande claude est dans le PATH et accepte les arguments. La cellule ne fait pas l’appel claude --version ici – elle vérifie seulement la présence du binaire. La version est interrogée juste après par get_claude_version() (cellule suivante), qui distingue une installation récente d’un vestige.

Test de régression : si la sortie devient Claude CLI n'est PAS installe, le notebook est rendu inopérant en aval – les appels run_claude() lèveraient une FileNotFoundError. C’est un fail-fast assumé : on préfère un notebook qui crie, à un notebook qui échoue en silence.

Installation et Authentification

Le notebook fait appel a la vraie CLI Claude Code (mode SIMULATION_MODE = False). Avant d’executer les cellules suivantes, l’environnement doit disposer de l’executable claude ET d’une session authentifiee. Sans cela, les cellules de verification ci-dessous retournent des erreurs d’environnement (WinError 2, [Errno 2]) — ces sorties sont attendues et documentees comme illustration de modes d’echec, mais un etudiant qui clone le notebook doit obtenir la vraie sortie.

Procedure d’installation

  1. Chemin canonique : installation native (défaut de la documentation officielle, Node.js non requis) : installateur Windows depuis claude.com/code, brew install --cask claude-code sur macOS, curl -fsSL https://install.claude.com | sh sur Linux/WSL (cf. https://code.claude.com/docs/en/quickstart).
  2. Alternative npm (si votre poste interdit les binaires hors gestionnaire de paquets) : npm install -g @anthropic-ai/claude-code, Node.js >= 18 requis pour cette seule brique.
  3. Verification : claude --version doit retourner une version 1.x ou 2.x. Si la commande n’est pas trouvee : installation native, verifier que ~/.local/bin est dans le PATH puis redemarrer le shell ; alternative npm, ajouter le prefix npm au PATH (%APPDATA%\npm sur Windows, ~/.npm-global/bin sur Linux/macOS).
  4. Authentification : claude auth login ouvre un navigateur pour relier la CLI a votre compte Anthropic (la cle est alors geree par la CLI). Autre voie, celle de ce module via proxy OpenRouter : la session vient des variables d’environnement ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN du .env — dans ce cas, on ne lance PAS claude auth login.

En cas d’erreur WinError 2 / [Errno 2] dans les cellules ci-dessous

  • Verifier que claude --version repond dans un terminal — si non, reinstaller.
  • Si la version repond mais que check_claude_status() renvoie une erreur, la session est expiree : relancer claude auth login (voie compte Anthropic) ou verifier les variables du proxy (voie OpenRouter).
  • Si vous travaillez derriere un proxy d’entreprise, configurer HTTPS_PROXY / HTTP_PROXY dans l’environnement avant de lancer Jupyter.

Cette cellule ne change pas la substance pedagogique : les cellules d’erreur illustrent ce qui peut mal tourner, et cette procedure est la clef pour les reparer. Sans installation prealable, ces erreurs d’environnement sont inevitables ; avec elle, les cellules retournent la sortie nominale documentee.

# Verifier la version (utilise le helper pour gerer simulation et erreurs)
version = get_claude_version()
print(f"Version: {version}")
Version: 2.1.283 (Claude Code)

Interpretation : La version detectee confirme que Claude Code est operationnel localement. Deux remarques de methode. D’abord, la version n’est pas un detail cosmétique : les capacites du CLI (formats de sortie, gestion des sessions, models disponibles) evoluent d’une version a l’autre — un notebook qui documente un comportement devrait citer la version avec laquelle il a ete valide. Ensuite, notez que le helper isole l’appel : si la commande echoue (CLI absent du PATH), c’est le helper qui formate l’erreur, pas la cellule qui plante — c’est le pattern qui rend ce notebook re-executable meme sur une machine sans installation.

# Verifier le statut de connexion
status = check_claude_status()

if status.get("connected"):
    print("Connexion OK")
    print(f"Modele actif: {status.get('model', 'inconnu')}")
else:
    print(f"Erreur de connexion: {status.get('error', 'inconnue')}")
Connexion OK
Modele actif: detected

Lecture. Sortie nominale : Connexion OK / Modele actif: detected — la session est authentifiee et l’appel de sonde a reussi. Le helper rend un statut structure au lieu de lever une exception — et c’est la decision de conception importante : un statut se teste (if status.get(...)), s’affiche et se journalise, alors qu’une exception interrompt le notebook. La branche else de la cellule ne serait empruntee qu’en cas d’echec (session expiree, proxy) : elle afficherait alors Erreur de connexion: <cause> — ce helper renseigne toujours la cle error quand connected est faux, le fallback inconnue du get restant donc theorique. Pour un outil en ligne de commande appele depuis du code, distinguer « l’outil a repondu » de « l’outil a repondu OK » est la frontiere entre un script qui casse et un script qui rapporte. Retenez le triplet retourne (sortie, erreur, code de retour) : il revient dans toutes les cellules suivantes.

3. Première Commande

La commande de base est claude -p "votre prompt" qui envoie une question ponctuelle (one-shot).

Syntaxe :

claude -p "Votre question ou instruction"

Explication des paramètres : - claude : L’executable CLI - -p ou --print : Mode “print” - pose une question unique et affiche la reponse (pas de conversation) - Le prompt est entre guillemets pour gerer les espaces

Note : Le mode -p est ideal pour des questions ponctuelles. Pour des conversations multi-tours, nous verrons le mode session dans le notebook suivant.

# Premiere commande simple
stdout, stderr, code = run_claude("Bonjour ! Reponds en une phrase.")

print_response(stdout, stderr, code)
=== Reponse Claude ===
Bonjour ! Comment puis-je vous aider aujourd'hui ?

Interpretation : La commande claude -p envoie une requête ponctuelle au modèle et affiche la reponse. Le flag -p (print) signifie “pas de conversation” – chaque appel est indépendant, sans memoire des echanges précédents.

Essayons maintenant une question plus technique pour voir la qualite des reponses :

# Question plus elaboree
stdout, stderr, code = run_claude(
    "Explique en 2 phrases ce qu'est Python."
)

print_response(stdout, stderr, code)
=== Reponse Claude ===
Python est un langage de programmation interprété, généraliste et à typage dynamique, connu pour sa syntaxe simple et lisible. Il est très utilisé en science des données, développement web, automatisation et intelligence artificielle grâce à son vaste écosystème de bibliothèques.

Lecture. Réponse obtenue : « Python est un langage de programmation interprété, généraliste et à typage dynamique, connu pour sa syntaxe simple et lisible… ». La forme est intéressante : la réponse commence directement par la définition, sans formule de politesse, et déroule sur deux phrases comme demandé. C’est la marque d’un prompt bien calibré – claude -p "..." traite la requête comme un one-shot et ne s’autorise pas le bavardage qu’une session interactive autoriserait.

Métadonnée visible : print_response masque la sortie brute par défaut, mais on peut inspecter duration_api_ms ou total_cost_usd en JSON (cf. cellule format JSON). Pour ce notebook, on reste en prose – la sortie est plus pédagogique.

4. Formats de Sortie

Claude peut retourner les reponses en différents formats : - text (defaut) : Reponse en texte brut, ideal pour la lecture humaine - json : Reponse structuree en JSON, ideal pour le traitement programmatique

claude -p "prompt" --output-format json

Quand utiliser JSON ? - Pour parser la reponse dans un script - Pour extraire des données structurees (listes, objets) - Pour integrer Claude dans un pipeline automatise

Attention : Le format JSON ne garantit pas une structure spécifique. Incluez des instructions dans votre prompt pour specifier le schema attendu (ex: “retourne un JSON avec les cles ‘nom’ et ‘description’”).

# Format texte (defaut)
stdout, stderr, code = run_claude(
    "Liste 3 langages de programmation populaires",
    output_format="text"
)

print("=== Format Texte ===")
print(stdout)
=== Format Texte ===
Python, JavaScript et Java.

Interpretation : Le format texte retourne une reponse lisible et naturelle, parfaite pour une consultation directe. Mais si vous devez traiter cette reponse programmatiquement (extraire les noms, les stocker dans une base de données), le format JSON est beaucoup plus adapte.

Voyons comment obtenir un résultat structure :

# Format JSON
from claude_cli import run_claude_json

result = run_claude_json(
    "Donne-moi une liste de 3 frameworks web en JSON avec nom et langage"
)

print("=== Format JSON ===")
import json
print(json.dumps(result, indent=2, ensure_ascii=False))
=== Format JSON ===
{
  "duration_api_ms": 2288,
  "stop_reason": "end_turn",
  "session_id": "df892127-e9a5-4f81-a18e-8de02140121f",
  "total_cost_usd": 0.0458559,
  "usage": {
    "input_tokens": 2,
    "cache_creation_input_tokens": 15711,
    "cache_read_input_tokens": 29172,
    "output_tokens": 74,
    "output_tokens_details": {
      "thinking_tokens": 0
    },
    "server_tool_use": {
      "web_search_requests": 0,
      "web_fetch_requests": 0
    },
    "service_tier": "standard",
    "cache_creation": {
      "ephemeral_1h_input_tokens": 0,
      "ephemeral_5m_input_tokens": 15711
    },
    "inference_geo": "global",
    "iterations": [
      {
        "input_tokens": 2,
        "output_tokens": 74,
        "cache_read_input_tokens": 29172,
        "cache_creation_input_tokens": 15711,
        "cache_creation": {
          "ephemeral_5m_input_tokens": 15711,
          "ephemeral_1h_input_tokens": 0
        },
        "type": "message"
      }
    ],
    "speed": "standard"
  },
  "modelUsage": {
    "claude-sonnet-5": {
      "inputTokens": 2,
      "outputTokens": 74,
      "cacheReadInputTokens": 29172,
      "cacheCreationInputTokens": 15711,
      "webSearchRequests": 0,
      "costUSD": 0.0458559,
      "contextWindow": 1000000,
      "maxOutputTokens": 64000,
      "thinkingTokens": 0,
      "canonicalModel": "claude-sonnet-5",
      "provider": "firstParty",
      "costBasis": "list"
    }
  },
  "permission_denials": [],
  "terminal_reason": "completed",
  "fast_mode_state": "off",
  "fast_mode_disabled_reason": "sdk_opt_in_required",
  "subagent_stats": {
    "spawned": 0,
    "requested": {
      "background": 0,
      "foreground": 0,
      "unset": 0
    },
    "started_in_background": 0,
    "max_depth": 0,
    "spawned_by_subagents": 0,
    "completed": 0,
    "failed": 0,
    "killed": {
      "parent": 0,
      "user": 0,
      "system": 0
    },
    "refused": {
      "depth_limit": 0,
      "concurrency_limit": 0,
      "budget": 0
    },
    "by_type": {}
  },
  "is_error": false,
  "num_turns": 1,
  "subtype": "success",
  "api_error_status": null,
  "result": "```json\n[\n  {\"nom\": \"Django\", \"langage\": \"Python\"},\n  {\"nom\": \"Express\", \"langage\": \"JavaScript\"},\n  {\"nom\": \"Spring\", \"langage\": \"Java\"}\n]\n```",
  "ttft_ms": 2423,
  "type": "result",
  "duration_ms": 2447,
  "uuid": "c978edf2-7902-49b8-85ba-7c27e40b580f",
  "ttft_stream_ms": 1644,
  "time_to_request_ms": 160,
  "first_content_frame_ms": 1644,
  "queued_turn_count": 0,
  "result_index": 0
}

Lecture. La réponse JSON est emballée par un wrapper Anthropic (type: result, subtype: success, session_id, total_cost_usd, usage). Le contenu utile est dans result – ici une string qui contient un bloc markdown JSON (\json...`).

Ce qu’il faut comprendre : le format run_claude_json() ne parse pas automatiquement le markdown dans la réponse – il rend la string result telle quelle. Pour récupérer la vraie structure JSON, il faudrait soit demander au modèle de répondre en JSON natif (sans markdown), soit appliquer un re.search(r'\[.*\]', result, re.DOTALL) côté client. C’est un piège classique documenté dans la cellule suivante.

5. Sélection du Modèle

Claude Code propose plusieurs modèles avec différents compromis performance/cout/vitesse :

Alias Modèle Vitesse Intelligence Cout Usage recommande
haiku Claude Haiku +++ + $ Questions simples, classification, extraction
sonnet Claude Sonnet ++ ++ \[ | Usage quotidien, code, analyse (defaut) | | `opus` | Claude Opus | + | +++ | \]$ Raisonnement complexe, preuves, recherche

Syntaxe :

claude --model opus -p "Question complexe"
claude --model haiku -p "Question simple"

Tip economique : Commencez toujours par haiku pour vos tests. Passez a sonnet ou opus uniquement si la qualite est insuffisante. Les couts peuvent varier de 1x (haiku) a 15x (opus) !

# Test avec Haiku (rapide)
stdout, stderr, code = run_claude(
    "En un mot, quelle est la capitale de la France ?",
    model="haiku"
)

print("=== Modele Haiku (rapide) ===")
print_response(stdout, stderr, code)
=== Modele Haiku (rapide) ===
=== Reponse Claude ===
Paris

Interpretation : Avec Haiku, la reponse est quasi-instantanee et concise – exactement ce qu’on attend pour une question simple. Le rapport cout/qualite est optimal pour ce type de requête.

Comparons maintenant avec le modèle Sonnet, plus performant pour les questions techniques :

# Test avec Sonnet (equilibre)
stdout, stderr, code = run_claude(
    "Explique la difference entre une liste et un tuple en Python",
    model="sonnet"
)

print("=== Modele Sonnet (equilibre) ===")
print_response(stdout, stderr, code)
=== Modele Sonnet (equilibre) ===
=== Reponse Claude ===
Voici la différence essentielle entre les deux :

## Liste (`list`)
```python
ma_liste = [1, 2, 3]
```
- **Mutable** : on peut modifier, ajouter, supprimer des éléments après création.
- Syntaxe : crochets `[...]`.
- Usage typique : collection d'éléments amenée à changer (ajout/suppression dynamique).

## Tuple (`tuple`)
```python
mon_tuple = (1, 2, 3)
```
- **Immuable** : une fois créé, impossible de modifier son contenu (pas d'`append`, `remove`, ni réaffectation d'un élément).
- Syntaxe : parenthèses `(...)` (la virgule est en réalité ce qui compte : `(1,)` pour un tuple à un élément).
- Usage typique : données fixes, regroupement de valeurs hétérogènes (ex. coordonnées `(x, y)`), clés de dictionnaire (car hashable), retour multiple d'une fonction.

## Conséquences pratiques

| | `list` | `tuple` |
|---|---|---|
| Modifiable | Oui | Non |
| Hashable (utilisable comme clé de dict) | Non | Oui (si contient uniquement des éléments hashables) |
| Performance | Légèrement plus lourde | Légèrement plus légère/rapide |
| Signal d'intention | "ça peut changer" | "ça ne doit pas changer" |

Exemple concret de la différence de mutabilité :
```python
l = [1, 2, 3]
l[0] = 99        # OK
l.append(4)      # OK

t = (1, 2, 3)
t[0] = 99        # TypeError: 'tuple' object does not support item assignment
```

Règle simple : si les données doivent rester constantes ou servir de clé de dictionnaire, utiliser un tuple ; sinon, une liste.

Lecture. Comparaison Liste/Tuple livrée en tableau markdown par Sonnet. La réponse distingue correctement les deux différences cardinales : mutabilité (liste mutable, tuple immuable) et hashabilité (tuple utilisable comme clé de dict si ses éléments le sont, jamais la liste). Les autres lignes (performance, « signal d’intention ») sont des conséquences de la première.

Pourquoi cette question est intéressante : elle force le modèle à comparer – ce qui est un bon test de cohérence conceptuelle. Si Sonnet se trompait ici (en prétendant par exemple que les tuples sont plus lents que les listes, ou que les listes sont hashables), on saurait qu’il confond les rôles ; les rôles bien séparés donnent des réponses bien séparées.

6. Analyse de Code

Claude excelle dans l’analyse et l’explication de code. Voici comment lui soumettre du code pour analyse :

Bonnes pratiques : 1. Delimitez clairement le code avec des triple backticks ou une indentation 2. Specifiez le langage (“ce code Python”, “cette fonction JavaScript”) 3. Posez une question precise (“identifie les bugs”, “explique la ligne 5”, “propose une optimisation”)

Structure recommandee du prompt :

[Contexte optionnel]
[Question precise]
[Code a analyser]
# Code a analyser
code_sample = '''
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
'''

# Demander une analyse
prompt = f"Analyse ce code Python et identifie un probleme de performance :\n{code_sample}"

stdout, stderr, code = run_claude(prompt)
print_response(stdout, stderr, code)
=== Reponse Claude ===
Le problème : cette implémentation récursive naïve de Fibonacci a une complexité **exponentielle O(2^n)** à cause des recalculs redondants — `fibonacci(n-1)` et `fibonacci(n-2)` recalculent chacun de nombreux sous-résultats communs (ex. `fibonacci(30)` fait ~2.7 millions d'appels).

**Fix simple — mémoïsation :**

```python
from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
```
→ O(n) temps, O(n) espace (récursion + cache).

**Alternative — itératif, encore mieux (pas de limite de récursion, O(1) espace) :**

```python
def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a
```

Interpretation : Claude a identifie le problème classique de la fonction fibonacci récursive naive – sa complexite est exponentielle (O(2^n)) car elle recalcule les mêmes valeurs de nombreuses fois. Pour n=40, cela represente déjà plus d’un milliard d’appels recursifs.

Voyons maintenant comment Claude peut proposer une version optimisee :

# Demander une amelioration
prompt = f"Propose une version optimisee de cette fonction :\n{code_sample}"

stdout, stderr, code = run_claude(prompt)
print_response(stdout, stderr, code)
=== Reponse Claude ===
Voici une version optimisée itérative, en O(n) temps et O(1) mémoire (contre O(2^n) pour la version récursive naïve) :

```python
def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a
```

Si tu dois calculer `fibonacci(n)` pour de très grands `n` (des milliers, millions), il existe une version en **O(log n)** par doublement rapide (fast doubling) :

```python
def fibonacci(n):
    def fib_pair(k):
        if k == 0:
            return (0, 1)
        a, b = fib_pair(k >> 1)
        c = a * (2 * b - a)
        d = a * a + b * b
        if k & 1:
            return (d, c + d)
        return (c, d)
    return fib_pair(n)[0]
```

**Pourquoi la version naïve est lente** : chaque appel refait deux fois le travail des sous-appels, sans mémoriser aucun résultat — d'où l'explosion exponentielle. Si tu préfères garder une forme récursive tout en gardant la complexité linéaire, une alternative est le memoization (`functools.lru_cache`), mais elle consomme de la pile et de la mémoire O(n), contrairement à l'itérative.

Pour un usage général je recommande la version itérative (simple, rapide, mémoire constante) ; la version fast-doubling n'est utile que si `n` est vraiment très grand.

Lecture. Claude a diagnostiqué la complexité O(2^n) de la version récursive naïve (recalcul des mêmes sous-problèmes en arbre) et proposé une version itérative O(n) temps / O(1) espace qui garde les deux valeurs précédentes dans (a, b). C’est la transformation classique – le bon réflexe Claude Code, et la valeur ajoutée de l’IA sur ce type d’exercice : non pas la réponse brute, mais le diagnostic de la complexité (O(2^n)) puis la preuve que la nouvelle version est O(n) (par l’itération unique). À cette exécution, la réponse propose en plus une version en O(log n) (« fast doubling »), en précisant qu’elle ne sert que pour de très grands n : le modèle ne pousse pas la solution la plus sophistiquée par défaut.

Limite : la version itérative documentée ici ne gère pas n < 0 (range(n) est vide, et la fonction renvoie 0 sans rien signaler). Un code de production ajouterait un if n < 0: raise ValueError(...). Ce n’est pas le sujet de l’exercice – mais c’est le genre de coin que la review doit repérer.

7. Exemples guidés et exercices

Les trois exemples guidés qui ouvrent cette section viennent du groupe 13 (PR #18563) : expliquer un code mystère, comparer deux modèles sur la même question, poser une question simple au petit modèle haiku. Chaque exemple est suivi d’une lecture écrite à partir de sa sortie.

Viennent ensuite six exercices à compléter, qui mesurent les trois compétences du notebook : composer un prompt, choisir le modèle adapté, exploiter la sortie (texte ou JSON). Les exercices 4 et 6 demandent d’écrire des fonctions réutilisables (extract_json_field, ask_about_topic), l’exercice 5 un appel direct à run_claude_json(). Les exercices 7 à 9 prolongent les exemples guidés : mesurer le coût et la latence d’un modèle, faire trouver un bug puis vérifier la correction, réutiliser une réponse du modèle dans le code. Chaque stub s’exécute sans erreur tant que la solution n’est pas écrite : vous pouvez exécuter le notebook de bout en bout, puis revenir remplir les stubs un par un.

Exemple guidé 1 : Expliquer un code mystère

Contribution étudiante d’Adam BENLAHARCHE et Yasmine NEHAD (@nehadyasmine-creator), PR #18563, intégrée comme exemple guidé.

Le fragment ci-dessous est volontairement peu lisible : une fonction f, un paramètre s, une seule ligne de logique. Avant d’exécuter la cellule, formulez votre hypothèse sur ce qu’il calcule : c’est elle que la réponse du modèle viendra confirmer ou contredire.

Le groupe demande deux choses dans le même prompt : une explication ligne par ligne, et un nom explicite pour chaque variable. La seconde exigence est la plus utile. Pour renommer une variable, le modèle doit dire ce qu’elle contient, et un renommage faux se repère tout de suite.

# EXEMPLE GUIDE 1 : faire expliquer un code mystere (groupe 13, PR #18563)
mystery_code = '''
def f(s):
    return s == s[::-1]
'''

stdout, stderr, code = run_claude(
    f"Explique ce que fait ce code Python, ligne par ligne, puis donne un nom explicite a chaque variable :\n{mystery_code}",
    timeout=180
)
print_response(stdout, stderr, code)
=== Reponse Claude ===
Voici l'explication ligne par ligne :

```python
def f(s):
    return s == s[::-1]
```

1. `def f(s):` — définit une fonction nommée `f` qui prend un paramètre `s` (une chaîne ou une séquence).
2. `return s == s[::-1]` — `s[::-1]` crée une copie de `s` inversée (slicing avec un pas de -1). L'expression compare `s` à sa version inversée : si elles sont égales, la fonction retourne `True` (c'est un palindrome), sinon `False`.

C'est donc un test de **palindrome**.

Avec des noms explicites :

```python
def est_palindrome(chaine):
    return chaine == chaine[::-1]
```

- `f` → `est_palindrome` (décrit ce que fait la fonction)
- `s` → `chaine` (précise le type de donnée attendu)

Lecture du résultat — exemple guidé 1

  • Le modèle identifie un test de palindrome : s[::-1] inverse la séquence, et la comparaison renvoie True ou False. C’est juste, et l’explication nomme le mécanisme (un découpage avec un pas de -1) au lieu de paraphraser la ligne.
  • Le renommage est la partie la plus parlante : f devient est_palindrome et s devient chaine. Un nom faux se serait vu tout de suite ; ici, le nom montre que le modèle a compris.
  • Ce que la réponse ne dit pas à cette exécution : rien sur la casse, les espaces ou les accents. "Kayak" ou "Esope reste ici et se repose" renvoient False. La réponse obtenue par le groupe signalait cette limite, celle-ci non : d’une exécution à l’autre, le modèle ne relève pas les mêmes détails. C’est l’intérêt de l’hypothèse écrite avant l’appel : elle permet de voir ce qui manque à la réponse.

Exemple guidé 2 : Comparer deux modèles sur la même question

Contribution étudiante d’Adam BENLAHARCHE et Yasmine NEHAD (@nehadyasmine-creator), PR #18563, intégrée comme exemple guidé.

Le choix du modèle (haiku, sonnet, opus) change la qualité, le coût et la latence de la réponse. La fonction compare_models du groupe pose la même question aux deux modèles et renvoie les deux réponses dans un dictionnaire : on peut ainsi les réutiliser, pas seulement les afficher. La question choisie, la différence entre == et is, est un bon test, car les deux notions se confondent facilement : une réponse juste doit parler d’identité des objets, pas seulement d’égalité des valeurs.

À l’intégration, seul l’affichage a changé : les deux réponses s’affichent l’une sous l’autre avec leur longueur, là où la version d’origine imprimait le dictionnaire brut, sauts de ligne compris.

# EXEMPLE GUIDE 2 : comparer deux modeles (groupe 13, PR #18563)
def compare_models(question, model1="haiku", model2="sonnet"):
    """Pose la meme question a deux modeles et retourne les reponses.
    
    Args:
        question: La question a poser.
        model1: Premier modele (par defaut haiku).
        model2: Second modele (par defaut sonnet).
        
    Returns:
        Dictionnaire {"model1": reponse1, "model2": reponse2}.
    """
    # Etape 1 : Appelez run_claude avec model1
    stdout1, stderr1, code1 = run_claude(question, model=model1)
    # Etape 2 : Appelez run_claude avec model2
    stdout2, stderr2, code2 = run_claude(question, model=model2)
    # Etape 3 : Retournez un dict avec les deux stdout
    result = {"model1": stdout1, "model2": stdout2}
    return result

# Appel reel : la meme question, posee a haiku puis a sonnet
comparaison = compare_models("Quelle est la difference entre == et is en Python ?")
for cle, modele in (("model1", "haiku"), ("model2", "sonnet")):
    reponse = comparaison[cle]
    print(f"=== {modele} : {len(reponse)} caracteres, {len(reponse.split())} mots ===")
    print(reponse)
=== haiku : 1042 caracteres, 204 mots ===
## `==` vs `is` en Python

**`==` compare les valeurs** — vérifie si deux objets ont le même contenu.

**`is` compare les identités** — vérifie si deux références pointent sur le **même objet en mémoire**.

### Exemple

```python
a = [1, 2, 3]
b = [1, 2, 3]
c = a

a == b  # True — même contenu
a is b  # False — objets différents en mémoire

a == c  # True
a is c  # True — c pointe sur le même objet que a
```

### Quand utiliser quoi

- **`==`** : comparaison métier (« les valeurs sont-elles égales ? »)
- **`is`** : cas spécifiques :
  - Vérifier `None` : `if x is None:` (préféré à `x == None`)
  - Vérifier les singletons : `True`, `False`
  - Optimisation performance quand on sait qu'on compare deux références au même objet

### Piège classique

```python
x = [1, 2, 3]
y = [1, 2, 3]
x == y  # True
x is y  # False — deux listes différentes avec même contenu
```

Pour les petits entiers et strings, Python les cache en mémoire, donc `is` peut donner un résultat surprenant, mais c'est un détail d'implémentation — ne pas s'y fier.

=== sonnet : 1067 caracteres, 204 mots ===
En Python, `==` et `is` testent des choses différentes :

## `==` — égalité de valeur
Compare le **contenu**/la valeur de deux objets (via la méthode `__eq__`).

```python
a = [1, 2, 3]
b = [1, 2, 3]
a == b  # True — même contenu
```

## `is` — identité
Compare si deux références pointent vers **le même objet en mémoire** (équivalent à `id(a) == id(b)`).

```python
a = [1, 2, 3]
b = [1, 2, 3]
a is b  # False — deux objets distincts en mémoire, même si contenu identique
```

## Piège classique

```python
a = [1, 2, 3]
b = a
a is b  # True — b référence le même objet que a
```

## Cas où on utilise `is`

- **`None`** : toujours `x is None` (jamais `x == None`), c'est la convention Python (PEP 8).
- **Singletons** : `True`, `False`.
- Vérifier qu'on manipule bien le même objet (pas juste une copie équivalente).

⚠️ Attention à l'interning des petits entiers/strings par CPython : `a = 5; b = 5; a is b` donne souvent `True` par optimisation interne, mais **ce n'est pas garanti** et dépend de l'implémentation — ne jamais s'y fier pour comparer des valeurs.

Lecture du résultat — exemple guidé 2

  • Les deux réponses sont justes et parlent bien d’identité : is teste si deux références désignent le même objet en mémoire, == compare le contenu. Toutes deux recommandent x is None et mettent en garde contre le cache des petits entiers.
  • Elles ont la même longueur à cette exécution (204 mots chacune, 1042 et 1067 caractères) : sur cette question, la taille ne départage pas les modèles.
  • Les différences portent sur la précision. La réponse de sonnet nomme la méthode __eq__ appelée par ==, donne l’équivalence avec id(a) == id(b) et cite la PEP 8 pour is None. Celle de haiku ajoute une rubrique « Quand utiliser quoi », mais y présente is comme une optimisation de performance, un conseil discutable : on choisit is pour tester l’identité, pas pour aller plus vite.
  • Quel modèle a répondu ? Depuis l’intégration, run_claude() transmet --model pour sonnet aussi. Auparavant, un appel « sonnet » partait sur le modèle par défaut de la CLI, qui dépend de la configuration de chacun (un compte neuf tourne sur Opus) : on comparait alors haiku à un modèle qu’on n’avait pas choisi. La sortie JSON de la section 4 dit quel modèle a répondu (modelUsage).
  • Ce qu’on ne peut pas conclure : une question, une exécution. Pour choisir un modèle, il faut aussi le coût et la latence, que compare_models ne mesure pas : c’est l’objet de l’exercice 7.

Exemple guidé 3 : Une question simple avec Haiku

Contribution étudiante d’Adam BENLAHARCHE et Yasmine NEHAD (@nehadyasmine-creator), PR #18563, intégrée comme exemple guidé.

Après la comparaison sur une question riche, le cas le plus simple : une question courte posée au petit modèle haiku. Le groupe demande la syntaxe d’une boucle for en Python.

L’énoncé d’origine proposait aussi une consigne de format (« réponds en exactement 5 mots »). La cellule suivante, ajoutée à l’intégration, pose une question fermée avec cette consigne et compte les mots de la réponse : le petit modèle respecte-t-il une contrainte précise ?

# EXEMPLE GUIDE 3 : une question simple a haiku (groupe 13, PR #18563)
stdout, stderr, code = run_claude("Quelle est la syntaxe d'une boucle for en Python ?", model="haiku")
print_response(stdout, stderr, code)
=== Reponse Claude ===
En Python, la syntaxe d'une boucle for est :

```python
for variable in sequence:
    # bloc d'instructions
    statement
```

**Exemples courants :**

```python
# Itération sur une liste
for i in [1, 2, 3]:
    print(i)

# Itération sur une plage (range)
for i in range(5):  # 0, 1, 2, 3, 4
    print(i)

# Itération sur une chaîne
for char in "Hello":
    print(char)

# Itération sur un dictionnaire
for key in {"a": 1, "b": 2}:
    print(key)
```

**Avec enumerate (pour avoir l'indice et la valeur) :**

```python
for index, value in enumerate(["apple", "banana"]):
    print(index, value)  # 0 apple, 1 banana
```

**Avec else (exécuté si la boucle se termine normalement) :**

```python
for i in range(3):
    print(i)
else:
    print("Boucle terminée")
```

La boucle `for` en Python itère directement sur les éléments d'une séquence, sans avoir besoin de gérer un indice manuellement (contrairement à d'autres langages).
# Complement (integration) : la consigne de format de l'enonce, verifiee par le code
stdout, stderr, code = run_claude(
    "Quelle est la capitale de l'Australie ? Reponds en exactement 5 mots.",
    model="haiku"
)
print_response(stdout, stderr, code)
mots = stdout.split()
print(f"Nombre de mots (separes par des espaces) : {len(mots)}")
=== Reponse Claude ===
Canberra est la capitale australienne.

Nombre de mots (separes par des espaces) : 5

Lecture du résultat — exemple guidé 3

  • La question du groupe (la syntaxe d’une boucle for) obtient de haiku une réponse juste, et bien plus longue que la question : la syntaxe générale, puis des exemples sur une liste, range, une chaîne, un dictionnaire, enumerate et la clause else. Sur une question ouverte, même courte, le petit modèle ne s’en tient pas à l’essentiel : sans consigne de longueur, il développe.
  • La consigne de format est respectée à cette exécution : « Canberra est la capitale australienne. » fait 5 mots, et la réponse est juste.
  • Portée de la mesure : un seul essai. Le comptage par split() est aussi fragile, puisqu’un signe de ponctuation isolé ou une balise Markdown compterait comme un mot. Pour affirmer que haiku respecte ce type de consigne, il faudrait répéter l’appel et varier la contrainte (3 mots, 10 mots).

Exercice 4 : Interpreter une reponse JSON

Le format JSON est essentiel pour integrer Claude dans des pipelines automatisees. Objectif : Completez la fonction extract_json_field qui parse la reponse JSON de Claude et extrait les champs pertinents. Cette competence est indispensable pour tout script qui traite les sorties de Claude programmatiquement.

# Exercice 4 : Extraire des donnees d'une reponse JSON
import json

def extract_json_field(response_json, field_name):
    """Extrait un champ specifique d'une reponse JSON de Claude.
    
    Args:
        response_json: Dictionnaire JSON retourne par run_claude_json().
        field_name: Nom du champ a extraire (ex: "result", "total_cost_usd").
        
    Returns:
        La valeur du champ, ou None si absent.
    """
    # TODO etudiant : extrayez le champ demande du dictionnaire
    # Indice : utilisez response_json.get(field_name) pour un acces safe
    # Etape 1 : Verifiez que response_json est bien un dictionnaire
    # Etape 2 : Retournez la valeur du champ demande
    value = None  # TODO etudiant : remplacez par l'extraction
    return value

# Test avec un echantillon de reponse JSON
sample_response = {
    "type": "result",
    "result": "Python est un langage polyvalent.",
    "total_cost_usd": 0.003,
    "num_turns": 1
}

cout = extract_json_field(sample_response, "total_cost_usd")
reponse = extract_json_field(sample_response, "result")
print(f"Cout : {cout}$")
print(f"Reponse : {reponse}")
Cout : None$
Reponse : None

Exercice 5 : Reponse structuree en JSON

Dans les exercices précédents, vous avez utilise le format texte et le modèle haiku. Maintenant, combinez les deux concepts : demandez une reponse structuree en JSON. Le format JSON est essentiel pour integrer Claude dans des pipelines automatisees.

# EXERCICE 5 : Demandez une reponse en JSON
# Demandez 5 bonnes pratiques Python avec nom et description

# Indice : Utilisez run_claude_json() et specifiez le format dans le prompt
# Exemple de schema a demander : {"practices": [{"name": "...", "description": "..."}]}

# Votre code ici
# result = run_claude_json(
#     "Liste 5 bonnes pratiques Python. Retourne un JSON avec le format: "
#     '{"practices": [{"name": "nom", "description": "description"}]}'
# )
# import json
# print(json.dumps(result, indent=2, ensure_ascii=False))

Exercice 6 : Composer un prompt avec paramètres

Dans les sections précédentes, vous avez utilise run_claude() avec des prompts simples. Un usage avance consiste a composer dynamiquement le prompt en fonction du contexte (langage, niveau de detail, format souhaite). Objectif : Completez la fonction ask_about_topic qui genere un prompt structure pour poser une question technique a Claude.

# Exercice 6 : Composer un prompt dynamique
def ask_about_topic(topic, language="Python", detail_level="intermediaire"):
    """Genere un prompt structure pour poser une question technique a Claude.
    
    Args:
        topic: Sujet de la question (ex: "list comprehension").
        language: Langage de programmation concerne.
        detail_level: "debutant", "intermediaire" ou "avance".
        
    Returns:
        Le prompt compose, pret a etre passe a run_claude().
    """
    # TODO etudiant : composez le prompt en integrant les 3 parametres
    # Indice : utilisez un f-string avec des instructions claires
    # Exemple de structure : "Explique [topic] en [language] pour un niveau [detail_level]"
    prompt = None  # TODO etudiant : remplacez par votre composition
    return prompt

# Test (sans appel API)
resultat = ask_about_topic("list comprehension", language="Python", detail_level="debutant")
print(f"Prompt genere : {resultat}")
Prompt genere : None

Exercice 7 : Mesurer le coût et la latence de deux modèles

L’exemple guidé 2 compare les réponses de deux modèles. Il ne dit rien de leur coût ni de leur temps de réponse, qui sont souvent le vrai critère de choix sur une question simple. Objectif : complétez la fonction mesurer_modele, qui pose une question à un modèle avec run_claude_json(), chronomètre l’appel et renvoie un dictionnaire {"modele", "secondes", "cout_usd", "tokens_sortie"}. Appliquez-la à haiku puis à sonnet sur la même question fermée, et comparez.

Attention : le coût renvoyé par la CLI compte tout le contexte envoyé, instructions système de la CLI comprises, et pas seulement votre question. Mesurez deux fois le même modèle avant de conclure sur un écart.

# Exercice 7 : Mesurer le cout et la latence d'un appel
import time

def mesurer_modele(question, model="haiku"):
    """Pose une question a un modele et mesure la duree, le cout et les tokens de sortie.

    Returns:
        Dictionnaire {"modele", "secondes", "cout_usd", "tokens_sortie"},
        ou None tant que le stub n'est pas complete.
    """
    # TODO etudiant
    # Etape 1 : notez l'instant de depart avec time.perf_counter()
    # Etape 2 : appelez run_claude_json(question, model=model)
    # Etape 3 : calculez la duree, puis lisez "total_cost_usd" et usage["output_tokens"] dans la reponse
    # Indice : la fonction extract_json_field de l'exercice 4 peut servir
    mesure = None  # TODO etudiant : remplacez par le dictionnaire de mesures
    return mesure

question = "Quelle est la capitale de l'Australie ? Reponds en un mot."
for modele in ("haiku", "sonnet"):
    print(modele, mesurer_modele(question, model=modele))
haiku None
sonnet None

Exercice 8 : Faire trouver un bug, puis vérifier la correction

L’exemple guidé 1 demande au modèle d’expliquer un code correct. Ici le code est faux, et la question change de nature : le modèle peut se tromper de bug, ou en inventer un. Objectif : demandez à Claude de trouver le bug de moyenne_positive et de proposer une version corrigée. Ne le croyez pas sur parole : écrivez la version corrigée dans moyenne_positive_corrigee et vérifiez-la sur les trois cas fournis, dont un qui fait planter la version d’origine.

Convention : sans valeur positive, la fonction renvoie 0.0.

# Exercice 8 : trouver un bug et verifier la correction
buggy_code = '''
def moyenne_positive(valeurs):
    positives = [v for v in valeurs if v > 0]
    return sum(positives) / len(valeurs)
'''

# Etape 1 : demandez a Claude de trouver le bug et de proposer une correction
# Indice : un prompt du type "Trouve le bug de cette fonction et propose une version corrigee"
# TODO etudiant : votre appel a run_claude ici

# Etape 2 : recopiez la correction (si vous la jugez juste) dans cette fonction
def moyenne_positive_corrigee(valeurs):
    return None  # TODO etudiant

# Etape 3 : verification sur trois cas
cas = [([1, 2, -3, 4], 7 / 3), ([-1, -2], 0.0), ([], 0.0)]
for valeurs, attendu in cas:
    obtenu = moyenne_positive_corrigee(valeurs)
    print(f"{valeurs} -> {obtenu} (attendu {attendu:.3f})")
[1, 2, -3, 4] -> None (attendu 2.333)
[-1, -2] -> None (attendu 0.000)
[] -> None (attendu 0.000)

Exercice 9 : Faire écrire une docstring et l’utiliser

Une réponse du modèle devient vraiment utile quand votre code peut la réutiliser : c’est le passage de « lire la réponse » à « s’en servir ». Objectif : demandez à Claude la docstring de la fonction est_palindrome (la fonction de l’exemple guidé 1, renommée), et seulement la docstring, sans guillemets triples ni balises Markdown. Attachez-la à la fonction avec est_palindrome.__doc__ = ..., puis affichez-la avec help(est_palindrome). Vérifiez enfin que le texte obtenu est propre et qu’il dit ce que l’exemple guidé 1 a établi.

# Exercice 9 : generer une docstring et l'attacher a la fonction
def est_palindrome(texte):
    return texte == texte[::-1]

# Etape 1 : demandez la docstring a Claude (reponse attendue : du texte brut)
# Indice : precisez le format attendu dans le prompt, puis nettoyez stdout avec .strip()
docstring = None  # TODO etudiant : remplacez par la docstring obtenue

# Etape 2 : attachez-la a la fonction et affichez l'aide
if docstring:
    est_palindrome.__doc__ = docstring
    help(est_palindrome)
else:
    print("Exercice a completer")
Exercice a completer

Corrections guidées — exercices 4 à 9

Exercice 4 (interpréter une réponse JSON). La réponse de run_claude_json() est déjà un dictionnaire. extract_json_field vérifie d’abord que c’est bien un dict, puisqu’un appel en erreur renvoie {"error": ...}, puis lit le champ avec .get(field_name), qui renvoie None au lieu de lever une KeyError. Le contenu utile est dans result, souvent une chaîne qui contient elle-même un bloc JSON en Markdown (section 4) : il faut alors un second json.loads. Une extraction par expressions régulières sur une réponse libre est l’anti-pattern : fragile face au moindre changement de formulation du modèle.

Exercice 5 (réponse structurée). La progression attendue : contrainte de format dans le prompt, validation par json.loads, puis exploitation programmatique (filtrer, trier, compter). Une fois la sortie structurée, la réponse du modèle cesse d’être du texte à lire : c’est une donnée que votre code peut consommer — c’est toute la différence entre « discuter avec un assistant » et « construire un pipeline ».

Exercice 6 (prompt paramétré). L’intérêt pédagogique est la séparation prompt / paramètres : ask_about_topic(topic, language, detail_level) fabrique le prompt final par template. C’est le pont direct vers les scripts reproductibles du notebook 02 : un prompt paramétré se met au propre dans une fonction, se teste, et se versionne — un prompt copié-collé dans le shell ne l’est jamais.

Exercice 7 (coût et latence). time.perf_counter() avant et après l’appel donne la durée ; la réponse JSON porte total_cost_usd et usage["output_tokens"]. Le piège est de conclure sur un seul appel : la latence varie d’un appel à l’autre, et le coût inclut tout le contexte que la CLI envoie avec votre question. Sur une question d’un mot, l’essentiel du coût ne vient pas de votre question. Mesurer deux fois le même modèle donne l’ordre de grandeur de ce bruit.

Exercice 8 (trouver un bug). Le bug est la division par len(valeurs) au lieu de len(positives), et la version d’origine plante sur une liste vide (division par zéro). Regardez le cas [-1, -2] : la version fausse renvoie déjà 0.0, la bonne valeur, par coïncidence. Un test qui passe ne prouve pas que le code est juste ; c’est pourquoi on vérifie sur plusieurs cas, dont un qui fait échouer l’ancienne version.

Exercice 9 (docstring). La difficulté n’est pas la docstring, c’est le format : sans consigne, le modèle entoure souvent sa réponse de balises Markdown ou de guillemets triples. Le préciser dans le prompt, puis .strip(), réduit le problème ; vérifiez-le sur la sortie obtenue. Contrôlez ensuite le contenu : la docstring doit dire que la fonction teste un palindrome et renvoie un booléen.

8. Resume

Ce que vous avez appris

Commande Description Exemple
claude -p "prompt" Question ponctuelle claude -p "Bonjour"
--model <nom> Choisir le modèle --model haiku
--output-format json Sortie JSON Pour traitement automatise

Points cles a retenir

  1. Le flag -p est pour les questions ponctuelles (one-shot), pas les conversations
  2. Haiku est le plus rapide et economique, Opus le plus intelligent
  3. Le format JSON necessite un prompt bien structure pour obtenir un schema previsible
  4. L’analyse de code fonctionne mieux avec des questions precises et du code bien delimite

Commandes utiles

# Verification
claude --version
claude /status

# Question simple
claude -p "Votre question"

# Avec modèle spécifique
claude --model haiku -p "Question simple"
claude --model opus -p "Question complexe"

# Sortie JSON
claude -p "Prompt structurant le JSON" --output-format json

Prochaine étape

Dans le notebook suivant, nous verrons comment gerer les sessions et conversations pour maintenir un contexte entre plusieurs echanges.

-> 02-Claude-CLI-Sessions.ipynb

Choisir son modele en pratique : question fermee ou tache repetitive -> petit modele (haiku), le differentiateur y est le cout et la latence, pas la qualite. Analyse de code, redaction structuree, comparaison argumentee -> modele equilibre (sonnet) : la section 6 a montre le gain qualitatif sur le diagnostic de complexite. Le reflexe a garder de ce notebook : mesurer sur VOTRE cas d usage (l exemple guide 2, compare_models, en est le gabarit) plutot que de croire les classements generaux — un benchmark ne connait pas vos prompts.

Retour au sommet