Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions dashboard/src/lib/model-breakdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ function resolveModelName(model: any, fallback: any) {
return fallback;
}

// Server-side resolution tiers that mean the price was guessed from a partial
// match rather than an exact model id (src/lib/pricing/index.js). A guessed
// price is plausible and therefore never looks wrong — worth flagging.
const FUZZY_PRICING_TIERS = new Set(["curated:fuzzy", "litellm:fuzzy", "litellm:prefix-strip"]);

function isKnownZeroCostModel(name: any) {
const lower = String(name || "").toLowerCase();
return lower.includes("free") || lower.includes("hy3-preview") || /^glm-[\d.]+-flash(?![a-z])/.test(lower);
Expand Down Expand Up @@ -81,17 +86,32 @@ export function buildFleetData(modelBreakdown: any, { copyFn }: AnyRecord = {})
: entry.totalCost > 0 && entry.totalTokens > 0
? (modelTokens / entry.totalTokens) * entry.totalCost
: null;
const pricingMissing =
modelTokens > 0 &&
(modelCost == null || modelCost <= 0) &&
!isKnownZeroCostModel(name);
return { id, name, share, usage: modelTokens, cost: modelCost, pricingMissing };
// Prefer the server's own account of how the price resolved. The
// cost<=0 heuristic below cannot tell an unpriced model from a
// genuinely free one, which is why isKnownZeroCostModel exists; it
// stays as the fallback for responses from an older server.
const pricingTier = typeof model?.pricing_tier === "string" ? model.pricing_tier : null;
const pricingMissing = pricingTier
? pricingTier === "miss" && modelTokens > 0
: modelTokens > 0 && (modelCost == null || modelCost <= 0) && !isKnownZeroCostModel(name);
const pricingFuzzy = Boolean(pricingTier && FUZZY_PRICING_TIERS.has(pricingTier));
return {
id,
name,
share,
usage: modelTokens,
cost: modelCost,
pricingTier,
pricingMissing,
pricingFuzzy,
};
})
.filter(Boolean);
const topCostModel = models
.filter((model: any) => Number.isFinite(Number(model?.cost)) && Number(model.cost) > 0)
.sort((a: any, b: any) => Number(b.cost) - Number(a.cost))[0] || null;
const missingPricingModels = models.filter((model: any) => model?.pricingMissing);
const fuzzyPricingModels = models.filter((model: any) => model?.pricingFuzzy);
return {
source: entry.source,
label,
Expand All @@ -100,6 +120,7 @@ export function buildFleetData(modelBreakdown: any, { copyFn }: AnyRecord = {})
usage: entry.totalTokens,
topCostModel,
missingPricingModels,
fuzzyPricingModels,
models,
};
});
Expand All @@ -125,7 +146,9 @@ export function buildUsageInsights(modelBreakdown: any, { copyFn }: AnyRecord =
.filter((model: any) => Number.isFinite(Number(model?.usage)) && Number(model.usage) > 0)
.sort((a: any, b: any) => Number(b.usage) - Number(a.usage))[0] || null;
const missingPricingModels = allModels.filter((model: any) => model?.pricingMissing);
const fuzzyPricingModels = allModels.filter((model: any) => model?.pricingFuzzy);
return {
fuzzyPricingModels,
totalTokens,
totalCost,
costPerMillionTokens: totalTokens > 0 ? totalCost / (totalTokens / 1_000_000) : null,
Expand Down
11 changes: 9 additions & 2 deletions dashboard/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ async function runLocalSyncCommand(extraEnv = {}) {
// at require-time, so dev-server mocks still get LiteLLM-backed cost data.
const __viteRequire = createRequire(import.meta.url);
const __pricing = __viteRequire(path.resolve(REPO_ROOT, "src/lib/pricing"));
const { getModelPricing, computeRowCost } = __pricing;
const { getModelPricing, getModelPricingMeta, computeRowCost } = __pricing;

async function handleLocalApi(req, res, url) {
// Honor the dashboard's tz / tz_offset_minutes params so hourly/daily
Expand Down Expand Up @@ -777,7 +777,14 @@ async function handleLocalApi(req, res, url) {
model: m.model,
source: s.source,
});
return { ...m, totals: { ...m.totals, total_cost_usd: cost.toFixed(6) } };
// Mirror the real handler: without pricing_tier the dev server cannot
// exercise the unpriced/fuzzy badges at all.
const { tier } = getModelPricingMeta(m.model, { source: s.source });
return {
...m,
pricing_tier: tier,
totals: { ...m.totals, total_cost_usd: cost.toFixed(6) },
};
}).sort((a, b) => b.totals.total_tokens - a.totals.total_tokens);
const sourceCost = s.models.reduce((sum, m) => sum + Number(m.totals.total_cost_usd), 0);
s.totals.total_cost_usd = sourceCost.toFixed(6);
Expand Down
26 changes: 13 additions & 13 deletions openwiki-facts/source-facts.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,87 +44,87 @@
"methods": [
"POST"
],
"evidence": "src/lib/local-api.js:896",
"evidence": "src/lib/local-api.js:898",
"mutation": true
},
{
"path": "/functions/tokentracker-wrapped",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:930",
"evidence": "src/lib/local-api.js:932",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-summary",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:941",
"evidence": "src/lib/local-api.js:943",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-daily",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1026",
"evidence": "src/lib/local-api.js:1028",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-heatmap",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1037",
"evidence": "src/lib/local-api.js:1039",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-model-breakdown",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1105",
"evidence": "src/lib/local-api.js:1107",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-category-breakdown",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1170",
"evidence": "src/lib/local-api.js:1186",
"mutation": false
},
{
"path": "/functions/tokentracker-project-usage-summary",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1216",
"evidence": "src/lib/local-api.js:1232",
"mutation": false
},
{
"path": "/functions/tokentracker-user-status",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1293",
"evidence": "src/lib/local-api.js:1309",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-hourly",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1303",
"evidence": "src/lib/local-api.js:1319",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-monthly",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1313",
"evidence": "src/lib/local-api.js:1329",
"mutation": false
},
{
Expand All @@ -133,15 +133,15 @@
"GET",
"POST"
],
"evidence": "src/lib/local-api.js:1347",
"evidence": "src/lib/local-api.js:1363",
"mutation": true
},
{
"path": "/functions/tokentracker-usage-limits",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1497",
"evidence": "src/lib/local-api.js:1513",
"mutation": false
}
]
Expand Down
20 changes: 18 additions & 2 deletions src/lib/local-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const avatarProxyCache = new Map();
const {
MODEL_PRICING,
getModelPricing,
getModelPricingMeta,
getPricingDiagnostics,
computeRowCost,
ensurePricingLoaded,
} = require("./pricing");
Expand Down Expand Up @@ -1147,7 +1149,15 @@ function createLocalApiHandler({ queuePath }) {
model: m.model,
source: s.source,
});
return { ...m, totals: { ...m.totals, total_cost_usd: cost.toFixed(6) } };
// How the price was resolved, so the dashboard can say "unpriced"
// or "matched by substring" instead of inferring it from a $0 cost
// — a genuinely free model and an unknown one both cost $0.
const { tier } = getModelPricingMeta(m.model, { source: s.source });
return {
...m,
pricing_tier: tier,
totals: { ...m.totals, total_cost_usd: cost.toFixed(6) },
};
})
.sort((a, b) => b.totals.total_tokens - a.totals.total_tokens);
const sourceCost = s.models.reduce((sum, m) => sum + Number(m.totals.total_cost_usd), 0);
Expand All @@ -1157,7 +1167,13 @@ function createLocalApiHandler({ queuePath }) {

json(res, {
from, to, days: 0, scope, excluded_sources: excludedSources, sources,
pricing: { model: "per-model", pricing_mode: "per_token_type", source: "litellm", effective_from: new Date().toISOString().slice(0, 10) },
pricing: {
model: "per-model",
pricing_mode: "per_token_type",
source: "litellm",
effective_from: new Date().toISOString().slice(0, 10),
...getPricingDiagnostics(),
},
});
return true;
}
Expand Down
Loading