Accéder au contenu principal

Tutoriel API Claude Opus 4.8 : ajuster le paramètre Effort

Créez une application Streamlit qui exécute Claude Opus 4.8 avec raisonnement adaptatif, note automatiquement chaque réponse avec Haiku 4.5, et visualise le compromis coût‑qualité.
Actualisé 19 sept. 2026  · 11 min lire

Explorer avec l’IA

ChatGPTClaudePerplexity

La plupart des benchmarks comparent les modèles entre eux. Mais lorsque vous concevez un pipeline d’IA en production, la question la plus utile est souvent plus simple : jusqu’où faut-il pousser le même modèle sur une tâche donnée, et à quel coût ?

Claude Opus 4.8 introduit un paramètre effort avec plusieurs niveaux qui contrôle directement la profondeur de raisonnement appliquée par le modèle. Un effort élevé signifie plus de tokens de réflexion, une meilleure couverture des cas limites et une réponse plus longue. Cela implique aussi une latence et un coût plus élevés.

Dans ce tutoriel, nous allons créer une application Streamlit qui rend ce compromis concret et mesurable. L’application effectue trois appels API (un par niveau d’effort) sur le même prompt, note automatiquement chaque réponse avec Claude Haiku 4.5 en tant que juge fondé sur une grille, et affiche une courbe interactive de projection des coûts pour voir exactement quel niveau d’effort est pertinent selon votre volume de tâches.

