@trazum/core 1.81.0 → 1.83.0
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/README.md +34 -18
- package/dist/advisories.d.ts +33 -1
- package/dist/advisories.d.ts.map +1 -1
- package/dist/advisories.js +130 -20
- package/dist/advisories.js.map +1 -1
- package/dist/band.d.ts +131 -0
- package/dist/band.d.ts.map +1 -0
- package/dist/band.js +210 -0
- package/dist/band.js.map +1 -0
- package/dist/conform.d.ts +1 -1
- package/dist/conform.d.ts.map +1 -1
- package/dist/conform.js +84 -1
- package/dist/conform.js.map +1 -1
- package/dist/helicone.d.ts +92 -0
- package/dist/helicone.d.ts.map +1 -0
- package/dist/helicone.js +194 -0
- package/dist/helicone.js.map +1 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +16 -3
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +17 -3
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/types.d.ts +60 -1
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.d.ts +16 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/langsmith.d.ts +104 -0
- package/dist/langsmith.d.ts.map +1 -0
- package/dist/langsmith.js +270 -0
- package/dist/langsmith.js.map +1 -0
- package/dist/levers.d.ts +1 -1
- package/dist/levers.js +2 -2
- package/dist/levers.js.map +1 -1
- package/dist/litellm.d.ts +98 -0
- package/dist/litellm.d.ts.map +1 -0
- package/dist/litellm.js +207 -0
- package/dist/litellm.js.map +1 -0
- package/dist/measured-profile.d.ts +4 -0
- package/dist/measured-profile.d.ts.map +1 -1
- package/dist/measured-profile.js +22 -1
- package/dist/measured-profile.js.map +1 -1
- package/dist/pricing.d.ts +98 -3
- package/dist/pricing.d.ts.map +1 -1
- package/dist/pricing.js +393 -35
- package/dist/pricing.js.map +1 -1
- package/dist/receipt.d.ts +253 -0
- package/dist/receipt.d.ts.map +1 -0
- package/dist/receipt.js +111 -0
- package/dist/receipt.js.map +1 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +36 -0
- package/dist/schemas.js.map +1 -1
- package/dist/tokenizer.d.ts +45 -49
- package/dist/tokenizer.d.ts.map +1 -1
- package/dist/tokenizer.js +100 -51
- package/dist/tokenizer.js.map +1 -1
- package/dist/types.d.ts +108 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/verdict-bridge.d.ts +113 -0
- package/dist/verdict-bridge.d.ts.map +1 -0
- package/dist/verdict-bridge.js +127 -0
- package/dist/verdict-bridge.js.map +1 -0
- package/package.json +1 -1
- package/src/advisories.ts +166 -19
- package/src/band.ts +213 -0
- package/src/conform.ts +100 -1
- package/src/helicone.ts +227 -0
- package/src/i18n/en.ts +19 -3
- package/src/i18n/es.ts +20 -3
- package/src/i18n/types.ts +63 -1
- package/src/index.ts +31 -2
- package/src/langsmith.ts +315 -0
- package/src/levers.ts +3 -3
- package/src/litellm.ts +243 -0
- package/src/measured-profile.ts +26 -2
- package/src/pricing.ts +430 -30
- package/src/receipt.ts +351 -0
- package/src/schemas.ts +36 -0
- package/src/tokenizer.ts +101 -51
- package/src/types.ts +104 -0
- package/src/verdict-bridge.ts +200 -0
package/src/helicone.ts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helicone's request export, read as a usage log.
|
|
3
|
+
*
|
|
4
|
+
* The third converter in the pattern `from-claude-code` started: a pure
|
|
5
|
+
* function turns one tool's export into Trazum's usage-log records, and every
|
|
6
|
+
* door prices it from there. Helicone sits as a proxy in front of the provider
|
|
7
|
+
* and keeps every request it saw, so a team using it already has the export.
|
|
8
|
+
*
|
|
9
|
+
* **The format is derived, not guessed.** The columns below are the SELECT
|
|
10
|
+
* that builds Helicone's own request table — `web/lib/api/request/request.ts`
|
|
11
|
+
* in Helicone/helicone — and the response shape its `POST /v1/request/query`
|
|
12
|
+
* endpoint documents. A converter written from memory of an API is a converter
|
|
13
|
+
* that silently mis-reads somebody's bill.
|
|
14
|
+
*
|
|
15
|
+
* ## The model, and why it takes three columns to answer
|
|
16
|
+
*
|
|
17
|
+
* Helicone carries `request_model`, `model_override` and `response_model`, and
|
|
18
|
+
* they can disagree: the override exists precisely because a proxy can send a
|
|
19
|
+
* different model than the caller asked for, and the response says what
|
|
20
|
+
* actually answered. **The response wins**, then the override, then the
|
|
21
|
+
* request — the bill is about what was billed, not what was intended — and
|
|
22
|
+
* `modelDisagreements` counts the rows where they differed so a reader can see
|
|
23
|
+
* the substitution happened rather than discover it in a total.
|
|
24
|
+
*
|
|
25
|
+
* ## What deliberately does not cross
|
|
26
|
+
*
|
|
27
|
+
* A Helicone row carries the request body and the response body — the prompt
|
|
28
|
+
* and the completion — and `request_user_id`, which is an email address in
|
|
29
|
+
* Helicone's own documented example. **None of it is read.** The converter
|
|
30
|
+
* names the fields it takes and takes nothing else, and a fixture plants a
|
|
31
|
+
* marker in each and greps the whole output.
|
|
32
|
+
*
|
|
33
|
+
* ## What it refuses to invent
|
|
34
|
+
*
|
|
35
|
+
* There is no cache-token split anywhere on the row: `cache_enabled` is a flag
|
|
36
|
+
* on the analytics table. A converted record therefore carries no cache fields
|
|
37
|
+
* and the caching questions come back `cannot-tell` rather than answered from
|
|
38
|
+
* a guess — the same refusal `from-otel` and `from-litellm` make.
|
|
39
|
+
*
|
|
40
|
+
* There is no conversation identity either. `request_id` is one call, so the
|
|
41
|
+
* records carry no `session` and the conversation-shaped findings stay
|
|
42
|
+
* unavailable. A custom property can carry one, and then it is there because
|
|
43
|
+
* the operator put it there — never because this file inferred it.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/** One converted record, shaped exactly as `parseUsageLine` reads it. */
|
|
47
|
+
export interface HeliconeRecord {
|
|
48
|
+
model: string;
|
|
49
|
+
ts?: string;
|
|
50
|
+
label?: string;
|
|
51
|
+
usage: {
|
|
52
|
+
input_tokens: number;
|
|
53
|
+
output_tokens: number;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface HeliconeConversion {
|
|
58
|
+
records: HeliconeRecord[];
|
|
59
|
+
/** Rows that were requests and converted. */
|
|
60
|
+
rows: number;
|
|
61
|
+
/** Rows naming no model in any of the three columns: counted, never guessed. */
|
|
62
|
+
unnamedModel: number;
|
|
63
|
+
/**
|
|
64
|
+
* Rows where the model that answered was not the model that was asked for.
|
|
65
|
+
*
|
|
66
|
+
* The response's model is what the bill rests on, and a proxy substituting
|
|
67
|
+
* one model for another is a fact worth seeing rather than a difference
|
|
68
|
+
* that only shows up as an unexplained total.
|
|
69
|
+
*/
|
|
70
|
+
modelDisagreements: number;
|
|
71
|
+
/** Rows carrying no token counts at all — a logged request nobody can price. */
|
|
72
|
+
noTokens: number;
|
|
73
|
+
/** Rows Helicone served from its cache, with no token split behind the flag. */
|
|
74
|
+
cacheFlagged: number;
|
|
75
|
+
/** Lines or documents that did not parse as JSON at all. */
|
|
76
|
+
unparseable: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const asObject = (value: unknown): Record<string, unknown> | null =>
|
|
80
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
81
|
+
? (value as Record<string, unknown>)
|
|
82
|
+
: null;
|
|
83
|
+
|
|
84
|
+
const tryParse = (text: string): unknown => {
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(text);
|
|
87
|
+
} catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const asCount = (value: unknown): number | undefined => {
|
|
93
|
+
const n = typeof value === 'string' ? Number(value) : value;
|
|
94
|
+
return typeof n === 'number' && Number.isFinite(n) && n >= 0 ? n : undefined;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const asName = (value: unknown): string | null =>
|
|
98
|
+
typeof value === 'string' && value.trim() !== '' ? value.trim() : null;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The label, from Helicone's custom properties.
|
|
102
|
+
*
|
|
103
|
+
* `request_properties` is the documented place for whatever a team tags its
|
|
104
|
+
* calls with, and the keys are theirs. Trazum looks for the ones a workload
|
|
105
|
+
* would plausibly be named by, in order, and takes the first — never joins
|
|
106
|
+
* several, because a workload has one name in a bill.
|
|
107
|
+
*/
|
|
108
|
+
function labelOf(row: Record<string, unknown>): string | undefined {
|
|
109
|
+
const properties = asObject(row.request_properties) ?? asObject(row.properties);
|
|
110
|
+
if (properties === null) return undefined;
|
|
111
|
+
for (const key of ['Helicone-Property-Label', 'label', 'workload', 'app', 'environment']) {
|
|
112
|
+
const found = asName(properties[key]);
|
|
113
|
+
if (found !== null) return found;
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The request's creation time as an ISO instant, or undefined. */
|
|
119
|
+
function timeOf(row: Record<string, unknown>): string | undefined {
|
|
120
|
+
const raw = row.request_created_at ?? row.created_at ?? row.response_created_at;
|
|
121
|
+
if (typeof raw !== 'string' && typeof raw !== 'number') return undefined;
|
|
122
|
+
const ms = typeof raw === 'number' ? raw : Date.parse(raw);
|
|
123
|
+
if (!Number.isFinite(ms)) return undefined;
|
|
124
|
+
return new Date(ms).toISOString();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Convert a Helicone request export.
|
|
129
|
+
*
|
|
130
|
+
* Accepts a JSON array of rows, a single row, `{ data: [...] }` as the query
|
|
131
|
+
* endpoint returns it, or newline-delimited rows. Pure over its input.
|
|
132
|
+
*/
|
|
133
|
+
export function heliconeRecords(text: string): HeliconeConversion {
|
|
134
|
+
const out: HeliconeConversion = {
|
|
135
|
+
records: [],
|
|
136
|
+
rows: 0,
|
|
137
|
+
unnamedModel: 0,
|
|
138
|
+
modelDisagreements: 0,
|
|
139
|
+
noTokens: 0,
|
|
140
|
+
cacheFlagged: 0,
|
|
141
|
+
unparseable: 0,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const rows: unknown[] = [];
|
|
145
|
+
const whole = tryParse(text);
|
|
146
|
+
if (whole !== undefined) {
|
|
147
|
+
if (Array.isArray(whole)) rows.push(...whole);
|
|
148
|
+
else {
|
|
149
|
+
const doc = asObject(whole);
|
|
150
|
+
if (doc !== null && Array.isArray(doc.data)) rows.push(...doc.data);
|
|
151
|
+
else if (doc !== null) rows.push(doc);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
for (const line of text.split('\n')) {
|
|
155
|
+
if (line.trim() === '') continue;
|
|
156
|
+
const parsed = tryParse(line);
|
|
157
|
+
if (parsed === undefined) out.unparseable += 1;
|
|
158
|
+
else if (Array.isArray(parsed)) rows.push(...parsed);
|
|
159
|
+
else rows.push(parsed);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (const raw of rows) {
|
|
164
|
+
const row = asObject(raw);
|
|
165
|
+
if (row === null) continue;
|
|
166
|
+
const input = asCount(row.prompt_tokens);
|
|
167
|
+
const output = asCount(row.completion_tokens);
|
|
168
|
+
// A request row is one carrying at least one token column. Anything else
|
|
169
|
+
// in the same file is not a call and is skipped rather than recorded as a
|
|
170
|
+
// zero, which would be a call nobody made.
|
|
171
|
+
if (input === undefined && output === undefined) continue;
|
|
172
|
+
|
|
173
|
+
out.rows += 1;
|
|
174
|
+
if (input === 0 && output === 0) out.noTokens += 1;
|
|
175
|
+
if (row.cache_enabled === true || row.cache_enabled === 1 || row.cache_enabled === '1') {
|
|
176
|
+
out.cacheFlagged += 1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/*
|
|
180
|
+
The response first, and this order is the whole point of counting the
|
|
181
|
+
disagreement. A proxy that substituted a model billed for the model that
|
|
182
|
+
answered, so that is what a bill has to price; taking the requested one
|
|
183
|
+
would report the intention and call it the cost.
|
|
184
|
+
*/
|
|
185
|
+
const answered = asName(row.response_model);
|
|
186
|
+
const overridden = asName(row.model_override);
|
|
187
|
+
const requested = asName(row.request_model);
|
|
188
|
+
const model = answered ?? overridden ?? requested;
|
|
189
|
+
if (model === null) {
|
|
190
|
+
out.unnamedModel += 1;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (requested !== null && answered !== null && requested !== answered) {
|
|
194
|
+
out.modelDisagreements += 1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const ts = timeOf(row);
|
|
198
|
+
const label = labelOf(row);
|
|
199
|
+
out.records.push({
|
|
200
|
+
model,
|
|
201
|
+
...(ts !== undefined ? { ts } : {}),
|
|
202
|
+
...(label !== undefined ? { label } : {}),
|
|
203
|
+
usage: { input_tokens: input ?? 0, output_tokens: output ?? 0 },
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Whether a file looks like a Helicone request export, by shape.
|
|
212
|
+
*
|
|
213
|
+
* Deliberately narrow: a token column beside one of the names only Helicone's
|
|
214
|
+
* own SELECT produces. A looser test would claim an OpenAI usage response or a
|
|
215
|
+
* LiteLLM spend log — both of which also carry `prompt_tokens` — and refuse it
|
|
216
|
+
* in this file's words instead of letting the code that can read it try.
|
|
217
|
+
*/
|
|
218
|
+
export function looksLikeHelicone(text: string, prefixBytes = 8192): boolean {
|
|
219
|
+
const head = text.slice(0, prefixBytes);
|
|
220
|
+
if (!head.includes('prompt_tokens') && !head.includes('completion_tokens')) return false;
|
|
221
|
+
return (
|
|
222
|
+
head.includes('"request_created_at"') ||
|
|
223
|
+
head.includes('"request_model"') ||
|
|
224
|
+
head.includes('"response_model"') ||
|
|
225
|
+
head.includes('"request_properties"')
|
|
226
|
+
);
|
|
227
|
+
}
|
package/src/i18n/en.ts
CHANGED
|
@@ -154,10 +154,12 @@ export const en: CoreMessages = {
|
|
|
154
154
|
};
|
|
155
155
|
},
|
|
156
156
|
|
|
157
|
-
promptCachingNotWorthIt: () => ({
|
|
157
|
+
promptCachingNotWorthIt: ({ readPct, writePct, breakEvenPct }) => ({
|
|
158
158
|
title: 'At that hit rate, caching does not pay off',
|
|
159
159
|
detail:
|
|
160
|
-
|
|
160
|
+
breakEvenPct === null
|
|
161
|
+
? `A cache write costs ${writePct}% of the input price on this model, the same as not caching, and a read costs ${readPct}%. So caching cannot lose you money here: at this hit rate it simply buys nothing. Raise prefix reuse and it starts paying immediately.`
|
|
162
|
+
: `A cache write costs ${writePct}% of the input price on this model and a read costs ${readPct}%. Below a ${breakEvenPct}% hit rate you pay more than you save. Either raise prefix reuse or leave caching off.`,
|
|
161
163
|
}),
|
|
162
164
|
|
|
163
165
|
belowCacheMinimum: ({
|
|
@@ -198,7 +200,12 @@ export const en: CoreMessages = {
|
|
|
198
200
|
|
|
199
201
|
modelDowngrade: ({ modelName, tier, candidateName, currentUsd, candidateUsd }) => ({
|
|
200
202
|
title: `This task may not need ${modelName}`,
|
|
201
|
-
detail: `By length and vocabulary the prompt looks like "${tier}" complexity. On ${candidateName} you would go from ${currentUsd} to ${candidateUsd} per month. This is a keyword heuristic, not a judgement about answer quality:
|
|
203
|
+
detail: `By length and vocabulary the prompt looks like "${tier}" complexity. On ${candidateName} you would go from ${currentUsd} to ${candidateUsd} per month. This is a keyword heuristic, not a judgement about answer quality. Measure it before switching in production: \`trazum route <log.jsonl> --prompt-file <prompt> --cases <cases> --yes\` sends the same cases to both models and reports whether the cheaper one still does the job.`,
|
|
204
|
+
}),
|
|
205
|
+
|
|
206
|
+
tierSignalsConflict: ({ complexSignals, simpleSignals }) => ({
|
|
207
|
+
title: 'The prompt asks for depth and for brevity at once',
|
|
208
|
+
detail: `It carries ${complexSignals} signal${complexSignals === 1 ? '' : 's'} of a hard task and ${simpleSignals} of an easy one, and this heuristic scores those against each other, so they cancel. No tier is recommended here: a number produced by two opposite readings cancelling out looks exactly like a prompt with no signals at all, and they are not the same thing. Decide it by measurement rather than by vocabulary: \`trazum route <log.jsonl> --prompt-file <prompt> --cases <cases> --yes\`.`,
|
|
202
209
|
}),
|
|
203
210
|
|
|
204
211
|
outputDominated: ({ outputUsd, inputUsd }) => ({
|
|
@@ -211,6 +218,15 @@ export const en: CoreMessages = {
|
|
|
211
218
|
detail: `${modelName} has introductory pricing of ${promoInput}/${promoOutput} per million tokens until ${until}. After that date it moves to ${listInput}/${listOutput}: your bill goes up even if you change nothing.`,
|
|
212
219
|
}),
|
|
213
220
|
|
|
221
|
+
modelRetired: ({ modelName, modelId, on, because }) => ({
|
|
222
|
+
title: `${modelName} is priced here and refused by its provider`,
|
|
223
|
+
detail:
|
|
224
|
+
`A real request for \`${modelId}\` was answered with an error on ${on}: "${because}" `
|
|
225
|
+
+ 'The price above is still the right one for calls already in your log, because that '
|
|
226
|
+
+ 'money was spent. A new call on this id will not run. Nothing here proposes what the '
|
|
227
|
+
+ 'replacement costs: that figure comes off the provider\'s own pricing page or not at all.',
|
|
228
|
+
}),
|
|
229
|
+
|
|
214
230
|
contradictoryInstructions: ({
|
|
215
231
|
axis,
|
|
216
232
|
firstValue,
|
package/src/i18n/es.ts
CHANGED
|
@@ -138,10 +138,12 @@ export const es: CoreMessages = {
|
|
|
138
138
|
};
|
|
139
139
|
},
|
|
140
140
|
|
|
141
|
-
promptCachingNotWorthIt: () => ({
|
|
141
|
+
promptCachingNotWorthIt: ({ readPct, writePct, breakEvenPct }) => ({
|
|
142
142
|
title: 'Con esa tasa de acierto, la caché no compensa',
|
|
143
143
|
detail:
|
|
144
|
-
|
|
144
|
+
breakEvenPct === null
|
|
145
|
+
? `En este modelo escribir en caché cuesta un ${writePct}% del precio de entrada, lo mismo que no usarla, y leer un ${readPct}%. Así que aquí la caché no puede costarte dinero: con esta tasa de acierto simplemente no te aporta nada. En cuanto subas la reutilización del prefijo, empieza a compensar.`
|
|
146
|
+
: `En este modelo escribir en caché cuesta un ${writePct}% del precio de entrada y leer un ${readPct}%. Por debajo de un ${breakEvenPct}% de aciertos pagas más de lo que ahorras. Sube la reutilización del prefijo o deja la caché desactivada.`,
|
|
145
147
|
}),
|
|
146
148
|
|
|
147
149
|
belowCacheMinimum: ({
|
|
@@ -182,7 +184,12 @@ export const es: CoreMessages = {
|
|
|
182
184
|
|
|
183
185
|
modelDowngrade: ({ modelName, tier, candidateName, currentUsd, candidateUsd }) => ({
|
|
184
186
|
title: `Esta tarea quizá no necesite ${modelName}`,
|
|
185
|
-
detail: `Por longitud y vocabulario, el prompt parece de complejidad "${tier}". Con ${candidateName} pasarías de ${currentUsd} a ${candidateUsd} al mes. Es una heurística por palabras clave, no un juicio de calidad:
|
|
187
|
+
detail: `Por longitud y vocabulario, el prompt parece de complejidad "${tier}". Con ${candidateName} pasarías de ${currentUsd} a ${candidateUsd} al mes. Es una heurística por palabras clave, no un juicio de calidad. Mídelo antes de cambiar en producción: \`trazum route <log.jsonl> --prompt-file <prompt> --cases <casos> --yes\` envía los mismos casos a ambos modelos y dice si el barato sigue haciendo el trabajo.`,
|
|
188
|
+
}),
|
|
189
|
+
|
|
190
|
+
tierSignalsConflict: ({ complexSignals, simpleSignals }) => ({
|
|
191
|
+
title: 'El prompt pide profundidad y brevedad a la vez',
|
|
192
|
+
detail: `Lleva ${complexSignals} señal${complexSignals === 1 ? '' : 'es'} de tarea difícil y ${simpleSignals} de tarea fácil, y esta heurística las resta entre sí, así que se anulan. Aquí no se recomienda ningún nivel: un número que sale de dos lecturas opuestas cancelándose se parece exactamente a un prompt sin ninguna señal, y no son lo mismo. Decídelo midiendo y no por vocabulario: \`trazum route <log.jsonl> --prompt-file <prompt> --cases <casos> --yes\`.`,
|
|
186
193
|
}),
|
|
187
194
|
|
|
188
195
|
outputDominated: ({ outputUsd, inputUsd }) => ({
|
|
@@ -195,6 +202,16 @@ export const es: CoreMessages = {
|
|
|
195
202
|
detail: `${modelName} tiene precio de lanzamiento ${promoInput}/${promoOutput} por millón de tokens hasta el ${until}. A partir de esa fecha pasa a ${listInput}/${listOutput}: tu factura subirá aunque no cambies nada.`,
|
|
196
203
|
}),
|
|
197
204
|
|
|
205
|
+
modelRetired: ({ modelName, modelId, on, because }) => ({
|
|
206
|
+
title: `${modelName} está tarifado aquí y su proveedor lo rechaza`,
|
|
207
|
+
detail:
|
|
208
|
+
`Una petición real a \`${modelId}\` se respondió con un error el ${on}: "${because}" `
|
|
209
|
+
+ 'El precio de arriba sigue siendo el correcto para las llamadas que ya están en tu '
|
|
210
|
+
+ 'registro, porque ese dinero se gastó. Una llamada nueva a este id no se ejecutará. '
|
|
211
|
+
+ 'Aquí no se propone lo que cuesta el reemplazo: esa cifra sale de la página de precios '
|
|
212
|
+
+ 'del proveedor o no sale.',
|
|
213
|
+
}),
|
|
214
|
+
|
|
198
215
|
contradictoryInstructions: ({
|
|
199
216
|
axis,
|
|
200
217
|
firstValue,
|
package/src/i18n/types.ts
CHANGED
|
@@ -160,6 +160,23 @@ export interface OutputDominatedParams {
|
|
|
160
160
|
inputUsd: string;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/**
|
|
164
|
+
* The provider's own refusal, carried through to the reader unchanged.
|
|
165
|
+
*
|
|
166
|
+
* `because` is quoted rather than summarised: it is the only part of this
|
|
167
|
+
* message that is evidence, it usually names the replacement, and a paraphrase
|
|
168
|
+
* of a provider's error is a sentence this repository would then be the author
|
|
169
|
+
* of.
|
|
170
|
+
*/
|
|
171
|
+
export interface ModelRetiredParams {
|
|
172
|
+
modelName: string;
|
|
173
|
+
modelId: string;
|
|
174
|
+
/** ISO date the refusal was recorded. */
|
|
175
|
+
on: string;
|
|
176
|
+
/** The provider's own words. */
|
|
177
|
+
because: string;
|
|
178
|
+
}
|
|
179
|
+
|
|
163
180
|
export interface PromoPricingParams {
|
|
164
181
|
modelName: string;
|
|
165
182
|
promoInput: number;
|
|
@@ -215,6 +232,49 @@ export interface LlmMessages {
|
|
|
215
232
|
suspiciousShrink(retainedPct: number): string;
|
|
216
233
|
}
|
|
217
234
|
|
|
235
|
+
/**
|
|
236
|
+
* What the caching-does-not-pay advisory needs to state a threshold that is
|
|
237
|
+
* true about **this** model.
|
|
238
|
+
*
|
|
239
|
+
* It used to take nothing and print one sentence for every model: *"a cache
|
|
240
|
+
* write costs 125% of the input price and a read costs 10%. Below roughly a 28%
|
|
241
|
+
* hit rate you pay more than you save."* Two of those three numbers were the
|
|
242
|
+
* Anthropic multipliers stated as universal, and the third was not derivable
|
|
243
|
+
* from any model in the catalogue: at 125% and 10% the break-even is 21.74%,
|
|
244
|
+
* not 28%.
|
|
245
|
+
*
|
|
246
|
+
* The multipliers were already in hand at the call site and already passed to
|
|
247
|
+
* the advisory next door. Only this message invented its own.
|
|
248
|
+
*/
|
|
249
|
+
export interface PromptCachingNotWorthItParams {
|
|
250
|
+
/** The model's cache read multiplier as a percentage of the input price. */
|
|
251
|
+
readPct: number;
|
|
252
|
+
/** Its 5-minute cache write multiplier, likewise. */
|
|
253
|
+
writePct: number;
|
|
254
|
+
/**
|
|
255
|
+
* The hit rate at which caching stops costing money, derived rather than
|
|
256
|
+
* quoted: `(1 - write) / (read - write)`.
|
|
257
|
+
*
|
|
258
|
+
* `null` when the write multiplier is 1, which is not a smaller threshold but
|
|
259
|
+
* a different situation: writing costs exactly what not caching costs, so
|
|
260
|
+
* caching can never lose money and there is no rate below which it does. Eight
|
|
261
|
+
* of the eighteen models in the catalogue are that shape, and the old sentence
|
|
262
|
+
* told all eight to consider turning caching off.
|
|
263
|
+
*/
|
|
264
|
+
breakEvenPct: number | null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* What the tier heuristic saw when it declined to answer.
|
|
269
|
+
*
|
|
270
|
+
* Both counts, because "three of each" and "one of each" are different degrees
|
|
271
|
+
* of the same refusal and the reader can act on the difference.
|
|
272
|
+
*/
|
|
273
|
+
export interface TierSignalsConflictParams {
|
|
274
|
+
complexSignals: number;
|
|
275
|
+
simpleSignals: number;
|
|
276
|
+
}
|
|
277
|
+
|
|
218
278
|
/** Why a proposed rewrite did not survive checking against the prompt. */
|
|
219
279
|
export interface SuggestMessages {
|
|
220
280
|
'not-found'(): string;
|
|
@@ -236,13 +296,15 @@ export interface CoreMessages {
|
|
|
236
296
|
contextOverflow(p: ContextOverflowParams): LocalizedMessage;
|
|
237
297
|
contextNearLimit(p: ContextNearLimitParams): LocalizedMessage;
|
|
238
298
|
promptCaching(p: PromptCachingParams): LocalizedMessage;
|
|
239
|
-
promptCachingNotWorthIt(): LocalizedMessage;
|
|
299
|
+
promptCachingNotWorthIt(p: PromptCachingNotWorthItParams): LocalizedMessage;
|
|
240
300
|
belowCacheMinimum(p: BelowCacheMinimumParams): LocalizedMessage;
|
|
241
301
|
cachePrefixReorder(p: CachePrefixReorderParams): LocalizedMessage;
|
|
242
302
|
batchApi(): LocalizedMessage;
|
|
243
303
|
modelDowngrade(p: ModelDowngradeParams): LocalizedMessage;
|
|
304
|
+
tierSignalsConflict(p: TierSignalsConflictParams): LocalizedMessage;
|
|
244
305
|
outputDominated(p: OutputDominatedParams): LocalizedMessage;
|
|
245
306
|
promoPricing(p: PromoPricingParams): LocalizedMessage;
|
|
307
|
+
modelRetired(p: ModelRetiredParams): LocalizedMessage;
|
|
246
308
|
contradictoryInstructions(p: ContradictoryInstructionsParams): LocalizedMessage;
|
|
247
309
|
redundantExamples(p: RedundantExamplesParams): LocalizedMessage;
|
|
248
310
|
restatedOutputFormat(p: RestatedOutputFormatParams): LocalizedMessage;
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,14 @@ export { explainGateFailure, gateMargin, GATE_MARGIN_TIGHT } from './gate-explai
|
|
|
52
52
|
export { measuredUsage, labelCoverage, MIN_SCALE_DAYS, SCALE_TO_DAYS } from './measured-profile.js';
|
|
53
53
|
export { assignSources, fleetRollup } from './fleet.js';
|
|
54
54
|
export { rollUp } from './rollup.js';
|
|
55
|
+
export { receiptFrom, RECEIPT_LINE_FIELDS } from './receipt.js';
|
|
56
|
+
export type {
|
|
57
|
+
ReceiptDocument,
|
|
58
|
+
ReceiptGap,
|
|
59
|
+
ReceiptLine,
|
|
60
|
+
ReceiptOptions,
|
|
61
|
+
ReceiptPricing,
|
|
62
|
+
} from './receipt.js';
|
|
55
63
|
export { heartbeats } from './heartbeat.js';
|
|
56
64
|
export { ruleYield } from './rule-yield.js';
|
|
57
65
|
export { buildPlan, planLabelName, parsePlanDocument, PLAN_ACTION_KINDS } from './plan.js';
|
|
@@ -166,7 +174,18 @@ export type {
|
|
|
166
174
|
GatewayStanding,
|
|
167
175
|
RefuseReason,
|
|
168
176
|
} from './gateway.js';
|
|
177
|
+
export {
|
|
178
|
+
BANDS,
|
|
179
|
+
MEASURED_FOREIGN_ERROR_PCT,
|
|
180
|
+
bandFor,
|
|
181
|
+
bucketFor,
|
|
182
|
+
foreignTokenizer,
|
|
183
|
+
measuredForeignError,
|
|
184
|
+
} from './band.js';
|
|
185
|
+
export type { BandBucket } from './band.js';
|
|
169
186
|
export { conform, CONTRACT_NAMES, requiredFieldsOf } from './conform.js';
|
|
187
|
+
export { readDroppedVerdict, verdictMatchesSlice } from './verdict-bridge.js';
|
|
188
|
+
export type { DroppedVerdict, BridgeReading } from './verdict-bridge.js';
|
|
170
189
|
export { CONTRACT_SCHEMAS, contractSchema } from './schemas.js';
|
|
171
190
|
export type {
|
|
172
191
|
ConformOptions,
|
|
@@ -203,6 +222,12 @@ export type { MeasuredIndex } from './position.js';
|
|
|
203
222
|
export { claudeCodeRecords, looksLikeClaudeCodeTranscript } from './claude-code.js';
|
|
204
223
|
export { otelRecords, looksLikeOtel } from './otel.js';
|
|
205
224
|
export type { OtelConversion, OtelRecord } from './otel.js';
|
|
225
|
+
export { litellmRecords, looksLikeLiteLlm } from './litellm.js';
|
|
226
|
+
export type { LiteLlmConversion, LiteLlmRecord } from './litellm.js';
|
|
227
|
+
export { heliconeRecords, looksLikeHelicone } from './helicone.js';
|
|
228
|
+
export type { HeliconeConversion, HeliconeRecord } from './helicone.js';
|
|
229
|
+
export { langsmithRecords, looksLikeLangsmith } from './langsmith.js';
|
|
230
|
+
export type { LangsmithConversion, LangsmithRecord } from './langsmith.js';
|
|
206
231
|
export type { ClaudeCodeConversion, ClaudeCodeRecord } from './claude-code.js';
|
|
207
232
|
export { positionReport } from './position-report.js';
|
|
208
233
|
export type {
|
|
@@ -368,6 +393,8 @@ export {
|
|
|
368
393
|
DEFAULT_MODEL,
|
|
369
394
|
COST_MULTIPLIERS,
|
|
370
395
|
PRICING_LAST_REVIEWED,
|
|
396
|
+
STALE_PRICING_DAYS,
|
|
397
|
+
PROVIDER_REVIEWED,
|
|
371
398
|
reviewAgeDays,
|
|
372
399
|
BUNDLED_CATALOGUE,
|
|
373
400
|
getModel,
|
|
@@ -377,6 +404,7 @@ export {
|
|
|
377
404
|
modelFrom,
|
|
378
405
|
cheapestOfTierIn,
|
|
379
406
|
multipliersFor,
|
|
407
|
+
isOffered,
|
|
380
408
|
} from './pricing.js';
|
|
381
409
|
export type { PricingCatalogue } from './pricing.js';
|
|
382
410
|
export type { CachingMode, Capability, CostMultipliers } from './types.js';
|
|
@@ -424,7 +452,8 @@ export type {
|
|
|
424
452
|
export { RULES, getRule } from './rules.js';
|
|
425
453
|
export { segment, join, protectedTexts } from './segment.js';
|
|
426
454
|
export { computeSavings, costOfCall, formatUsd, formatSignedUsd } from './savings.js';
|
|
427
|
-
export { buildAdvisories, recommendTier } from './advisories.js';
|
|
455
|
+
export { buildAdvisories, recommendTier, recommendTierDetailed } from './advisories.js';
|
|
456
|
+
export type { TierRecommendation } from './advisories.js';
|
|
428
457
|
export type { AdvisoryOptions } from './advisories.js';
|
|
429
458
|
export { analyzeCachePrefix } from './cache.js';
|
|
430
459
|
|
|
@@ -541,7 +570,7 @@ export { NothingToPrune, plannedCalls, pruneExamples, withoutExample } from './p
|
|
|
541
570
|
export type { ExampleContribution, PruneOptions, PruneReport } from './prune.js';
|
|
542
571
|
|
|
543
572
|
// Golden-set evaluation (optional, costs three LLM calls per case)
|
|
544
|
-
export { evaluate, fillPrompt, verdictFor } from './evaluate.js';
|
|
573
|
+
export { agreement, evaluate, fillPrompt, pooled, verdictFor } from './evaluate.js';
|
|
545
574
|
export type { EvalCase, EvalReport, EvalVerdict, EvaluateOptions } from './evaluate.js';
|
|
546
575
|
|
|
547
576
|
// Comparing two prompt versions
|