@linxiraos/pi-stats 1.1.26 → 1.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/client/index.css +1 -1
- package/dist/client/index.js +73 -73
- package/dist/types/client/data/formatters.d.ts +2 -0
- package/dist/types/types.d.ts +1 -1
- package/package.json +5 -5
- package/src/aggregator.ts +3 -0
- package/src/client/data/formatters.ts +7 -0
- package/src/client/routes/ModelsRoute.tsx +3 -2
- package/src/client/routes/OverviewRoute.tsx +2 -1
- package/src/client/routes/ProjectsRoute.tsx +2 -1
- package/src/client/routes/ProvidersRoute.tsx +3 -2
- package/src/client/routes/RequestsRoute.tsx +2 -2
- package/src/client/routes/ToolsRoute.tsx +4 -3
- package/src/client/styles.css +1 -0
- package/src/client/ui/RequestDrawer.tsx +1 -1
- package/src/index.ts +2 -1
- package/src/types.ts +1 -1
|
@@ -14,6 +14,8 @@ export declare function isUnpricedMessage(message: Pick<MessageStats, "provider"
|
|
|
14
14
|
/** Format one request's cost, distinguishing unpriced usage from free usage. */
|
|
15
15
|
export declare function formatMessageCost(message: Pick<MessageStats, "provider" | "usage" | "costUnpriced">, digits?: number): string;
|
|
16
16
|
export declare function formatPercent(value: number, digits?: number): string;
|
|
17
|
+
/** Show small nonzero failure rates without rounding them to zero. */
|
|
18
|
+
export declare function formatErrorRate(value: number): string;
|
|
17
19
|
export declare function formatDurationMs(value: number | null, digits?: number): string;
|
|
18
20
|
/** Wall-clock length: seconds below a minute, then `17m 41s` / `2h 05m`. */
|
|
19
21
|
export declare function formatElapsed(ms: number): string;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -61,7 +61,7 @@ export interface MessageStatsInput extends Omit<MessageStats, "usage" | "costUnp
|
|
|
61
61
|
export interface RequestDetails extends MessageStats {
|
|
62
62
|
/** The full conversation history or just the last turn. */
|
|
63
63
|
messages: unknown[];
|
|
64
|
-
/** The model's response. */
|
|
64
|
+
/** The model's response; `null` for `model_usage` entries, which journal no payload. */
|
|
65
65
|
output: unknown;
|
|
66
66
|
}
|
|
67
67
|
/**
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@linxiraos/pi-stats",
|
|
4
|
-
"version": "1.1.
|
|
5
|
-
"description": "Local observability dashboard for
|
|
4
|
+
"version": "1.1.27",
|
|
5
|
+
"description": "Local observability dashboard for omp AI usage statistics",
|
|
6
6
|
"homepage": "https://linxira-os.github.io/zeta/",
|
|
7
7
|
"author": "Linxira-OS",
|
|
8
8
|
"license": "MIT",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@linxiraos/pi-ai": "1.1.
|
|
43
|
-
"@linxiraos/pi-catalog": "1.1.
|
|
44
|
-
"@linxiraos/pi-utils": "1.1.
|
|
42
|
+
"@linxiraos/pi-ai": "1.1.27",
|
|
43
|
+
"@linxiraos/pi-catalog": "1.1.27",
|
|
44
|
+
"@linxiraos/pi-utils": "1.1.27",
|
|
45
45
|
"lucide-react": "^1.24.0",
|
|
46
46
|
"react": "19.2.7",
|
|
47
47
|
"react-dom": "19.2.7"
|
package/src/aggregator.ts
CHANGED
|
@@ -565,6 +565,9 @@ export async function getRequestDetails(id: number): Promise<RequestDetails | nu
|
|
|
565
565
|
if (!msg) return null;
|
|
566
566
|
|
|
567
567
|
const entry = await getSessionEntry(msg.sessionFile, msg.entryId);
|
|
568
|
+
// Role-model attempts (judge, auto-thinking, …) journal only a `model_usage`
|
|
569
|
+
// record: usage and outcome, no request or response payload.
|
|
570
|
+
if (entry?.type === "model_usage") return { ...msg, messages: [entry], output: null };
|
|
568
571
|
if (entry?.type !== "message" || !("message" in entry)) return null;
|
|
569
572
|
|
|
570
573
|
return {
|
|
@@ -56,6 +56,13 @@ export function formatPercent(value: number, digits = 1): string {
|
|
|
56
56
|
return `${(value * 100).toFixed(digits)}%`;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/** Show small nonzero failure rates without rounding them to zero. */
|
|
60
|
+
export function formatErrorRate(value: number): string {
|
|
61
|
+
const percent = value * 100;
|
|
62
|
+
if (percent > 0 && percent < 0.005) return "<0.01%";
|
|
63
|
+
return formatPercent(value, percent > 0 && percent < 0.1 ? 2 : 1);
|
|
64
|
+
}
|
|
65
|
+
|
|
59
66
|
export function formatDurationMs(value: number | null, digits?: number): string {
|
|
60
67
|
if (value === null) return "-";
|
|
61
68
|
const sec = value / 1000;
|
|
@@ -7,6 +7,7 @@ import { buildModelColorLookup, modelKey, OTHER_COLOR } from "../data/colors";
|
|
|
7
7
|
import {
|
|
8
8
|
formatCompact,
|
|
9
9
|
formatDurationMs,
|
|
10
|
+
formatErrorRate,
|
|
10
11
|
formatEstimatedCost,
|
|
11
12
|
formatInteger,
|
|
12
13
|
formatPercent,
|
|
@@ -388,7 +389,7 @@ function buildModelColumns(view: ModelsView, expandedKey: string | null, bucketW
|
|
|
388
389
|
) : (
|
|
389
390
|
<span title={`${formatInteger(row.failedRequests)} failed`}>
|
|
390
391
|
<Badge tone={errorRateTone(row.errorRate)} mono>
|
|
391
|
-
{
|
|
392
|
+
{formatErrorRate(row.errorRate)}
|
|
392
393
|
</Badge>
|
|
393
394
|
</span>
|
|
394
395
|
),
|
|
@@ -477,7 +478,7 @@ function ModelDetail({
|
|
|
477
478
|
label: "Error rate",
|
|
478
479
|
value: (
|
|
479
480
|
<span className={`tone-${errorRateTone(model.errorRate)}`}>
|
|
480
|
-
{
|
|
481
|
+
{formatErrorRate(model.errorRate)}{" "}
|
|
481
482
|
<span className="dim">({formatInteger(model.failedRequests)} failed)</span>
|
|
482
483
|
</span>
|
|
483
484
|
),
|
|
@@ -5,6 +5,7 @@ import { Legend, ShareBar, TimeChart } from "../charts";
|
|
|
5
5
|
import {
|
|
6
6
|
formatCompact,
|
|
7
7
|
formatDurationMs,
|
|
8
|
+
formatErrorRate,
|
|
8
9
|
formatEstimatedCost,
|
|
9
10
|
formatInteger,
|
|
10
11
|
formatMessageCost,
|
|
@@ -136,7 +137,7 @@ export function OverviewRoute({ active, range, onRequestClick }: OverviewRoutePr
|
|
|
136
137
|
/>
|
|
137
138
|
<Stat
|
|
138
139
|
label="Error rate"
|
|
139
|
-
value={
|
|
140
|
+
value={formatErrorRate(overall.errorRate)}
|
|
140
141
|
hint={`${formatInteger(overall.successfulRequests)} succeeded`}
|
|
141
142
|
spark={series.errors}
|
|
142
143
|
sparkColor="var(--bad)"
|
|
@@ -4,6 +4,7 @@ import { BarList } from "../charts";
|
|
|
4
4
|
import {
|
|
5
5
|
formatCompact,
|
|
6
6
|
formatDurationMs,
|
|
7
|
+
formatErrorRate,
|
|
7
8
|
formatEstimatedCost,
|
|
8
9
|
formatFolder,
|
|
9
10
|
formatInteger,
|
|
@@ -296,7 +297,7 @@ function folderColumns(maxRequests: number, maxCost: number): Column<FolderRowVi
|
|
|
296
297
|
render: row => (
|
|
297
298
|
<span title={`${formatInteger(row.failedRequests)} failed`}>
|
|
298
299
|
<Badge tone={row.failedRequests > 0 ? errorRateTone(row.errorRate) : "neutral"} mono>
|
|
299
|
-
{
|
|
300
|
+
{formatErrorRate(row.errorRate)}
|
|
300
301
|
</Badge>
|
|
301
302
|
</span>
|
|
302
303
|
),
|
|
@@ -6,6 +6,7 @@ import { buildColorLookup, OTHER_COLOR, SERIES_COLORS } from "../data/colors";
|
|
|
6
6
|
import {
|
|
7
7
|
formatCompact,
|
|
8
8
|
formatCost,
|
|
9
|
+
formatErrorRate,
|
|
9
10
|
formatEstimatedCost,
|
|
10
11
|
formatInteger,
|
|
11
12
|
formatPercent,
|
|
@@ -179,7 +180,7 @@ export function ProvidersRoute({ active, range }: ProvidersRouteProps) {
|
|
|
179
180
|
/>
|
|
180
181
|
<Stat
|
|
181
182
|
label="Error rate"
|
|
182
|
-
value={
|
|
183
|
+
value={formatErrorRate(t.requests > 0 ? t.failed / t.requests : 0)}
|
|
183
184
|
hint={`${formatInteger(t.requests - t.failed)} succeeded`}
|
|
184
185
|
/>
|
|
185
186
|
</StatGrid>
|
|
@@ -339,7 +340,7 @@ function ProviderTotalsTable({
|
|
|
339
340
|
<span className="row" style={{ gap: 8, justifyContent: "flex-end" }}>
|
|
340
341
|
<span className="num dim">{formatInteger(p.failedRequests)}</span>
|
|
341
342
|
<Badge tone={p.failedRequests > 0 ? errorRateTone(rate) : "neutral"} mono>
|
|
342
|
-
{
|
|
343
|
+
{formatErrorRate(rate)}
|
|
343
344
|
</Badge>
|
|
344
345
|
</span>
|
|
345
346
|
);
|
|
@@ -3,11 +3,11 @@ import { getRecentRequests } from "../api";
|
|
|
3
3
|
import {
|
|
4
4
|
formatCompact,
|
|
5
5
|
formatDurationMs,
|
|
6
|
+
formatErrorRate,
|
|
6
7
|
formatEstimatedCost,
|
|
7
8
|
formatFolder,
|
|
8
9
|
formatInteger,
|
|
9
10
|
formatMessageCost,
|
|
10
|
-
formatPercent,
|
|
11
11
|
formatRelativeTime,
|
|
12
12
|
formatTimestamp,
|
|
13
13
|
} from "../data/formatters";
|
|
@@ -125,7 +125,7 @@ export function RequestsRoute({ active, range, onRequestClick }: RequestsRoutePr
|
|
|
125
125
|
<Stat
|
|
126
126
|
label="Failed"
|
|
127
127
|
value={formatInteger(summary.failed)}
|
|
128
|
-
hint={`${summary.requests > 0 ?
|
|
128
|
+
hint={`${summary.requests > 0 ? formatErrorRate(summary.failed / summary.requests) : "–"} · ${formatInteger(summary.aborted)} aborted`}
|
|
129
129
|
/>
|
|
130
130
|
<Stat
|
|
131
131
|
label="Tokens"
|
|
@@ -5,6 +5,7 @@ import { Legend, Sparkline, TimeChart, useHiddenSeries } from "../charts";
|
|
|
5
5
|
import { buildColorLookup, OTHER_COLOR } from "../data/colors";
|
|
6
6
|
import {
|
|
7
7
|
formatCompact,
|
|
8
|
+
formatErrorRate,
|
|
8
9
|
formatEstimatedCost,
|
|
9
10
|
formatInteger,
|
|
10
11
|
formatPercent,
|
|
@@ -96,7 +97,7 @@ export function ToolsRoute({ active, range }: ToolsRouteProps) {
|
|
|
96
97
|
<Stat
|
|
97
98
|
label="Error rate"
|
|
98
99
|
title="Tool results that came back flagged as errors"
|
|
99
|
-
value={
|
|
100
|
+
value={formatErrorRate(t.calls > 0 ? t.errors / t.calls : 0)}
|
|
100
101
|
hint={`${formatInteger(t.calls - t.errors)} succeeded`}
|
|
101
102
|
spark={view.totalErrors}
|
|
102
103
|
sparkColor="var(--bad)"
|
|
@@ -389,7 +390,7 @@ function buildToolColumns(
|
|
|
389
390
|
<span className="row" style={{ gap: 8, justifyContent: "flex-end" }}>
|
|
390
391
|
<span className="num dim">{formatInteger(row.errors)}</span>
|
|
391
392
|
<Badge tone={row.errors > 0 ? errorRateTone(row.errorRate) : "neutral"} mono>
|
|
392
|
-
{
|
|
393
|
+
{formatErrorRate(row.errorRate)}
|
|
393
394
|
</Badge>
|
|
394
395
|
</span>
|
|
395
396
|
),
|
|
@@ -519,7 +520,7 @@ function ToolModelTable({
|
|
|
519
520
|
<span className="row" style={{ gap: 8, justifyContent: "flex-end" }}>
|
|
520
521
|
<span className="num dim">{formatInteger(row.errors)}</span>
|
|
521
522
|
<Badge tone={row.errors > 0 ? errorRateTone(row.errorRate) : "neutral"} mono>
|
|
522
|
-
{
|
|
523
|
+
{formatErrorRate(row.errorRate)}
|
|
523
524
|
</Badge>
|
|
524
525
|
</span>
|
|
525
526
|
),
|
package/src/client/styles.css
CHANGED
|
@@ -175,7 +175,7 @@ function RequestDetailsBody({ details, aborted }: { details: RequestDetails; abo
|
|
|
175
175
|
</div>
|
|
176
176
|
</section>
|
|
177
177
|
|
|
178
|
-
<JsonBlock data={output} title="Output message" />
|
|
178
|
+
{output != null && <JsonBlock data={output} title="Output message" />}
|
|
179
179
|
<JsonBlock data={messages} title="Session entry" initialCollapsed />
|
|
180
180
|
<JsonBlock data={row} title="Stats row" initialCollapsed />
|
|
181
181
|
</>
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { formatDuration, formatNumber, formatPercent, normalizePremiumRequests } from "@linxiraos/pi-utils";
|
|
5
5
|
import chalk from "@linxiraos/pi-utils/chalk";
|
|
6
|
+
import { formatErrorRate } from "./client/data/formatters";
|
|
6
7
|
import { getDashboardStats, getTotalMessageCount, syncAllSessions } from "./aggregator";
|
|
7
8
|
import { closeDb } from "./db";
|
|
8
9
|
import { refreshRollups } from "./rollup";
|
|
@@ -58,7 +59,7 @@ export async function printStatsSummary(): Promise<void> {
|
|
|
58
59
|
|
|
59
60
|
console.log(chalk.bold("Overall:"));
|
|
60
61
|
console.log(` Requests: ${formatNumber(overall.totalRequests)} (${formatNumber(overall.failedRequests)} errors)`);
|
|
61
|
-
console.log(` Error Rate: ${
|
|
62
|
+
console.log(` Error Rate: ${formatErrorRate(overall.errorRate)}`);
|
|
62
63
|
console.log(` Total Tokens: ${formatNumber(overall.totalInputTokens + overall.totalOutputTokens)}`);
|
|
63
64
|
console.log(` Input Tokens: ${formatNumber(overall.totalInputTokens)}`);
|
|
64
65
|
console.log(` Output Tokens: ${formatNumber(overall.totalOutputTokens)}`);
|
package/src/types.ts
CHANGED
|
@@ -63,7 +63,7 @@ export interface MessageStatsInput extends Omit<MessageStats, "usage" | "costUnp
|
|
|
63
63
|
export interface RequestDetails extends MessageStats {
|
|
64
64
|
/** The full conversation history or just the last turn. */
|
|
65
65
|
messages: unknown[];
|
|
66
|
-
/** The model's response. */
|
|
66
|
+
/** The model's response; `null` for `model_usage` entries, which journal no payload. */
|
|
67
67
|
output: unknown;
|
|
68
68
|
}
|
|
69
69
|
|