Vai al contenuto principale

Tutorial API di Claude Opus 4.8: Regolare il parametro Effort

Crea un'app Streamlit che esegue Claude Opus 4.8 con ragionamento adattivo, valuta automaticamente ogni risposta con Haiku 4.5 e mostra il compromesso costo-qualità.
Aggiornato 29 set 2026  · 11 min leggi

Scopri con l'IA

ChatGPTClaudePerplexity

La maggior parte dei benchmark confronta i modelli tra loro. Ma quando costruisci una pipeline di AI in produzione, la domanda più utile è spesso più semplice: quanto deve “impegnarsi” lo stesso modello su un dato task, e quanto costa?

Claude Opus 4.8 introduce un parametro effort con vari livelli che controlla direttamente quanta capacità di ragionamento il modello applica. Un effort più alto significa più token di thinking, migliore copertura dei casi limite e una risposta più lunga. Significa anche maggiore latenza e costo.

In questo tutorial, costruiremo un'app Streamlit che rende concreto e misurabile questo compromesso. L'app esegue tre chiamate API (una per livello di effort) sullo stesso prompt, assegna automaticamente un punteggio a ciascuna risposta usando Claude Haiku 4.5 come giudice basato su rubric, e mostra una curva interattiva di proiezione dei costi così puoi vedere esattamente quale livello di effort ha senso per il tuo volume di task.

Alla fine di questo tutorial, saprai come:

  • Passare il parametro effort nell'API di Claude Opus 4.8

  • Usare thinking: {type: "adaptive"} come flag complementare richiesto

  • Valutare i risultati del modello in modo programmatico con un modello giudice più economico

  • Costruire una UI di proiezione dei costi con Streamlit

Che cos'è Claude Opus 4.8?

Claude Opus 4.8 è il modello di punta di Anthropic large language model (LLM). È projetto per ragionamento complesso, coding agentico a lungo orizzonte e task ad alta autonomia in cui il modello deve restare coerente su molti passaggi, gestire contesti estesi e ridurre gli errori di compattazione.

Alcuni aspetti che rendono Opus 4.8 diverso dai modelli Claude precedenti includono:

  • Finestra di contesto da 1M token: Disponibile di default su Claude API, Amazon Bedrock e Vertex AI

  • 128k token di output massimi: I token di output aiutano a produrre artefatti long-form senza troncamenti

  • Ragionamento adattivo: Il modello decide a ogni turno se è necessario un ragionamento profondo, invece di bruciare sempre un budget di thinking fisso

  • Parametro effort: Una manopola per controllare profondità di ragionamento e costo per richiesta

  • Soglia cache prompt più bassa:  La soglia di prompt memorizzabili in cache è scesa da ~2.048 a 1.024 token, il che significa che più prompt di sistema ora ottengono cache hit senza modifiche al codice

Se stai confrontando Opus 4.8 con l'altra opzione di frontiera, il nostro confronto Claude Opus 4.8 vs. GPT-5.5 spiega dove ciascun modello eccelle su coding e ragionamento.

Opus 4.8 inoltre inasprisce alcune limitazioni API ereditate da Opus 4.7, tra cui temperature, top_p e top_k che non possono essere impostati su valori non predefiniti, e i budget di thinking estesi tramite budget_tokens non sono più supportati. Entrambi restituiscono un errore 400 se passati. 

Il parametro effort e il ragionamento adattivo li sostituiscono come leve principali per controllare il comportamento di ragionamento.

Che cos'è il parametro effort?

Claude Opus 4.8 ha aggiunto un parametro effort che ti permette di controllare la profondità di ragionamento per richiesta. Pensalo come una manopola con tre posizioni:

  • low: Il modello risponde in modo più diretto, usando meno token di thinking, adatto a task ben delimitati, fattuali o strutturati.

  • medium: Una via di mezzo in cui il modello ragiona con più attenzione ma senza esplorare in modo esaustivo i casi limite.

  • high: È il valore predefinito del modello, con maggiore profondità di thinking, ideale per ragionamento complesso, problemi di progettazione multi-step e task in cui perdere un caso limite è costoso.

  • xhigh: Progettato per task di coding e agentici a lungo orizzonte che richiedono coerenza sostenuta su molti passaggi.

  • max: Il livello di capacità massimo assoluto, che riserva l'intero budget di calcolo ai task più impegnativi.

