Skip to content

test(pricing): accept litellm's off-peak DeepSeek rate in cost_per_token check - #3684

Closed
Ayushraj06-bit wants to merge 1 commit into
headroomlabs-ai:mainfrom
Ayushraj06-bit:test/deepseek-cost-per-token-off-peak
Closed

Ayushraj06-bit wants to merge 1 commit into
headroomlabs-ai:mainfrom
Ayushraj06-bit:test/deepseek-cost-per-token-off-peak

Conversation

@Ayushraj06-bit

Copy link
Copy Markdown
Contributor

Description

test_cost_per_token_resolves_deepseek_v4_flash fails on main and on unrelated PRs whenever the test (3) shard runs outside Mon–Fri 01:00–04:00 / 06:00–10:00 UTC (assert 0.15 == 0.3): main run 35462876818 (Fri 19:21 UTC) failed, #3668 (Sun 16:26 UTC) failed, #3679 (Mon 03:12 UTC) passed.

Cause: between 2026-09-18 22:00 and 2026-09-19 12:00 UTC litellm's live pricing map gained an off_peak_pricing block for deepseek-v4-flash (input 0.15/M vs 0.30/M; windows Mon–Fri 00:00–01:00, 04:00–06:00, 10:00–00:00 UTC, all weekend), and litellm 1.101.0 (uv.lock) bills from it in cost_per_token. The test compares that clock-aware result against the flat input_cost_per_token field of the same entry, so the two sides now disagree most of the week.

Fix: accept either tier from the active entry. With no off-peak block the accepted set is the single flat rate, so nothing changes for older litellm; with one, cost_per_token is still pinned to the entry's own rates and the assertion no longer depends on the time of day. The test's purpose — that resolve_litellm_model adds the deepseek/ prefix so cost_per_token can resolve the provider — is unchanged.

#3575 rewrites this test as part of the V4.1 rate-card refresh and supersedes this; this is the one-hunk unblock for every open PR until it lands.

Closes #3683

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Performance improvement
  • Code refactoring (no functional changes)

Changes Made

  • tests/test_providers/test_deepseek.py: in test_cost_per_token_resolves_deepseek_v4_flash, build the accepted input/output rate sets from active_pricing plus its off_peak_pricing block (if any) and assert cost_per_token matches one of them.

Testing

  • Unit tests pass (pytest) — tests/test_providers/test_deepseek.py: 20 passed (local litellm 1.97.0, no off-peak block → identical behaviour to before); under litellm 1.101.0 the single test passes at a peak instant and at the exact failing instant (below).
  • Linting passes (ruff check .) — All checks passed! (ruff 0.16.7)
  • Type checking passes (mypy headroom) — tests only, no source changed.
  • New tests added for new functionality — existing test made clock-independent.
  • Manual testing performed

Test Output

$ pytest tests/test_providers/test_deepseek.py -q          # litellm 1.97.0
20 passed in 6.35s

# litellm 1.101.0 (CI's version), clock pinned via llm_cost_calc.utils.datetime:
[off-peak Fri 19:21 UTC] patched test -> PASS
[peak     Mon 08:00 UTC] patched test -> PASS
# same instants, unpatched test:
E   assert 0.15 == 0.3 ± 3.0e-07
[off-peak Fri 19:21 UTC] patched test -> FAIL (rc=1)
[peak     Mon 08:00 UTC] patched test -> PASS

Real Behavior Proof

  • Environment: Windows 11, Python 3.11; scratch venv with litellm==1.101.0 (the uv.lock version) loading litellm's live pricing map as CI does; repo litellm 1.97.0 for the unchanged-behaviour check.
  • Exact command / steps: litellm.cost_per_token(model="deepseek/deepseek-v4-flash", prompt_tokens=1_000_000, completion_tokens=1_000_000) with llm_cost_calc.utils.datetime.now pinned to Fri 2026-09-19 19:21 UTC (main's failing run), Sun 2026-09-20 16:26 UTC (feat(opencode): exclude hosts from transport plugin routing #3668's failing run) and Mon 2026-09-21 03:00/08:00 UTC (peak); then the test itself at the same instants, patched and unpatched.
  • Observed result: off-peak instants return input=0.150 output=0.600, peak instants input=0.300 output=1.200; unpatched test fails at off-peak with assert 0.15 == 0.3, patched passes at all four instants. Independently, re-running feat(opencode): exclude hosts from transport plugin routing #3668's test (3) inside the peak window (03:29–03:37 UTC Monday) turned it green with no code change.
  • Not tested: a CI run of this branch during an off-peak window (that is what the pinned-clock run stands in for); litellm versions between 1.97 and 1.101.

