Files
gbrain/src/core/embedding-pricing.ts
T
593ba16535 fix(embed): support hosted Perplexity embeddings (pplx-embed-v1-*) (#1046) (#3099)
Adds a `perplexity` embedding recipe (OpenAI-compatible at
https://api.perplexity.ai/v1, auth via PERPLEXITY_API_KEY only — never an
OPENAI_API_KEY fallback) covering pplx-embed-v1-0.6b and pplx-embed-v1-4b.

Perplexity's /embeddings endpoint diverges from OpenAI's wire shape in two
places that break the AI SDK adapter, handled by a new perplexityCompatFetch
shim (mirrors the Voyage/ZeroEntropy pattern incl. the two-layer OOM caps):
- encoding_format only accepts base64_int8/base64_binary; the SDK's 'float'
  default is forced to 'base64_int8' outbound.
- The response embedding is base64-encoded signed int8 components (natively
  quantized); decoded to number[] inbound so the SDK's Zod schema validates.
  Cosine similarity is scale-invariant, so raw int8 components rank correctly.

Flexible dims (Matryoshka-style 128..native max: 1024 for 0.6b, 2560 for 4b)
validate fail-loud in dims.ts + the init preflight; `dimensions` is
Perplexity's native field so no wire translation is needed. default_dims is
1024 (works on a plain vector column for both models); the 4b model's full
2560 width rides the existing halfvec (>2000 dims) storage/ANN path. Pricing
entries land in embedding-pricing.ts.

Co-authored-by: Sinabina <sinabina@Sinabinas-MacBook-Pro-4.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 16:43:59 -07:00

82 lines
3.6 KiB
TypeScript

/**
* v0.32.7 CJK wave — embedding model pricing lookup table.
*
* Sibling to `anthropic-pricing.ts`. Used by `gbrain upgrade`'s post-upgrade
* 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.
*
* Codex outside-voice C3 fold: non-OpenAI embedding providers (Voyage,
* 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.
*/
export interface EmbeddingPricing {
/** USD per 1M tokens (embedding cost; embeddings have no separate output rate). */
pricePerMTok: number;
}
/**
* `provider:model` keyed pricing. The colon-separated key matches
* 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: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: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)
'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 },
// Perplexity (https://docs.perplexity.ai/getting-started/pricing, verified 2026-07-21)
'perplexity:pplx-embed-v1-0.6b': { pricePerMTok: 0.004 },
'perplexity:pplx-embed-v1-4b': { pricePerMTok: 0.03 },
};
export type PriceLookupResult =
| { kind: 'known'; pricePerMTok: number; key: string }
| { kind: 'unknown'; provider: string; model: string };
/**
* Resolve a model string into a price-per-1M-tokens. Accepts both
* `provider:model` and bare `model` forms (bare assumes openai).
*/
export function lookupEmbeddingPrice(modelString: string): PriceLookupResult {
const [providerRaw, modelRaw] = modelString.includes(':')
? modelString.split(':', 2)
: ['openai', modelString];
const provider = providerRaw.trim().toLowerCase();
const model = (modelRaw ?? '').trim();
const key = `${provider}:${model}`;
const hit = EMBEDDING_PRICING[key];
if (hit) return { kind: 'known', pricePerMTok: hit.pricePerMTok, key };
return { kind: 'unknown', provider, model };
}
/**
* Estimate USD cost for embedding `charCount` characters. Uses
* 3.5 chars/token as the OpenAI tiktoken-shaped approximation for English;
* CJK-heavy brains will under-estimate by ~2x (one char ≈ one token), but
* we'd rather under-estimate than spook users with a 10x worst-case figure.
*/
export function estimateCostFromChars(charCount: number, pricePerMTok: number): number {
const tokens = Math.ceil(charCount / 3.5);
return (tokens / 1_000_000) * pricePerMTok;
}