fix(pricing): add the zeroentropyai:zerank-2 reranker entry the budget tracker needs (#3223) (#3233)

zerank-2 is the default reranker under search_mode: tokenmax, but had no
pricing entry — any --max-cost-capped rerank call TX2 hard-failed in
BudgetTracker.reserve() with "no pricing entry".

Adding the entry to EMBEDDING_PRICING alone (the issue's suggested fix)
does not resolve this: lookupPricing()'s rerank branch in
budget-tracker.ts never consulted that table at all, only
ANTHROPIC_PRICING and the FREE_LOCAL_RERANK_PROVIDERS zero-price set.
Verified by reproducing the hard-fail with only the pricing-table entry
added and confirming it still threw.

Fix: add the $0.025/1M-token entry (docs/ai-providers/zeroentropy.md)
and wire the rerank branch to fall back to lookupEmbeddingPrice, reusing
the existing provider:model-keyed table instead of duplicating a third
pricing surface.

Addresses the report in #3223.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Masa
2026-07-23 14:22:01 -07:00
committed by GitHub
co-authored by Claude Fable 5
parent 526c597ccf
commit 2f4ad2c0a4
3 changed files with 50 additions and 3 deletions
+15 -3
View File
@@ -166,9 +166,13 @@ const FREE_LOCAL_EMBED_PROVIDERS: ReadonlySet<string> = new Set([
* local-inference providers (FREE_LOCAL_EMBED_PROVIDERS) price at $0 so
* `--max-cost` callers don't hard-fail.
* - Rerank: try ANTHROPIC_PRICING (legacy path for any Claude-priced
* rerank); else if the provider half is in FREE_LOCAL_RERANK_PROVIDERS,
* return zero pricing so `--max-cost` callers don't TX2 hard-fail on
* local inference recipes (electricity, not tokens); else unknown.
* rerank); else try lookupEmbeddingPrice — paid rerank providers (e.g.
* ZeroEntropy's zerank-2) share the same provider:model-keyed,
* $/1M-token table as their embedding siblings, so it's reused here
* rather than duplicated into a third table; else if the provider half
* is in FREE_LOCAL_RERANK_PROVIDERS, return zero pricing so `--max-cost`
* callers don't TX2 hard-fail on local inference recipes (electricity,
* not tokens); else unknown.
*/
function lookupPricing(modelId: string, kind: BudgetKind): ModelPricing | null {
if (kind === 'embed') {
@@ -194,6 +198,14 @@ function lookupPricing(modelId: string, kind: BudgetKind): ModelPricing | null {
const tailHit = ANTHROPIC_PRICING[modelTail];
if (tailHit) return tailHit;
}
// Paid rerank providers (e.g. ZeroEntropy's zerank-2) aren't Claude-priced,
// so they miss the ANTHROPIC_PRICING checks above. Reuse the embedding
// pricing table (issue #3223) — same provider:model key shape, same
// $/1M-token unit — instead of hand-copying a third pricing surface.
if (kind === 'rerank') {
const hit = lookupEmbeddingPrice(modelId);
if (hit.kind === 'known') return { input: hit.pricePerMTok, output: 0 };
}
// v0.40.6.1: zero-price local-inference rerank providers so the budget
// tracker's TX2 hard-fail doesn't trip on `llama-server-reranker:<model>`
// under `--max-cost`. Only the rerank kind — chat/embed already have
+4
View File
@@ -37,6 +37,10 @@ export const EMBEDDING_PRICING: Record<string, EmbeddingPricing> = {
'voyage:voyage-4-large': { pricePerMTok: 0.18 },
// ZeroEntropy (https://zeroentropy.dev/pricing — zembed-1)
'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:mistral-embed': { pricePerMTok: 0.10 },
'mistral:mistral-embed-2312': { pricePerMTok: 0.10 },
+31
View File
@@ -248,6 +248,37 @@ describe('BudgetTracker.reserve', () => {
expect((caught as BudgetExhausted).reason).toBe('no_pricing');
});
test('#3223: rerank kind for zeroentropyai:zerank-2 prices from the embedding table (no TX2 throw under --max-cost)', () => {
// Pre-fix: `search_mode: tokenmax` defaults the zerank-2 reranker ON
// (docs/ai-providers/zeroentropy.md), but lookupPricing's rerank branch
// never consulted the embedding pricing table (where ZeroEntropy's
// provider:model-keyed prices live) — so any --max-cost run that
// reranked TX2 hard-failed with "no pricing entry" even after adding
// the entry to EMBEDDING_PRICING alone. Fixed by wiring the rerank
// branch to fall back to lookupEmbeddingPrice.
const t = new BudgetTracker({ maxCostUsd: 0.0001, label: 'test', auditPath });
expect(() =>
t.reserve({
modelId: 'zeroentropyai:zerank-2',
estimatedInputTokens: 3000,
maxOutputTokens: 0,
kind: 'rerank',
}),
).not.toThrow();
expect(t.totalSpent).toBe(0); // reserve() only projects; record() below banks it.
expect(() =>
t.record({
modelId: 'zeroentropyai:zerank-2',
inputTokens: 3000,
outputTokens: 0,
kind: 'rerank',
}),
).not.toThrow();
// $0.025/1M * 3000 tokens = $0.000075, under the $0.0001 cap — proves the
// real ZeroEntropy price was used, not a $0 fallback.
expect(t.totalSpent).toBeCloseTo(0.000075, 9);
});
test('v0.40.x: local embed providers price at $0 (no TX2 throw under --max-cost)', () => {
// FREE_LOCAL_EMBED_PROVIDERS — ollama / llama-server run on local inference
// (electricity, not tokens). Pre-fix a --max-cost embed/reindex job