Chuyển đến nội dung chính

Hướng dẫn API Claude Opus 4.8: Tinh chỉnh tham số Effort

Xây dựng ứng dụng Streamlit chạy Claude Opus 4.8 với tư duy thích ứng, tự chấm điểm mỗi phản hồi bằng Haiku 4.5 và vẽ biểu đồ đánh đổi chi phí - chất lượng.
· 11 phút đọc

Khám phá cùng AI

Hầu hết các bài benchmark so sánh các mô hình với nhau. Nhưng khi bạn xây dựng một pipeline AI cho sản xuất, câu hỏi hữu ích hơn thường đơn giản hơn, tức là cùng một mô hình nên nỗ lực đến mức nào cho một tác vụ nhất định, và chi phí là bao nhiêu?

Claude Opus 4.8 giới thiệu tham số effort với nhiều mức, kiểm soát trực tiếp lượng suy luận mà mô hình áp dụng. Effort cao hơn đồng nghĩa với nhiều token suy nghĩ hơn, bao phủ tốt hơn các trường hợp biên và câu trả lời dài hơn. Điều đó cũng đồng nghĩa độ trễ và chi phí cao hơn.

Trong hướng dẫn này, chúng ta sẽ xây dựng một ứng dụng Streamlit giúp biến sự đánh đổi đó trở nên cụ thể và có thể đo lường. Ứng dụng chạy ba lượt gọi API (mỗi lượt tương ứng một mức effort) trên cùng một prompt, tự động chấm điểm mỗi phản hồi bằng Claude Haiku 4.5 theo tiêu chí rubric, và hiển thị đường cong dự phóng chi phí tương tác để bạn thấy chính xác mức effort nào phù hợp với khối lượng tác vụ của mình.

Kết thúc hướng dẫn, bạn sẽ biết cách:

  • Truyền tham số effort trên API Claude Opus 4.8

  • Sử dụng thinking: {type: "adaptive"} như cờ bắt buộc đi kèm

  • Chấm điểm đầu ra của mô hình bằng lập trình với một mô hình chấm điểm rẻ hơn

  • Xây dựng giao diện dự phóng chi phí với Streamlit

Claude Opus 4.8 là gì?

Claude Opus 4.8 là mô hình ngôn ngữ lớn (LLM) chủ lực của Anthropic. Nó được thiết kế cho suy luận phức tạp, lập trình tác tử dài hạn và các tác vụ tự chủ cao, nơi mô hình cần duy trì tính mạch lạc qua nhiều bước, xử lý ngữ cảnh lớn và giảm lỗi nén thông tin.

Một vài điểm khiến Opus 4.8 khác với các phiên bản Claude trước đây gồm:

  • Context window 1M token: Có sẵn theo mặc định trên Claude API, Amazon Bedrock và Vertex AI

  • Tối đa 128k token đầu ra: Token đầu ra giúp tạo các sản phẩm nội dung dài mà không bị cắt cụt

  • Tư duy thích ứng: Mô hình quyết định theo từng lượt xem có cần suy luận sâu hay không, thay vì luôn tiêu tốn một ngân sách suy nghĩ cố định

  • Tham số effort: Núm vặn để kiểm soát độ sâu suy luận và chi phí mỗi yêu cầu

  • Giảm ngưỡng bộ nhớ đệm prompt: Ngưỡng prompt có thể lưu đệm giảm từ ~2.048 xuống 1.024 token, nghĩa là nhiều system prompt hơn sẽ được cache mà không cần đổi code

Nếu bạn đang cân nhắc Opus 4.8 với lựa chọn tiên phong khác, bài so sánh Claude Opus 4.8 vs. GPT-5.5 của chúng tôi phân tích nơi mỗi mô hình vượt trội về mã hoá và suy luận.

