@trazum/core 1.9.0 → 1.25.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 +12 -5
- package/dist/advisories.d.ts.map +1 -1
- package/dist/advisories.js +94 -4
- package/dist/advisories.js.map +1 -1
- package/dist/against.d.ts +32 -0
- package/dist/against.d.ts.map +1 -0
- package/dist/against.js +34 -0
- package/dist/against.js.map +1 -0
- package/dist/config-schema.d.ts +42 -1
- package/dist/config-schema.d.ts.map +1 -1
- package/dist/config-schema.js +61 -0
- package/dist/config-schema.js.map +1 -1
- package/dist/conversation.d.ts +121 -0
- package/dist/conversation.d.ts.map +1 -0
- package/dist/conversation.js +157 -0
- package/dist/conversation.js.map +1 -0
- package/dist/csv.d.ts +61 -0
- package/dist/csv.d.ts.map +1 -0
- package/dist/csv.js +149 -0
- package/dist/csv.js.map +1 -0
- package/dist/evaluate.d.ts +24 -0
- package/dist/evaluate.d.ts.map +1 -1
- package/dist/evaluate.js +5 -2
- package/dist/evaluate.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +18 -7
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +18 -7
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/types.d.ts +28 -1
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/dist/input-shape.d.ts +104 -0
- package/dist/input-shape.d.ts.map +1 -0
- package/dist/input-shape.js +132 -0
- package/dist/input-shape.js.map +1 -0
- package/dist/levers.d.ts +151 -0
- package/dist/levers.d.ts.map +1 -0
- package/dist/levers.js +160 -0
- package/dist/levers.js.map +1 -0
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/output-shape.d.ts +96 -0
- package/dist/output-shape.d.ts.map +1 -0
- package/dist/output-shape.js +145 -0
- package/dist/output-shape.js.map +1 -0
- package/dist/pricing-overlay.d.ts +1 -1
- package/dist/pricing-overlay.d.ts.map +1 -1
- package/dist/pricing-overlay.js +46 -0
- package/dist/pricing-overlay.js.map +1 -1
- package/dist/repeats.d.ts +75 -0
- package/dist/repeats.d.ts.map +1 -0
- package/dist/repeats.js +82 -0
- package/dist/repeats.js.map +1 -0
- package/dist/reprice.d.ts +143 -0
- package/dist/reprice.d.ts.map +1 -0
- package/dist/reprice.js +82 -0
- package/dist/reprice.js.map +1 -0
- package/dist/session-cost.d.ts +70 -0
- package/dist/session-cost.d.ts.map +1 -0
- package/dist/session-cost.js +90 -0
- package/dist/session-cost.js.map +1 -0
- package/dist/session-ledger.d.ts +77 -0
- package/dist/session-ledger.d.ts.map +1 -0
- package/dist/session-ledger.js +99 -0
- package/dist/session-ledger.js.map +1 -0
- package/dist/tokenizer.d.ts +33 -26
- package/dist/tokenizer.d.ts.map +1 -1
- package/dist/tokenizer.js +95 -34
- package/dist/tokenizer.js.map +1 -1
- package/dist/ttl-fit.d.ts +103 -0
- package/dist/ttl-fit.d.ts.map +1 -0
- package/dist/ttl-fit.js +184 -0
- package/dist/ttl-fit.js.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/usage.d.ts +637 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +634 -0
- package/dist/usage.js.map +1 -0
- package/package.json +1 -1
- package/src/advisories.ts +96 -4
- package/src/against.ts +48 -0
- package/src/config-schema.ts +106 -0
- package/src/conversation.ts +305 -0
- package/src/csv.ts +184 -0
- package/src/evaluate.ts +33 -3
- package/src/i18n/en.ts +19 -6
- package/src/i18n/es.ts +19 -6
- package/src/i18n/types.ts +29 -1
- package/src/index.ts +65 -1
- package/src/input-shape.ts +259 -0
- package/src/levers.ts +331 -0
- package/src/node.ts +1 -1
- package/src/output-shape.ts +254 -0
- package/src/pricing-overlay.ts +52 -1
- package/src/repeats.ts +166 -0
- package/src/reprice.ts +227 -0
- package/src/session-cost.ts +170 -0
- package/src/session-ledger.ts +189 -0
- package/src/tokenizer.ts +93 -30
- package/src/ttl-fit.ts +251 -0
- package/src/types.ts +1 -0
- package/src/usage.ts +1267 -0
package/src/csv.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { UNLABELLED } from './usage.js';
|
|
2
|
+
import type { UsageProfileReport } from './usage.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The profile as a spreadsheet.
|
|
6
|
+
*
|
|
7
|
+
* ## Why a file format is a feature
|
|
8
|
+
*
|
|
9
|
+
* The terminal report is read once and closed. The people who decide what a
|
|
10
|
+
* workload is allowed to cost live in spreadsheets, and handing them a
|
|
11
|
+
* screenshot of a terminal is how a finding stops at the person who ran the
|
|
12
|
+
* command. `--json` is for machines; this is for the pivot table that gets
|
|
13
|
+
* shown to whoever signs off the bill.
|
|
14
|
+
*
|
|
15
|
+
* ## One row per label and model, and no total row
|
|
16
|
+
*
|
|
17
|
+
* `byLabelAndModel` is the grouping a decision is actually made at — routing
|
|
18
|
+
* `classify` to a cheaper model is a question about one label's calls to one
|
|
19
|
+
* model — so it is the grain of the file.
|
|
20
|
+
*
|
|
21
|
+
* **There is deliberately no TOTAL row.** A total inside a data file is the
|
|
22
|
+
* oldest spreadsheet trap there is: somebody sums the column, the total row is
|
|
23
|
+
* included, and every figure downstream is exactly twice what it should be.
|
|
24
|
+
* The sum of this file is the bill, and a spreadsheet can compute it.
|
|
25
|
+
*
|
|
26
|
+
* ## Unpriced models get empty cells, never zeros
|
|
27
|
+
*
|
|
28
|
+
* A model the catalogue does not know has real tokens and unknown dollars.
|
|
29
|
+
* Writing `0` there would be a claim — that those calls were free — and it
|
|
30
|
+
* would survive into every chart built on the file. An empty cell is the
|
|
31
|
+
* absence it actually is, and spreadsheets already know how to skip one.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** Columns, in order. Exported so a test can pin the header rather than a string. */
|
|
35
|
+
export const PROFILE_CSV_COLUMNS = [
|
|
36
|
+
'label',
|
|
37
|
+
'model',
|
|
38
|
+
'calls',
|
|
39
|
+
'input_tokens',
|
|
40
|
+
'cache_read_tokens',
|
|
41
|
+
'cache_write_tokens',
|
|
42
|
+
'output_tokens',
|
|
43
|
+
'input_usd',
|
|
44
|
+
'cache_read_usd',
|
|
45
|
+
'cache_write_usd',
|
|
46
|
+
'output_usd',
|
|
47
|
+
'total_usd',
|
|
48
|
+
] as const;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* One CSV field, RFC 4180.
|
|
52
|
+
*
|
|
53
|
+
* Labels are arbitrary strings out of somebody's log: a label containing a
|
|
54
|
+
* comma would shift every column after it, and one containing a quote would
|
|
55
|
+
* break the row it sits in. Both are quoted here rather than sanitised,
|
|
56
|
+
* because changing the label would make the file disagree with every other
|
|
57
|
+
* rendering about what the workload is called.
|
|
58
|
+
*
|
|
59
|
+
* A leading `=`, `+`, `-` or `@` is prefixed with an apostrophe: those are
|
|
60
|
+
* how a spreadsheet is told a cell is a formula, and a label out of a log is
|
|
61
|
+
* data. This is the one place a value is altered, and it is altered to stop
|
|
62
|
+
* a log from executing anything when the file is opened.
|
|
63
|
+
*/
|
|
64
|
+
function field(value: string): string {
|
|
65
|
+
const guarded = /^[=+\-@\t\r]/.test(value) ? `'${value}` : value;
|
|
66
|
+
return /[",\n\r]/.test(guarded) ? `"${guarded.replace(/"/g, '""')}"` : guarded;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A dollar figure with enough places to survive being summed. */
|
|
70
|
+
const usd = (value: number): string => value.toFixed(6);
|
|
71
|
+
|
|
72
|
+
export interface ProfileCsvOptions {
|
|
73
|
+
/** What to call the bucket for calls carrying no label. */
|
|
74
|
+
unlabelled: string;
|
|
75
|
+
/**
|
|
76
|
+
* Which table to write.
|
|
77
|
+
*
|
|
78
|
+
* `slice` is one row per label and model — the grain a routing or budget
|
|
79
|
+
* decision is made at. `day` and `hour` are the time series, which is what
|
|
80
|
+
* a spreadsheet gets asked to chart; keeping them behind a choice rather
|
|
81
|
+
* than in extra columns means every file has one row shape, and a
|
|
82
|
+
* spreadsheet that has to filter before it can sum is a spreadsheet
|
|
83
|
+
* somebody sums wrong.
|
|
84
|
+
*/
|
|
85
|
+
shape?: ProfileCsvShape;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type ProfileCsvShape = 'slice' | 'day' | 'hour';
|
|
89
|
+
|
|
90
|
+
/** Columns for the per-day series. */
|
|
91
|
+
export const PROFILE_CSV_DAY_COLUMNS = ['day', 'usd', 'calls', 'top_label', 'top_label_usd'] as const;
|
|
92
|
+
|
|
93
|
+
/** Columns for the per-hour-of-UTC-day series. */
|
|
94
|
+
export const PROFILE_CSV_HOUR_COLUMNS = ['hour_utc', 'usd', 'calls'] as const;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The report as CSV text, one row per label and model.
|
|
98
|
+
*
|
|
99
|
+
* Rows arrive in the report's own order — largest bill first — because a
|
|
100
|
+
* spreadsheet can re-sort and a reader opening the file should see the
|
|
101
|
+
* expensive workload at the top either way.
|
|
102
|
+
*/
|
|
103
|
+
export function profileToCsv(report: UsageProfileReport, options: ProfileCsvOptions): string {
|
|
104
|
+
if (options.shape === 'day') {
|
|
105
|
+
const rows: string[] = [PROFILE_CSV_DAY_COLUMNS.join(',')];
|
|
106
|
+
for (const day of report.spendByDay) {
|
|
107
|
+
rows.push(
|
|
108
|
+
[
|
|
109
|
+
day.day,
|
|
110
|
+
usd(day.usd),
|
|
111
|
+
String(day.calls),
|
|
112
|
+
// A day whose calls carried no label at all has no top label, and an
|
|
113
|
+
// empty cell is that absence. Naming the unlabelled bucket here
|
|
114
|
+
// would claim a label the log never carried.
|
|
115
|
+
day.topLabel === null
|
|
116
|
+
? ''
|
|
117
|
+
: field(day.topLabel === UNLABELLED ? options.unlabelled : day.topLabel),
|
|
118
|
+
day.topLabel === null ? '' : usd(day.topLabelUsd),
|
|
119
|
+
].join(','),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return `${rows.join('\n')}\n`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (options.shape === 'hour') {
|
|
126
|
+
const rows: string[] = [PROFILE_CSV_HOUR_COLUMNS.join(',')];
|
|
127
|
+
for (const hour of report.spendByHour) {
|
|
128
|
+
rows.push([String(hour.hour), usd(hour.usd), String(hour.calls)].join(','));
|
|
129
|
+
}
|
|
130
|
+
return `${rows.join('\n')}\n`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const rows: string[] = [PROFILE_CSV_COLUMNS.join(',')];
|
|
134
|
+
|
|
135
|
+
for (const { label, model, breakdown } of report.byLabelAndModel) {
|
|
136
|
+
rows.push(
|
|
137
|
+
[
|
|
138
|
+
field(label === UNLABELLED ? options.unlabelled : label),
|
|
139
|
+
field(model),
|
|
140
|
+
String(breakdown.calls),
|
|
141
|
+
String(breakdown.inputTokens),
|
|
142
|
+
String(breakdown.cacheReadTokens),
|
|
143
|
+
String(breakdown.cacheWriteTokens),
|
|
144
|
+
String(breakdown.outputTokens),
|
|
145
|
+
usd(breakdown.inputUsd),
|
|
146
|
+
usd(breakdown.cacheReadUsd),
|
|
147
|
+
usd(breakdown.cacheWriteUsd),
|
|
148
|
+
usd(breakdown.outputUsd),
|
|
149
|
+
usd(breakdown.totalUsd),
|
|
150
|
+
].join(','),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The unpriced calls, with their tokens and no dollars.
|
|
156
|
+
*
|
|
157
|
+
* They are absent from `byLabelAndModel` — which holds what could be priced
|
|
158
|
+
* — and leaving them out of the file entirely would make its token columns
|
|
159
|
+
* disagree with the log. `byModel` keeps them, so they are recovered from
|
|
160
|
+
* there, with empty dollar cells rather than zeros.
|
|
161
|
+
*/
|
|
162
|
+
for (const model of report.unpricedModels) {
|
|
163
|
+
const row = report.byModel.find((entry) => entry.model === model);
|
|
164
|
+
if (!row) continue;
|
|
165
|
+
rows.push(
|
|
166
|
+
[
|
|
167
|
+
field(options.unlabelled),
|
|
168
|
+
field(model),
|
|
169
|
+
String(row.breakdown.calls),
|
|
170
|
+
String(row.breakdown.inputTokens),
|
|
171
|
+
String(row.breakdown.cacheReadTokens),
|
|
172
|
+
String(row.breakdown.cacheWriteTokens),
|
|
173
|
+
String(row.breakdown.outputTokens),
|
|
174
|
+
'',
|
|
175
|
+
'',
|
|
176
|
+
'',
|
|
177
|
+
'',
|
|
178
|
+
'',
|
|
179
|
+
].join(','),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return `${rows.join('\n')}\n`;
|
|
184
|
+
}
|
package/src/evaluate.ts
CHANGED
|
@@ -41,6 +41,16 @@ export type EvalVerdict = 'indistinguishable' | 'within-noise' | 'diverges' | 'i
|
|
|
41
41
|
export interface EvalReport {
|
|
42
42
|
provider: string;
|
|
43
43
|
model: string;
|
|
44
|
+
/**
|
|
45
|
+
* The model the candidate answer came from.
|
|
46
|
+
*
|
|
47
|
+
* Equal to `model` on the ordinary comparison — same model, two prompts. It
|
|
48
|
+
* differs when the question is the other one: **same prompt, two models**, which
|
|
49
|
+
* is what a routing decision is. `profile` prices that route exactly and can say
|
|
50
|
+
* nothing at all about whether the cheaper model still does the job; this is the
|
|
51
|
+
* measurement that can.
|
|
52
|
+
*/
|
|
53
|
+
candidateModel: string;
|
|
44
54
|
cases: EvalCase[];
|
|
45
55
|
/** Mean agreement of the original prompt with itself. The yardstick. */
|
|
46
56
|
selfAgreement: number;
|
|
@@ -58,6 +68,20 @@ export interface EvaluateOptions {
|
|
|
58
68
|
* call already paid for.
|
|
59
69
|
*/
|
|
60
70
|
concurrency?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Where the candidate answer comes from. Defaults to `provider`.
|
|
73
|
+
*
|
|
74
|
+
* This is the whole routing axis, and it needed no new yardstick. The baseline
|
|
75
|
+
* prompt is still run **twice on the original model** to measure that model's own
|
|
76
|
+
* variance, and the candidate is still judged against it — so the question
|
|
77
|
+
* becomes "does the cheaper model agree with the expensive one more closely than
|
|
78
|
+
* the expensive one agrees with itself?", which is the honest form of "is this
|
|
79
|
+
* route safe".
|
|
80
|
+
*
|
|
81
|
+
* A verdict built any other way would be a threshold somebody picked. This one is
|
|
82
|
+
* the model's own noise floor, measured on the same cases in the same run.
|
|
83
|
+
*/
|
|
84
|
+
candidateProvider?: LlmProvider;
|
|
61
85
|
}
|
|
62
86
|
|
|
63
87
|
/**
|
|
@@ -149,9 +173,13 @@ export async function evaluate(
|
|
|
149
173
|
options: EvaluateOptions = {},
|
|
150
174
|
): Promise<EvalReport> {
|
|
151
175
|
const concurrency = Math.max(1, options.concurrency ?? 3);
|
|
176
|
+
const candidate = options.candidateProvider ?? provider;
|
|
152
177
|
|
|
153
|
-
const run = (
|
|
154
|
-
|
|
178
|
+
const run = (
|
|
179
|
+
prompt: string,
|
|
180
|
+
input: string,
|
|
181
|
+
on: LlmProvider = provider,
|
|
182
|
+
): Promise<string> => on.complete({ system: fillPrompt(prompt, input), user: input });
|
|
155
183
|
|
|
156
184
|
const cases = await pooled(
|
|
157
185
|
inputs.map((input) => async (): Promise<EvalCase> => {
|
|
@@ -160,7 +188,8 @@ export async function evaluate(
|
|
|
160
188
|
// serve one from a cache and report a variance of zero.
|
|
161
189
|
const baselineA = await run(originalPrompt, input);
|
|
162
190
|
const baselineB = await run(originalPrompt, input);
|
|
163
|
-
|
|
191
|
+
// On `candidate`, which is `provider` unless a route is being measured.
|
|
192
|
+
const optimized = await run(optimizedPrompt, input, candidate);
|
|
164
193
|
|
|
165
194
|
return {
|
|
166
195
|
input,
|
|
@@ -179,6 +208,7 @@ export async function evaluate(
|
|
|
179
208
|
return {
|
|
180
209
|
provider: provider.name,
|
|
181
210
|
model: provider.model,
|
|
211
|
+
candidateModel: candidate.model,
|
|
182
212
|
cases,
|
|
183
213
|
selfAgreement,
|
|
184
214
|
crossAgreement,
|
package/src/i18n/en.ts
CHANGED
|
@@ -91,9 +91,18 @@ export const en: CoreMessages = {
|
|
|
91
91
|
},
|
|
92
92
|
|
|
93
93
|
advisories: {
|
|
94
|
-
contextOverflow: ({ tokens, modelName, contextWindow }) => ({
|
|
95
|
-
title:
|
|
96
|
-
|
|
94
|
+
contextOverflow: ({ tokens, modelName, contextWindow, uncertain }) => ({
|
|
95
|
+
title: uncertain
|
|
96
|
+
? 'The prompt probably does not fit in the context window'
|
|
97
|
+
: 'The prompt does not fit in the context window',
|
|
98
|
+
detail: uncertain
|
|
99
|
+
? `The optimised prompt is ~${n(tokens)} tokens against ${modelName}'s ${n(contextWindow)}. That count is an estimate and it is close to the line, so the call will probably fail but might not — settle it with --exact-tokens before rewriting anything. The counting endpoint is free. If it does exceed the window, split the content or move to a model with a larger one.`
|
|
100
|
+
: `The optimised prompt is ~${n(tokens)} tokens and ${modelName} accepts ${n(contextWindow)}. The call will fail: split the content or move to a model with a larger window.`,
|
|
101
|
+
}),
|
|
102
|
+
|
|
103
|
+
contextNearLimit: ({ tokens, modelName, contextWindow }) => ({
|
|
104
|
+
title: 'The prompt may not fit in the context window',
|
|
105
|
+
detail: `The optimised prompt is ~${n(tokens)} tokens against ${modelName}'s ${n(contextWindow)}, which fits — but that count is an estimate and its error range reaches past the window, so the real prompt may not. A call that exceeds the window fails outright rather than degrading, and nothing else here warns about it. Confirm with --exact-tokens; the counting endpoint is free.`,
|
|
97
106
|
}),
|
|
98
107
|
|
|
99
108
|
promptCaching: ({
|
|
@@ -106,6 +115,7 @@ export const en: CoreMessages = {
|
|
|
106
115
|
readPct,
|
|
107
116
|
writePct,
|
|
108
117
|
explicit,
|
|
118
|
+
nearMinimum,
|
|
109
119
|
}) => {
|
|
110
120
|
const scope = placeholder
|
|
111
121
|
? `The stable prefix — everything before the first placeholder ${placeholder} — is ~${n(prefixTokens)} of the prompt's ${n(totalTokens)} tokens, and clears ${modelName}'s ${n(minTokens)}-token cacheable minimum.`
|
|
@@ -113,9 +123,12 @@ export const en: CoreMessages = {
|
|
|
113
123
|
const how = explicit
|
|
114
124
|
? 'Put the cache marker at the end of the stable prefix: any byte that changes before the cut invalidates everything after it.'
|
|
115
125
|
: `${modelName} caches automatically above its minimum, so there is nothing to set — but the same rule applies: any byte that changes before the cut invalidates everything after it.`;
|
|
126
|
+
const hedge = nearMinimum
|
|
127
|
+
? ` One caveat on the figure: that prefix count is an estimate and it is close to the line, so the real one may be below the ${n(minTokens)}-token minimum — in which case nothing caches and this saving is not there. Settle it with --exact-tokens before budgeting from it. The counting endpoint is free.`
|
|
128
|
+
: '';
|
|
116
129
|
return {
|
|
117
130
|
title: 'Turn on prompt caching for the stable prefix',
|
|
118
|
-
detail: `${scope} At a ${hitRatePct}% hit rate, a cache read costs ${readPct}% of the input price and a write costs ${writePct}%. ${how}`,
|
|
131
|
+
detail: `${scope} At a ${hitRatePct}% hit rate, a cache read costs ${readPct}% of the input price and a write costs ${writePct}%. ${how}${hedge}`,
|
|
119
132
|
};
|
|
120
133
|
},
|
|
121
134
|
|
|
@@ -150,9 +163,9 @@ export const en: CoreMessages = {
|
|
|
150
163
|
};
|
|
151
164
|
},
|
|
152
165
|
|
|
153
|
-
cachePrefixReorder: ({ staticTokensAfter, sharePct, placeholder }) => ({
|
|
166
|
+
cachePrefixReorder: ({ staticTokensAfter, sharePct, placeholder, command }) => ({
|
|
154
167
|
title: 'Move the stable instructions ahead of the first placeholder',
|
|
155
|
-
detail: `About ~${n(staticTokensAfter)} tokens of stable content (${sharePct}% of the prompt) sit after the first variable placeholder ${placeholder}, so today they never get cached.
|
|
168
|
+
detail: `About ~${n(staticTokensAfter)} tokens of stable content (${sharePct}% of the prompt) sit after the first variable placeholder ${placeholder}, so today they never get cached. Fixed instructions and context first, placeholders last, and that content starts being read from cache at 10% of the price. Run \`${command}\` to attempt it: whole blocks only, and it refuses to move anything that refers back to earlier text. Read the diff — order carries meaning, and "summarise the text above" is nonsense in front of the text it points at.`,
|
|
156
169
|
}),
|
|
157
170
|
|
|
158
171
|
batchApi: () => ({
|
package/src/i18n/es.ts
CHANGED
|
@@ -91,9 +91,18 @@ export const es: CoreMessages = {
|
|
|
91
91
|
},
|
|
92
92
|
|
|
93
93
|
advisories: {
|
|
94
|
-
contextOverflow: ({ tokens, modelName, contextWindow }) => ({
|
|
95
|
-
title:
|
|
96
|
-
|
|
94
|
+
contextOverflow: ({ tokens, modelName, contextWindow, uncertain }) => ({
|
|
95
|
+
title: uncertain
|
|
96
|
+
? 'El prompt probablemente no cabe en la ventana de contexto'
|
|
97
|
+
: 'El prompt no cabe en la ventana de contexto',
|
|
98
|
+
detail: uncertain
|
|
99
|
+
? `El prompt optimizado ocupa ~${n(tokens)} tokens frente a los ${n(contextWindow)} de ${modelName}. Ese recuento es una estimación y está cerca del límite, así que la llamada fallará probablemente, pero puede que no —confírmalo con --exact-tokens antes de reescribir nada. El endpoint de conteo es gratis. Si de verdad se pasa, divide el contenido o cambia a un modelo con ventana mayor.`
|
|
100
|
+
: `El prompt optimizado ocupa ~${n(tokens)} tokens y ${modelName} admite ${n(contextWindow)}. La llamada fallará: divide el contenido o cambia a un modelo con ventana mayor.`,
|
|
101
|
+
}),
|
|
102
|
+
|
|
103
|
+
contextNearLimit: ({ tokens, modelName, contextWindow }) => ({
|
|
104
|
+
title: 'El prompt puede no caber en la ventana de contexto',
|
|
105
|
+
detail: `El prompt optimizado ocupa ~${n(tokens)} tokens frente a los ${n(contextWindow)} de ${modelName}, así que cabe —pero ese recuento es una estimación y su margen de error se pasa de la ventana, así que el prompt real puede no caber. Una llamada que excede la ventana falla del todo en lugar de degradarse, y nada más aquí avisa de eso. Confírmalo con --exact-tokens; el endpoint de conteo es gratis.`,
|
|
97
106
|
}),
|
|
98
107
|
|
|
99
108
|
promptCaching: ({
|
|
@@ -106,6 +115,7 @@ export const es: CoreMessages = {
|
|
|
106
115
|
readPct,
|
|
107
116
|
writePct,
|
|
108
117
|
explicit,
|
|
118
|
+
nearMinimum,
|
|
109
119
|
}) => {
|
|
110
120
|
const scope = placeholder
|
|
111
121
|
? `El prefijo estable —lo anterior al primer marcador ${placeholder}— son ~${n(prefixTokens)} de los ${n(totalTokens)} tokens del prompt, y supera el mínimo cacheable de ${n(minTokens)} de ${modelName}.`
|
|
@@ -113,9 +123,12 @@ export const es: CoreMessages = {
|
|
|
113
123
|
const how = explicit
|
|
114
124
|
? 'Coloca el marcador de caché al final del prefijo estable: cualquier byte que cambie antes del corte invalida todo lo que va detrás.'
|
|
115
125
|
: `${modelName} cachea automáticamente por encima de su mínimo, así que no hay nada que activar; pero la regla es la misma: cualquier byte que cambie antes del corte invalida todo lo que va detrás.`;
|
|
126
|
+
const hedge = nearMinimum
|
|
127
|
+
? ` Un aviso sobre la cifra: ese recuento del prefijo es una estimación y está cerca del límite, así que el real puede quedar por debajo del mínimo de ${n(minTokens)} tokens —y entonces no se cachea nada y este ahorro no existe. Confírmalo con --exact-tokens antes de presupuestar sobre él. El endpoint de conteo es gratis.`
|
|
128
|
+
: '';
|
|
116
129
|
return {
|
|
117
130
|
title: 'Activa prompt caching en el prefijo estable',
|
|
118
|
-
detail: `${scope} Con una tasa de acierto del ${hitRatePct}%, la lectura de caché cuesta un ${readPct}% del precio de entrada y la escritura un ${writePct}%. ${how}`,
|
|
131
|
+
detail: `${scope} Con una tasa de acierto del ${hitRatePct}%, la lectura de caché cuesta un ${readPct}% del precio de entrada y la escritura un ${writePct}%. ${how}${hedge}`,
|
|
119
132
|
};
|
|
120
133
|
},
|
|
121
134
|
|
|
@@ -150,9 +163,9 @@ export const es: CoreMessages = {
|
|
|
150
163
|
};
|
|
151
164
|
},
|
|
152
165
|
|
|
153
|
-
cachePrefixReorder: ({ staticTokensAfter, sharePct, placeholder }) => ({
|
|
166
|
+
cachePrefixReorder: ({ staticTokensAfter, sharePct, placeholder, command }) => ({
|
|
154
167
|
title: 'Mueve las instrucciones estables antes del primer marcador',
|
|
155
|
-
detail: `Unos ~${n(staticTokensAfter)} tokens de contenido estable (el ${sharePct}% del prompt) están después del primer marcador variable ${placeholder}, así que hoy no se cachean nunca.
|
|
168
|
+
detail: `Unos ~${n(staticTokensAfter)} tokens de contenido estable (el ${sharePct}% del prompt) están después del primer marcador variable ${placeholder}, así que hoy no se cachean nunca. Instrucciones y contexto fijos primero, marcadores al final, y ese contenido empieza a leerse de caché al 10% del precio. Ejecuta \`${command}\` para intentarlo: solo mueve bloques completos y se niega a mover cualquiera que se refiera a texto anterior. Lee el diff —el orden significa algo, y «resume el texto de arriba» no tiene sentido delante del texto al que apunta.`,
|
|
156
169
|
}),
|
|
157
170
|
|
|
158
171
|
batchApi: () => ({
|
package/src/i18n/types.ts
CHANGED
|
@@ -51,6 +51,17 @@ export interface RuleCopy {
|
|
|
51
51
|
// --------------------------------------------------------------------------
|
|
52
52
|
|
|
53
53
|
export interface ContextOverflowParams {
|
|
54
|
+
/**
|
|
55
|
+
* The count is an estimate and its band reaches back under the window, so
|
|
56
|
+
* "the call will fail" is a prediction rather than a fact.
|
|
57
|
+
*/
|
|
58
|
+
uncertain: boolean;
|
|
59
|
+
tokens: number;
|
|
60
|
+
modelName: string;
|
|
61
|
+
contextWindow: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ContextNearLimitParams {
|
|
54
65
|
tokens: number;
|
|
55
66
|
modelName: string;
|
|
56
67
|
contextWindow: number;
|
|
@@ -60,6 +71,20 @@ export interface PromptCachingParams {
|
|
|
60
71
|
/** First template placeholder, or `null` when the prompt has none. */
|
|
61
72
|
placeholder: string | null;
|
|
62
73
|
prefixTokens: number;
|
|
74
|
+
/**
|
|
75
|
+
* The prefix is an estimate and the band reaches below the minimum, so the
|
|
76
|
+
* saving may not be collectable at all.
|
|
77
|
+
*
|
|
78
|
+
* The mirror of `BelowCacheMinimumParams.couldReachMinimum`, and the asymmetry
|
|
79
|
+
* was a real gap: that one hedged an estimate landing just *under* a hard
|
|
80
|
+
* threshold, while this one promised money on an estimate landing just *over*
|
|
81
|
+
* it. With a ±10% band an estimated 528-token prefix can truly be 475, in which
|
|
82
|
+
* case nothing caches and the figure beside this advisory is uncollectable.
|
|
83
|
+
*
|
|
84
|
+
* The cautionary direction matters more than the encouraging one, because this
|
|
85
|
+
* is the side with a dollar sign attached.
|
|
86
|
+
*/
|
|
87
|
+
nearMinimum: boolean;
|
|
63
88
|
totalTokens: number;
|
|
64
89
|
minTokens: number;
|
|
65
90
|
modelName: string;
|
|
@@ -93,7 +118,7 @@ export interface BelowCacheMinimumParams {
|
|
|
93
118
|
* count could be above it.
|
|
94
119
|
*
|
|
95
120
|
* Without this the advisory asserts "caching will not work here" from a number
|
|
96
|
-
* measured to ±
|
|
121
|
+
* measured to ±10%, and on a prefix near the threshold that is not an imprecise
|
|
97
122
|
* figure — it is wrong advice, and it costs the reader the largest saving
|
|
98
123
|
* Trazum offers.
|
|
99
124
|
*/
|
|
@@ -104,6 +129,8 @@ export interface CachePrefixReorderParams {
|
|
|
104
129
|
staticTokensAfter: number;
|
|
105
130
|
sharePct: number;
|
|
106
131
|
placeholder: string;
|
|
132
|
+
/** The command that attempts it, because Trazum can do this itself. */
|
|
133
|
+
command: string;
|
|
107
134
|
}
|
|
108
135
|
|
|
109
136
|
export interface ModelDowngradeParams {
|
|
@@ -193,6 +220,7 @@ export interface CoreMessages {
|
|
|
193
220
|
suggest: SuggestMessages;
|
|
194
221
|
advisories: {
|
|
195
222
|
contextOverflow(p: ContextOverflowParams): LocalizedMessage;
|
|
223
|
+
contextNearLimit(p: ContextNearLimitParams): LocalizedMessage;
|
|
196
224
|
promptCaching(p: PromptCachingParams): LocalizedMessage;
|
|
197
225
|
promptCachingNotWorthIt(): LocalizedMessage;
|
|
198
226
|
belowCacheMinimum(p: BelowCacheMinimumParams): LocalizedMessage;
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export { ESTIMATE_ERROR_BAND_PCT, estimateTokens, countTokensAnthropic } from './tokenizer.js';
|
|
3
|
+
export {
|
|
4
|
+
UNLABELLED,
|
|
5
|
+
cacheEconomics,
|
|
6
|
+
cacheHitRate,
|
|
7
|
+
parseUsageLine,
|
|
8
|
+
profileUsage,
|
|
9
|
+
sharesOf,
|
|
10
|
+
} from './usage.js';
|
|
11
|
+
export type {
|
|
12
|
+
CacheEconomics,
|
|
13
|
+
CacheVerdict,
|
|
14
|
+
UsageProfileOptions,
|
|
15
|
+
UsageBreakdown,
|
|
16
|
+
UsageProfileReport,
|
|
17
|
+
UsageRecord,
|
|
18
|
+
UsageShares,
|
|
19
|
+
} from './usage.js';
|
|
20
|
+
export { conversationGrowth, createConversationTracker } from './conversation.js';
|
|
21
|
+
// Whether the cache TTL fits how fast the turns arrive — the mechanism behind a
|
|
22
|
+
// losing cache, readable only when the log carries a clock. See ttl-fit.ts.
|
|
23
|
+
export { TTL_1H_MS, TTL_5M_MS, cacheTtlFit, createTtlFitTracker } from './ttl-fit.js';
|
|
24
|
+
export type { CacheTtlFit, TtlFitOptions, TtlFitTracker, TtlFitVerdict } from './ttl-fit.js';
|
|
25
|
+
// Cache writes by conversations that never came back — a ceiling on waste,
|
|
26
|
+
// named as one, and a fact when the slice read nothing. See session-ledger.ts.
|
|
27
|
+
// The drivers of a change between two bills — one implementation, because the
|
|
28
|
+
// sign convention (positive means the bill grew) has flipped once already
|
|
29
|
+
// when restated by hand. See against.ts.
|
|
30
|
+
// The profile as a spreadsheet — one row per label and model, no total row,
|
|
31
|
+
// empty cells where dollars are unknown. See csv.ts.
|
|
32
|
+
export {
|
|
33
|
+
PROFILE_CSV_COLUMNS,
|
|
34
|
+
PROFILE_CSV_DAY_COLUMNS,
|
|
35
|
+
PROFILE_CSV_HOUR_COLUMNS,
|
|
36
|
+
profileToCsv,
|
|
37
|
+
} from './csv.js';
|
|
38
|
+
export type { ProfileCsvOptions, ProfileCsvShape } from './csv.js';
|
|
39
|
+
export { driversBetween } from './against.js';
|
|
40
|
+
export type { AgainstDriver } from './against.js';
|
|
41
|
+
// The same tokens at another model's rates — arithmetic, not advice, and it
|
|
42
|
+
// refuses to price a call the target could not have accepted. See reprice.ts.
|
|
43
|
+
// The shape of a call's input — the half of the bill a total could only name.
|
|
44
|
+
// See input-shape.ts.
|
|
45
|
+
export { createInputShapeTracker, inputShapes } from './input-shape.js';
|
|
46
|
+
export type { InputShape, InputShapeOptions, InputShapeTracker } from './input-shape.js';
|
|
47
|
+
// The same request sent again a moment later — a retry or a loop, named as
|
|
48
|
+
// the pattern it is and never as a certainty. See repeats.ts.
|
|
49
|
+
export { createRepeatsTracker, repeatedTurns } from './repeats.js';
|
|
50
|
+
export type { RepeatedTurns, RepeatsOptions, RepeatsTracker } from './repeats.js';
|
|
51
|
+
export { priceTokensOn, repriceProfile } from './reprice.js';
|
|
52
|
+
export type { OverContextSlice, RepriceReport, RepricedSlice } from './reprice.js';
|
|
53
|
+
export { createSessionLedgerTracker, singleTurnCacheWrites } from './session-ledger.js';
|
|
54
|
+
// What one conversation costs — median and p95, exact. See session-cost.ts.
|
|
55
|
+
export { createSessionCostTracker, sessionCostShapes } from './session-cost.js';
|
|
56
|
+
export type { SessionCostOptions, SessionCostShape, SessionCostTracker } from './session-cost.js';
|
|
57
|
+
export type {
|
|
58
|
+
SessionLedgerOptions,
|
|
59
|
+
SessionLedgerTracker,
|
|
60
|
+
SingleTurnCacheWrites,
|
|
61
|
+
} from './session-ledger.js';
|
|
62
|
+
export { createOutputShapeTracker, outputShapes } from './output-shape.js';
|
|
63
|
+
export type { OutputShape, OutputShapeOptions, OutputShapeTracker } from './output-shape.js';
|
|
64
|
+
export type { ConversationGrowth, ConversationOptions, ConversationTracker } from './conversation.js';
|
|
65
|
+
export { billLevers } from './levers.js';
|
|
66
|
+
export type { BillLevers, BillLeverOptions, LeverId, SliceLevers } from './levers.js';
|
|
3
67
|
export { DETECTABLE_LANGUAGES, detectTextLanguage } from './language.js';
|
|
4
68
|
export { countSentences, profilePrompt } from './profile.js';
|
|
5
69
|
export { PHRASE_LANGUAGES } from './phrases.js';
|
|
@@ -226,4 +290,4 @@ export {
|
|
|
226
290
|
budgetFor,
|
|
227
291
|
parseConfig,
|
|
228
292
|
} from './config-schema.js';
|
|
229
|
-
export type { ResolvedBudget, TrazumConfig } from './config-schema.js';
|
|
293
|
+
export type { ResolvedBudget, SpendConfig, TrazumConfig } from './config-schema.js';
|