@trazum/core 1.50.3 → 1.50.4
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/dist/config-schema.d.ts +18 -1
- package/dist/config-schema.d.ts.map +1 -1
- package/dist/config-schema.js +58 -0
- package/dist/config-schema.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/outcome.d.ts +144 -0
- package/dist/outcome.d.ts.map +1 -0
- package/dist/outcome.js +117 -0
- package/dist/outcome.js.map +1 -0
- package/dist/usage.d.ts +22 -0
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +37 -1
- package/dist/usage.js.map +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +97 -0
- package/src/index.ts +10 -0
- package/src/outcome.ts +214 -0
- package/src/usage.ts +57 -1
package/src/usage.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { createInputShapeTracker } from './input-shape.js';
|
|
|
5
5
|
import { createRepeatsTracker } from './repeats.js';
|
|
6
6
|
import { createTruncationRetryTracker } from './truncation-retry.js';
|
|
7
7
|
import { createTtlFitTracker } from './ttl-fit.js';
|
|
8
|
+
import type { OutcomeTally } from './outcome.js';
|
|
8
9
|
import { createSessionLedgerTracker } from './session-ledger.js';
|
|
9
10
|
import { createSessionCostTracker } from './session-cost.js';
|
|
10
11
|
import type { SessionCostShape } from './session-cost.js';
|
|
@@ -111,6 +112,17 @@ export interface UsageRecord {
|
|
|
111
112
|
* refuses to read a log until it is annotated is a profile nobody runs.
|
|
112
113
|
*/
|
|
113
114
|
label: string | null;
|
|
115
|
+
/**
|
|
116
|
+
* What happened, in the product's own vocabulary — `resolved`, `escalated`,
|
|
117
|
+
* `thumbs-down`, whatever the words are.
|
|
118
|
+
*
|
|
119
|
+
* The counterpart every other figure here has been missing. **Recorded by
|
|
120
|
+
* the caller and never inferred**: no absence of complaint counts as
|
|
121
|
+
* success, no short conversation counts as resolution, no retry counts as
|
|
122
|
+
* failure. `null` is "not recorded" and is reported as such, which is a
|
|
123
|
+
* different answer from any outcome and is treated as one everywhere.
|
|
124
|
+
*/
|
|
125
|
+
outcome: string | null;
|
|
114
126
|
/**
|
|
115
127
|
* Optional conversation identifier, for measuring what re-sent history costs.
|
|
116
128
|
*
|
|
@@ -241,6 +253,8 @@ export interface FieldCoverage {
|
|
|
241
253
|
label: number;
|
|
242
254
|
/** Records with a usable `session` or `conversation_id`. */
|
|
243
255
|
session: number;
|
|
256
|
+
/** Records with a usable `outcome` or `trazum_outcome`. */
|
|
257
|
+
outcome: number;
|
|
244
258
|
/** Records with a readable timestamp. */
|
|
245
259
|
ts: number;
|
|
246
260
|
/** Records with a `stop_reason` or `finish_reason`. */
|
|
@@ -426,6 +440,14 @@ export interface UsageProfileReport {
|
|
|
426
440
|
* and a boolean would call it that.
|
|
427
441
|
*/
|
|
428
442
|
fieldCoverage: FieldCoverage;
|
|
443
|
+
/**
|
|
444
|
+
* What each recorded outcome cost — measurement only, no judgement.
|
|
445
|
+
*
|
|
446
|
+
* `outcomeReport` turns this into a success rate where the config declares
|
|
447
|
+
* one, because which values mean success is a product judgement this module
|
|
448
|
+
* has no standing to make. An aggregate, never a list of calls.
|
|
449
|
+
*/
|
|
450
|
+
outcomeTally: OutcomeTally;
|
|
429
451
|
/**
|
|
430
452
|
* Spend per hour of the UTC day, 0–23, over priced records that carry a
|
|
431
453
|
* clock — and only the hours that saw traffic.
|
|
@@ -778,6 +800,13 @@ export function parseUsageLine(line: string): UsageRecord | null {
|
|
|
778
800
|
* nobody sets measures nothing.
|
|
779
801
|
*/
|
|
780
802
|
session: nameOf(record.session) ?? nameOf(record.conversation_id),
|
|
803
|
+
/**
|
|
804
|
+
* Read from either spelling, for the same reason `session` is: `outcome`
|
|
805
|
+
* in a log somebody wrote for this, `trazum_outcome` in one where a
|
|
806
|
+
* namespace was wanted. A field nobody sets measures nothing, and making
|
|
807
|
+
* its adoption a chore is how that happens.
|
|
808
|
+
*/
|
|
809
|
+
outcome: nameOf(record.outcome) ?? nameOf(record.trazum_outcome),
|
|
781
810
|
ts: moment.kind === 'ok' ? moment.ms : null,
|
|
782
811
|
/**
|
|
783
812
|
* Anthropic spells it `stop_reason: "max_tokens"`, OpenAI
|
|
@@ -977,7 +1006,9 @@ export function profileUsage(text: string, options: UsageProfileOptions): UsageP
|
|
|
977
1006
|
const ledger = createSessionLedgerTracker({ catalogue, on });
|
|
978
1007
|
const sessionCosts = createSessionCostTracker({ catalogue, on });
|
|
979
1008
|
let hasSessions = false;
|
|
980
|
-
const
|
|
1009
|
+
const outcomes = new Map<string, { calls: number; usd: number }>();
|
|
1010
|
+
let unrecordedOutcomeUsd = 0;
|
|
1011
|
+
const coverage = { label: 0, session: 0, outcome: 0, ts: 0, stopReason: 0, cacheTtl: 0, cacheWrites: 0, parsed: 0 };
|
|
981
1012
|
/**
|
|
982
1013
|
* Raw lines already seen, for the duplicate check. Bounded by the number of
|
|
983
1014
|
* *timestamped* lines — the price of catching a doubled bill, paid only on
|
|
@@ -1040,6 +1071,7 @@ export function profileUsage(text: string, options: UsageProfileOptions): UsageP
|
|
|
1040
1071
|
coverage.parsed += 1;
|
|
1041
1072
|
if (record.label !== null) coverage.label += 1;
|
|
1042
1073
|
if (record.session !== null) coverage.session += 1;
|
|
1074
|
+
if (record.outcome !== null) coverage.outcome += 1;
|
|
1043
1075
|
if (record.ts !== null) coverage.ts += 1;
|
|
1044
1076
|
if (record.truncated !== null) coverage.stopReason += 1;
|
|
1045
1077
|
if (record.cacheWrite5mTokens + record.cacheWrite1hTokens > 0) {
|
|
@@ -1095,6 +1127,22 @@ export function profileUsage(text: string, options: UsageProfileOptions): UsageP
|
|
|
1095
1127
|
if (record.session !== null) {
|
|
1096
1128
|
sessionUsd.set(record.session, (sessionUsd.get(record.session) ?? 0) + (total.totalUsd - usdBefore));
|
|
1097
1129
|
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Tallied from the same per-record dollar as everything else here, so a
|
|
1132
|
+
* success rate by spend and the bill it is a share of can never be two
|
|
1133
|
+
* different arithmetics that drifted apart.
|
|
1134
|
+
*/
|
|
1135
|
+
{
|
|
1136
|
+
const usd = total.totalUsd - usdBefore;
|
|
1137
|
+
if (record.outcome === null) {
|
|
1138
|
+
unrecordedOutcomeUsd += usd;
|
|
1139
|
+
} else {
|
|
1140
|
+
const bucket = outcomes.get(record.outcome) ?? { calls: 0, usd: 0 };
|
|
1141
|
+
bucket.calls += 1;
|
|
1142
|
+
bucket.usd += usd;
|
|
1143
|
+
outcomes.set(record.outcome, bucket);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1098
1146
|
if (record.ts !== null) {
|
|
1099
1147
|
const day = new Date(record.ts).toISOString().slice(0, 10);
|
|
1100
1148
|
const usd = total.totalUsd - usdBefore;
|
|
@@ -1189,6 +1237,14 @@ export function profileUsage(text: string, options: UsageProfileOptions): UsageP
|
|
|
1189
1237
|
}),
|
|
1190
1238
|
duplicateLines: duplicates,
|
|
1191
1239
|
fieldCoverage: coverage,
|
|
1240
|
+
outcomeTally: {
|
|
1241
|
+
byValue: [...outcomes.entries()]
|
|
1242
|
+
.map(([value, bucket]) => ({ value, calls: bucket.calls, usd: bucket.usd }))
|
|
1243
|
+
.sort((a, b) => b.usd - a.usd),
|
|
1244
|
+
recorded: coverage.outcome,
|
|
1245
|
+
parsed: coverage.parsed,
|
|
1246
|
+
unrecordedUsd: unrecordedOutcomeUsd,
|
|
1247
|
+
},
|
|
1192
1248
|
modelMixDrift: (() => {
|
|
1193
1249
|
const ordered = [...days.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
1194
1250
|
if (ordered.length < 4) return null;
|