Opus 4.8 cũng siết chặt một vài ràng buộc API kế thừa từ Opus 4.7, bao gồm temperature, top_p và top_k không thể đặt khác mặc định, và không còn hỗ trợ mở rộng ngân sách suy nghĩ qua budget_tokens. Cả hai sẽ trả lỗi 400 nếu được truyền. 

Tham số effort và tư duy thích ứng thay thế chúng như các đòn bẩy chính để kiểm soát hành vi suy luận.

Tham số effort là gì?

Claude Opus 4.8 bổ sung tham số effort cho phép bạn kiểm soát độ sâu suy luận theo từng yêu cầu. Hãy hình dung đây là một núm vặn với ba vị trí:

  • low: Mô hình trả lời trực diện hơn, dùng ít token suy nghĩ, phù hợp với các tác vụ phạm vi rõ ràng, mang tính thực tế hoặc có cấu trúc.

  • medium: Ở mức trung gian, mô hình suy luận cẩn trọng hơn nhưng không khám phá cạn kiệt mọi trường hợp biên.

  • high: Mặc định của mô hình, tăng độ sâu suy nghĩ, phù hợp nhất cho suy luận phức tạp, bài toán thiết kế nhiều bước và các tác vụ mà việc bỏ sót trường hợp biên gây tốn kém.

  • xhigh: Thiết kế cho các tác vụ tác tử và lập trình dài hạn đòi hỏi duy trì mạch lạc qua nhiều bước.

  • max: Mức năng lực tối đa tuyệt đối, dành trọn ngân sách tính toán cho các tác vụ đòi hỏi khắt khe nhất.

Tham số effort chỉ hoạt động khi đi kèm thinking: {type: "adaptive"}. Tư duy thích ứng cho phép mô hình quyết định theo từng lượt xem có cần suy luận sâu không. Kết hợp lại, chúng giúp bạn kiểm soát có ý nghĩa cách mô hình sử dụng ngân sách tính toán.

Hướng dẫn này tập trung vào low, medium và high, ba mức liên quan nhất tới khối lượng công việc sản xuất đa mục đích, nhưng cùng cấu trúc ứng dụng này vẫn áp dụng được nếu bạn muốn mở rộng sang xhigh hoặc max.

Những mức effort cao nhất được xây dựng cho công việc tác tử kéo dài — kiểu đã được mô tả trong hướng dẫn Phát triển dựa trên đặc tả với Claude Code của chúng tôi.

Xây núm vặn Effort để đo lường đánh đổi Chất lượng so với Chi phí của Claude Opus 4.8

Ứng dụng chỉ gồm một tệp Python. Khi chạy, bạn sẽ thấy thanh bên với ô nhập prompt và thanh trượt số tác vụ mỗi ngày, cùng bảng chính với bốn tab: 

  • Biểu đồ phân tán chất lượng so với chi phí
  • Biểu đồ dự phóng theo khối lượng
  • Phản hồi thô
  • Bảng kết quả có thể tải xuống

Quy trình cho mỗi lần chạy là:

  1. Gọi ba yêu cầu API tuần tự tới claude-opus-4-8 với effort lần lượt là low, medium và high
  2. Chuyển từng phản hồi sang claude-haiku-4-5 để chấm điểm theo rubric
  3. Hiển thị toàn bộ kết quả trong UI Streamlit với các thanh trượt điều chỉnh để ghi đè thủ công

Prompt mặc định là một câu hỏi thiết kế hệ thống phân tán, có thể mở rộng tự nhiên theo mức effort.

Demo

Bước 0: Yêu cầu tiên quyết

Để theo dõi hướng dẫn này, bạn sẽ cần:

  • Python 3.10 trở lên

  • Khoá API Anthropic có quyền truy cập claude-opus-4-8

  • Hiểu biết cơ bản về Streamlit

Bước 1: Cài đặt phụ thuộc

Tạo môi trường ảo và cài các gói cần thiết:

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

Trên Windows, dùng .venv\Scripts\activate thay cho source .venv/bin/activate.

