跳至主要内容

Claude Opus 4.8 API 教程:调优 effort 参数

构建一个 Streamlit 应用,以自适应思考方式运行 Claude Opus 4.8,使用 Haiku 4.5 为每个响应自动评分,并绘制成本与质量的权衡。
· 11分钟 阅读

使用 AI 探索

大多数基准测试都是在不同模型之间横向比较。但在构建生产级 AI 流水线时,更有用的问题往往更简单:同一个模型在给定任务上应该投入多大“力气”,这需要付出多少成本?

Claude Opus 4.8 介绍了一个 effort 参数,包含多个级别,可直接控制模型投入的推理量。更高的 effort 意味着更多思考令牌、更好的边界情况覆盖以及更长的输出;同时也意味着更高的延迟与成本。

在本教程中,我们将构建一个 Streamlit 应用,使这种权衡变得具体且可度量。该应用对同一提示运行三次 API 调用(每个 effort 级别一次),使用 Claude Haiku 4.5 作为基于量表的评审对每个响应进行自动评分,并渲染交互式成本预测曲线,帮助您根据任务量直观看到哪个 effort 级别更合适。

在教程结束时,您将学会:

  • 在 Claude Opus 4.8 API 中传递 effort 参数

  • 使用必需的配套标志 thinking: {type: "adaptive"}

  • 使用更便宜的评审模型以编程方式为模型输出打分

  • 用 Streamlit 构建成本预测界面

什么是 Claude Opus 4.8?

Claude Opus 4.8 是 Anthropic 的旗舰大语言模型(LLM)。它被设计用于复杂推理、长周期的代理式编码,以及高自治度的任务场景,模型需要在多步过程中保持连贯、处理大上下文,并尽量减少压缩错误。

Opus 4.8 相比早期 Claude 模型的不同之处包括:

  • 100 万令牌上下文窗口:默认可在 Claude API、Amazon Bedrock 和 Vertex AI 上使用

  • 128k 最大输出令牌:支持生成长文本产物而不被截断

  • 自适应思考:模型按轮次判断是否需要深度推理,而不是固定消耗一个思考预算

  • effort 参数:用于控制单次请求的推理深度与成本的调节旋钮

  • 更低的提示缓存门槛:可缓存提示阈值从约 2,048 令牌降至 1,024 令牌,意味着更多系统提示无需改动代码即可获得缓存命中

如果您在权衡 Opus 4.8 与另一前沿选项,我们的 Claude Opus 4.8 与 GPT-5.5 对比拆解了两者在编码与推理方面各自的优势。

Opus 4.8 也收紧了从 Opus 4.7 延续的一些 API 约束,包括 temperature、top_p 和 top_k 不能设置为非默认值,以及通过 budget_tokens 扩展思考预算不再支持。若传入,将返回 400 错误。

effort 参数与自适应思考共同成为控制推理行为的主要手段。

什么是 effort 参数?

Claude Opus 4.8 增加了 effort 参数,可让您按请求控制推理深度。将其视为一个具有三种档位的旋钮:

  • low:模型更直接地回答,使用更少的思考令牌,适合范围明确、基于事实或结构化的任务。

  • medium:居于中间地带,模型推理更谨慎,但不会穷尽边界情况。

  • high:模型的默认值,具有更深的思考深度,最适合复杂推理、多步设计问题,以及错过边界情况代价较高的任务。

  • xhigh:为长周期的代理式与编码任务设计,需要在多步中保持持续连贯。

  • max:绝对最大能力级别,为最苛刻的任务预留完整算力预算。

effort 参数只能与 thinking: {type: "adaptive"} 搭配使用。自适应思考允许模型在每轮决定是否需要深度推理。两者结合,能让您对模型如何使用算力预算进行有效控制。

本教程聚焦 low、medium 与 high,它们对通用生产工作负载最相关;如果您想扩展到 xhigh 或 max,应用结构相同。

更高的 effort 级别面向持续的代理式工作——这类用法可参考我们的 使用 Claude Code 的规格驱动开发 教程。

构建一个 Effort 旋钮,度量 Claude Opus 4.8 的质量与成本权衡

该应用是一个单文件 Python 脚本。运行后,左侧边栏包含提示输入与每日任务量滑块,主面板包含四个选项卡:

  • 质量与成本散点图
  • 任务量预测图
  • 原始响应
  • 可下载的结果表

每次运行的工作流为:

  1. 对 claude-opus-4-8 顺序发起三次 API 调用,effort 分别设为 low、medium 与 high
  2. 将每个响应交给 claude-haiku-4-5,按量表进行评分
  3. 在 Streamlit UI 中渲染所有结果,并提供可调滑块用于手动覆盖

默认提示是一个分布式系统设计问题,会随 effort 级别自然扩展。

Demo

第 0 步:先决条件

要跟随本教程,您需要:

  • Python 3.10 或更高版本

  • 具有 claude-opus-4-8 访问权限的 Anthropic API 密钥

  • 对 Streamlit 的基本了解

第 1 步:安装依赖

创建虚拟环境并安装必需的包:

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

