SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
4.0 KB · 96 lines typescript
Raw Blame History
1import type { PolyModel } from "@/lib/client/types";2import { estimateCost, estimateTextTokens } from "@/lib/client/tokens";34/**5 * Live per-response metrics for the Arena. While a model streams we estimate output tokens from the6 * character count and price them with `estimateCost`; once the `done` frame arrives the exact usage7 * and cost recorded by the server replace the estimate (`exact: true`).8 */9export type ArenaStatus = "idle" | "waiting" | "thinking" | "streaming" | "done" | "error" | "stopped";1011export interface MetricsSource {12  status: ArenaStatus;13  text: string;14  reasoning?: string;15  startedAt: number;16  firstTokenAt?: number;17  response: {18    ttftMs?: number | null;19    latencyMs?: number | null;20    costUsd?: number | null;21    usage?: { inputTokens?: number; outputTokens?: number; reasoningTokens?: number; cachedInputTokens?: number } | null;22  } | null;23}2425export interface LiveMetrics {26  status: ArenaStatus;27  /** Wall-clock time since the request started (or total latency once final). */28  elapsedMs: number;29  ttftMs: number | null;30  inputTokens: number | null;31  outputTokens: number | null;32  reasoningTokens: number | null;33  cachedTokens: number | null;34  tokensPerSecond: number | null;35  costUsd: number | null;36  /** True when tokens/cost come from the provider, false while estimated client-side. */37  exact: boolean;38}3940export function isFinalStatus(s: ArenaStatus): boolean {41  return s === "done" || s === "error" || s === "stopped";42}4344/**45 * @param inputTokensEstimate client estimate of the prompt (+system prompt, attachments) in tokens46 * @param now current timestamp — pass it in so renders stay pure (ticked by a timer component)47 */48export function liveMetrics(src: MetricsSource, model: PolyModel | null | undefined, inputTokensEstimate: number, now: number): LiveMetrics {49  const final = isFinalStatus(src.status);50  const r = src.response;51  if (final && r && (r.usage || typeof r.costUsd === "number" || typeof r.latencyMs === "number")) {52    const u = r.usage ?? null;53    const out = u?.outputTokens ?? null;54    const gen = typeof r.latencyMs === "number" ? Math.max(1, r.latencyMs - (r.ttftMs ?? 0)) : null;55    return {56      status: src.status,57      elapsedMs: r.latencyMs ?? Math.max(0, now - src.startedAt),58      ttftMs: r.ttftMs ?? null,59      inputTokens: u?.inputTokens ?? null,60      outputTokens: out,61      reasoningTokens: u?.reasoningTokens ?? null,62      cachedTokens: u?.cachedInputTokens ?? null,63      tokensPerSecond: out && gen ? Math.round((out / gen) * 1000) : null,64      costUsd: typeof r.costUsd === "number" ? r.costUsd : null,65      exact: true,66    };67  }68  const outputTokens = src.text ? estimateTextTokens(src.text) + (src.reasoning ? estimateTextTokens(src.reasoning) : 0) : null;69  const ttftMs = src.firstTokenAt ? Math.max(0, src.firstTokenAt - src.startedAt) : null;70  const genMs = src.firstTokenAt ? Math.max(1, now - src.firstTokenAt) : null;71  const tokensPerSecond = outputTokens && genMs && genMs > 400 ? Math.round((outputTokens / genMs) * 1000) : null;72  const cost = outputTokens !== null || src.status !== "idle" ? estimateCost(model, inputTokensEstimate, outputTokens ?? 0).usd : null;73  return {74    status: src.status,75    elapsedMs: Math.max(0, now - src.startedAt),76    ttftMs,77    inputTokens: inputTokensEstimate || null,78    outputTokens,79    reasoningTokens: null,80    cachedTokens: null,81    tokensPerSecond,82    costUsd: cost,83    exact: false,84  };85}8687/** Signed delta formatting helper: "−$0.0031", "+120 ms", "−340 tok". */88export function formatDelta(value: number | null, kind: "usd" | "ms" | "tokens"): string {89  if (value === null || !Number.isFinite(value)) return "—";90  const sign = value < 0 ? "−" : value > 0 ? "+" : "±";91  const abs = Math.abs(value);92  if (kind === "usd") return `${sign}$${abs < 0.01 ? abs.toFixed(abs < 0.001 ? 5 : 4) : abs.toFixed(2)}`;93  if (kind === "ms") return `${sign}${abs < 1000 ? `${Math.round(abs)} ms` : `${(abs / 1000).toFixed(abs < 10_000 ? 2 : 1)} s`}`;94  return `${sign}${abs >= 10_000 ? `${(abs / 1000).toFixed(1)}K` : Math.round(abs)} tok`;95}96