SPB Git

Repository Public Research preview

cost.mjs 3.1 KB 51 lines javascript

1import { KB, resolveModel } from "./knowledge.mjs";23const round = (n) => Math.round(n * 1e6) / 1e6;45/** Cost in USD from token counts. Prices: official pricing page (date in knowledge.json → sources). */6export function estimateCost(input) {7  const other = KB.other_prices_usd_per_mtok[input.model];8  if (other) {9    const usd = ((input.input_tokens ?? 0) * other.input) / 1e6;10    return { model: input.model, requests: input.requests ?? 1, usd_per_request: round(usd), usd_total: round(usd * (input.requests ?? 1)), rates_usd_per_mtok: other, notes: ["Embedding and moderation models bill input tokens only."] };11  }12  const m = resolveModel(input.model);13  const p = m?.pricing_usd_per_mtok;14  if (!p) return { error: `No price is recorded for "${input.model}".` };15  const notes = [];16  const tok = { input: input.input_tokens ?? 0, cached_input: input.cached_input_tokens ?? 0, output: input.output_tokens ?? 0 };17  let inRate = p.input, outRate = p.output, cachedRate = p.cached_input ?? p.input;18  if (input.batch && input.fast) return { error: "Batch and fast mode cannot be combined." };19  const prompt = tok.input + tok.cached_input;20  if (p.long_context && prompt > p.long_context.above_input_tokens) {21    const f = p.long_context.input / p.input;22    inRate = p.long_context.input; outRate = p.long_context.output; cachedRate *= f;23    notes.push(`long-context rates applied (prompt over ${p.long_context.above_input_tokens} tokens)`);24  }25  if (input.batch) {26    if (p.batch_input == null) return { error: `${m.id} has no batch price.` };27    const fi = p.batch_input / p.input, fo = p.batch_output / p.output;28    inRate *= fi; outRate *= fo; cachedRate *= fi; notes.push("batch rates applied");29  }30  if (input.fast) {31    if (p.fast_input == null) return { error: `${m.id} has no fast-mode price.` };32    const fi = p.fast_input / p.input, fo = p.fast_output / p.output;33    inRate *= fi; outRate *= fo; cachedRate *= fi; notes.push("fast-mode (priority) rates applied");34  }35  if (tok.cached_input && p.cached_input == null) notes.push(`${m.id} has no cached-input discount: cached tokens billed as input`);36  const per = { input: (tok.input * inRate) / 1e6, cached_input: (tok.cached_input * cachedRate) / 1e6, output: (tok.output * outRate) / 1e6 };37  const single = per.input + per.cached_input + per.output;38  const n = input.requests ?? 1;39  return {40    model: m.id, requests: n, tokens_per_request: tok, usd_per_request: round(single), usd_total: round(single * n),41    breakdown_usd_per_request: Object.fromEntries(Object.entries(per).map(([k, v]) => [k, round(v)])),42    rates_usd_per_mtok: { input: round(inRate), cached_input: round(cachedRate), output: round(outRate) },43    notes: [...notes, "Reasoning tokens are billed as output tokens.", "Hosted tools (web search, code interpreter, file search) are billed separately.", "Estimate only: verify against response.usage."],44  };45}4647export function compareCost(input, models) {48  return models.map((id) => estimateCost({ ...input, model: id })).filter((x) => !x.error).sort((a, b) => a.usd_total - b.usd_total)49    .map((x) => ({ model: x.model, usd_per_request: x.usd_per_request, usd_total: x.usd_total }));50}51
Files main
Menu

Theme