Files
gbrain/test/embedding-pricing.test.ts
Time Attakc 022a443e9b fix(pricing): correct voyage-4-large rate and add the missing voyage-4 family entries (#3480)
Adversarial review: survived a hostile reviewer plus two independent refuters, each told to assume the PR was broken and to default to refuting when uncertain. 19 of 62 PRs cleared that bar.

voyage-4-large was billed at the voyage-3-large rate — $0.18 against a published $0.12 — so every cost estimate using it was wrong by 50%. Corrected in the canonical table only, per CLAUDE.md's rule that every other pricing table is a derived view, and the drift guard passes. Rate checked against the live vendor page.

Verified before merge: the PR's own tests fail when the production change is reverted; typecheck clean; MERGEABLE/CLEAN with 22/22 checks on the current base after batches 1-3 landed.

Known gap, recorded rather than hidden: this PR previously failed the JSONB parity guard on a 32-commit-stale base. I rebased it onto current master and re-ran rather than accepting 'flaky' — the guard passes on the real base, 22/22 green.
2026-08-01 03:53:41 +08:00

104 lines
3.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Pricing table contract — Voyage + ZeroEntropy coverage gate.
*
* The post-upgrade reembed cost prompt in `gbrain upgrade` falls back to
* "estimate unavailable" on unknown providers, which is fine for safety
* but bad UX if the provider IS in the recipe registry. These tests pin
* the providers that v0.35.x officially supports as first-class.
*/
import { describe, test, expect } from 'bun:test';
import {
EMBEDDING_PRICING,
lookupEmbeddingPrice,
estimateCostFromChars,
} from '../src/core/embedding-pricing.ts';
describe('lookupEmbeddingPrice — first-class providers', () => {
test('OpenAI text-embedding-3-large at $0.13/MTok', () => {
const r = lookupEmbeddingPrice('openai:text-embedding-3-large');
expect(r.kind).toBe('known');
if (r.kind === 'known') expect(r.pricePerMTok).toBe(0.13);
});
test('Voyage voyage-3-large at $0.18/MTok', () => {
const r = lookupEmbeddingPrice('voyage:voyage-3-large');
expect(r.kind).toBe('known');
if (r.kind === 'known') expect(r.pricePerMTok).toBe(0.18);
});
// Voyage v4 family, verified against docs.voyageai.com/docs/pricing 2026-07-28.
test.each([
['voyage:voyage-4-large', 0.12],
['voyage:voyage-4', 0.06],
['voyage:voyage-4-lite', 0.02],
])('Voyage %s at $%d/MTok', (model, expected) => {
const r = lookupEmbeddingPrice(model);
expect(r.kind).toBe('known');
if (r.kind === 'known') expect(r.pricePerMTok).toBe(expected);
});
// voyage-4-nano is the open-weight variant with no hosted rate published;
// it must stay unpriced so callers say "estimate unavailable" (see table comment).
test('voyage-4-nano is deliberately unpriced', () => {
expect(lookupEmbeddingPrice('voyage:voyage-4-nano').kind).toBe('unknown');
});
test('ZeroEntropy zembed-1 at $0.05/MTok (v0.35.1.0+)', () => {
const r = lookupEmbeddingPrice('zeroentropyai:zembed-1');
expect(r.kind).toBe('known');
if (r.kind === 'known') expect(r.pricePerMTok).toBe(0.05);
});
});
describe('lookupEmbeddingPrice — fall-through behavior', () => {
test('returns unknown for bogus provider', () => {
const r = lookupEmbeddingPrice('madeup:model-9000');
expect(r.kind).toBe('unknown');
if (r.kind === 'unknown') {
expect(r.provider).toBe('madeup');
expect(r.model).toBe('model-9000');
}
});
test('bare model strings default to openai', () => {
const r = lookupEmbeddingPrice('text-embedding-3-small');
expect(r.kind).toBe('known');
if (r.kind === 'known') expect(r.key).toBe('openai:text-embedding-3-small');
});
test('provider name is case-insensitive', () => {
const r = lookupEmbeddingPrice('ZeroEntropyAI:zembed-1');
expect(r.kind).toBe('known');
if (r.kind === 'known') expect(r.pricePerMTok).toBe(0.05);
});
});
describe('EMBEDDING_PRICING — table integrity', () => {
test('all entries have pricePerMTok as a non-negative finite number', () => {
for (const [key, val] of Object.entries(EMBEDDING_PRICING)) {
expect(Number.isFinite(val.pricePerMTok)).toBe(true);
expect(val.pricePerMTok).toBeGreaterThanOrEqual(0);
expect(key).toContain(':');
}
});
test('keys use lowercase provider names', () => {
for (const key of Object.keys(EMBEDDING_PRICING)) {
const provider = key.split(':')[0];
expect(provider).toBe(provider.toLowerCase());
}
});
});
describe('estimateCostFromChars', () => {
test('returns 0 for 0 chars', () => {
expect(estimateCostFromChars(0, 0.13)).toBe(0);
});
test('100M chars @ $0.13/MTok ≈ $3.71 (100M / 3.5 ≈ 28.57M tokens × 0.13)', () => {
const c = estimateCostFromChars(100_000_000, 0.13);
expect(c).toBeGreaterThan(3.7);
expect(c).toBeLessThan(3.8);
});
});