Thêm khoá API Anthropic của bạn vào tệp .env ở thư mục gốc dự án:

.env
ANTHROPIC_API_KEY=sk-ant-...

Khi môi trường đã sẵn sàng, chúng ta sẽ định nghĩa schema effort_dial cho UI.

Bước 2: Thiết lập hằng số dự án

Tạo tệp tên effort_dial.py và thêm khối cấu hình ở đầu:

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}

Giá trị MAX_OUTPUT_TOKENS đặt là 16.000 thay vì mặc định 4.096. Điều này quan trọng vì với trần 4.096 token, cả medium và high đều bị cắt ở cùng điểm, xoá đi khác biệt nhìn thấy giữa chúng. Bạn cần đủ khoảng trống để phản hồi tách bạch tự nhiên. 

Lý do dừng end_turn trong đầu ra cuối cùng, thay vì max_tokens, cũng xác nhận mô hình tự kết thúc theo điều kiện của nó.

Bước 3: Đặt prompt mặc định

Một prompt đơn giản như "giải thích đệ quy" cho câu trả lời tương tự ở cả ba mức effort vì không đủ khó để kích hoạt suy luận sâu. Vì vậy, chúng tôi dùng một câu hỏi thiết kế hệ thống nhiều phần:

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

Prompt này có năm bài toán con riêng biệt. Phương án mức thấp thường xử lý ở bề mặt, nêu khái niệm chính nhưng bỏ qua chi tiết triển khai. Mức cao bao phủ điều kiện biên, đưa ví dụ Lua script, bàn về tính nguyên tử EVALSHA và bổ sung kế hoạch kiểm thử tải với ngưỡng cụ thể.

Bước 4: Gọi Claude Opus 4.8 với tham số effort

Giờ hãy định nghĩa hàm lõi gọi mô hình. Hai chi tiết quan trọng là thinking={"type": "adaptive"} và 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,
    }

Hãy điểm qua các quyết định chính trong hàm này.

  • thinking={"type": "adaptive"}: Bắt buộc để tham số effort có hiệu lực. Nếu thiếu, cả ba lượt gọi sẽ hành xử giống hệt nhau bất kể bạn truyền effort là gì. Tư duy thích ứng cho phép mô hình quyết định theo từng lượt xem có cần suy luận sâu không, rồi kiểm soát ngân sách phân bổ khi suy luận.

  • output_config={"effort": effort}: Cách SDK Python của Anthropic cho phép bạn truyền các tham số chưa được lộ ra thành đối số có tên. Bên trong, nó trộn dict vào thân yêu cầu API thô trước khi gửi.

  • time.perf_counter(): Dùng để đo độ trễ theo thời gian thực thay vì time.time() vì perf_counter có độ phân giải cao hơn và không bị ảnh hưởng bởi điều chỉnh đồng hồ hệ thống. 

  • response.content: Opus 4.8 với tư duy thích ứng có thể trả về nhiều khối nội dung, gồm khối thinking chứa chuỗi suy luận nội bộ và khối text chứa phản hồi cuối. Chúng ta chỉ cần khối text để hiển thị. Lệnh next() bỏ qua khối thinking và chỉ trích xuất câu trả lời cho người dùng.

  • hit_token_cap: Cờ chẩn đoán đặt True khi out_tokens bằng MAX_OUTPUT_TOKENS. Nếu True với medium hoặc high, nghĩa là phản hồi bị cắt giữa chừng. 

Bước 5: Tự động chấm điểm phản hồi bằng Claude Haiku 4.5

Tự tay chấm ba phản hồi dài cho mỗi lượt chạy vừa chậm vừa chủ quan. Thay vào đó, chuyển mỗi phản hồi sang Claude Haiku 4.5 kèm rubric và để nó trả về điểm dạng JSON có cấu trúc.

