improves display of large token numbers

This commit is contained in:
nai-degen 2023-08-31 13:23:36 -05:00
parent 4b32130eaa
commit fe0f04ceb8
6 changed files with 66 additions and 45 deletions

View File

@ -11,7 +11,8 @@ import {
UserSchema,
HttpError,
} from "../common";
import { keyPool } from "../../key-management";
import { ModelFamily, keyPool } from "../../key-management";
import { getTokenCostUsd, prettyTokens } from "../../stats";
const router = Router();
@ -56,15 +57,22 @@ router.get("/view-user/:token", (req, res) => {
});
router.get("/list-users", (req, res) => {
const sort = parseSort(req.query.sort) || ["promptCount", "lastUsedAt"];
const sort = parseSort(req.query.sort) || ["sumCost", "createdAt"];
const requestedPageSize =
Number(req.query.perPage) || Number(req.cookies.perPage) || 20;
const perPage = Math.max(1, Math.min(1000, requestedPageSize));
const users = userStore
.getUsers()
.map((user) => {
const sum = Object.values(user.tokenCounts).reduce((a, b) => a + b, 0); // TODO: cache
return { ...user, sumTokenCounts: sum };
const sums = { sumTokens: 0, sumCost: 0, prettyUsage: "" };
Object.entries(user.tokenCounts).forEach(([model, tokens]) => {
sums.sumTokens += tokens;
sums.sumCost += getTokenCostUsd(model as ModelFamily, tokens);
});
sums.prettyUsage = `${prettyTokens(
sums.sumTokens
)} ($${sums.sumCost.toFixed(2)}) `;
return { ...user, ...sums };
})
.sort(sortBy(sort, false));

View File

@ -12,6 +12,7 @@ import {
import { getUniqueIps } from "./proxy/rate-limit";
import { getEstimatedWaitTime, getQueueLength } from "./proxy/queue";
import { logger } from "./logger";
import { getTokenCostUsd, prettyTokens } from "./stats";
const INFO_PAGE_TTL = 2000;
let infoPageHtml: string | undefined;
@ -49,27 +50,6 @@ type ServiceAggregates = {
const modelStats = new Map<ModelAggregateKey, number>();
const serviceStats = new Map<keyof ServiceAggregates, number>();
// technically slightly underestimates, because completion tokens cost more
// than prompt tokens but we don't track those separately right now
function getTokenCostUsd(model: ModelFamily, tokens: number) {
let cost = 0;
switch (model) {
case "gpt4-32k":
cost = 0.00006;
break;
case "gpt4":
cost = 0.00003;
break;
case "turbo":
cost = 0.0000015;
break;
case "claude":
cost = 0.00001102;
break;
}
return cost * tokens;
}
export const handleInfoPage = (req: Request, res: Response) => {
if (infoPageLastUpdated + INFO_PAGE_TTL > Date.now()) {
res.send(infoPageHtml);
@ -96,6 +76,7 @@ function cacheInfoPageHtml(baseUrl: string) {
const anthropicKeys = serviceStats.get("anthropicKeys") || 0;
const proompts = serviceStats.get("proompts") || 0;
const tokens = serviceStats.get("tokens") || 0;
const tokenCost = serviceStats.get("tokenCost") || 0;
const info = {
uptime: Math.floor(process.uptime()),
@ -104,7 +85,7 @@ function cacheInfoPageHtml(baseUrl: string) {
...(anthropicKeys ? { anthropic: baseUrl + "/proxy/anthropic" } : {}),
},
proompts,
tookens: `${tokens} ($${(serviceStats.get("tokenCost") || 0).toFixed(2)})`,
tookens: `${prettyTokens(tokens)} ($${tokenCost.toFixed(2)})`,
...(config.modelRateLimit ? { proomptersNow: getUniqueIps() } : {}),
openaiKeys,
anthropicKeys,
@ -243,7 +224,7 @@ function getOpenAIInfo() {
const cost = getTokenCostUsd(f, tokens);
info[f] = {
usage: `${tokens} tokens ($${cost.toFixed(2)})`,
usage: `${prettyTokens(tokens)} tokens ($${cost.toFixed(2)})`,
activeKeys: modelStats.get(`${f}__active`) || 0,
trialKeys: modelStats.get(`${f}__trial`) || 0,
revokedKeys: modelStats.get(`${f}__revoked`) || 0,
@ -282,7 +263,7 @@ function getAnthropicInfo() {
const cost = getTokenCostUsd("claude", tokens);
return {
claude: {
usage: `${tokens} tokens ($${cost.toFixed(2)})`,
usage: `${prettyTokens(tokens)} tokens ($${cost.toFixed(2)})`,
activeKeys: claudeInfo.active,
proomptersInQueue: claudeInfo.queued,
estimatedQueueTime: claudeInfo.queueTime,

View File

@ -16,11 +16,10 @@ import { logger } from "../../logger";
const log = logger.child({ module: "users" });
export type UserTokenCounts = {
turbo: number;
gpt4: number;
claude: number;
"gpt4-32k"?: number;
type UserTokenCounts = {
[K in Exclude<ModelFamily, "gpt4-32k">]: number;
} & {
[K in "gpt4-32k"]?: number; // Optional because it was added later
};
export interface User {

34
src/stats/index.ts Normal file
View File

@ -0,0 +1,34 @@
import { ModelFamily } from "../key-management";
// technically slightly underestimates, because completion tokens cost more
// than prompt tokens but we don't track those separately right now
export function getTokenCostUsd(model: ModelFamily, tokens: number) {
let cost = 0;
switch (model) {
case "gpt4-32k":
cost = 0.00006;
break;
case "gpt4":
cost = 0.00003;
break;
case "turbo":
cost = 0.0000015;
break;
case "claude":
cost = 0.00001102;
break;
}
return cost * tokens;
}
export function prettyTokens(tokens: number): string {
if (tokens < 1000) {
return tokens.toString();
} else if (tokens < 1000000) {
return (tokens / 1000).toFixed(1) + "k";
} else if (tokens < 1000000000) {
return (tokens / 1000000).toFixed(2) + "m";
} else {
return (tokens / 1000000000).toFixed(2) + "b";
}
}

View File

@ -2,5 +2,14 @@
<footer>
<a href="/admin">Index</a> | <a href="/admin/logout">Logout</a>
</footer>
<script>
document.querySelectorAll("td").forEach(function(td) {
if (td.innerText.match(/^\d{13}$/)) {
if (td.innerText == 0) return 'never';
var date = new Date(parseInt(td.innerText));
td.innerText = date.toISOString().replace("T", " ").replace(/\.\d+Z$/, "");
}
});
</script>
</body>
</html>

View File

@ -12,7 +12,7 @@
<th>User</th>
<th <% if (sort.includes("ip")) { %>class="active"<% } %> ><a href="/admin/manage/list-users?sort=ip">IPs</a></th>
<th <% if (sort.includes("promptCount")) { %>class="active"<% } %> ><a href="/admin/manage/list-users?sort=promptCount">Prompts</a></th>
<th <% if (sort.includes("sumTokenCounts")) { %>class="active"<% } %> ><a href="/admin/manage/list-users?sort=sumTokenCounts">Tokens</a></th>
<th <% if (sort.includes("sumCost")) { %>class="active"<% } %> ><a href="/admin/manage/list-users?sort=sumCost">Usage</a></th>
<th>Type</th>
<th <% if (sort.includes("createdAt")) { %>class="active"<% } %> ><a href="/admin/manage/list-users?sort=createdAt">Created (UTC)</a></th>
<th <% if (sort.includes("lastUsedAt")) { %>class="active"<% } %> ><a href="/admin/manage/list-users?sort=lastUsedAt">Last Used (UTC)</a></th>
@ -34,7 +34,7 @@
</td>
<td><%= user.ip.length %></td>
<td><%= user.promptCount %></td>
<td><%= user.sumTokenCounts %></td>
<td><%= user.prettyUsage %></td>
<td><%= user.type %></td>
<td><%= user.createdAt %></td>
<td><%= user.lastUsedAt ?? "never" %></td>
@ -117,14 +117,4 @@
});
});
</script>
<script>
document.querySelectorAll("td").forEach(function(td) {
if (td.innerText.match(/^\d{13}$/)) {
if (td.innerText == 0) return 'never';
var date = new Date(parseInt(td.innerText));
td.innerText = date.toISOString().replace("T", " ").replace(/\.\d+Z$/, "");
}
});
</script>
<%- include("../_partials/admin-footer") %>