Il parametro effort funziona solo insieme a thinking: {type: "adaptive"}. Il ragionamento adattivo consente al modello di decidere a ogni turno se è necessario un ragionamento profondo. Insieme, ti danno un controllo effettivo su come il modello spende il suo budget computazionale.

Questo tutorial si concentra su low, medium e high, i tre livelli più rilevanti per carichi di lavoro generici in produzione, ma la stessa struttura dell'app si applica se vuoi estenderla a xhigh o max.

Quei livelli di effort superiori sono pensati per lavoro agentico sostenuto — il tipo illustrato nel nostro tutorial Sviluppo guidato da specifiche con Claude Code.

Costruire una manopola di effort per misurare il compromesso qualità vs costo di Claude Opus 4.8

L'app è un singolo file Python. Quando la esegui, vedi una sidebar con un campo per il prompt e un cursore per i task al giorno, e un pannello principale con quattro tab: 

  • Grafico a dispersione qualità vs costo
  • Grafici di proiezione del volume
  • Risposte grezze
  • Una tabella dei risultati scaricabile

Il flusso di lavoro a ogni esecuzione è:

  1. Lancia tre chiamate API sequenziali a claude-opus-4-8 con effort impostato rispettivamente su low, medium e high
  2. Passa ciascuna risposta a claude-haiku-4-5 come valutatore basato su rubric
  3. Mostra tutti i risultati nella UI di Streamlit con cursori regolabili per l'override manuale

Il prompt predefinito è una domanda di progettazione di sistemi distribuiti che scala naturalmente con il livello di effort.

Demo

Step 0: Prerequisiti

Per seguire questo tutorial, ti serviranno:

  • Python 3.10 o successivo

  • Una chiave API Anthropic con accesso a claude-opus-4-8

  • Familiarità di base con Streamlit

Step 1: Installa le dipendenze

Crea un ambiente virtuale e installa i pacchetti richiesti:

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

Su Windows, usa .venv\Scripts\activate invece di source .venv/bin/activate.

Aggiungi la tua chiave API Anthropic a un file .env nella root del progetto:

.env
ANTHROPIC_API_KEY=sk-ant-...

Una volta configurato l'ambiente, definiremo lo schema effort_dial per la UI.

Step 2: Imposta le costanti del progetto

Crea un file chiamato effort_dial.py e aggiungi il blocco di configurazione all'inizio:

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}

Il valore di MAX_OUTPUT_TOKENS è impostato a 16.000 invece del default 4.096. Questo è importante perché con un limite di 4.096 token, sia medium che high vengono troncati allo stesso punto, cancellando qualsiasi differenza visibile tra loro. Serve abbastanza margine perché le risposte possano differenziarsi naturalmente. 

La ragione di stop end_turn nell'output finale, anziché max_tokens, conferma anche che il modello ha terminato secondo i propri criteri.

Step 3: Imposta il prompt predefinito

