Skip to content

Calendar versions, prices kept current, and the Anthropic prices corrected - #86

Merged
bircex merged 4 commits into
mainfrom
claude/gallant-albattani-l09ize
Sep 27, 2026
Merged

bircex merged 4 commits into
mainfrom
claude/gallant-albattani-l09ize

Conversation

@bircex

@bircex bircex commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

What this changes

1. Prices kept current (new)

llmr now takes a model's price from one of three sources. Each one overrides the one before it:

Source Where it comes from Edition recorded on a cost
shipped The tables in the image (models/*-prices.toml) anthropic-2026-09
synced A public price list, read once a day. LiteLLM's by default synced-2026-09-27
manual Set by hand, per provider and model manual-2026-09-27

The sync.

  • The sync never runs on the request path.
  • A restart does not fetch again before the interval is up. A failed sync keeps the prices already in place and tries again within the hour.
  • A new model's price applies straight away. So does a change of up to half, and it is logged with the old and new rate.
  • A price that moves by more than half, or drops to nothing, is held. The old price keeps applying until someone accepts or rejects the new one. A rejected price is not held again.
  • Models priced in context bands (e.g. gpt-5.5 above 272K) stay unpriced, the same as in the shipped tables. Anthropic's one-hour cache price (_above_1hr) is not treated as a band.
  • Shipped and synced prices apply only to a provider on the vendor's own endpoint.
  • A manual price applies to any provider, including openai-compatible and self-hosted ones. A self-hosted model stays free until someone sets a price for it.

Media units. Rates can now charge per picture, per second of audio, and per million characters, alongside the token rates. So image, speech and transcription models get priced.

  • A model sold by a unit the provider did not report is unpriced, never zero.
  • The engine gains Units, PriceBook::price_with, PriceBook::new, and Rate::tokens with with_image, with_audio_second and with_character.

Book on every cost. Every priced cost names the price edition that priced it, as book in llmr_cost and on each usage row. A later price change therefore never re-prices past requests.

Endpoints.

  • GET /manage/prices: last sync, held prices, manual prices.
  • POST /manage/prices/sync: sync now.
  • POST /manage/prices/held/{vendor}/{model} with {"decision": "accept" | "reject"}.
  • GET /manage/providers/{id}/prices: each model's rate and where it came from. Add ?all=true for every priced model.
  • PUT / DELETE /manage/providers/{id}/prices/{model}: set or remove a manual price. Amounts must be decimal text; floats are refused.
  • /manage/status gains a prices block.

Environment variables: LLMR_PRICE_SYNC (off disables it), LLMR_PRICE_SYNC_URL, LLMR_PRICE_SYNC_HOURS (default 24).

Schema v5: new synced_prices and price_overrides tables, and price_book on usage rows. A v4 database is brought forward on start.

2. Weekly drift check (prices.yml)

Every Monday the workflow compares the shipped price books with the list, using the sync's own code.

  • Where they differ, it keeps one issue open, "Shipped prices differ from the price list". The issue has a row per model: shipped rate, list rate, and what a sync would do.
  • It closes the issue once the two agree.
  • It opens an issue rather than a PR, because a book's verified_at claims that a person read the vendor's page.

3. Calendar versions (YYYY.M.PATCH)

  • The next release is 2026.9.0 instead of 0.3.0. 0.3.0 was never tagged.
  • The format is still valid semver, so Cargo accepts it. The image is tagged 2026.9.0 and 2026.9.
  • release.yml refuses a tag that is not a calendar version for the month it is pushed in.
  • CHANGELOG.md merges everything unreleased into ## Unreleased: 2026.9.0, with a Breaking section.

4. The Anthropic price book was wrong

Model Was (in / out per 1M tokens) Now
claude-opus-5 $15 / $75 $5 / $25
claude-opus-4-8 $15 / $75 $5 / $25
claude-sonnet-5 $3 / $15 $2 / $10
  • Cache prices are corrected to match. The book is now anthropic-2026-09.
  • Rows already recorded keep the cost they were recorded with.

Checks

  • cargo fmt --all -- --check, and cargo clippy -- -D warnings for all features, no default features, and default.
  • cargo test --all-features (450 passed) and cargo test (274 passed).
  • New tests:
    • Unit rates and partial coverage.
    • Parsing the list, band detection including _above_1hr, and the hold rules.
    • Precedence: manual > synced > shipped, and vendor prices never reach another endpoint.
    • Schema v4 → v5 migration.
    • Held, rejected and accepted store round trips; manual prices removed along with their provider.
    • An HTTP test that runs sync → hold → accept → manual → delete → the list goes down (502).
  • Against the real LiteLLM list:
    • 181 rows read for the three vendors, none held.
    • Every shipped row it covers matches, so the drift report is empty.
    • On a copy altered by hand, the report lists both changed rows correctly.
  • In the Docker image, with the real list served locally:
    • First start syncs.
    • Anthropic shows 14 synced and 4 shipped models.
    • A −80% change was held; a +20% change was applied and logged.
    • Reject worked and the price was not held again.
    • A restart did not sync again, and prices survived it.
    • Manual prices priced a transcription by the second (90.5 s → $0.00905), speech by the character, and an image per picture.
  • Not verified in this environment:
    • The container fetching raw.githubusercontent.com directly (the sandbox proxy is in the way). The code path is the same GET.
    • The prices.yml workflow itself, which runs only on schedule or dispatch. It can be triggered by hand after merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J5yngL8AGNx2FZKffQTaAv

- Versions are YYYY.M.PATCH. The next release is 2026.9.0 rather than 0.3.0;
  Cargo.toml, the changelog, CONTRIBUTING, the workflows and examples follow.
  The release workflow refuses a tag that is not a calendar version for the
  month it is pushed in. Compatibility is carried by a Breaking section in the
  changelog, since the number no longer says.
- The shipped Anthropic book priced claude-opus-5 and claude-opus-4-8 at
  $15/$75 per million and claude-sonnet-5 at $3/$15; the rates are $5/$25 and
  $2/$10, with cache reads and writes to match. The book is anthropic-2026-09.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5yngL8AGNx2FZKffQTaAv
- Three sources, each overruling the one before: the shipped tables, a daily
  sync from a public price list (LiteLLM's by default), and prices set by hand
  per provider and model through the management API.
- The sync never touches the request path. A price that moves by more than
  half is held for a person to accept or reject; a refused price is not held
  again. A failed sync keeps the prices in place and says why.
- Rates can charge per picture, per second of audio and per million
  characters, so image, speech and transcription models can be priced. A rate
  whose unit nobody measured is unpriced, never zero.
- Every priced cost names the book edition that priced it, on the reply and on
  its usage row. Schema version 5.
- Endpoints: GET /manage/prices, POST /manage/prices/sync,
  POST /manage/prices/held/{vendor}/{model}, GET /manage/providers/{id}/prices,
  PUT and DELETE /manage/providers/{id}/prices/{model}. /manage/status says
  when prices last synced and how many are held.
- LLMR_PRICE_SYNC, LLMR_PRICE_SYNC_URL and LLMR_PRICE_SYNC_HOURS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5yngL8AGNx2FZKffQTaAv
- `_above_1hr` prices Anthropic's one hour prompt cache and is not a context
  band. Reading it as one left every Anthropic row in the list unpriced. Only
  `_above_<N>k_tokens` marks a band now, and a test says which is which.
- A dry run against the real list, kept as an ignored test, reads 181 rows for
  the three vendors, holds none, and matches every shipped row it covers.
- Documented: the Prices section of MANAGEMENT.md, the new environment
  variables and the one outbound request, `book` on a cost, the design choice,
  the changelog, and the file tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5yngL8AGNx2FZKffQTaAv
@bircex bircex changed the title Calendar versions, and the Anthropic prices corrected Calendar versions, prices kept current, and the Anthropic prices corrected Sep 27, 2026
prices.yml fetches the list every Monday and runs the sync's own comparison
against the shipped books. Where they differ, it keeps one issue open with a
table of each row, the shipped rate, the list's rate and what a sync would do;
it closes the issue once they agree. An issue rather than a pull request: a
book's verified_at says a person read the vendor's page, and a bot cannot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5yngL8AGNx2FZKffQTaAv
@bircex
bircex marked this pull request as ready for review September 27, 2026 18:49
@bircex
bircex merged commit cfa900b into main Sep 27, 2026
9 checks passed
@bircex
bircex deleted the claude/gallant-albattani-l09ize branch September 27, 2026 18:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants