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.
This commit is contained in:
Time Attakc
2026-08-01 03:53:41 +08:00
committed by GitHub
parent e7439828f1
commit 022a443e9b
4 changed files with 41 additions and 17 deletions
File diff suppressed because one or more lines are too long
+4 -2
View File
@@ -37,8 +37,10 @@ export const voyage: Recipe = {
'voyage-multimodal-3',
],
default_dims: 1024,
cost_per_1m_tokens_usd: 0.18,
price_last_verified: '2026-04-20',
// Display hint for `gbrain providers` only (billing math goes through
// src/core/embedding-pricing.ts). Rate for the default voyage-4-large.
cost_per_1m_tokens_usd: 0.12,
price_last_verified: '2026-07-28',
// Voyage enforces 120K tokens per batch. Voyage's tokenizer runs
// ~3-4× denser than OpenAI tiktoken on mixed content (code/JSON/CJK),
// so the per-recipe pre-split uses 1 char ≈ 1 token at 0.5 utilization
+22 -11
View File
@@ -5,12 +5,15 @@
* cost-estimate prompt so users with large brains see a dollar figure
* before the chunker-version sweep re-embeds.
*
* Prices in USD per 1M tokens. Numbers as of 2026-05-11. Verify alongside
* the Anthropic-pricing refresh cycle; drift here produces estimates
* that mislead operators.
* Prices in USD per 1M tokens. Every entry carries the official page it came
* from plus the date it was last read against that page — re-verify alongside
* the Anthropic-pricing refresh cycle; drift here produces estimates that
* mislead operators. This table is for EMBEDDINGS only; chat/completion
* pricing lives in `model-pricing.ts` (different unit) and must never be
* mixed in here.
*
* Codex outside-voice C3 fold: non-OpenAI embedding providers (Voyage,
* Hunyuan, Dashscope, etc.) return UNKNOWN_PROVIDER from `lookupPrice`
* Codex outside-voice C3 fold: embedding providers with no entry below
* (Hunyuan, Dashscope, etc.) return UNKNOWN_PROVIDER from `lookupPrice`
* so the cost-estimate prompt can fall back to a "estimate unavailable
* for <provider>; press Ctrl-C in 10s to abort" message rather than
* fabricate numbers.
@@ -26,25 +29,33 @@ export interface EmbeddingPricing {
* gateway model strings (e.g. 'openai:text-embedding-3-large').
*/
export const EMBEDDING_PRICING: Record<string, EmbeddingPricing> = {
// OpenAI (https://openai.com/api/pricing/, verified 2026-05-11)
// OpenAI (https://developers.openai.com/api/docs/pricing, verified 2026-07-28)
'openai:text-embedding-3-large': { pricePerMTok: 0.13 },
'openai:text-embedding-3-small': { pricePerMTok: 0.02 },
// Legacy OpenAI ada (still common in older brains)
'openai:text-embedding-ada-002': { pricePerMTok: 0.10 },
// Voyage (https://www.voyageai.com/pricing)
// Voyage (https://docs.voyageai.com/docs/pricing, verified 2026-07-28)
'voyage:voyage-4-large': { pricePerMTok: 0.12 },
'voyage:voyage-4': { pricePerMTok: 0.06 },
'voyage:voyage-4-lite': { pricePerMTok: 0.02 },
// voyage-4-nano is deliberately absent: it's the open-weight variant (see
// src/core/ai/recipes/voyage.ts) and Voyage's pricing page lists no hosted
// rate for it. A 0 entry would under-estimate anyone paying for it via the
// hosted API; no entry means lookupEmbeddingPrice returns `unknown` and the
// caller prints "estimate unavailable" instead of a wrong number.
// Legacy Voyage models (same page, "older models" section — no free tokens):
'voyage:voyage-3-large': { pricePerMTok: 0.18 },
'voyage:voyage-3': { pricePerMTok: 0.06 },
'voyage:voyage-4-large': { pricePerMTok: 0.18 },
// ZeroEntropy (https://zeroentropy.dev/pricing — zembed-1)
// ZeroEntropy (https://www.zeroentropy.dev/pricing, verified 2026-07-28)
'zeroentropyai:zembed-1': { pricePerMTok: 0.05 },
// ZeroEntropy reranker (docs/ai-providers/zeroentropy.md — $0.025/1M tokens).
// Reused here (not a separate rerank table) because budget-tracker.ts's
// rerank-kind lookup falls back to this same table for paid providers.
'zeroentropyai:zerank-2': { pricePerMTok: 0.025 },
// Mistral (https://mistral.ai/pricing/api/, verified 2026-07-19)
// Mistral (https://mistral.ai/pricing/api/, verified 2026-07-28)
'mistral:mistral-embed': { pricePerMTok: 0.10 },
'mistral:mistral-embed-2312': { pricePerMTok: 0.10 },
// Perplexity (https://docs.perplexity.ai/getting-started/pricing, verified 2026-07-21)
// Perplexity (https://docs.perplexity.ai/getting-started/pricing, verified 2026-07-28)
'perplexity:pplx-embed-v1-0.6b': { pricePerMTok: 0.004 },
'perplexity:pplx-embed-v1-4b': { pricePerMTok: 0.03 },
};
+14 -3
View File
@@ -26,10 +26,21 @@ describe('lookupEmbeddingPrice — first-class providers', () => {
if (r.kind === 'known') expect(r.pricePerMTok).toBe(0.18);
});
test('Voyage voyage-4-large at $0.18/MTok (v0.35.1.0+)', () => {
const r = lookupEmbeddingPrice('voyage:voyage-4-large');
// 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(0.18);
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+)', () => {