mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-04 07:47:27 +01:00
fix(pricing): Sonnet 5.5, Gemini 3.x and gbrain embedding rows from fetched pages; Opus 4.7 at its current list price
Each new row carries its source URL and fetch date (2026-10-12). Embedding models bill input only, so they live in EMBEDDING_PRICING beside PRICING (eval arms never resolve to them); pricingRow also accepts gbrain's provider:model ids (voyage:voyage-4). claude-opus-4-7 moves from 15/75 to the 5/25 the Anthropic pricing page lists; benchmark-runner's expected costs follow. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
1898142986
commit
6590cbb35b
3 files changed
+59
-11
No files matched your search
+40
-4
@@ -20,7 +20,8 @@
|
||||
* start a priced run on such a model (bin/gstack-eval-plan dry-run).
|
||||
* - Anthropic: https://www.anthropic.com/pricing#api
|
||||
* - OpenAI: https://openai.com/api/pricing/
|
||||
* - Google AI: https://ai.google.dev/pricing
|
||||
* - Google AI: https://ai.google.dev/gemini-api/docs/pricing
|
||||
* - Voyage AI (embeddings): https://docs.voyageai.com/docs/pricing
|
||||
*/
|
||||
|
||||
export interface ModelPricing {
|
||||
@@ -45,7 +46,10 @@ export const PRICING: Record<string, ModelPricing> = {
|
||||
'claude-fable-5-1': { input_per_mtok: 10.00, output_per_mtok: 50.00, cached_input_per_mtok: 0.25, as_of: '2026-09' },
|
||||
// https://platform.claude.com/docs/en/models/opus-5-5/overview#pricing (list prices effective 2026-10-07)
|
||||
'claude-opus-5-5': { input_per_mtok: 4.00, output_per_mtok: 20.00, cached_input_per_mtok: 0.20, as_of: '2026-10' },
|
||||
'claude-opus-4-7': { input_per_mtok: 15.00, output_per_mtok: 75.00, as_of: '2026-04' },
|
||||
// https://platform.claude.com/docs/en/about-claude/pricing (fetched 2026-10-12)
|
||||
'claude-opus-4-7': { input_per_mtok: 5.00, output_per_mtok: 25.00, cached_input_per_mtok: 0.50, as_of: '2026-10' },
|
||||
// https://platform.claude.com/docs/en/about-claude/pricing (fetched 2026-10-12)
|
||||
'claude-sonnet-5-5': { input_per_mtok: 2.00, output_per_mtok: 10.00, cached_input_per_mtok: 0.10, as_of: '2026-10' },
|
||||
'claude-sonnet-4-6': { input_per_mtok: 3.00, output_per_mtok: 15.00, as_of: '2026-04' },
|
||||
'claude-haiku-4-5': { input_per_mtok: 1.00, output_per_mtok: 5.00, as_of: '2026-04' },
|
||||
|
||||
@@ -60,14 +64,46 @@ export const PRICING: Record<string, ModelPricing> = {
|
||||
'o4-mini': { input_per_mtok: 1.10, output_per_mtok: 4.40, as_of: '2026-04' },
|
||||
|
||||
// Google
|
||||
// https://ai.google.dev/gemini-api/docs/pricing (fetched 2026-10-12; standard tier through 2026-12-31,
|
||||
// the page lists $1.50 / $7.50 / $0.15 cached starting 2027-01-01)
|
||||
'gemini-3.8-flash': { input_per_mtok: 0.75, output_per_mtok: 3.75, cached_input_per_mtok: 0.075, as_of: '2026-10' },
|
||||
// https://ai.google.dev/gemini-api/docs/pricing (fetched 2026-10-12; same schedule as gemini-3.8-flash)
|
||||
'gemini-3.6-flash': { input_per_mtok: 0.75, output_per_mtok: 3.75, cached_input_per_mtok: 0.075, as_of: '2026-10' },
|
||||
// https://ai.google.dev/gemini-api/docs/pricing (fetched 2026-10-12; prompts <= 200k tokens, the long-context rate is excluded)
|
||||
'gemini-3.1-pro-preview': { input_per_mtok: 2.00, output_per_mtok: 12.00, cached_input_per_mtok: 0.20, as_of: '2026-10' },
|
||||
'gemini-2.5-pro': { input_per_mtok: 1.25, output_per_mtok: 5.00, as_of: '2026-04' },
|
||||
'gemini-2.5-flash': { input_per_mtok: 0.30, output_per_mtok: 1.20, as_of: '2026-04' },
|
||||
};
|
||||
|
||||
/** A provider-prefixed id (`openai/gpt-6-astra`, `anthropic/claude-fable-5-1`) resolves to its table row. */
|
||||
/**
|
||||
* Embedding models bill input tokens only (output_per_mtok 0), so they live
|
||||
* beside PRICING rather than in it: eval arms never resolve to an embedding
|
||||
* model, but a call plan (docs/eval-calls.md) prices gbrain's embedding calls.
|
||||
*/
|
||||
export const EMBEDDING_PRICING: Record<string, ModelPricing> = {
|
||||
// https://docs.voyageai.com/docs/pricing (fetched 2026-10-12; list price after the free-token allowance)
|
||||
'voyage-4': { input_per_mtok: 0.06, output_per_mtok: 0, as_of: '2026-10' },
|
||||
'voyage-4-large': { input_per_mtok: 0.12, output_per_mtok: 0, as_of: '2026-10' },
|
||||
'voyage-4-lite': { input_per_mtok: 0.02, output_per_mtok: 0, as_of: '2026-10' },
|
||||
'voyage-code-4': { input_per_mtok: 0.12, output_per_mtok: 0, as_of: '2026-10' },
|
||||
'voyage-code-3': { input_per_mtok: 0.18, output_per_mtok: 0, as_of: '2026-10' },
|
||||
'voyage-3-large': { input_per_mtok: 0.18, output_per_mtok: 0, as_of: '2026-10' },
|
||||
'voyage-multimodal-3': { input_per_mtok: 0.12, output_per_mtok: 0, as_of: '2026-10' }, // text tokens; pixels excluded
|
||||
// https://developers.openai.com/api/docs/pricing (fetched 2026-10-12)
|
||||
'text-embedding-3-large': { input_per_mtok: 0.13, output_per_mtok: 0, as_of: '2026-10' },
|
||||
'text-embedding-3-small': { input_per_mtok: 0.02, output_per_mtok: 0, as_of: '2026-10' },
|
||||
// https://ai.google.dev/gemini-api/docs/pricing (fetched 2026-10-12; text input, standard tier)
|
||||
'gemini-embedding-2': { input_per_mtok: 0.20, output_per_mtok: 0, as_of: '2026-10' },
|
||||
};
|
||||
|
||||
/**
|
||||
* A provider-prefixed id (`openai/gpt-6-astra`, `anthropic/claude-fable-5-1`,
|
||||
* or gbrain's `voyage:voyage-4`) resolves to its table row.
|
||||
*/
|
||||
export function pricingRow(model: string | undefined): ModelPricing | undefined {
|
||||
if (!model) return undefined;
|
||||
return PRICING[model] ?? PRICING[model.replace(/^[a-z-]+\//, '')];
|
||||
const bare = model.replace(/^[a-z-]+[/:]/, '');
|
||||
return PRICING[model] ?? PRICING[bare] ?? EMBEDDING_PRICING[model] ?? EMBEDDING_PRICING[bare];
|
||||
}
|
||||
|
||||
export function hasPricing(model: string | undefined): boolean {
|
||||
|
||||
@@ -23,10 +23,10 @@ test('estimateCostUsd returns 0 for unknown model (no crash)', () => {
|
||||
});
|
||||
|
||||
test('estimateCostUsd computes correctly for known Claude model', () => {
|
||||
// claude-opus-4-7: $15/MTok input, $75/MTok output
|
||||
// 1M input + 0.5M output = $15 + $37.50 = $52.50
|
||||
// claude-opus-4-7: $5/MTok input, $25/MTok output (platform.claude.com pricing, fetched 2026-10-12)
|
||||
// 1M input + 0.5M output = $5 + $12.50 = $17.50
|
||||
const cost = estimateCostUsd({ input: 1_000_000, output: 500_000 }, 'claude-opus-4-7');
|
||||
expect(cost).toBeCloseTo(52.50, 2);
|
||||
expect(cost).toBeCloseTo(17.50, 2);
|
||||
});
|
||||
|
||||
for (const model of [CLAUDE_FRONTIER_EVAL_MODEL, CODEX_FRONTIER_MODEL]) {
|
||||
@@ -48,12 +48,12 @@ test('Astra cache reads use its standard cached-input rate', () => {
|
||||
|
||||
test('estimateCostUsd applies cached input discount alongside uncached input', () => {
|
||||
// tokens.input is uncached-only; tokens.cached is disjoint cache-reads at 10%.
|
||||
// 0 uncached input, 1M cached → 10% of 15 = $1.50
|
||||
// 0 uncached input, 1M cached → 10% of 5 = $0.50
|
||||
const cost1 = estimateCostUsd({ input: 0, output: 0, cached: 1_000_000 }, 'claude-opus-4-7');
|
||||
expect(cost1).toBeCloseTo(1.50, 2);
|
||||
// 500K uncached input + 500K cached → $7.50 + $0.75 = $8.25
|
||||
expect(cost1).toBeCloseTo(0.50, 2);
|
||||
// 500K uncached input + 500K cached → $2.50 + $0.25 = $2.75
|
||||
const cost2 = estimateCostUsd({ input: 500_000, output: 0, cached: 500_000 }, 'claude-opus-4-7');
|
||||
expect(cost2).toBeCloseTo(8.25, 2);
|
||||
expect(cost2).toBeCloseTo(2.75, 2);
|
||||
});
|
||||
|
||||
test('PRICING table covers the key model families', () => {
|
||||
|
||||
@@ -156,3 +156,15 @@ describe('priced pilot', () => {
|
||||
expect(r.projection.verdict).toBe('unknown');
|
||||
});
|
||||
});
|
||||
|
||||
describe('pricing rows with fetched sources (2026-10-12)', () => {
|
||||
test('the newest Sonnet, the newest Gemini Flash and gbrain embedding models are priced; Opus 4.7 is at its current list price', () => {
|
||||
expect(hasPricing('anthropic/claude-sonnet-5-5')).toBe(true);
|
||||
expect(estimateCostUsd({ input: 1_000_000, output: 1_000_000 }, 'claude-sonnet-5-5')).toBe(12);
|
||||
expect(estimateCostUsd({ input: 1_000_000, output: 1_000_000 }, 'claude-opus-4-7')).toBe(30);
|
||||
expect(hasPricing('google/gemini-3.8-flash')).toBe(true);
|
||||
for (const m of ['voyage:voyage-4', 'openai:text-embedding-3-large', 'text-embedding-3-small', 'gemini-embedding-2']) expect(hasPricing(m)).toBe(true);
|
||||
expect(estimateCostUsd({ input: 1_000_000, output: 0 }, 'voyage:voyage-4')).toBe(0.06);
|
||||
expect(hasPricing('voyage-4-nano')).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user