À la fin de ce tutoriel, vous saurez :

  • Passer le paramètre effort sur l’API Claude Opus 4.8

  • Utiliser thinking: {type: \"adaptive\"} comme indicateur compagnon requis

  • Noter programmatiquement les sorties du modèle avec un modèle juge plus économique

  • Construire une interface de projection des coûts avec Streamlit

Qu’est-ce que Claude Opus 4.8 ?

Claude Opus 4.8 est le grand modèle de langage (LLM) phare d’Anthropic. Il est conçu pour un raisonnement complexe, du codage agentique à long horizon, et des tâches à forte autonomie où le modèle doit rester cohérent sur de nombreuses étapes, gérer de grands contextes et réduire les erreurs de condensation.

Plusieurs éléments distinguent Opus 4.8 des versions précédentes de Claude :

  • Fenêtre de contexte à 1 M de tokens : disponible par défaut sur l’API Claude, Amazon Bedrock et Vertex AI

  • 128k tokens de sortie max : permet de produire des livrables longs sans troncature

  • Raisonnement adaptatif : le modèle décide à chaque tour si un raisonnement en profondeur est nécessaire, au lieu de consommer systématiquement un budget fixe

  • Paramètre effort : une molette pour contrôler la profondeur de raisonnement et le coût par requête

  • Seuil de cache de prompt réduit : le seuil de mise en cache des prompts passe d’environ 2 048 à 1 024 tokens, ce qui permet à davantage de prompts système de profiter du cache sans changer une ligne de code

Si vous hésitez entre Opus 4.8 et une autre option de pointe, notre comparatif Claude Opus 4.8 vs. GPT-5.5 détaille les domaines où chaque modèle prend l’avantage en code et en raisonnement.

Opus 4.8 renforce également certaines contraintes d’API héritées d’Opus 4.7 : temperature, top_p et top_k ne peuvent pas être modifiés, et les budgets de réflexion étendus via budget_tokens ne sont plus pris en charge. Dans ces cas, l’API renvoie une erreur 400 si vous les passez.

Le paramètre effort et le raisonnement adaptatif deviennent les principaux leviers pour piloter le comportement de raisonnement.

Présentation des modèles Claude

Découvrez comment utiliser Claude avec l'API Anthropic pour résoudre des problèmes concrets et créer des applications basées sur l'IA.
Découvrez Le Cours

Qu’est-ce que le paramètre effort ?

Claude Opus 4.8 a ajouté un paramètre effort qui vous permet de contrôler la profondeur de raisonnement par requête. Voyez-le comme une molette à cinq positions :

  • low : le modèle répond plus directement en utilisant moins de tokens de réflexion ; adapté aux tâches bien cadrées, factuelles ou structurées.

  • medium : un juste milieu où le modèle raisonne avec plus de soin sans explorer de manière exhaustive tous les cas limites.

  • high : valeur par défaut du modèle, avec une profondeur de réflexion accrue ; recommandé pour le raisonnement complexe, les problèmes de conception multi‑étapes et les tâches où rater un cas limite coûte cher.

  • xhigh : pensé pour le travail agentique et le codage à long horizon qui exigent une cohérence soutenue sur de nombreuses étapes.

  • max : le niveau de capacité maximal, qui réserve l’intégralité du budget de calcul aux tâches les plus exigeantes.

Le paramètre effort ne fonctionne qu’avec thinking: {type: \"adaptive\"}. Le raisonnement adaptatif permet au modèle de décider, à chaque tour, si un raisonnement poussé est nécessaire. Ensemble, ils vous donnent un contrôle pertinent sur la façon dont le modèle dépense son budget de calcul.

Ce tutoriel se concentre sur low, medium et high, les trois niveaux les plus pertinents pour des charges de production généralistes, mais la même structure d’application s’étend facilement à xhigh ou max.

Les niveaux d’effort supérieurs sont conçus pour un travail agentique soutenu — le type illustré dans notre tutoriel Spec-Driven Development with Claude Code.

Construire une molette d’effort pour mesurer le compromis qualité/coût de Claude Opus 4.8

L’application tient en un seul fichier Python. Au lancement, vous verrez une barre latérale avec un champ de prompt et un curseur de tâches par jour, et un panneau principal avec quatre onglets :

  • Nuage de points qualité vs coût
  • Graphiques de projection de volume
  • Réponses brutes
  • Un tableau de résultats téléchargeable

À chaque exécution :

  1. Lancez trois appels API séquentiels à claude-opus-4-8 avec l’effort réglé sur low, medium et high
  2. Passez chaque réponse à claude-haiku-4-5 pour une notation fondée sur une grille
  3. Affichez tous les résultats dans l’UI Streamlit avec des curseurs ajustables pour la correction manuelle

Le prompt par défaut est une question de conception de systèmes distribués qui s’adapte naturellement au niveau d’effort.

Demo

Étape 0 : prérequis

Pour suivre ce tutoriel, vous aurez besoin de :

  • Python 3.10 ou version ultérieure

  • Une clé API Anthropic avec accès à claude-opus-4-8

  • Des bases sur Streamlit

Étape 1 : installer les dépendances

Créez un environnement virtuel et installez les paquets requis :

bashpython -m venv .venv
source .venv/bin/activate
pip install anthropic streamlit plotly pandas python-dotenv

Sous Windows, utilisez .venv\Scripts\activate au lieu de source .venv/bin/activate.

Ajoutez votre clé API Anthropic dans un fichier .env à la racine du projet :

.env
ANTHROPIC_API_KEY=sk-ant-...

Une fois l’environnement prêt, nous allons définir le schéma effort_dial pour l’UI.

Étape 2 : définir les constantes du projet

Créez un fichier nommé effort_dial.py et ajoutez le bloc de configuration en tête :

import os
import time
import json
import re
import anthropic
import pandas as pd
import plotly.graph_objects as go
import streamlit as st
from dotenv import load_dotenv

load_dotenv()
st.set_page_config(
    page_title="Opus 4.8 Effort Dial — Claude Opus 4.8",
    layout="wide",
)
MODEL              = "claude-opus-4-8"
SCORER_MODEL       = "claude-haiku-4-5"
MAX_OUTPUT_TOKENS  = 16000
SCORER_MAX_TOKENS  = 500
PRICE_OUT_PER_TOK        = 25 / 1_000_000
PRICE_IN_PER_TOK         = 5  / 1_000_000
SCORER_PRICE_OUT_PER_TOK = 5  / 1_000_000
SCORER_PRICE_IN_PER_TOK  = 1  / 1_000_000
EFFORT_ORDER  = ["low", "medium", "high"]
EFFORT_COLORS = {"low": "#185FA5", "medium": "#639922", "high": "#EF9F27"}
DEFAULT_QUALITY = {"low": 68, "medium": 82, "high": 91}

Le MAX_OUTPUT_TOKENS est fixé à 16 000 plutôt que la valeur par défaut de 4 096. C’est important, car avec un plafond de 4 096 tokens, les niveaux medium et high sont tronqués au même point, ce qui efface toute différence visible. Il faut suffisamment de marge pour que les réponses se distinguent naturellement.

La présence de end_turn comme motif d’arrêt dans la sortie finale, plutôt que max_tokens, confirme aussi que le modèle a terminé de lui‑même.

Étape 3 : définir le prompt par défaut

Un prompt simple comme « expliquer la récursivité » produit des réponses similaires aux trois niveaux d’effort, car rien n’exige un raisonnement poussé. Nous utilisons donc une question de conception de systèmes en plusieurs parties :

DEFAULT_PROMPT = """\
Design a production-ready rate-limiting system for a distributed API gateway
handling 100k requests/second. Cover:
1. Algorithm choice (token bucket vs sliding window vs leaky bucket) with tradeoffs
2. Redis data structure design and TTL strategy
3. Race condition handling across 50 pods
4. Failure mode behavior when Redis is unavailable
5. How you'd test this under load
Be specific about implementation details, not just concepts.
"""

Ce prompt comporte cinq sous‑problèmes distincts. Les approches à faible effort les traitent de façon superficielle en couvrant les concepts clés mais en omettant des détails d’implémentation. Un effort élevé aborde les conditions limites, inclut des exemples en Lua, discute de l’atomicité d’EVALSHA et ajoute un plan de test de charge avec des seuils concrets.

Étape 4 : appeler Claude Opus 4.8 avec le paramètre effort

Définissez maintenant la fonction principale qui appelle le modèle. Les deux points critiques sont thinking={\"type\": \"adaptive\"} et output_config={\"effort\": effort} :

def call_opus(client: anthropic.Anthropic, effort: str, prompt: str) -> dict:
    t0 = time.perf_counter()
    response = client.messages.create(
        model=MODEL,
        max_tokens=MAX_OUTPUT_TOKENS,
        thinking={"type": "adaptive"},
        output_config={"effort": effort},
        # Do NOT set temperature, top_p, or top_k — those 400-error on Opus 4.8
        messages=[{"role": "user", "content": prompt}],
    )
    latency_ms = (time.perf_counter() - t0) * 1000
    in_tok  = response.usage.input_tokens
    out_tok = response.usage.output_tokens
    text = next(
        (b.text for b in response.content if b.type == "text"), "(no text block)"
    )
    return {
        "effort":        effort,
        "in_tokens":     in_tok,
        "out_tokens":    out_tok,
        "latency_ms":    latency_ms,
        "cost_usd":      in_tok * PRICE_IN_PER_TOK + out_tok * PRICE_OUT_PER_TOK,
        "response":      text,
        "stop_reason":   response.stop_reason,
        "hit_token_cap": out_tok >= MAX_OUTPUT_TOKENS,
    }

Passons en revue les choix clés de cette fonction.

  • thinking={\"type\": \"adaptive\"} : requis pour que le paramètre effort ait un effet. Sans cela, les trois appels se comportent de manière identique, quel que soit effort. Le raisonnement adaptatif permet au modèle de décider, à chaque tour, s’il doit raisonner en profondeur et, le cas échéant, combien de budget y allouer.

  • output_config={\"effort\": effort} : c’est ainsi que le SDK Python d’Anthropic permet de passer des paramètres pas encore exposés en arguments nommés. En interne, le dict est fusionné dans le corps brut de la requête avant envoi.

  • time.perf_counter() : utilisé pour mesurer la latence murale plutôt que time.time(), car perf_counter offre une meilleure résolution et n’est pas affecté par les ajustements d’horloge système.

  • response.content : Opus 4.8 avec raisonnement adaptatif peut renvoyer plusieurs blocs, dont un bloc de réflexion (chaîne de raisonnement interne) et un bloc texte (réponse finale). Nous n’affichons que le texte. L’appel à next() ignore le bloc de réflexion et extrait la réponse destinée à l’utilisateur.

  • hit_token_cap : drapeau de diagnostic à True lorsque out_tokens atteint MAX_OUTPUT_TOKENS. Si c’est le cas pour medium ou high, cela signifie que la réponse a été coupée.

Étape 5 : noter automatiquement avec Claude Haiku 4.5

Noter manuellement trois longues réponses à chaque exécution est lent et subjectif. À la place, transmettez chaque réponse à Claude Haiku 4.5 avec une grille et récupérez une note JSON structurée.

Évaluer un modèle avec un autre est une forme d’évaluation LLM-as-a-judge ; notre guide sur l’évaluation des LLM explique comment concevoir des grilles fiables.

La grille décompose la qualité en quatre sous‑notes :

  • exhaustivité,
  • exactitude,
  • couverture des cas limites, et
  • spécificité d’implémentation

Elles correspondent directement à ce qui différencie les niveaux d’effort sur un prompt de conception de systèmes :

def score_response_with_haiku(client: anthropic.Anthropic, original_prompt: str,
                               response_text: str, effort: str) -> dict:
    rubric = (
        "You are grading an answer to a difficult systems design prompt. "
        "Score the response from 0 to 100 using this rubric: "
        "completeness 30 points, technical correctness 30 points, "
        "edge-case coverage 20 points, implementation specificity 20 points. "
        "Return strict JSON only with keys: score, rationale, completeness, "
        "correctness, edge_cases, specificity. "
        "The rationale must be concise, concrete, and no more than 80 words."
    )
    grader_prompt = (
        f"{rubric}\n\n"
        f"Original prompt:\n{original_prompt}\n\n"
        f"Effort level being graded: {effort}\n\n"
        f"Candidate response:\n{response_text}"
    )
    result = client.messages.create(
        model=SCORER_MODEL,
        max_tokens=SCORER_MAX_TOKENS,
        messages=[{"role": "user", "content": grader_prompt}],
    )
    raw    = next((b.text for b in result.content if b.type == "text"), "")
    parsed = extract_json_object(raw)
    in_tok  = result.usage.input_tokens
    out_tok = result.usage.output_tokens
    score = max(0, min(100, int(round(float(parsed["score"])))))
    return {
        "score":     score,
        "rationale": str(parsed.get("rationale", "")).strip(),
        "subscores": {
            "completeness": parsed.get("completeness"),
            "correctness":  parsed.get("correctness"),
            "edge_cases":   parsed.get("edge_cases"),
            "specificity":  parsed.get("specificity"),
        },
        "scorer_model":      SCORER_MODEL,
        "scorer_cost_usd":   in_tok * SCORER_PRICE_IN_PER_TOK + out_tok * SCORER_PRICE_OUT_PER_TOK,
        "scorer_in_tokens":  in_tok,
        "scorer_out_tokens": out_tok,
    }

Le helper extract_json gère les cas où Haiku encapsule occasionnellement sa sortie JSON dans des balises Markdown. C’est un écueil fréquent avec les modèles plus petits.

Le coût total de notation pour les trois réponses est généralement inférieur à 0,002 $. Un surcoût négligeable qui remplace une étape manuelle subjective par une grille reproductible.

Étape 6 : construire les graphiques Plotly

L’application comporte trois graphiques :

  • Un nuage de points score de qualité vs coût par appel, qui montre la forme de la frontière coût‑qualité.
  • Un histogramme groupé comparant les tokens de sortie et le coût journalier à un volume de tâches paramétrable.
  • Une courbe montrant le coût journalier cumulé de 100 à 5 000 tâches par jour.

Construisons chacun de ces graphiques :

def quality_tradeoff_chart(results: list[dict], scores: dict) -> go.Figure:
    fig = go.Figure()
    for r in results:
        effort = r["effort"]
        fig.add_trace(go.Scatter(
            x=[r["cost_usd"]],
            y=[scores[effort]],
            mode="markers+text",
            name=effort.capitalize(),
            text=[effort.capitalize()],
            textposition="top center",
            marker=dict(size=18, color=EFFORT_COLORS[effort],
                        line=dict(color="white", width=2)),
            hovertemplate=(
                "<b>%{text}</b><br>"
                "Cost / call: $%{x:.5f}<br>"
                "Quality: %{y}<extra></extra>"
            ),
        ))
    fig.update_layout(
        plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)",
        margin=dict(t=20, b=20, l=0, r=0),
        xaxis=dict(title="Cost / call (USD)", gridcolor="rgba(128,128,128,0.15)"),
        yaxis=dict(title="Quality score", range=[0, 100],
                   gridcolor="rgba(128,128,128,0.15)"),
        legend=dict(orientation="h", yanchor="bottom", y=1.02, x=0),
    )
    return fig

def projection_chart(results: list[dict], tasks: int) -> go.Figure:
    fig = go.Figure()
    fig.add_traces([
        go.Bar(
            name="Output tokens", x=[r["effort"] for r in results],
            y=[r["out_tokens"] for r in results],
            marker_color=[EFFORT_COLORS[r["effort"]] for r in results],
            yaxis="y1", text=[f"{r['out_tokens']:,}" for r in results],
            textposition="outside", width=0.35, offset=-0.2,
        ),
        go.Bar(
            name=f"Daily cost ({tasks:,} tasks)",
            x=[r["effort"] for r in results],
            y=[r["cost_usd"] * tasks for r in results],
            marker_color=[EFFORT_COLORS[r["effort"]] for r in results],
            marker_opacity=0.45, yaxis="y2",
            text=[f"${r['cost_usd']*tasks:.4f}" for r in results],
            textposition="outside", width=0.35, offset=0.2,
        ),
    ])
    fig.update_layout(
        barmode="group", plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)",
        margin=dict(t=20, b=20, l=0, r=0),
        yaxis=dict(title="Output tokens", gridcolor="rgba(128,128,128,0.15)"),
        yaxis2=dict(title="Daily cost (USD)", overlaying="y", side="right"),
        legend=dict(orientation="h", yanchor="bottom", y=1.02, x=0),
    )
    return fig

def breakeven_chart(results: list[dict]) -> go.Figure:
    volumes = list(range(100, 5001, 100))
    fig = go.Figure()
    for r in results:
        fig.add_trace(go.Scatter(
            x=volumes, y=[r["cost_usd"] * v for v in volumes],
            mode="lines", name=r["effort"].capitalize(),
            line=dict(color=EFFORT_COLORS[r["effort"]], width=2.5),
        ))
    fig.update_layout(
        plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)",
        margin=dict(t=20, b=20, l=0, r=0),
        xaxis=dict(title="Tasks / day", gridcolor="rgba(128,128,128,0.15)"),
        yaxis=dict(title="Daily cost (USD)", gridcolor="rgba(128,128,128,0.15)"),
        legend=dict(orientation="h", yanchor="bottom", y=1.02, x=0),
    )
    return fig

Chaque graphique répond à une question différente à partir des trois mêmes points de données.

  • quality_tradeoff_chart place le coût en abscisse et le score qualité en ordonnée, rendant la frontière immédiatement lisible.

  • projection_chart reprend le coût par appel issu des résultats API et le multiplie par la valeur du curseur tâches/jour ; chaque barre reflète donc un comptage de tokens réel, pas une estimation.

  • breakeven_chart balaie de 100 à 5 000 tâches/jour avec les mêmes ancrages de coût réels, si bien que les courbes divergent à un rythme fondé sur les réponses de l’API. Une équipe à 500 tâches/jour n’a pas le même profil de coûts qu’une à 5 000 ; ce graphique le rend explicite.

À l’étape suivante, nous relierons ces trois fonctions de graphiques à l’UI Streamlit et aux résultats d’API en direct.

Étape 7 : construire l’UI Streamlit

Dernier élément : l’interface Streamlit. La barre latérale gère le chargement de la clé, la saisie du prompt et le curseur de tâches par jour. Le panneau principal enchaîne les appels et met à jour les cartes de progression au fil de l’eau.

def main():
    api_key = os.getenv("ANTHROPIC_API_KEY", "").strip()
    with st.sidebar:
        st.title("Opus 4.8 Effort Dial")
        st.divider()
        if api_key:
            st.success("Key loaded.")
        else:
            st.error("Missing ANTHROPIC_API_KEY. Set it in your .env file.")
        prompt = st.text_area(
            "Prompt (runs at all 3 effort levels)",
            value=DEFAULT_PROMPT, height=220,
        )
        run = st.button("▶ Run 3 calls", use_container_width=True, type="primary")
        st.divider()
        tasks = st.slider("Tasks / day", min_value=10, max_value=5000, value=500, step=10)
    if "results" not in st.session_state:
        st.session_state.results = []
    for effort, default in DEFAULT_QUALITY.items():
        if f"quality_{effort}" not in st.session_state:
            st.session_state[f"quality_{effort}"] = default
    if run:
        if not api_key:
            st.error("Set ANTHROPIC_API_KEY first.")
            st.stop()
        client = anthropic.Anthropic(api_key=api_key)
        collected = []
        progress   = st.progress(0, text="Starting calls…")
        cols       = st.columns(3)
        holders    = {e: cols[i].empty() for i, e in enumerate(EFFORT_ORDER)}
        for i, effort in enumerate(EFFORT_ORDER):
            holders[effort].info(f"**{effort.upper()}** — calling…")
            progress.progress(i / 3, text=f"Running {effort} effort…")
            try:
                result = call_opus(client, effort, prompt)
                try:
                    scored = score_response_with_haiku(
                        client, prompt, result["response"], effort
                    )
                    result.update(scored)
                    st.session_state[f"quality_{effort}"] = scored["score"]
                    score_line = f"auto-score: {scored['score']}/100"
                except Exception as e:
                    result["score_error"] = str(e)
                    score_line = "auto-score unavailable"
                collected.append(result)
                holders[effort].success(
                    f"**{effort.upper()}**\n\n"
                    f"{result['out_tokens']:,} output tokens\n\n"
                    f"{result['latency_ms']/1000:.2f}s\n\n"
                    f"${result['cost_usd']:.5f} / call\n\n"
                    f"{score_line}"
                )
            except Exception as exc:
                holders[effort].error(f"**{effort.upper()}** failed: {exc}")
            progress.progress((i + 1) / 3)
        st.session_state.results = sorted(
            collected, key=lambda r: EFFORT_ORDER.index(r["effort"])
        )
        progress.empty()
    st.header("Opus 4.8 Effort Dial: Quality vs. Cost Tradeoff")
    results = st.session_state.results
    if not results:
        st.info("Set your API key and click **▶ Run 3 calls** to start.")
        st.stop()
    if any(r.get("hit_token_cap") for r in results):
        st.warning(
            f"One or more calls hit the {MAX_OUTPUT_TOKENS:,}-token cap. "
            "Raise MAX_OUTPUT_TOKENS if medium and high still look similar."
        )
    scorer_spend = sum(r.get("scorer_cost_usd", 0) for r in results)
    if scorer_spend > 0:
        st.caption(
            f"Auto-scoring used {SCORER_MODEL} and added "
            f"${scorer_spend:.6f} across all three responses."
        )
    # Per-call metric cards
    st.subheader("Per-call results")
    scores = {e: int(st.session_state.get(f"quality_{e}", DEFAULT_QUALITY[e]))
              for e in EFFORT_ORDER}
    mcols = st.columns(3)
    for row, col in zip(results, mcols):
        effort = row["effort"]
        with col:
            st.metric(f"{effort.upper()} effort", f"{row['out_tokens']:,} output tokens")
            st.caption(
                f"Input: {row['in_tokens']:,} · "
                f"Latency: {row['latency_ms']/1000:.2f}s · "
                f"Cost: ${row['cost_usd']:.5f} · "
                f"Stop: {row.get('stop_reason', 'n/a')}"
            )
            st.slider(
                f"Quality score — {effort.UPPER()}",
                min_value=0, max_value=100,
                value=DEFAULT_QUALITY[effort],
                key=f"quality_{effort}",
            )
            if row.get("rationale"):
                st.caption(f"Auto-scored by {SCORER_MODEL}: {row['rationale']}")
    st.divider()
    # Tabs
    t1, t2, t3, t4 = st.tabs(
        ["Quality vs cost", "Volume projection", "Raw responses", "Raw data"]
    )
    with t1:
        left, right = st.columns([1.1, 1], gap="large")
        with left:
            st.plotly_chart(quality_tradeoff_chart(results, scores),
                            use_container_width=True)
        with right:
            low  = next((r for r in results if r["effort"] == "low"),  None)
            med  = next((r for r in results if r["effort"] == "medium"), None)
            high = next((r for r in results if r["effort"] == "high"), None)
            if low and med and high:
                st.markdown("#### Read on the current anchor set")
                st.write(
                    f"Low → medium adds **${med['cost_usd'] - low['cost_usd']:.5f}** per call "
                    f"for **{scores['medium'] - scores['low']}** quality points."
                )
                st.write(
                    f"Medium → high adds **${high['cost_usd'] - med['cost_usd']:.5f}** per call "
                    f"for **{scores['high'] - scores['medium']}** more quality points."
                )
                st.write(
                    f"High vs low: **${high['cost_usd'] - low['cost_usd']:.5f}** "
                    f"total per-call delta."
                )
                st.caption(
                    "Quality scores are auto-generated by Haiku 4.5 using a rubric, "
                    "but you can override them with the sliders above."
                )
    with t2:
        st.plotly_chart(projection_chart(results, tasks), use_container_width=True)
        st.plotly_chart(breakeven_chart(results), use_container_width=True)
        if low and high:
            daily_delta   = (high["cost_usd"] - low["cost_usd"]) * tasks
            monthly_delta = daily_delta * 30
            st.caption(
                f"High vs low at {tasks:,} tasks/day: "
                f"**+${daily_delta:.4f}/day** · **+${monthly_delta:.2f}/month**"
            )
    with t3:
        for row in results:
            with st.expander(
                f"{row['effort'].upper()} — {row['out_tokens']:,} tokens",
                expanded=True,
            ):
                if row.get("rationale"):
                    st.info(
                        f"Auto-score: **{scores[row['effort']]}/100**. "
                        f"{row['rationale']}"
                    )
                st.markdown(row["response"])
    with t4:
        df = pd.DataFrame([{
            "Effort":            r["effort"],
            "Quality score":     scores[r["effort"]],
            "Input tokens":      r["in_tokens"],
            "Output tokens":     r["out_tokens"],
            "Latency (s)":       round(r["latency_ms"] / 1000, 2),
            "Cost / call ($)":   round(r["cost_usd"], 6),
            "Auto-score cost ($)": round(r.get("scorer_cost_usd", 0), 6),
            f"Daily @ {tasks} tasks ($)": round(r["cost_usd"] * tasks, 4),
            f"Monthly @ {tasks} tasks/day ($)": round(r["cost_usd"] * tasks * 30, 2),
        } for r in results])
        st.dataframe(df, use_container_width=True, hide_index=True)
        st.download_button(
            "Download CSV",
            df.to_csv(index=False),
            file_name="effort_dial_results.csv",
            mime="text/csv",
        )
if __name__ == "__main__":
    main()

La barre latérale gère toute la configuration, y compris le chargement de la clé API, la saisie du prompt et le curseur tâches/jour, pour laisser le panneau principal se consacrer aux résultats. Le panneau principal est divisé en quatre onglets pour structurer le flux.

Quality vs cost

  • L’onglet qualité vs coût est la vue principale. Il affiche le nuage de points avec le coût en abscisse, la qualité en ordonnée, ainsi qu’un résumé textuel des deltas de coût et de qualité par palier.

Volume projection

  • L’onglet projection de volume sert d’aide à la décision. Il affiche l’histogramme groupé et la courbe de seuil de rentabilité, pour projeter les coûts du compromis d’effort à votre volume réel de tâches via le curseur de la barre latérale.
  • L’onglet réponses brutes montre la sortie complète du modèle à chaque niveau d’effort dans des sections dépliables, avec la justification de la note Haiku épinglée au‑dessus. C’est ici que l’on peut lire les réponses et vérifier si les scores de qualité paraissent justes.

Raw data

  • L’onglet données brutes affiche tous les résultats sous forme de DataFrame, avec un bouton de téléchargement CSV, pratique pour exécuter la démo plusieurs fois avec des prompts différents et comparer les runs hors de l’application.

Avec le backend, les graphiques et l’UI en place, l’application est prête à tourner.

Étape 8 : lancer l’application

Démarrez le serveur Streamlit :

streamlit run effort_dial.py

Ouvrez http://localhost:8501 et gardez le prompt par défaut ou remplacez‑le par un exemple issu de votre pipeline, puis cliquez sur  Run 3 calls.

L’application exécute chaque niveau d’effort séquentiellement et affiche une carte d’état après chaque fin. Une fois les trois terminés, elle effectue les trois notations Haiku et alimente les graphiques. Le temps total au mur est généralement de 2 à 3 minutes pour le prompt par défaut, la majeure partie provenant de l’appel high.

Lecture des résultats

Voici les chiffres d’une exécution représentative sur le prompt de rate limiting :

Effort

Tokens de sortie

Latence

Coût/appel

Auto‑score

Low

3 276

46 s

0,0827 $

72/100

Medium

5 109

70 s

0,1285 $

82/100

High

5 382

71 s

0,1354 $

92/100

Voici quelques points marquants :

  • Les comptes de tokens sont croissants (3 276 -> 5 109 -> 5 382) avec end_turn comme motif d’arrêt pour les trois. Cela confirme que le paramètre d’effort est bien pris en compte et que les réponses se terminent naturellement.

  • Le saut de qualité de low à medium (+10 points) coûte 0,046 $ par appel. Le saut de medium à high (+10 points) ne coûte que 0,007 $. Medium capte l’essentiel du gain de qualité pour un coût incrémental environ six fois plus faible que le dernier palier vers high.

  • Le nuage de points rend cette asymétrie évidente. Low est en bas à gauche, peu cher mais moins complet. Medium est au centre. High est en haut à droite, mais seulement un peu plus cher que medium car l’écart de tokens entre ces deux niveaux est faible sur ce prompt.

  • La justification du score par Haiku est également instructive. Sur la réponse low : « forte profondeur technique sur la sélection d’algorithmes et le scripting Lua, mais manque de logique concrète de routage pod‑vers‑shard et de détails sur la coordination du sharding entre pods ». Sur high : « réponse exceptionnelle couvrant les cinq exigences avec une profondeur de niveau production, incluant des scripts EVALSHA et des exemples concrets de cas limites ».

Conclusion

Dans ce tutoriel, nous avons construit une application Streamlit qui exécute trois appels API réels à Claude Opus 4.8 (un par niveau d’effort), note automatiquement chaque réponse avec Haiku 4.5 et projette les coûts selon votre volume de tâches.

Les idées clés sont que effort et thinking: {type: \"adaptive\"} fonctionnent de concert pour contrôler la profondeur de raisonnement, et qu’un modèle juge économique peut remplacer une notation manuelle par une grille reproductible à coût négligeable. Nous avons aussi vu que la frontière coût‑qualité n’est pas linéaire : l’effort medium capte la majeure partie du gain de qualité pour une fraction du coût incrémental de high.

À 1 000 tâches/jour, passer de high à medium économise environ 207 $/mois pour une baisse d’environ 10 points sur une grille sur 100. Pour des tâches bien cadrées comme la classification, l’extraction ou le résumé, medium est souvent suffisant. En revanche, pour des tâches nécessitant un raisonnement technique poussé ou une couverture exhaustive des cas limites (revue de code, conception d’architectures, analyse de politiques), l’effort high justifie son coût.

Parmi les pistes d’extension : ajouter un second type de prompt pour montrer comment l’écart d’effort se réduit quand le problème ne requiert pas de raisonnement profond, ou une vue côte à côte qui met en évidence précisément où l’effort high apporte des détails concrets manquants en medium.

FAQ du tutoriel API Claude Opus 4.8

Pourquoi ne puis-je pas régler la température sur Claude Opus 4.8 ?

Claude Opus 4.8 ne prend pas en charge la modification des paramètres d’échantillonnage. Passer une valeur non par défaut pour temperature, top_p ou top_k renvoie une erreur 400. Utilisez des techniques de prompting pour orienter le style de réponse.

Que se passe‑t‑il si j’ignore thinking: {type: \"adaptive\"} ?

Le paramètre effort n’a aucun effet sans l’activation du raisonnement adaptatif. Sans cela, les trois appels se comportent de façon identique, quelle que soit la valeur d’effort passée.

Pourquoi ne pas lancer les trois appels en parallèle ?

C’est possible, et cela réduirait le temps total d’environ deux tiers. L’approche séquentielle de ce tutoriel est intentionnelle : elle fait apparaître les cartes de progression une par une, ce qui rend le déroulé plus lisible en contexte de démo. En production, encapsulez les trois appels à call_opus dans concurrent.futures.ThreadPoolExecutor() pour les exécuter en parallèle.

Le notateur automatique Haiku est‑il fiable ?

Avec une grille aux dimensions concrètes et un prompt de notation clair, Haiku 4.5 est cohérent d’un run à l’autre sur la même réponse. Ce n’est pas un substitut à l’évaluation humaine pour des enjeux critiques, mais c’est suffisant pour ancrer un nuage de points coût‑qualité et éviter de lire trois longues réponses à chaque exécution.

Quel est le coût total d’une exécution de démo ?

Sur le prompt de rate limiting avec MAX_OUTPUT_TOKENS=16000, une exécution complète coûte environ 0,35 $ pour la génération du modèle, plus moins de 0,002 $ pour les trois notations Haiku ; soit environ 0,35 $ au total.


Aashi Dutt's photo
Author
Aashi Dutt
LinkedIn
Twitter

Je suis experte Google Developers en ML (Gen AI), triple experte Kaggle et ambassadrice Women Techmakers, avec plus de trois ans d’expérience dans la tech. J’ai cofondé une startup dans le domaine de la santé en 2020 et je poursuis actuellement un master en informatique à Georgia Tech, avec une spécialisation en apprentissage automatique.

Sujets
Intelligence artificielle
Grands modèles linguistiques

Formez-vous à l’IA avec DataCamp !

Cours

Introduction aux modèles Claude

3 h
14.4K
Découvrez comment utiliser Claude avec l'API Anthropic pour résoudre des problèmes concrets et créer des applications basées sur l'IA.
Afficher les détailsRight Arrow
Commencer Le Cours
Voir plusRight Arrow