Un prompt semplice come "spiega la ricorsione" produce risposte simili ai tre livelli di effort perché non c'è nulla di abbastanza difficile da attivare un ragionamento profondo. Quindi, abbiamo usato invece una domanda di progettazione di sistemi multi-parte:

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.
"""

Questo prompt ha cinque sottoproblemi distinti. Gli approcci a basso effort tendono ad affrontarli in superficie, toccando i concetti principali ma saltando i dettagli di implementazione. Un effort alto copre le condizioni limite, include esempi di scripting Lua, discute l'atomicità di EVALSHA e aggiunge un piano di load testing con soglie concrete.

Step 4: Chiama Claude Opus 4.8 con il parametro effort

Ora definisci la funzione core che chiama il modello. I due dettagli critici qui sono thinking={"type": "adaptive"} e 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,
    }

Esaminiamo le decisioni chiave in questa funzione.

  • thinking={"type": "adaptive"}: È richiesto perché il parametro effort abbia effetto. Senza, tutte e tre le chiamate si comportano in modo identico indipendentemente dal valore passato in effort. Il ragionamento adattivo consente al modello di decidere, a ogni turno, se è necessario un ragionamento profondo e quindi controlla quanto budget allocare quando ragiona.

  • output_config={"effort": effort}: Questo è il modo in cui l'SDK Python di Anthropic ti consente di passare parametri non ancora esposti come argomenti nominati. Internamente, unisce il dizionario nel body grezzo della richiesta API prima dell'invio.

  • time.perf_counter(): Questo metodo viene usato per la latenza wall-clock invece di time.time() perché perf_counter ha una risoluzione più alta e non è influenzato da regolazioni dell'orologio di sistema. 

  • response.content: Opus 4.8 con ragionamento adattivo può restituire più blocchi di contenuto, incluso un blocco di thinking con la catena di ragionamento interna del modello e un blocco di testo con la risposta finale. Per la visualizzazione vogliamo solo il blocco di testo. La chiamata a next() salta il blocco di thinking ed estrae solo la risposta per l'utente.

  • hit_token_cap: È un flag diagnostico impostato su True quando out_tokens è uguale a MAX_OUTPUT_TOKENS. Se questo è True per medium o high, significa che la risposta è stata interrotta a metà. 

Step 5: Valuta automaticamente le risposte con Claude Haiku 4.5

Valutare manualmente tre risposte lunghe a ogni esecuzione è lento e soggettivo. Al contrario, passa ciascuna risposta a Claude Haiku 4.5 con una rubric e fai restituire un punteggio strutturato in JSON.

Usare un modello per valutare un altro è una forma di valutazione LLM-as-a-judge; la nostra guida alla valutazione degli LLM copre come progettare rubric di cui ci si può fidare davvero.

La rubric scompone la qualità in quattro sotto-punteggi:

  • completezza, 
  • correttezza, 
  • copertura dei casi limite, e 
  • implementazione 

Questi mappano direttamente su ciò che differenzia i livelli di effort in un prompt di progettazione di sistemi:

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,
    }

L'helper extract_json gestisce i casi in cui Haiku occasionalmente avvolge il suo output JSON tra fence Markdown. Questo è un caso di errore comune quando si fa prompting di modelli più piccoli. 

Il costo totale della valutazione per tutte e tre le risposte è in genere inferiore a 0,002 $. È un overhead trascurabile che sostituisce un passaggio manuale soggettivo con una rubric ripetibile.

Step 6: Costruisci i grafici Plotly

L'app include tre grafici:

  • Un grafico a dispersione del punteggio di qualità rispetto al costo per chiamata, che mostra la forma della frontiera costo-qualità. 
  • Un grafico a barre raggruppate che confronta i token di output e il costo giornaliero a un volume di task configurabile. 
  • Un grafico a linee che mostra il costo giornaliero cumulativo tra 100 e 5.000 task al giorno.

Costruiamo ciascuno di questi grafici:

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

Ogni grafico risponde a una domanda diversa sugli stessi tre punti dati.

  • quality_tradeoff_chart mette il costo sull'asse x e il punteggio di qualità sull'asse y, così la forma della frontiera è immediatamente visibile. 

  • projection_chart prende l'ancora del costo per chiamata dai risultati dell'API e la moltiplica per il valore del cursore “task al giorno”, così ogni barra riflette un conteggio di token reale invece di una stima. 

  • breakeven_chart varia da 100 a 5.000 task al giorno usando le stesse ancore di costo reali, così le linee divergono a un tasso ancorato alle risposte dell'API. Un team che esegue 500 task al giorno vede un quadro dei costi molto diverso da uno che ne esegue 5.000, e questo grafico lo rende concreto.

Nel prossimo step, collegheremo queste tre funzioni di grafico alla UI di Streamlit e ai risultati live dell'API.

Step 7: Costruisci la UI in Streamlit

L'ultimo pezzo è l'interfaccia Streamlit. La sidebar gestisce il recupero della chiave, l'input del prompt e il cursore dei task al giorno. Il pannello principale esegue le chiamate in sequenza e aggiorna le schede di avanzamento man mano che ognuna termina.

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 sidebar gestisce tutta la configurazione, inclusi il caricamento della chiave API, l'inserimento del prompt e il cursore dei task al giorno, mantenendo il pannello principale pulito per i risultati. Il pannello principale è diviso in quattro tab per mantenere il flusso di lavoro strutturato.

Quality vs cost

  • La tab qualità vs costo è la vista principale. Mostra il grafico a dispersione con il costo sull'asse x, la qualità sull'asse y, insieme a un riepilogo testuale dei delta di costo e qualità tra i passaggi. 

Volume projection

  • La tab proiezione del volume è lo strato di supporto decisionale. Mostra il grafico a barre raggruppate e il grafico a linee di break-even, così chi legge può proiettare i costi del trade-off di effort sul proprio volume di task reale usando il cursore nella sidebar.
  • La tab risposte grezze mostra l'output completo del modello a ciascun livello di effort in sezioni espandibili, con la motivazione dell'auto-punteggio di Haiku fissata sopra ognuna. Qui chi legge può valutare le risposte effettive e calibrare se i punteggi di qualità sono plausibili.

Raw data

  • La tab dati grezzi mostra i risultati completi come DataFrame, con un pulsante per scaricare il CSV, utile per chi desidera eseguire la demo più volte con prompt diversi e confrontare le esecuzioni fuori dall'app.

Con backend, grafici e UI pronti, l'app è pronta all'uso.

Step 8: Esegui l'app

Avvia il server Streamlit:

streamlit run effort_dial.py

Apri http://localhost:8501 e mantieni il prompt predefinito o sostituiscilo con qualcosa della tua pipeline, quindi fai clic su  Run 3 calls.

L'app esegue ciascun livello di effort in sequenza, mostrando una scheda di stato dopo ogni completamento. Una volta terminati tutti e tre, esegue le tre chiamate di scoring con Haiku e popola i grafici. Il tempo totale di wall-clock è in genere 2–3 minuti per il prompt predefinito, per lo più dovuto alla chiamata ad alto effort.

Lettura dei risultati

Ecco i numeri di un'esecuzione rappresentativa sul prompt di rate limiting:

Effort

Token di output

Latenza

Costo/chiamata

Auto-score

Low

3.276

46s

$0.0827

72/100

Medium

5.109

70s

$0.1285

82/100

High

5.382

71s

$0.1354

92/100

Ecco alcuni aspetti che ci hanno colpito:

  • I conteggi dei token sono ordinati (3.276 -> 5.109 -> 5.382) con end_turn come motivo di stop su tutti e tre. Questo conferma che il parametro di effort viene applicato correttamente e che le risposte terminano in modo naturale.

  • Il salto di qualità da low a medium (+10 punti) costa 0,046 $ per chiamata. Il salto da medium a high (+10 punti) costa solo 0,007 $. Medium cattura la maggior parte del guadagno di qualità con un costo incrementale circa sei volte inferiore rispetto all'ultimo passaggio a high.

  • Il grafico a dispersione rende questa asimmetria immediatamente visibile. Low sta in basso a sinistra, economico ma meno completo. Medium è a metà grafico. High è in alto a destra, ma solo leggermente più costoso di medium perché la differenza di token tra quei due livelli è piccola su questo prompt.

  • La motivazione del valutatore Haiku è anche istruttiva. Sulla risposta low ha notato: "forte profondità tecnica sulla selezione degli algoritmi e sullo scripting Lua, ma mancano logiche concrete di routing da pod a shard e dettagli su come il sharding si coordina tra i pod." Su high: "risposta eccezionale che copre tutte e cinque le richieste con profondità da produzione, con script EVALSHA ed esempi concreti di casi limite."

Conclusioni

In questo tutorial, abbiamo costruito un'app Streamlit che esegue tre vere chiamate API a Claude Opus 4.8 (una per livello di effort), valuta automaticamente ogni risposta con Haiku 4.5 e proietta i costi sul tuo volume di task. 

Le idee chiave sono che effort e thinking: {type: "adaptive"} lavorano insieme per controllare la profondità del ragionamento, e che un modello giudice economico può sostituire la valutazione manuale con una rubric ripetibile a costo trascurabile. Abbiamo anche visto che la frontiera costo-qualità non è lineare: l'effort medium cattura gran parte del guadagno di qualità a una frazione del costo incrementale dell'effort high.

A 1.000 task al giorno, passare da high a medium fa risparmiare circa 207 $/mese con una riduzione del punteggio di qualità di circa 10 punti su una rubric da 100 punti. Per task ben definiti come classificazione, estrazione o sintesi, medium è probabilmente sufficiente. Tuttavia, per task che richiedono ragionamento tecnico profondo o copertura completa dei casi limite, come code review, progettazione di architetture e analisi di policy, l'effort high vale il suo costo. 

Alcune estensioni possibili includono l'aggiunta di un secondo tipo di prompt per mostrare come il delta di effort collassi quando il problema non richiede ragionamento profondo, o l'aggiunta di una vista di risposte affiancate che evidenzi esattamente dove l'high effort aggiunge dettagli concreti che il medium manca.

FAQ sul tutorial API di Claude Opus 4.8

Perché non posso impostare la temperatura su Claude Opus 4.8?

Claude Opus 4.8 non supporta l'override dei parametri di sampling. Passare un temperature, top_p o top_k non predefinito restituisce un errore 400. Usa tecniche di prompting per guidare lo stile della risposta.

Cosa succede se salto thinking: {type: "adaptive"}?

Il parametro effort non ha alcun effetto senza il ragionamento adattivo abilitato. Senza questo, tutte e tre le chiamate si comportano in modo identico indipendentemente dal valore di effort passato.

Perché non eseguire tutte e tre le chiamate in parallelo?

Puoi farlo, e ridurrebbe il tempo totale di circa due terzi. L'approccio sequenziale in questo tutorial è intenzionale perché fa aggiornare le schede di avanzamento una alla volta, rendendo il flusso più leggibile in una demo. Incapsula le tre chiamate a call_opus in concurrent.futures.ThreadPoolExecutor() se vuoi l'esecuzione in parallelo in produzione.

Quanto è affidabile l'auto-valutatore Haiku?

Con una rubric con dimensioni concrete e un prompt di valutazione chiaro, Haiku 4.5 è coerente tra le esecuzioni sulla stessa risposta. Non sostituisce la valutazione umana in task ad alto impatto, ma è sufficiente per ancorare un grafico a dispersione costo-qualità ed evitare di dover leggere manualmente tre risposte lunghe a ogni esecuzione.

Qual è il costo totale di una singola esecuzione della demo?

Sul prompt di rate limiting con MAX_OUTPUT_TOKENS=16000, un'esecuzione completa costa circa 0,35 $ in output del modello più meno di 0,002 $ per le tre chiamate di scoring con Haiku, cioè intorno a 0,35 $ in totale.


Aashi Dutt's photo
Author
Aashi Dutt
LinkedIn
Twitter

Sono una Google Developers Expert in ML (Gen AI), una Kaggle 3x Expert e una Women Techmakers Ambassador con oltre 3 anni di esperienza nel tech. Ho co-fondato una startup health-tech nel 2020 e sto conseguendo un master in informatica al Georgia Tech, con specializzazione in machine learning.

Argomenti
Intelligenza artificiale
Large Language Models

Impara l'AI con DataCamp!

Corso

Introduzione ai modelli Claude

3 ore
14.7K
Scopri come usare Claude con l'API di Anthropic per risolvere problemi del mondo reale e creare app basate sull'intelligenza artificiale.
Vedi i dettagliRight Arrow
Inizia Il Corso
Mostra altroRight Arrow
Correlato

blog

Tokenizzazione nel NLP: come funziona, sfide e casi d'uso

Guida al preprocessing NLP nel machine learning. Copriamo spaCy, i transformer di Hugging Face e come funziona la tokenizzazione in casi d'uso reali.
Abid Ali Awan's photo

Abid Ali Awan

10 min

blog

I 15 migliori server MCP remoti che ogni AI builder dovrebbe conoscere nel 2026

Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Abid Ali Awan's photo

Abid Ali Awan

15 min

blog

Che cos'è Snowflake? Guida per principianti alla piattaforma dati cloud

Esplora le basi di Snowflake, la piattaforma dati cloud. Scopri la sua architettura, le sue funzionalità e come integrarla nelle tue pipeline di dati.
Tim Lu's photo

Tim Lu

12 min

Mostra AltroMostra Altro