anthropics / anthropics/claude-agent-sdk-python

claude-sonnet-5 overreporting cost

Aperta
#1,182 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
Lingua principale
Python
Stelle
8.1k
Fork
1.3k
Merge medio
2g 31m
PR unite (30g)
1

Descrizione

# `total_cost_usd` reports Opus rates for `claude-sonnet-5`

## Summary

`ResultMessage.total_cost_usd` prices `claude-sonnet-5` at **$5 / $25 per Mtok** (Opus 5's published
rate) instead of Sonnet 5's published **$3 / $15**. `claude-sonnet-4-6` in the same test run prices
correctly, so this looks specific to the `claude-sonnet-5` model ID rather than to the Sonnet family.

The response identifies itself as `claude-sonnet-5`, so this appears to be the reported cost figure,
not the model being served. I have no way to check from a subscription whether plan accounting uses
the same figure — that part is a question, not a claim.

## Environment

- Claude Code 2.1.195
- claude-agent-sdk 0.2.108
- Python 3.12.9, Windows 11 (10.0.26200)
- Max subscription auth (no `ANTHROPIC_API_KEY`)

## Reproduction

```python
import asyncio, claude_agent_sdk as sdk

async def one(model_id):
opts = sdk.ClaudeAgentOptions(model=model_id, max_turns=1, allowed_tools=[],
system_prompt="Be terse.", setting_sources=[])
cost, u = None, {}
async for msg in sdk.query(prompt="Reply with exactly: OK", options=opts):
if isinstance(msg, sdk.ResultMessage):
cost, u = msg.total_cost_usd, (msg.usage or {})
i, o = u.get("input_tokens", 0), u.get("output_tokens", 0)
cr, cw = u.get("cache_read_input_tokens", 0), u.get("cache_creation_input_tokens", 0)
den = i + 5 * o + 0.1 * cr + 2.0 * cw # published 1:5 in:out, 0.1x read, 2.0x write
print(f"{model_id:20s} in={i} out={o} cr={cr} cw={cw} "
f"cost={cost} -> implied ${cost * 1e6 / den:.2f}/Mtok input")

async def main():
for m in ("claude-haiku-4-5", "claude-sonnet-4-6", "claude-sonnet-5", "claude-opus-5"):
await one(m)

asyncio.run(main())
```

## Observed

| requested | served (`msg.model`) | implied input $/Mtok | published |
|---|---|---|---|
| `claude-haiku-4-5` | `claude-haiku-4-5-20251001` | $1.04 | $1.00 ✓ |
| `claude-sonnet-4-6` | `claude-sonnet-4-6` | $3.01 | $3.00 ✓ |
| `claude-sonnet-5` | `claude-sonnet-5` | **$5.08** | $3.00 ✗ |
| `claude-opus-5` | `claude-opus-5` | $5.09 | $5.00 ✓ |

The cleanest evidence is a **zero-cache** `claude-sonnet-5` call, where no cache multiplier
assumptions enter the arithmetic at all:

```
in=682 out=6 cache_creation=0 cache_read=0 total_cost_usd=0.003560
682 x $5/M + 6 x $25/M = $0.003560 <- exact match, Opus rates
682 x $3/M + 6 x $15/M = $0.002136 <- Sonnet 5 published
682 x $2/M + 6 x $10/M = $0.001424 <- Sonnet 5 introductory
```

Reproduced across three independent calls with different token mixes: implied $5.08, $5.085, $5.00.

## Expected

`total_cost_usd` for `claude-sonnet-5` computed at Sonnet 5 rates ($3/$15, or $2/$10 during the
introductory period).

## Ruled out

- **Not the wrong model being served.** `msg.model` reports `claude-sonnet-5`, and `claude-sonnet-4-6`
in the same run prices correctly — so the model field does vary meaningfully.
- **Not "undiscounted Sonnet."** Sonnet 5 without the introductory discount is $3/$15, which predicts
$0.002136 on the call above. The measured figure is $0.003560.
- **Not a cache-multiplier assumption.** The decisive call has zero cache tokens in both directions.

## Not established

Whether an unrecognised model ID falls back to Opus rates — I could not construct the control,
because the API rejects IDs it cannot serve, so there is no "known-unknown" model to probe with.

## Impact

Any tool that trusts `total_cost_usd` over-books Sonnet 5 usage by ~2.5x. In our case that inflates
per-window utilisation figures and erases the measured cost gap between Sonnet and Opus, which is
the gap a cost-aware model-routing policy exists to exploit — so the reported number actively
inverts the routing decision it is used to make.

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Direzione di ricerca

Start by tracing ResultMessage.total_cost_usd through the claude_agent_sdk implementation and inspect how model IDs are mapped to pricing. Run the zero-cache reproduction for claude-sonnet-5, then compare the reported value with the published $3/$15 Sonnet rates. Done means the reported cost uses the expected Sonnet pricing rather than Opus rates.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
python
Ambito
api
Tipo di issue
Bug
Difficoltà
3/5
Tempo stimato
1-2 giorni
Stato di attività
Tranquilla
Chiarezza
Abbastanza chiara
Idoneità per principianti
64/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.