在 Windows 下,使用 .venv\Scripts\activate 替代 source .venv/bin/activate。

将您的 Anthropic API 密钥添加到项目根目录的 .env 文件:

.env
ANTHROPIC_API_KEY=sk-ant-...

环境设置完毕后,我们将定义 UI 的 effort_dial 架构。

第 2 步:设置项目常量

创建名为 effort_dial.py 的文件,并在文件顶部添加如下配置块:

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}

将 MAX_OUTPUT_TOKENS 设为 16,000 而非默认的 4,096。原因在于,当上限为 4,096 时,medium 与 high 两个级别会在同一点被截断,肉眼不可区分。需要足够的“抬头空间”让两者响应自然拉开差距。

最终输出中的 end_turn 作为停止原因,而非 max_tokens,也能确认模型是“自然收尾”而非被截断。

第 3 步:设置默认提示

像“解释递归”这样的简单提示,在三个 effort 级别下给出的答案会很相似,因为不足以触发深度推理。因此我们改用一个多部分的系统设计问题:

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

该提示包含五个彼此独立的子问题。低 effort 往往流于表面,只覆盖关键概念而忽略实现细节;高 effort 会覆盖边界条件,包含 Lua 脚本示例、讨论 EVALSHA 的原子性,并给出包含具体阈值的压测方案。

第 4 步:使用 effort 参数调用 Claude Opus 4.8

现在定义调用模型的核心函数。两个关键点是 thinking={"type": "adaptive"} 与 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,
    }

下面逐条解释这些关键决策。

  • thinking={"type": "adaptive"}:这是让 effort 参数生效的必要条件。否则,无论传什么 effort,三次调用表现都一样。自适应思考让模型在每轮判断是否需要深度推理,并在需要时决定分配多少预算。

  • output_config={"effort": effort}:Anthropic Python SDK 通过此方式传递尚未暴露为具名参数的字段。内部会在发送前将该字典合并进原始 API 请求体。

  • time.perf_counter():用于测量墙钟延迟,而非 time.time(),因为 perf_counter 具有更高分辨率,且不受系统时钟调整影响。

  • response.content:启用自适应思考的 Opus 4.8 可能返回多个内容块,包括包含内部推理链的思考块,以及包含最终答案的文本块。我们只需要用于展示的文本块。next() 会跳过思考块,仅提取用户可见的答案。

  • hit_token_cap:当 out_tokens 等于 MAX_OUTPUT_TOKENS 时,该诊断标志为 True。若 medium 或 high 为 True,说明响应在中途被截断。

第 5 步:用 Claude Haiku 4.5 自动评分

每次运行人工为三份长答案打分既慢又主观。改为将每个响应与量表一并交给 Claude Haiku 4.5,并让其返回结构化的 JSON 分数。

用一个模型评价另一个模型是一种“LLM 作为评审”的评测形式;我们的 LLM 评测 指南介绍了如何设计值得信赖的评分量表。

量表将质量拆分为四个子分项:

  • 完整性,
  • 正确性,
  • 边界情况覆盖,以及
  • 实现细节

这些与系统设计提示下各 effort 级别的差异点一一对应:

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

extract_json 帮助函数用于处理 Haiku 偶尔将 JSON 输出包裹在 Markdown 栅栏中的情况。这是小模型提示时的常见失败模式。

为三份响应打分的总成本通常低于 $0.002。该开销可以忽略,却用可复用的量表替代了主观的人工评审。

第 6 步:构建 Plotly 图表

该应用包含三张图:

  • 质量分数与单次调用成本的散点图,展示成本-质量前沿的形状。
  • 在可配置任务量下,比较输出令牌与每日成本的分组柱状图。
  • 展示每日 100–5,000 个任务的累计每日成本的折线图。

我们来构建这些图表:

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

每张图都在回答关于这三组数据的不同问题。

  • quality_tradeoff_chart 将成本置于 x 轴、质量分数置于 y 轴,使前沿形状一目了然。

  • projection_chart 以 API 结果中的单次调用成本为锚点,乘以每日任务量滑块的值,因此每根柱子都反映真实的令牌计数而非估算。

  • breakeven_chart 使用同样的真实成本锚点对每日 100 至 5,000 个任务进行扫描,因此曲线的分叉速率建立在 API 响应之上。每天 500 个任务的团队与每天 5,000 个任务的团队看到的成本图景截然不同,这张图将其量化呈现。

下一步,我们将把这三张图表函数接入 Streamlit UI,并与实时 API 结果联动。

第 7 步:构建 Streamlit 界面

最后一部分是 Streamlit 界面。侧边栏负责密钥加载、提示输入与每日任务量滑块。主面板顺序运行调用,并在每次完成后更新进度卡片。

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()

侧边栏承载所有配置,包括加载 API 密钥、输入提示与每日任务量滑块,使主面板保持聚焦于结果。主面板分为四个选项卡,以保持流程清晰有序。

Quality vs cost

  • 质量与成本选项卡是主视图。它绘制以成本为横轴、质量为纵轴的散点图,并配有逐步成本与质量增量的文字摘要。