Runtime Rollout Safety

  • Rollout-managed feature(s): none.
  • Minimum rollout channel: n/a.
  • Stable/default behavior changed: no — test-only change.
  • Kill switch / disable path: n/a.
  • Unsafe override required: no.
  • Qualification impact: none.
  • Rollback path: revert this commit.

Review Readiness

  • I have performed a self-review
  • This PR is ready for human review

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I did not edit CHANGELOG.md — it is generated by release-please from my Conventional Commit PR title (a CI guard enforces this)

Additional Notes

CONTRIBUTING asks not to open test-only PRs chasing a known main failure; this one was not tracked anywhere, so the linked issue documents it, and this is the smallest change that stops it failing other people's PRs. Happy to close it in favour of #3575 if that is landing first.

…ken check

`test_cost_per_token_resolves_deepseek_v4_flash` compares
`litellm.cost_per_token()` against the flat `input_cost_per_token` of
the active `litellm.model_cost` entry. Between 2026-09-18 22:00 and
2026-09-19 12:00 UTC litellm's live pricing map gained an
`off_peak_pricing` block for `deepseek-v4-flash` (input 0.15/M vs
0.30/M), and litellm 1.101.0 — the version in uv.lock — bills from it
during DeepSeek's off-peak windows: Mon–Fri 10:00–01:00 and
04:00–06:00 UTC, all weekend. The two sides of the assertion now
disagree for ~17 hours of every weekday and all weekend, so the test
fails on `main` and on every PR whose `test (3)` shard runs then
(main run 35462876818 at 19:21 UTC, headroomlabs-ai#3668 at 16:26 UTC Sunday) and
passes only in the peak hours (headroomlabs-ai#3679 at 03:12 UTC Monday).

Accept either tier from the active entry. With a litellm that has no
off-peak block the set collapses to the single flat rate, so nothing
changes there; with one, the assertion still pins cost_per_token to the
entry's own rates and stays clock-independent.

Verified under litellm 1.101.0 with the clock pinned to the failing
instant (Fri 19:21 UTC): unpatched `assert 0.15 == 0.3`, patched
passes; both pass at a peak instant.
@github-actions

Copy link
Copy Markdown
Contributor

PR governance

This PR follows the template and is marked ready for human review.

@github-actions github-actions Bot added the status: ready for review Pull request body is complete and the author marked it ready for human review label Sep 21, 2026
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@JerrettDavis

Copy link
Copy Markdown
Collaborator

This test-only change is now superseded by merged #3699, which fixed the same wall-clock-dependent DeepSeek assertion on current main using a relationship-based check. This branch is also conflicted. Recommend closing it rather than rebasing a duplicate test fix.

@Ayushraj06-bit

Copy link
Copy Markdown
Contributor Author

Superseded: #3699 landed on main with the same clock-independent assertion (list rate or the documented off-peak half), so this is no longer needed. Closing.

@Ayushraj06-bit
Ayushraj06-bit deleted the test/deepseek-cost-per-token-off-peak branch September 22, 2026 10:34
@Ayushraj06-bit
Ayushraj06-bit restored the test/deepseek-cost-per-token-off-peak branch September 22, 2026 10:34
chopratejas added a commit that referenced this pull request Sep 23, 2026
…ired one (#3722)

## What broke

`litellm` downloads `model_prices_and_context_window.json` from GitHub
**at import time**, so `litellm.model_cost` is live third-party data we
do not control.

On 2026-09-23 BerriAI pruned retired Anthropic ids from that table.
`claude-sonnet-4-20250514` disappeared, and every test that priced it
started failing with `KeyError: 'input_cost_per_token'` — on every open
PR simultaneously, with no change on our side. `main` was green at
`5ff4ea1e` on 09-22 and the identical shards went red on #3720 and
#3721, which share that base and touch no pricing code.

## What this does

The model id in those tests is **incidental**. They assert that
Headroom's cost arithmetic agrees with litellm's numbers — not that any
particular model is priced correctly. So `tests/_pricing_models.py` asks
for *a* model carrying the fields a test needs, newest first, and raises
with an actionable message if litellm prices none of them.

```python
MODEL = anthropic_pricing_model(
    "input_cost_per_token_above_200k_tokens",
    "cache_creation_input_token_cost",
    "cache_read_input_token_cost",
)
```

Retiring one candidate is now a no-op until the list runs dry.

## Why not pin to the wheel's vendored copy

That was this PR's first attempt and **it was wrong** — I'm recording
why, because it looks like the obvious fix.

The live table and the copy vendored in the litellm wheel are
*complementary, not ordered*:

| | live | vendored |
|---|---|---|
| `claude-sonnet-4-20250514` | ✗ pruned | ✓ |
| `claude-sonnet-5` | ✓ | ✗ predates it |
| live-only entries | 1743 | — |
| vendored-only entries | — | 314 |

Pinning fixed the 25 `KeyError`s and **broke
`test_proxy_savings_history`**, which prices `claude-sonnet-5`. Taking
the union does not rescue it either: the vendored
`claude-sonnet-4-20250514` entry is an older schema that has no
`input_cost_per_token_above_200k_tokens` field at all, so
`test_cost_card_attribution` still fails against it.

## A second, separate breakage in the same data drop


`test_provider_prices_models_its_table_never_covered[groq/llama-3.3-70b-versatile]`
was failing for a different reason: litellm dropped that model too, so
`_get_pricing` fell through to the `$2.50/$10.00` GPT-4o default — which
that test's own comment names as the "unknown default" it exists to
catch. Repointed at `groq/llama-guard-3-8b`, which litellm still prices.

**The product-side problem is filed separately as #3732**: an
unresolvable model is silently billed at the GPT-4o tier, so cost
tracking and `check_budget` — a hard control that refuses requests — can
act on a fabricated number. For that groq model the default overstated
output cost by ~12x.

## Verification

Same six modules, same machine:

```
origin/main          25 failed, 82 passed
this branch                    107 passed
```

Ruff 0.16.7 `check` + `format --check` clean.

## Prior art

#3683 / #3684 was the same class of failure (litellm started billing
DeepSeek off-peak) and was patched narrowly at the assertion. This
removes the category for model *identity*; #3732 covers what happens in
production when identity goes missing.
JerrettDavis pushed a commit that referenced this pull request Sep 24, 2026
…3700)

## Description

Bounds the `int()` conversion added by #3616 to the range where a double
is exact, so a legacy `.xls` cell stops inventing digits (#3695).

`_xls_cell` rendered any integral double with `int(value)`. xlrd hands
back an IEEE-754 double, and above 2**53 consecutive integers are not
representable, so the conversion prints the double's *exact* value
rather than the number the sheet held:

```text
typed  123456789012345678   stored  1.2345678901234568e+17   rendered  123456789012345680
```

Two trailing digits, fabricated, delivered to an agent in a form that
reads as an exact identifier. Before #3616 both loaders rendered the
float and simply disagreed about `12.0` vs `12`; after it they agree on
small whole numbers and disagree again -- in the worse direction -- at
the top of the range, which is also where the PR's own goal ("render an
.xls cell the way the .xlsx loader renders it") stops holding.

Closes #3695

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- `headroom/transforms/spreadsheet_ingest.py`: the numeric branch
becomes `if kind == xlrd.XL_CELL_NUMBER and float(value).is_integer()
and abs(value) <= 2**53:`, so values past the exactly-representable
range`return value` and render as the float. The comment records why the
bound exists so it is not "simplified" away again.
- `tests/test_transforms_tabular.py`: adds
`test_load_xls_and_xlsx_agree_above_the_exact_integer_range`, written
against the same xlwt/xlrd fixture style the three existing `.xls` tests
use. It pins both halves of the requirement: `12` still renders without
a decimal point (the #3616 win survives the bound), and
1.2345678901234568e+17 renders as `repr()` of the double rather than an
invented integer.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed

Type checking was run on the changed module rather than the whole
package, so that line means `mypy --config-file pyproject.toml
headroom/transforms/spreadsheet_ingest.py`, not a full-package pass.

### Test Output

Red, with the fix removed and the new test kept (this is also the state
of `main` for a cell in that range):

```text
$ python -m pytest tests/test_transforms_tabular.py -k exact_integer_range -q
E   AssertionError: assert '123456789012345680' == '1.2345678901234568e+17'
E
E     - 1.2345678901234568e+17
E     ?  -                ^^^^
E     + 123456789012345680
FAILED tests/test_transforms_tabular.py::test_load_xls_and_xlsx_agree_above_the_exact_integer_range
1 failed, 45 deselected in 4.10s
```

Green, with the fix:

```text
$ python -m pytest tests/test_transforms_tabular.py -q
46 passed in 5.48s

$ uvx ruff@0.16.7 check headroom/transforms/spreadsheet_ingest.py tests/test_transforms_tabular.py
All checks passed!
$ uvx ruff@0.16.7 format --check headroom/transforms/spreadsheet_ingest.py tests/test_transforms_tabular.py
2 files already formatted
$ uvx mypy@1.14.1 --config-file pyproject.toml headroom/transforms/spreadsheet_ingest.py
Success: no issues found in 1 source file
```

## Real Behavior Proof

- Environment: Windows 11 (10.0.26200) x64, Python 3.12.13, working tree
at `origin/main` 85fac8c plus this one-file change. Optional extras
present for this path only: `xlrd 2.0.2`, `xlwt 1.3.0`, `openpyxl`. No
provider, no API key, no network -- the loader reads a local file.
- Exact command / steps: build a real legacy workbook with `xlwt`
holding one small whole number and one whole-valued double beyond 2**53,
save it as `.xls`, and read it through the shipped `load_spreadsheet()`;
then run the new test twice, once with the bound in place and once with
the pre-fix line restored in the working tree.
- Observed result: the same cell renders `123456789012345680` before
this change and `1.2345678901234568e+17` after it, while `12` renders as
`12` in both states.

```text
$ python -c "xlwt writes [12, 1.2345678901234568e17] into legacy.xls; print(load_spreadsheet(...)['Data'].splitlines()[1])"
post-fix row: 12,1.2345678901234568e+17

$ python -m pytest tests/test_transforms_tabular.py -k exact_integer_range -q   # fix reverted in the tree
E   AssertionError: assert '123456789012345680' == '1.2345678901234568e+17'
```

The same cell, both states: the pre-fix render carries two invented
digits
(`123456789012345680`), the post-fix render is the double the sheet
holds. The
`12` half is identical in both, which is the point of bounding rather
than
deleting the conversion -- whole numbers at or below 2**53 keep the
#3616 rendering,
and only the range where a double cannot hold an integer reverts to the
float.
- Not tested: a workbook authored in real Excel or LibreOffice (the
fixture is written by `xlwt`, which is also how the three existing
`.xls` tests build theirs), and `.xls` cells in the 2**53..2**1024 range
carrying `#`-formatted number styles, which `_xls_cell` does not see at
all. One measurement worth flagging for whoever picks this up: I first
asserted that the two loaders produce the same *text*, and it failed for
a reason unrelated to this fix -- a value written and read back through
`openpyxl` comes back as `1.234567890123457e+17`, because openpyxl
serializes the literal to 15 significant digits, so the `.xlsx` path
loses a digit on write while the `.xls` path loses one on render. The
test therefore asserts the property that is this fix's subject (no
invented integer; small integers still `12`) instead of cross-format
textual equality, which the loaders do not actually guarantee for values
in that range.

## Runtime Rollout Safety

- Rollout-managed feature(s): none. One comparison in a pure function
inside the spreadsheet loader; no flag service or staged rollout is
involved.
- Minimum rollout channel: n/a -- ordinary release.
- Stable/default behavior changed: yes, narrowly. `.xls` cells holding a
whole-valued double at or above 9007199254740992 now render as
`1.2345678901234568e+17` instead of `123456789012345680`. Below that
bound, and for every non-numeric cell type, output is byte-identical to
today.
- Kill switch / disable path: none needed and none added. Reverting the
single commit restores the previous rendering exactly; there is no new
code path to turn off, and no cache or stored format is involved
(`load_spreadsheet` is a read path).
- Unsafe override required: no.
- Qualification impact: text of affected cells changes, so any test or
prompt-cache fixture that captured `.xls` output for a cell in that
range sees a diff. Existing tests: all 46 in
`tests/test_transforms_tabular.py` pass, including the three `.xls`
tests from #3616 whose expectation (`12`) is deliberately preserved.
- Rollback path: revert the one commit; no migration, no persisted
state, no store rewrite.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] I did **not** edit `CHANGELOG.md` -- it is generated by
release-please from my Conventional Commit PR title (a CI guard enforces
this)

## Screenshots (if applicable)

Not applicable; the observable artifact is the rendered CSV row, quoted
in the Real Behavior Proof.

## Additional Notes

Two judgement calls, both flagged for review:

- The bound is `abs(value) <= 2**53`. #3695 proposed the exclusive form,
and the review here correctly pointed out that binary64 represents 2**53
and -2**53 exactly while openpyxl loads those as integers, so excluding
them left the two loaders disagreeing at the boundary itself. A stricter
bound (e.g. 1e15, where decimal-style output still lo A stricter bound
(e.g. 1e15, where decimal-style output still looks like a plain integer)
would change more cells than necessary, and a looser one would keep
inventing digits.
- A negative whole number is also rendered as an int when small; `abs()`
keeps that behavior symmetric, which is what the existing tests expect
for positives and what `repr()` would otherwise contradict for
negatives.

Note on CI: `test (3)` on this branch is expected to show the same
pre-existing `test_cost_per_token_resolves_deepseek_v4_flash` failure
described in #3683 (litellm's off-peak pricing window), which #3684
addresses. Nothing in this diff is on that test's path.

---------

Co-authored-by: sxh313 <250161920+sxh313@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: ready for review Pull request body is complete and the author marked it ready for human review

Projects

None yet

3 participants