Dùng một mô hình chấm một mô hình khác là dạng đánh giá LLM-as-a-judge; hướng dẫn Đánh giá LLM của chúng tôi trình bày cách thiết kế rubric đủ tin cậy.

Rubric chia chất lượng thành bốn điểm con:

  • tính đầy đủ, 
  • độ chính xác kỹ thuật, 
  • bao phủ trường hợp biên, và 
  • mức độ cụ thể khi triển khai 

Những yếu tố này khớp trực tiếp với khác biệt giữa các mức effort trên một prompt thiết kế hệ thống:

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

Hàm trợ giúp extract_json xử lý các trường hợp Haiku đôi khi bọc JSON đầu ra trong các khung Markdown. Đây là lỗi thường gặp khi prompt các mô hình nhỏ hơn. 

Tổng chi phí chấm điểm cho cả ba phản hồi thường dưới $0,002. Đây là phần chi phí không đáng kể thay thế bước thủ công chủ quan bằng một rubric có thể lặp lại.

Bước 6: Xây dựng biểu đồ Plotly

Ứng dụng có ba biểu đồ:

  • Một biểu đồ phân tán giữa điểm chất lượng và chi phí mỗi lượt gọi, cho thấy hình dạng đường biên chi phí-chất lượng. 
  • Một biểu đồ cột nhóm so sánh số token đầu ra và chi phí hàng ngày ở một khối lượng tác vụ có thể cấu hình. 
  • Một biểu đồ đường thể hiện chi phí cộng dồn theo ngày từ 100–5.000 tác vụ mỗi ngày.

Hãy xây dựng từng biểu đồ:

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

Mỗi biểu đồ trả lời một câu hỏi khác nhau về cùng ba điểm dữ liệu.

  • quality_tradeoff_chart vẽ chi phí trên trục x và điểm chất lượng trên trục y, nên hình dạng đường biên hiện ra ngay. 

  • projection_chart lấy mỏ neo chi phí mỗi lượt gọi từ kết quả API và nhân với giá trị thanh trượt tác vụ mỗi ngày, nên mỗi cột phản ánh số token thực thay vì ước lượng. 

  • breakeven_chart quét từ 100 đến 5.000 tác vụ mỗi ngày dựa trên các mỏ neo chi phí thực, khiến các đường phân kỳ với tốc độ bám sát phản hồi API. Nhóm chạy 500 tác vụ/ngày sẽ thấy bức tranh chi phí rất khác so với nhóm chạy 5.000, và biểu đồ này khiến điều đó cụ thể.

Ở bước tiếp theo, chúng ta sẽ nối ba hàm biểu đồ này vào UI Streamlit và liên kết với kết quả API trực tiếp.

Bước 7: Xây dựng UI Streamlit

Mảnh ghép cuối là giao diện Streamlit. Thanh bên xử lý nạp khoá, nhập prompt và thanh trượt tác vụ mỗi ngày. Bảng chính chạy các lượt gọi tuần tự và cập nhật thẻ tiến trình khi từng lượt hoàn tất.

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

Thanh bên xử lý toàn bộ cấu hình, bao gồm nạp khoá API, nhập prompt và thanh trượt tác vụ mỗi ngày, giúp bảng chính sạch sẽ để hiển thị kết quả. Bảng chính chia thành bốn tab để giữ quy trình làm việc có cấu trúc.

Quality vs cost

  • Tab chất lượng so với chi phí là chế độ xem chính. Nó hiển thị biểu đồ phân tán với trục x là chi phí, trục y là chất lượng, kèm tóm tắt văn bản về chênh lệch chi phí và chất lượng theo từng bước. 