Volume projection

  • 任务量预测选项卡是决策支持层。它展示分组柱状图与盈亏平衡折线图,读者可结合侧边栏滑块,在自己的真实任务量下预测 effort 权衡成本。
  • 原始响应选项卡以可展开的分节形式展示各 effort 级别的完整模型输出,并在每段上方固定显示 Haiku 自动评分的理由。读者可以在此阅读真实答案,并校准质量分数是否合理。

Raw data

  • 原始数据选项卡以 DataFrame 展示完整结果,并提供 CSV 下载按钮,适合希望用不同提示多次运行并在应用外对比结果的读者。

后端、图表与 UI 均已就绪,应用可以运行。

第 8 步:运行应用

启动 Streamlit 服务器:

streamlit run effort_dial.py

打开 http://localhost:8501,保持默认提示或替换为您流水线中的真实问题,然后点击 Run 3 calls。

应用会依次运行三个 effort 级别,并在每次完成后显示状态卡片。三次结束后,会运行三次 Haiku 评分调用并填充图表。对于默认提示,总墙钟时间通常为 2–3 分钟,其中大部分时间在 high effort 调用。

解读结果

以下是一轮具有代表性的限流提示运行结果:

Effort

Output tokens

Latency

Cost/call

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

这里有几点值得注意:

  • 令牌数单调递增(3,276 -> 5,109 -> 5,382),三者的停止原因均为 end_turn。这确认了 effort 参数生效,且响应自然结束。

  • 从 low 到 medium 的质量提升(+10 分)每次调用多花 $0.046;从 medium 到 high 的同等提升(+10 分)只多花 $0.007。与最后一步到 high 相比,medium 以约六分之一的增量成本获得了大部分质量提升。

  • 散点图使这种不对称性一目了然。low 位于左下角,便宜但不够全面;medium 居中;high 位于右上角,但比 medium 只略贵,因为该提示下两者的令牌差距很小。

  • Haiku 评审的理由也很有参考价值。对 low 响应,它指出:“在算法选择与 Lua 脚本方面技术深度不错,但缺乏具体的 pod 到分片路由逻辑,以及分片在多 pod 之间如何协调的细节。”对 high: “覆盖了全部五项需求,具备生产级深度,包含 EVALSHA 脚本与具体边界案例示例。”

结语

在本教程中,我们构建了一个 Streamlit 应用,对 Claude Opus 4.8 进行了三次真实 API 调用(每个 effort 级别一次),使用 Haiku 4.5 为每个响应自动评分,并在您的任务量上进行成本预测。

核心思想是 effort 与 thinking: {type: "adaptive"} 协同控制推理深度;使用廉价的评审模型可用可复用的量表替代人工评分,且成本可忽略。我们还看到成本-质量前沿并非线性,即 medium 以较低的增量成本获取了大部分质量提升,而 high 的剩余提升成本更高。

在每天 1,000 个任务的规模下,从 high 切换到 medium 每月可节省约 $207,质量分数在 100 分量表上约下降 10 分。对于分类、抽取、摘要等范围明确的任务,medium 很可能足够;而对于需要深度技术推理或全面覆盖边界情况的任务,如代码评审、架构设计与政策分析,high 的成本是值得的。

后续可扩展的方向包括:增加第二类提示,展示在问题不需要深度推理时 effort 差距如何收敛;或增加并排对比视图,突出显示 high 相比 medium 在哪些地方补充了具体细节。

Claude Opus 4.8 API 教程常见问答

为什么我不能在 Claude Opus 4.8 上设置 temperature?

Claude Opus 4.8 不支持采样参数覆盖。传入非默认的 temperature、top_p 或 top_k 会返回 400 错误。请使用提示工程技巧来引导回复风格。

如果我跳过 thinking: {type: "adaptive"} 会怎样?

未启用自适应思考时,effort 参数不会生效。否则,无论传入何种 effort 值,三次调用的表现都是一样的。

为什么不并行运行三次调用?

当然可以,这样可将墙钟时间减少约三分之二。教程中采用顺序方式是出于演示考虑:进度卡片会逐一更新,使流程更清晰。若要在生产中并行执行,请将三次 call_opus 调用包裹在 concurrent.futures.ThreadPoolExecutor() 中。

Haiku 自动评分的可靠性如何?

对于具有明确维度、清晰评分提示的量表,Haiku 4.5 在对同一响应的多次评分中表现一致。它无法替代高风险任务中的人工评估,但足以支撑成本-质量散点图,并免去每次运行后人工阅读三份长答案的需要。

单次演示运行的总成本是多少?

在将 MAX_OUTPUT_TOKENS=16000 用于限流提示时,一次完整运行的模型输出成本约 $0.35,加上三次 Haiku 评分调用不足 $0.002,总计约 $0.35。

Aashi Dutt

作者

Aashi Dutt

我是一名 Google Developers 机器学习(生成式 AI)领域的专家、Kaggle 三项专家,以及 Women Techmakers 大使,拥有 3 年以上的技术从业经验。2020 年我共同创办了一家健康科技初创公司,目前在佐治亚理工学院攻读计算机科学硕士,专攻机器学习。

主题