anthropics / anthropics/claude-agent-sdk-python

claude-sonnet-5 overreporting cost

Abierto
#1,182 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
Python
Estrellas
8.1k
Forks
1.3k
Merge medio
2 d 31 min
PR fusionados (30 d)
1

Descripción

# `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.

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python
Área
api
Tipo de issue
Error
Dificultad
3/5
Tiempo estimado
1-2 días
Estado de actividad
Tranquilo
Claridad
Bastante claro
Aptitud para principiantes
64/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.