Volume projection

  • Tab dự phóng theo khối lượng là lớp hỗ trợ quyết định. Nó hiển thị biểu đồ cột nhóm và biểu đồ đường điểm hoà vốn, giúp người đọc dự phóng chi phí đánh đổi effort ở khối lượng tác vụ thực tế bằng thanh trượt trên thanh bên.
  • Tab phản hồi thô hiển thị toàn bộ đầu ra mô hình ở mỗi mức effort trong các mục có thể thu gọn, với lý do chấm điểm tự động của Haiku ghim phía trên. Đây là nơi người đọc có thể xem câu trả lời thực tế và hiệu chỉnh cảm nhận xem điểm chất lượng có hợp lý không.

Raw data

  • Tab dữ liệu thô hiển thị đầy đủ kết quả dưới dạng DataFrame, kèm nút tải CSV, hữu ích cho ai muốn chạy demo nhiều lần với prompt khác nhau và so sánh các lần chạy bên ngoài ứng dụng.

Với backend, biểu đồ và UI đã sẵn sàng, ứng dụng có thể chạy.

Bước 8: Chạy ứng dụng

Khởi động máy chủ Streamlit:

streamlit run effort_dial.py

Mở http://localhost:8501 và giữ prompt mặc định hoặc thay bằng nội dung từ pipeline của bạn, rồi nhấp  Run 3 calls.

Ứng dụng chạy từng mức effort tuần tự, hiển thị thẻ trạng thái sau khi mỗi mức hoàn tất. Khi cả ba xong, ứng dụng chạy ba lượt chấm điểm Haiku và điền dữ liệu cho các biểu đồ. Tổng thời gian thực thường 2–3 phút với prompt mặc định, phần lớn là lượt gọi mức cao.

Đọc kết quả

Dưới đây là các con số từ một lần chạy đại diện với prompt giới hạn tốc độ:

Effort

Token đầu ra

Độ trễ

Chi phí/lượt gọi

Tự chấm

Low

3,276

46s

$0.0827

72/100

Medium

5,109

70s

$0.1285

82/100

High

5,382

71s

$0.1354

92/100

Dưới đây là vài điểm nổi bật:

  • Số token có thứ tự (3.276 -> 5.109 -> 5.382) với end_turn là lý do dừng ở cả ba. Điều này xác nhận tham số effort được áp dụng đúng và phản hồi hoàn tất tự nhiên.

  • Nhảy chất lượng từ thấp lên trung bình (+10 điểm) tốn $0,046 mỗi lượt. Nhảy từ trung bình lên cao (+10 điểm) chỉ tốn $0,007. Mức trung bình thu được phần lớn cải thiện chất lượng với chi phí gia tăng thấp hơn khoảng sáu lần so với bước cuối lên mức cao.

  • Biểu đồ phân tán khiến sự bất đối xứng này hiện rõ ngay. Low ở góc dưới-trái, rẻ nhưng kém đầy đủ. Medium ở giữa. High ở trên-phải, nhưng chỉ đắt hơn chút so với medium vì chênh lệch token giữa hai mức này nhỏ với prompt này.

  • Lý do của bộ chấm Haiku cũng hữu ích. Với phản hồi mức thấp, nó nhận xét: "độ sâu kỹ thuật tốt về chọn thuật toán và Lua scripting, nhưng thiếu logic định tuyến pod-to-shard cụ thể và thiếu chi tiết cách sharding phối hợp giữa các pod." Với mức cao: "phản hồi xuất sắc bao phủ đủ năm yêu cầu với chiều sâu cấp độ sản xuất, có script EVALSHA và ví dụ trường hợp biên cụ thể."

Kết luận

Trong hướng dẫn này, chúng ta đã xây dựng ứng dụng Streamlit chạy ba lượt gọi API Claude Opus 4.8 thực (mỗi lượt một mức effort), tự chấm điểm mỗi phản hồi bằng Haiku 4.5 và dự phóng chi phí theo khối lượng tác vụ của bạn. 

