@trazum/core 1.60.3 → 1.60.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/conform.d.ts +1 -1
- package/dist/conform.d.ts.map +1 -1
- package/dist/conform.js +22 -0
- package/dist/conform.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/optimize.d.ts.map +1 -1
- package/dist/optimize.js +15 -0
- package/dist/optimize.js.map +1 -1
- package/dist/types.d.ts +10 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -1
- package/dist/types.js.map +1 -1
- package/dist/write.d.ts +193 -0
- package/dist/write.d.ts.map +1 -0
- package/dist/write.js +243 -0
- package/dist/write.js.map +1 -0
- package/package.json +1 -1
- package/src/conform.ts +30 -0
- package/src/index.ts +24 -0
- package/src/optimize.ts +17 -0
- package/src/types.ts +11 -1
- package/src/write.ts +412 -0
package/dist/write.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { optimize } from './optimize.js';
|
|
2
|
+
import { BUNDLED_CATALOGUE } from './pricing.js';
|
|
3
|
+
/**
|
|
4
|
+
* The interview behind `trazum write`.
|
|
5
|
+
*
|
|
6
|
+
* Every other command in this product reads a prompt somebody already wrote.
|
|
7
|
+
* This one starts from nothing and asks. What it asks is the product: a
|
|
8
|
+
* question whose answer cannot change the output is waste, and waste is this
|
|
9
|
+
* tool's entire subject.
|
|
10
|
+
*
|
|
11
|
+
* **Deterministic, and deliberately so.** No model decides what to ask. The
|
|
12
|
+
* catalogue below is fixed, the gates are predicates over the answers so far,
|
|
13
|
+
* and the same answers produce the same interview on any machine — which is
|
|
14
|
+
* what lets the offline rule hold without a footnote.
|
|
15
|
+
*
|
|
16
|
+
* **Ids here, words in the CLI.** Same split as the rules catalogue: this file
|
|
17
|
+
* knows a slot exists and what opens it; `packages/cli/src/i18n` knows how to
|
|
18
|
+
* ask it in a locale. A locale changes the question, never which questions.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The sections of an assembled prompt, in the order they are written.
|
|
22
|
+
*
|
|
23
|
+
* The order is fixed, and for a reason this tool can price: prompt caching is
|
|
24
|
+
* a byte-for-byte prefix match, so everything stable goes first and everything
|
|
25
|
+
* that varies per call goes last, which makes the cacheable prefix as long as
|
|
26
|
+
* the prompt allows.
|
|
27
|
+
*/
|
|
28
|
+
export const SECTIONS = [
|
|
29
|
+
'role',
|
|
30
|
+
'task',
|
|
31
|
+
'inputs',
|
|
32
|
+
'output',
|
|
33
|
+
'constraints',
|
|
34
|
+
'examples',
|
|
35
|
+
'failure-modes',
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* The closed vocabulary `output-shape` accepts.
|
|
39
|
+
*
|
|
40
|
+
* Named `OutputFormat` rather than `OutputShape` because that name is already
|
|
41
|
+
* taken by the usage report's finding about where output spend concentrates,
|
|
42
|
+
* and two unrelated things under one name in one package is how a consumer
|
|
43
|
+
* imports the wrong one.
|
|
44
|
+
*/
|
|
45
|
+
export const OUTPUT_FORMATS = ['prose', 'json', 'list', 'table'];
|
|
46
|
+
const answered = (answers, id) => Object.prototype.hasOwnProperty.call(answers, id) && answers[id] !== null;
|
|
47
|
+
const shapeIs = (...formats) => (answers) => {
|
|
48
|
+
const shape = answers['output-shape'];
|
|
49
|
+
return typeof shape === 'string' && formats.includes(shape);
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The catalogue, in the order the interview walks it.
|
|
53
|
+
*
|
|
54
|
+
* Required first is not an accident: somebody who abandons the interview
|
|
55
|
+
* halfway should have answered the things without which there is no prompt at
|
|
56
|
+
* all, rather than having spent their attention on the optional half.
|
|
57
|
+
*/
|
|
58
|
+
export const SLOTS = [
|
|
59
|
+
{ id: 'task', section: 'task', required: true },
|
|
60
|
+
{ id: 'role', section: 'role', required: true },
|
|
61
|
+
{ id: 'inputs', section: 'inputs', required: true },
|
|
62
|
+
{ id: 'output-shape', section: 'output', required: true },
|
|
63
|
+
{ id: 'output-schema', section: 'output', required: true, opensWhen: shapeIs('json', 'table') },
|
|
64
|
+
{ id: 'output-length', section: 'output', required: false, opensWhen: shapeIs('prose', 'list') },
|
|
65
|
+
{ id: 'audience', section: 'role', required: false },
|
|
66
|
+
{ id: 'constraints', section: 'constraints', required: false },
|
|
67
|
+
{ id: 'refusal', section: 'constraints', required: false },
|
|
68
|
+
{ id: 'examples', section: 'examples', required: false },
|
|
69
|
+
{
|
|
70
|
+
id: 'example-inputs',
|
|
71
|
+
section: 'examples',
|
|
72
|
+
required: false,
|
|
73
|
+
opensWhen: (answers) => answered(answers, 'examples'),
|
|
74
|
+
},
|
|
75
|
+
{ id: 'failure-modes', section: 'failure-modes', required: false },
|
|
76
|
+
{ id: 'model', section: null, required: false },
|
|
77
|
+
{ id: 'budget', section: null, required: false },
|
|
78
|
+
];
|
|
79
|
+
export const SLOT_IDS = SLOTS.map((entry) => entry.id);
|
|
80
|
+
export function slot(id) {
|
|
81
|
+
return SLOTS.find((entry) => entry.id === id);
|
|
82
|
+
}
|
|
83
|
+
/** Whether a slot is worth asking, given what is known so far. */
|
|
84
|
+
export function isOpen(entry, answers) {
|
|
85
|
+
return entry.opensWhen === undefined || entry.opensWhen(answers);
|
|
86
|
+
}
|
|
87
|
+
export function interview(answers) {
|
|
88
|
+
const open = SLOTS.filter((entry) => isOpen(entry, answers));
|
|
89
|
+
const has = (entry) => Object.prototype.hasOwnProperty.call(answers, entry.id);
|
|
90
|
+
const unasked = open.filter((entry) => !has(entry));
|
|
91
|
+
return {
|
|
92
|
+
next: unasked[0]?.id ?? null,
|
|
93
|
+
done: unasked.length === 0,
|
|
94
|
+
open: open.map((entry) => entry.id),
|
|
95
|
+
answered: open.filter((entry) => answered(answers, entry.id)).map((entry) => entry.id),
|
|
96
|
+
declined: open
|
|
97
|
+
.filter((entry) => has(entry) && answers[entry.id] === null)
|
|
98
|
+
.map((entry) => entry.id),
|
|
99
|
+
missing: unasked.filter((entry) => entry.required).map((entry) => entry.id),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* How each slot appears in the assembled prompt.
|
|
104
|
+
*
|
|
105
|
+
* A slot either supplies the section's opening line on its own, or arrives
|
|
106
|
+
* under a label. Nothing here paraphrases an answer: the words are the
|
|
107
|
+
* author's, and a writer that rewrote them would be answering a question
|
|
108
|
+
* nobody asked it.
|
|
109
|
+
*/
|
|
110
|
+
const LABEL = {
|
|
111
|
+
role: null,
|
|
112
|
+
audience: 'Audience:',
|
|
113
|
+
task: null,
|
|
114
|
+
inputs: null,
|
|
115
|
+
'output-shape': 'Format:',
|
|
116
|
+
'output-schema': 'Fields:',
|
|
117
|
+
'output-length': 'At most:',
|
|
118
|
+
constraints: null,
|
|
119
|
+
refusal: 'When you cannot answer:',
|
|
120
|
+
examples: null,
|
|
121
|
+
'example-inputs': 'Input:',
|
|
122
|
+
'failure-modes': null,
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* The heading each section is written under.
|
|
126
|
+
*
|
|
127
|
+
* **English, in every locale, on purpose.** These are structure rather than
|
|
128
|
+
* prose — a contract with the model, not words for a human reader — and the
|
|
129
|
+
* arc promises the same answers produce the same prompt byte for byte on any
|
|
130
|
+
* machine *and in any locale*. A heading that moved with `TRAZUM_LOCALE` would
|
|
131
|
+
* make the assembled prompt a function of the machine that ran the interview,
|
|
132
|
+
* which is the one thing [the locale rule](../../../ROADMAP.md) forbids.
|
|
133
|
+
*/
|
|
134
|
+
const HEADING = {
|
|
135
|
+
role: 'Role',
|
|
136
|
+
task: 'Task',
|
|
137
|
+
inputs: 'Inputs',
|
|
138
|
+
output: 'Output',
|
|
139
|
+
constraints: 'Constraints',
|
|
140
|
+
examples: 'Examples',
|
|
141
|
+
'failure-modes': 'Failure modes',
|
|
142
|
+
};
|
|
143
|
+
const line = (id, answer) => {
|
|
144
|
+
const label = LABEL[id] ?? null;
|
|
145
|
+
return label === null ? answer.trim() : `${label} ${answer.trim()}`;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Assemble the prompt, or refuse and say what is missing.
|
|
149
|
+
*
|
|
150
|
+
* Deterministic in both senses that matter: the same answers produce the same
|
|
151
|
+
* bytes, and nothing here consults the network, the clock or the locale.
|
|
152
|
+
*/
|
|
153
|
+
export function assemble(answers, options = {}) {
|
|
154
|
+
const state = interview(answers);
|
|
155
|
+
const sections = [];
|
|
156
|
+
for (const section of SECTIONS) {
|
|
157
|
+
const filled = SLOTS.filter((entry) => entry.section === section &&
|
|
158
|
+
isOpen(entry, answers) &&
|
|
159
|
+
typeof answers[entry.id] === 'string' &&
|
|
160
|
+
answers[entry.id].trim().length > 0);
|
|
161
|
+
if (filled.length === 0)
|
|
162
|
+
continue;
|
|
163
|
+
const body = filled.map((entry) => line(entry.id, answers[entry.id])).join('\n');
|
|
164
|
+
sections.push({
|
|
165
|
+
section,
|
|
166
|
+
text: `${HEADING[section]}\n${body}`,
|
|
167
|
+
from: filled.map((entry) => entry.id),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const prompt = state.missing.length > 0 ? null : sections.map((entry) => entry.text).join('\n\n');
|
|
171
|
+
return {
|
|
172
|
+
schemaVersion: 1,
|
|
173
|
+
prompt,
|
|
174
|
+
sections,
|
|
175
|
+
answered: state.answered,
|
|
176
|
+
declined: state.declined,
|
|
177
|
+
missing: state.missing,
|
|
178
|
+
measured: prompt === null ? null : measure(prompt, answers, state, options),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const measure = (prompt, answers, state, options) => {
|
|
182
|
+
const model = typeof answers['model'] === 'string' ? answers['model'].trim() : null;
|
|
183
|
+
const budgetAnswer = typeof answers['budget'] === 'string' ? Number(answers['budget']) : NaN;
|
|
184
|
+
const budgetUsd = Number.isFinite(budgetAnswer) ? budgetAnswer : null;
|
|
185
|
+
/*
|
|
186
|
+
Priced only when the catalogue knows the model.
|
|
187
|
+
|
|
188
|
+
`optimize` throws on a model it cannot price, which is right for a command
|
|
189
|
+
somebody typed a model into and wrong here: an unpriced model is one of the
|
|
190
|
+
three answers this measurement gives, not a crash. So the tokens and the
|
|
191
|
+
rules — neither of which needs a price — are measured first, and the money
|
|
192
|
+
is asked for separately and only when there is something to ask.
|
|
193
|
+
*/
|
|
194
|
+
const known = model !== null && (options.pricing ?? BUNDLED_CATALOGUE).byId.has(model);
|
|
195
|
+
const usage = {};
|
|
196
|
+
if (known)
|
|
197
|
+
usage.model = model;
|
|
198
|
+
if (options.callsPerMonth !== undefined)
|
|
199
|
+
usage.callsPerMonth = options.callsPerMonth;
|
|
200
|
+
if (options.avgOutputTokens !== undefined)
|
|
201
|
+
usage.avgOutputTokens = options.avgOutputTokens;
|
|
202
|
+
const report = optimize(prompt, {
|
|
203
|
+
level: options.level ?? 'safe',
|
|
204
|
+
pricing: options.pricing,
|
|
205
|
+
usage,
|
|
206
|
+
});
|
|
207
|
+
const monthlyUsd = known && report.savings !== null ? report.savings.perMonth.before.totalUsd : null;
|
|
208
|
+
let verdict = 'cannot-tell';
|
|
209
|
+
let reason = null;
|
|
210
|
+
if (budgetUsd === null)
|
|
211
|
+
reason = 'no-budget';
|
|
212
|
+
else if (model === null)
|
|
213
|
+
reason = 'no-model';
|
|
214
|
+
else if (monthlyUsd === null)
|
|
215
|
+
reason = 'model-unpriced';
|
|
216
|
+
else
|
|
217
|
+
verdict = monthlyUsd > budgetUsd ? 'over' : 'within';
|
|
218
|
+
return {
|
|
219
|
+
complete: {
|
|
220
|
+
required: SLOTS.filter((entry) => entry.required && isOpen(entry, answers)).length,
|
|
221
|
+
answered: state.answered.length,
|
|
222
|
+
declined: state.declined,
|
|
223
|
+
missing: state.missing,
|
|
224
|
+
},
|
|
225
|
+
cheap: {
|
|
226
|
+
tokens: report.tokensBefore,
|
|
227
|
+
tokenSource: report.tokenSource,
|
|
228
|
+
model,
|
|
229
|
+
monthlyUsd,
|
|
230
|
+
provenance: 'estimated',
|
|
231
|
+
budgetUsd,
|
|
232
|
+
verdict,
|
|
233
|
+
reason,
|
|
234
|
+
},
|
|
235
|
+
clean: {
|
|
236
|
+
rules: report.rules
|
|
237
|
+
.filter((entry) => entry.hits > 0)
|
|
238
|
+
.map((entry) => ({ id: entry.id, hits: entry.hits })),
|
|
239
|
+
tokensRecoverable: report.tokensSaved,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
//# sourceMappingURL=write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.js","sourceRoot":"","sources":["../src/write.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trazum/core",
|
|
3
|
-
"version": "1.60.
|
|
3
|
+
"version": "1.60.4",
|
|
4
4
|
"description": "Trazum core: priced advisories for LLM prompts (caching, model tier, batching, schemas), plus deterministic trimming, token counting and pricing.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "David Mu\u00f1oz Rey",
|
package/src/conform.ts
CHANGED
|
@@ -45,6 +45,7 @@ export const CONTRACT_NAMES = [
|
|
|
45
45
|
'outcome-report',
|
|
46
46
|
'annual-record',
|
|
47
47
|
'roll-up',
|
|
48
|
+
'prompt-draft',
|
|
48
49
|
] as const;
|
|
49
50
|
|
|
50
51
|
export type ContractName = (typeof CONTRACT_NAMES)[number];
|
|
@@ -326,6 +327,35 @@ const DOCUMENT_RULES: Record<Exclude<ContractName, 'usage-log'>, FieldRule[]> =
|
|
|
326
327
|
rule('call', 'an object, or null when nothing was described', (v) => v === null || isObject(v)),
|
|
327
328
|
rule('budget', 'an object, or null when there is no budget', (v) => v === null || isObject(v)),
|
|
328
329
|
],
|
|
330
|
+
/**
|
|
331
|
+
* A prompt somebody was interviewed into, and what the interview could not
|
|
332
|
+
* get out of them.
|
|
333
|
+
*
|
|
334
|
+
* `prompt` is **null and never an empty string** when required answers are
|
|
335
|
+
* missing: an empty string reads as a prompt that came out blank, and the
|
|
336
|
+
* difference between "not built" and "built and empty" is the one this
|
|
337
|
+
* format refuses to lose everywhere else.
|
|
338
|
+
*
|
|
339
|
+
* `declined` and `missing` are separate arrays for the same reason. Somebody
|
|
340
|
+
* who was asked and said no is not somebody who was never asked, and folding
|
|
341
|
+
* the two would turn a decision into a gap.
|
|
342
|
+
*/
|
|
343
|
+
'prompt-draft': [
|
|
344
|
+
rule(
|
|
345
|
+
'prompt',
|
|
346
|
+
'the assembled prompt, or **null** when required answers are missing — never an empty string',
|
|
347
|
+
(v) => v === null || typeof v === 'string',
|
|
348
|
+
),
|
|
349
|
+
rule('sections', 'an array of the sections that got words, in the order they are written', isArray),
|
|
350
|
+
rule('answered', 'an array of the slots that were answered', isArray),
|
|
351
|
+
rule('declined', 'an array of the slots somebody was asked and declined — never folded into missing', isArray),
|
|
352
|
+
rule('missing', 'an array of required slots still unanswered; empty exactly when prompt is a string', isArray),
|
|
353
|
+
rule(
|
|
354
|
+
'measured',
|
|
355
|
+
'an object of the three claims, or **null** when there is no prompt to measure — never an object of zeros',
|
|
356
|
+
(v) => v === null || isObject(v),
|
|
357
|
+
),
|
|
358
|
+
],
|
|
329
359
|
};
|
|
330
360
|
|
|
331
361
|
/**
|
package/src/index.ts
CHANGED
|
@@ -547,3 +547,27 @@ export {
|
|
|
547
547
|
} from './config-schema.js';
|
|
548
548
|
export type { ResolvedBudget, SpendConfig, TrazumConfig, WaiveEntry } from './config-schema.js';
|
|
549
549
|
export { WAIVABLE_GATES } from './config-schema.js';
|
|
550
|
+
|
|
551
|
+
export { RULE_LEVELS } from './types.js';
|
|
552
|
+
export {
|
|
553
|
+
assemble,
|
|
554
|
+
interview,
|
|
555
|
+
isOpen,
|
|
556
|
+
OUTPUT_FORMATS,
|
|
557
|
+
SECTIONS,
|
|
558
|
+
slot,
|
|
559
|
+
SLOT_IDS,
|
|
560
|
+
SLOTS,
|
|
561
|
+
} from './write.js';
|
|
562
|
+
export type {
|
|
563
|
+
Answer,
|
|
564
|
+
Answers,
|
|
565
|
+
AssembleOptions,
|
|
566
|
+
DraftMeasurement,
|
|
567
|
+
DraftSection,
|
|
568
|
+
Interview,
|
|
569
|
+
OutputFormat,
|
|
570
|
+
PromptDraft,
|
|
571
|
+
Section,
|
|
572
|
+
Slot,
|
|
573
|
+
} from './write.js';
|
package/src/optimize.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { computeSavings } from './savings.js';
|
|
|
9
9
|
import { join, segment } from './segment.js';
|
|
10
10
|
import { estimateTokens } from './tokenizer.js';
|
|
11
11
|
import type { AsyncTokenCounter } from './tokenizer.js';
|
|
12
|
+
import { RULE_LEVELS } from './types.js';
|
|
12
13
|
import type {
|
|
13
14
|
OptimizationResult,
|
|
14
15
|
OptimizeOptions,
|
|
@@ -92,6 +93,22 @@ function distinctProtected(segments: Segment[]): string[] {
|
|
|
92
93
|
*/
|
|
93
94
|
export function optimize(prompt: string, options: OptimizeOptions = {}): OptimizationResult {
|
|
94
95
|
const level = options.level ?? 'safe';
|
|
96
|
+
/*
|
|
97
|
+
An unknown level used to run `safe` and say nothing.
|
|
98
|
+
|
|
99
|
+
The rule loop skips aggressive rules unless the level *is* `aggressive`, so
|
|
100
|
+
every other string — a typo, a plausible-sounding `balanced` — silently
|
|
101
|
+
produced safe-level results. The CLI has always refused `--level balanced`
|
|
102
|
+
by name; a library caller got the quiet downgrade instead, and a report
|
|
103
|
+
that says `safe` work was done at the level somebody asked for is the
|
|
104
|
+
swallowed-flag defect one layer down. Nothing correct depends on the old
|
|
105
|
+
behaviour: no program means `safe` by writing something else.
|
|
106
|
+
*/
|
|
107
|
+
if (!RULE_LEVELS.includes(level)) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`level must be one of ${RULE_LEVELS.join(', ')} (received: ${JSON.stringify(level)})`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
95
112
|
const locale: Locale = options.locale ?? DEFAULT_LOCALE;
|
|
96
113
|
const t = getMessages(locale);
|
|
97
114
|
const disabled = new Set<RuleId>(options.disableRules ?? []);
|
package/src/types.ts
CHANGED
|
@@ -5,7 +5,17 @@ import type { Locale, RuleId } from './i18n/types.js';
|
|
|
5
5
|
import type { PricingCatalogue } from './pricing.js';
|
|
6
6
|
|
|
7
7
|
/** How aggressive the deterministic rules are allowed to be. */
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The levels an optimisation runs at, from the one place that defines them.
|
|
10
|
+
*
|
|
11
|
+
* Exported as a value, not only as a type. `@trazum/core` had the union and no
|
|
12
|
+
* list, so the valid set was not discoverable from the package and a caller
|
|
13
|
+
* reaching for a plausible third name — `balanced` — got no answer from the
|
|
14
|
+
* types at runtime and no error either.
|
|
15
|
+
*/
|
|
16
|
+
export const RULE_LEVELS = ['safe', 'aggressive'] as const;
|
|
17
|
+
|
|
18
|
+
export type RuleLevel = (typeof RULE_LEVELS)[number];
|
|
9
19
|
|
|
10
20
|
/**
|
|
11
21
|
* A deterministic rule applied to the mutable text of a prompt.
|