Các ý chính là effort và thinking: {type: "adaptive"} phối hợp để kiểm soát độ sâu suy luận, rằng một mô hình chấm điểm rẻ có thể thay thế việc chấm thủ công bằng rubric lặp lại với chi phí không đáng kể. Chúng ta cũng thấy đường biên chi phí-chất lượng không tuyến tính, tức là medium thu được phần lớn lợi ích chất lượng với một phần nhỏ chi phí gia tăng so với high.

Ở mức 1.000 tác vụ mỗi ngày, chuyển từ high sang medium tiết kiệm khoảng $207/tháng với mức giảm điểm chất lượng khoảng 10 điểm trên thang 100. Với các tác vụ phạm vi rõ như phân loại, trích xuất hoặc tóm tắt, medium có thể là đủ. Tuy nhiên, với các tác vụ đòi hỏi suy luận kỹ thuật sâu hoặc bao phủ trường hợp biên toàn diện, như review mã, thiết kế kiến trúc và phân tích chính sách, mức high xứng đáng với chi phí. 

Một số mở rộng tiếp theo gồm thêm loại prompt thứ hai để cho thấy chênh lệch effort thu hẹp khi vấn đề không cần suy luận sâu, hoặc thêm chế độ xem so sánh song song làm nổi bật chính xác nơi mức cao bổ sung chi tiết cụ thể mà mức trung bình bỏ lỡ.

Câu hỏi thường gặp về Hướng dẫn API Claude Opus 4.8

Vì sao tôi không thể đặt temperature trên Claude Opus 4.8?

Claude Opus 4.8 không hỗ trợ ghi đè tham số sampling. Truyền temperature, top_p hoặc top_k khác mặc định sẽ trả lỗi 400. Hãy dùng kỹ thuật prompt để định hướng phong cách phản hồi thay thế.

Điều gì xảy ra nếu tôi bỏ qua thinking: {type: "adaptive"}?

Tham số effort không có hiệu lực nếu không bật tư duy thích ứng. Nếu thiếu, cả ba lượt gọi sẽ hành xử giống hệt nhau, bất kể giá trị effort bạn truyền.

Tại sao không chạy cả ba lượt gọi song song?

Bạn có thể, và điều đó sẽ giảm thời gian tổng thể khoảng hai phần ba. Cách tuần tự trong hướng dẫn này là có chủ đích vì giúp thẻ tiến trình cập nhật lần lượt, khiến quy trình minh bạch hơn trong bối cảnh demo. Hãy bọc ba lời gọi call_opus trong concurrent.futures.ThreadPoolExecutor() nếu bạn muốn thực thi song song trong môi trường sản xuất.

Bộ chấm tự động Haiku đáng tin cậy đến mức nào?

Với một rubric có chiều đo cụ thể và prompt chấm điểm rõ ràng, Haiku 4.5 nhất quán qua các lần chạy trên cùng một phản hồi. Nó không thay thế đánh giá của con người cho các tác vụ rủi ro cao, nhưng đủ tốt để làm mỏ neo cho biểu đồ phân tán chi phí-chất lượng và loại bỏ nhu cầu đọc thủ công ba phản hồi dài sau mỗi lần chạy.

Tổng chi phí của một lần chạy demo là bao nhiêu?

Với prompt giới hạn tốc độ và MAX_OUTPUT_TOKENS=16000, một lượt chạy đầy đủ tốn khoảng $0,35 cho đầu ra mô hình cộng dưới $0,002 cho ba lượt chấm Haiku, tức tổng khoảng $0,35.

Aashi Dutt

Tác giả

Aashi Dutt

Tôi là Chuyên gia Google Developers trong lĩnh vực ML (Gen AI), Chuyên gia Kaggle 3x và Đại sứ Women Techmakers với hơn 3 năm kinh nghiệm trong ngành công nghệ. Tôi đồng sáng lập một startup công nghệ y tế vào năm 2020 và hiện đang theo học thạc sĩ khoa học máy tính tại Georgia Tech, chuyên sâu về học máy.

Chủ đề

Liên quan