@trazum/core 1.8.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/LICENSE +21 -0
- package/README.md +163 -0
- package/dist/advisories.d.ts +23 -0
- package/dist/advisories.d.ts.map +1 -0
- package/dist/advisories.js +376 -0
- package/dist/advisories.js.map +1 -0
- package/dist/aws-sigv4.d.ts +88 -0
- package/dist/aws-sigv4.d.ts.map +1 -0
- package/dist/aws-sigv4.js +117 -0
- package/dist/aws-sigv4.js.map +1 -0
- package/dist/baseline.d.ts +171 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +273 -0
- package/dist/baseline.js.map +1 -0
- package/dist/cache.d.ts +26 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +28 -0
- package/dist/cache.js.map +1 -0
- package/dist/changes.d.ts +29 -0
- package/dist/changes.d.ts.map +1 -0
- package/dist/changes.js +142 -0
- package/dist/changes.js.map +1 -0
- package/dist/compare.d.ts +65 -0
- package/dist/compare.d.ts.map +1 -0
- package/dist/compare.js +58 -0
- package/dist/compare.js.map +1 -0
- package/dist/config-schema.d.ts +118 -0
- package/dist/config-schema.d.ts.map +1 -0
- package/dist/config-schema.js +315 -0
- package/dist/config-schema.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +112 -0
- package/dist/config.js.map +1 -0
- package/dist/detect.d.ts +70 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +228 -0
- package/dist/detect.js.map +1 -0
- package/dist/evaluate.d.ts +98 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +110 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/extract.d.ts +81 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +280 -0
- package/dist/extract.js.map +1 -0
- package/dist/gcp-auth.d.ts +58 -0
- package/dist/gcp-auth.d.ts.map +1 -0
- package/dist/gcp-auth.js +113 -0
- package/dist/gcp-auth.js.map +1 -0
- package/dist/glob.d.ts +49 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +154 -0
- package/dist/glob.js.map +1 -0
- package/dist/host.d.ts +30 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +69 -0
- package/dist/host.js.map +1 -0
- package/dist/i18n/en.d.ts +4 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +168 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/es.d.ts +4 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +168 -0
- package/dist/i18n/es.js.map +1 -0
- package/dist/i18n/index.d.ts +36 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +50 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/types.d.ts +180 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +11 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/llm.d.ts +226 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +485 -0
- package/dist/llm.js.map +1 -0
- package/dist/nearest.d.ts +20 -0
- package/dist/nearest.d.ts.map +1 -0
- package/dist/nearest.js +54 -0
- package/dist/nearest.js.map +1 -0
- package/dist/net.d.ts +90 -0
- package/dist/net.d.ts.map +1 -0
- package/dist/net.js +203 -0
- package/dist/net.js.map +1 -0
- package/dist/node.d.ts +32 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +33 -0
- package/dist/node.js.map +1 -0
- package/dist/openrouter.d.ts +25 -0
- package/dist/openrouter.d.ts.map +1 -0
- package/dist/openrouter.js +72 -0
- package/dist/openrouter.js.map +1 -0
- package/dist/optimize.d.ts +38 -0
- package/dist/optimize.d.ts.map +1 -0
- package/dist/optimize.js +183 -0
- package/dist/optimize.js.map +1 -0
- package/dist/otlp.d.ts +91 -0
- package/dist/otlp.d.ts.map +1 -0
- package/dist/otlp.js +102 -0
- package/dist/otlp.js.map +1 -0
- package/dist/phrases.d.ts +169 -0
- package/dist/phrases.d.ts.map +1 -0
- package/dist/phrases.js +939 -0
- package/dist/phrases.js.map +1 -0
- package/dist/pricing-overlay.d.ts +55 -0
- package/dist/pricing-overlay.d.ts.map +1 -0
- package/dist/pricing-overlay.js +241 -0
- package/dist/pricing-overlay.js.map +1 -0
- package/dist/pricing.d.ts +115 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +400 -0
- package/dist/pricing.js.map +1 -0
- package/dist/profile.d.ts +71 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +55 -0
- package/dist/profile.js.map +1 -0
- package/dist/promptfoo.d.ts +58 -0
- package/dist/promptfoo.d.ts.map +1 -0
- package/dist/promptfoo.js +149 -0
- package/dist/promptfoo.js.map +1 -0
- package/dist/prune.d.ts +91 -0
- package/dist/prune.d.ts.map +1 -0
- package/dist/prune.js +110 -0
- package/dist/prune.js.map +1 -0
- package/dist/reorder.d.ts +82 -0
- package/dist/reorder.d.ts.map +1 -0
- package/dist/reorder.js +215 -0
- package/dist/reorder.js.map +1 -0
- package/dist/review.d.ts +54 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +131 -0
- package/dist/review.js.map +1 -0
- package/dist/rules.d.ts +5 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +279 -0
- package/dist/rules.js.map +1 -0
- package/dist/savings.d.ts +36 -0
- package/dist/savings.d.ts.map +1 -0
- package/dist/savings.js +83 -0
- package/dist/savings.js.map +1 -0
- package/dist/segment.d.ts +8 -0
- package/dist/segment.d.ts.map +1 -0
- package/dist/segment.js +74 -0
- package/dist/segment.js.map +1 -0
- package/dist/shared-prefix.d.ts +63 -0
- package/dist/shared-prefix.d.ts.map +1 -0
- package/dist/shared-prefix.js +151 -0
- package/dist/shared-prefix.js.map +1 -0
- package/dist/similarity.d.ts +13 -0
- package/dist/similarity.d.ts.map +1 -0
- package/dist/similarity.js +30 -0
- package/dist/similarity.js.map +1 -0
- package/dist/structure.d.ts +144 -0
- package/dist/structure.d.ts.map +1 -0
- package/dist/structure.js +455 -0
- package/dist/structure.js.map +1 -0
- package/dist/suggest.d.ts +100 -0
- package/dist/suggest.d.ts.map +1 -0
- package/dist/suggest.js +151 -0
- package/dist/suggest.js.map +1 -0
- package/dist/tokenizer.d.ts +57 -0
- package/dist/tokenizer.d.ts.map +1 -0
- package/dist/tokenizer.js +157 -0
- package/dist/tokenizer.js.map +1 -0
- package/dist/types.d.ts +296 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/walk.d.ts +40 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +95 -0
- package/dist/walk.js.map +1 -0
- package/package.json +56 -0
- package/src/advisories.ts +431 -0
- package/src/aws-sigv4.ts +174 -0
- package/src/baseline.ts +390 -0
- package/src/cache.ts +54 -0
- package/src/changes.ts +158 -0
- package/src/compare.ts +131 -0
- package/src/config-schema.ts +451 -0
- package/src/config.ts +161 -0
- package/src/detect.ts +312 -0
- package/src/evaluate.ts +188 -0
- package/src/extract.ts +336 -0
- package/src/gcp-auth.ts +166 -0
- package/src/glob.ts +160 -0
- package/src/host.ts +90 -0
- package/src/i18n/en.ts +236 -0
- package/src/i18n/es.ts +236 -0
- package/src/i18n/index.ts +68 -0
- package/src/i18n/types.ts +230 -0
- package/src/index.ts +228 -0
- package/src/llm.ts +708 -0
- package/src/nearest.ts +61 -0
- package/src/net.ts +233 -0
- package/src/node.ts +63 -0
- package/src/openrouter.ts +125 -0
- package/src/optimize.ts +228 -0
- package/src/otlp.ts +179 -0
- package/src/phrases.ts +1047 -0
- package/src/pricing-overlay.ts +319 -0
- package/src/pricing.ts +468 -0
- package/src/profile.ts +124 -0
- package/src/promptfoo.ts +213 -0
- package/src/prune.ts +211 -0
- package/src/reorder.ts +307 -0
- package/src/review.ts +180 -0
- package/src/rules.ts +324 -0
- package/src/savings.ts +121 -0
- package/src/segment.ts +106 -0
- package/src/shared-prefix.ts +198 -0
- package/src/similarity.ts +28 -0
- package/src/structure.ts +652 -0
- package/src/suggest.ts +254 -0
- package/src/tokenizer.ts +190 -0
- package/src/types.ts +323 -0
- package/src/walk.ts +117 -0
package/dist/optimize.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { buildAdvisories } from './advisories.js';
|
|
2
|
+
import { extractChanges } from './changes.js';
|
|
3
|
+
import { DEFAULT_LOCALE, getMessages } from './i18n/index.js';
|
|
4
|
+
import { BUNDLED_CATALOGUE, DEFAULT_MODEL } from './pricing.js';
|
|
5
|
+
import { RULES } from './rules.js';
|
|
6
|
+
import { computeSavings } from './savings.js';
|
|
7
|
+
import { join, segment } from './segment.js';
|
|
8
|
+
import { estimateTokens } from './tokenizer.js';
|
|
9
|
+
const MASK_OPEN = '\uE000';
|
|
10
|
+
const MASK_CLOSE = '\uE001';
|
|
11
|
+
const MASK_BASE = 0xe100;
|
|
12
|
+
/** Private-use-area slots available for masks. */
|
|
13
|
+
const MASK_CAPACITY = 0xf8ff - MASK_BASE;
|
|
14
|
+
export const DEFAULT_USAGE = {
|
|
15
|
+
model: DEFAULT_MODEL,
|
|
16
|
+
callsPerMonth: 1000,
|
|
17
|
+
avgOutputTokens: 500,
|
|
18
|
+
cacheHitRate: 0.9,
|
|
19
|
+
batchEligible: false,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Replaces every protected segment with a 3-character marker from the Unicode
|
|
23
|
+
* private use area. The rules work on the masked text, so none of them can
|
|
24
|
+
* touch code, URLs or template placeholders.
|
|
25
|
+
*/
|
|
26
|
+
function mask(segments) {
|
|
27
|
+
const vault = [];
|
|
28
|
+
let text = '';
|
|
29
|
+
for (const seg of segments) {
|
|
30
|
+
if (seg.kind === 'mutable') {
|
|
31
|
+
text += seg.text;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (vault.length >= MASK_CAPACITY) {
|
|
35
|
+
// A prompt with thousands of protected blocks is left as-is rather than
|
|
36
|
+
// risking a mask collision.
|
|
37
|
+
text += seg.text;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const slot = String.fromCharCode(MASK_BASE + vault.length);
|
|
41
|
+
vault.push(seg.text);
|
|
42
|
+
text += MASK_OPEN + slot + MASK_CLOSE;
|
|
43
|
+
}
|
|
44
|
+
return { text, vault };
|
|
45
|
+
}
|
|
46
|
+
/** Puts the protected content back. */
|
|
47
|
+
function unmask(text, vault) {
|
|
48
|
+
return text.replace(new RegExp(`${MASK_OPEN}([\\s\\S])${MASK_CLOSE}`, 'g'), (whole, slot) => {
|
|
49
|
+
const index = slot.charCodeAt(0) - MASK_BASE;
|
|
50
|
+
const original = vault[index];
|
|
51
|
+
return original === undefined ? whole : original;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Distinct protected texts that must still be present after optimising. */
|
|
55
|
+
function distinctProtected(segments) {
|
|
56
|
+
return [...new Set(segments.filter((s) => s.kind === 'protected').map((s) => s.text))];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Optimises a prompt by applying deterministic rules.
|
|
60
|
+
*
|
|
61
|
+
* The result is reproducible: the same input always yields the same output and
|
|
62
|
+
* running it costs nothing. The optional LLM pass lives separately, in
|
|
63
|
+
* `refineWithLlm`.
|
|
64
|
+
*/
|
|
65
|
+
export function optimize(prompt, options = {}) {
|
|
66
|
+
const level = options.level ?? 'safe';
|
|
67
|
+
const locale = options.locale ?? DEFAULT_LOCALE;
|
|
68
|
+
const t = getMessages(locale);
|
|
69
|
+
const disabled = new Set(options.disableRules ?? []);
|
|
70
|
+
const count = options.tokenCounter ?? estimateTokens;
|
|
71
|
+
const segments = segment(prompt);
|
|
72
|
+
const mustSurvive = distinctProtected(segments);
|
|
73
|
+
const { text: maskedOriginal, vault } = mask(segments);
|
|
74
|
+
let current = maskedOriginal;
|
|
75
|
+
const ruleResults = [];
|
|
76
|
+
for (const rule of RULES) {
|
|
77
|
+
if (disabled.has(rule.id))
|
|
78
|
+
continue;
|
|
79
|
+
if (rule.level === 'aggressive' && level !== 'aggressive')
|
|
80
|
+
continue;
|
|
81
|
+
const currentUnmasked = unmask(current, vault);
|
|
82
|
+
const before = count(currentUnmasked);
|
|
83
|
+
const { text: candidate, hits } = rule.apply(current);
|
|
84
|
+
if (hits === 0 || candidate === current)
|
|
85
|
+
continue;
|
|
86
|
+
const candidateUnmasked = unmask(candidate, vault);
|
|
87
|
+
// Safety net: if a rule made protected content disappear, that rule is
|
|
88
|
+
// dropped rather than returning a broken prompt.
|
|
89
|
+
const lostProtected = mustSurvive.some((text) => !candidateUnmasked.includes(text));
|
|
90
|
+
if (lostProtected)
|
|
91
|
+
continue;
|
|
92
|
+
const after = count(candidateUnmasked);
|
|
93
|
+
// Captured against the unmasked text on both sides, so the snippets read
|
|
94
|
+
// as the author wrote them rather than showing private-use markers.
|
|
95
|
+
const changes = extractChanges(currentUnmasked, candidateUnmasked);
|
|
96
|
+
current = candidate;
|
|
97
|
+
const copy = t.rules[rule.id];
|
|
98
|
+
ruleResults.push({
|
|
99
|
+
id: rule.id,
|
|
100
|
+
title: copy.title,
|
|
101
|
+
rationale: copy.rationale,
|
|
102
|
+
level: rule.level,
|
|
103
|
+
hits,
|
|
104
|
+
tokensSaved: Math.max(0, before - after),
|
|
105
|
+
changes,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const optimized = unmask(current, vault).trim();
|
|
109
|
+
const tokensBefore = count(prompt);
|
|
110
|
+
const tokensAfter = count(optimized);
|
|
111
|
+
const usage = { ...DEFAULT_USAGE, ...options.usage };
|
|
112
|
+
const pricing = options.pricing ?? BUNDLED_CATALOGUE;
|
|
113
|
+
const savings = computeSavings(tokensBefore, tokensAfter, usage, new Date(), pricing);
|
|
114
|
+
const advisories = buildAdvisories(optimized, tokensAfter, usage, { count, locale, pricing });
|
|
115
|
+
return {
|
|
116
|
+
original: prompt,
|
|
117
|
+
optimized,
|
|
118
|
+
tokensBefore,
|
|
119
|
+
tokensAfter,
|
|
120
|
+
tokensSaved: tokensBefore - tokensAfter,
|
|
121
|
+
reductionPct: tokensBefore > 0 ? ((tokensBefore - tokensAfter) / tokensBefore) * 100 : 0,
|
|
122
|
+
rules: ruleResults,
|
|
123
|
+
advisories,
|
|
124
|
+
savings,
|
|
125
|
+
usage,
|
|
126
|
+
locale,
|
|
127
|
+
tokenSource: options.tokenCounter ? 'external' : 'heuristic',
|
|
128
|
+
pricingSource: {
|
|
129
|
+
lastReviewed: pricing.lastReviewed,
|
|
130
|
+
overriddenModels: pricing.overriddenModels,
|
|
131
|
+
addedModels: pricing.addedModels,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Recomputes the report with an exact token counter (e.g. the official
|
|
137
|
+
* token-counting endpoint).
|
|
138
|
+
*
|
|
139
|
+
* Only the headline numbers and the saving are recomputed. Per-rule token
|
|
140
|
+
* attribution stays on the heuristic estimate: asking a remote counter once
|
|
141
|
+
* per rule would multiply the calls without changing any decision.
|
|
142
|
+
*/
|
|
143
|
+
export async function withExactTokenCounts(result, counter, pricing = BUNDLED_CATALOGUE) {
|
|
144
|
+
// A result priced against an overlay cannot be recomputed against the bundled
|
|
145
|
+
// catalogue: the token counts would come from one source and the money from
|
|
146
|
+
// another, and the report would disagree with itself with nothing to show why.
|
|
147
|
+
// Throwing is the only honest option — silently reverting to bundled prices is
|
|
148
|
+
// the exact failure the overlay exists to prevent.
|
|
149
|
+
const overlaid = result.pricingSource.overriddenModels.length > 0 ||
|
|
150
|
+
result.pricingSource.addedModels.length > 0;
|
|
151
|
+
if (overlaid && pricing === BUNDLED_CATALOGUE) {
|
|
152
|
+
throw new Error('This result was priced against a pricing overlay, so withExactTokenCounts ' +
|
|
153
|
+
'needs the same catalogue passed as its third argument. Recomputing against ' +
|
|
154
|
+
'the bundled prices would make the token counts and the costs disagree.');
|
|
155
|
+
}
|
|
156
|
+
const [tokensBefore, tokensAfter] = await Promise.all([
|
|
157
|
+
counter(result.original),
|
|
158
|
+
counter(result.optimized),
|
|
159
|
+
]);
|
|
160
|
+
const savings = computeSavings(tokensBefore, tokensAfter, result.usage, new Date(), pricing);
|
|
161
|
+
const advisories = buildAdvisories(result.optimized, tokensAfter, result.usage, {
|
|
162
|
+
locale: result.locale,
|
|
163
|
+
pricing,
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
...result,
|
|
167
|
+
tokensBefore,
|
|
168
|
+
tokensAfter,
|
|
169
|
+
tokensSaved: tokensBefore - tokensAfter,
|
|
170
|
+
reductionPct: tokensBefore > 0 ? ((tokensBefore - tokensAfter) / tokensBefore) * 100 : 0,
|
|
171
|
+
savings,
|
|
172
|
+
advisories,
|
|
173
|
+
tokenSource: 'external',
|
|
174
|
+
pricingSource: {
|
|
175
|
+
lastReviewed: pricing.lastReviewed,
|
|
176
|
+
overriddenModels: pricing.overriddenModels,
|
|
177
|
+
addedModels: pricing.addedModels,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** Re-exports of internals useful to anyone extending the library. */
|
|
182
|
+
export { join, mask as maskProtected, segment, unmask as unmaskProtected };
|
|
183
|
+
//# sourceMappingURL=optimize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optimize.js","sourceRoot":"","sources":["../src/optimize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9D,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAWhD,MAAM,SAAS,GAAG,QAAQ,CAAC;AAC3B,MAAM,UAAU,GAAG,QAAQ,CAAC;AAC5B,MAAM,SAAS,GAAG,MAAM,CAAC;AACzB,kDAAkD;AAClD,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,CAAC;AAEzC,MAAM,CAAC,MAAM,aAAa,GAAiB;IACzC,KAAK,EAAE,aAAa;IACpB,aAAa,EAAE,IAAI;IACnB,eAAe,EAAE,GAAG;IACpB,YAAY,EAAE,GAAG;IACjB,aAAa,EAAE,KAAK;CACrB,CAAC;AAQF;;;;GAIG;AACH,SAAS,IAAI,CAAC,QAAmB;IAC/B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAI,GAAG,EAAE,CAAC;IAEd,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC3B,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC;YACjB,SAAS;QACX,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC;YAClC,wEAAwE;YACxE,4BAA4B;YAC5B,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC;YACjB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3D,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrB,IAAI,IAAI,SAAS,GAAG,IAAI,GAAG,UAAU,CAAC;IACxC,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACzB,CAAC;AAED,uCAAuC;AACvC,SAAS,MAAM,CAAC,IAAY,EAAE,KAAe;IAC3C,OAAO,IAAI,CAAC,OAAO,CACjB,IAAI,MAAM,CAAC,GAAG,SAAS,aAAa,UAAU,EAAE,EAAE,GAAG,CAAC,EACtD,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;QAC7C,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAC9B,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;IACnD,CAAC,CACF,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,SAAS,iBAAiB,CAAC,QAAmB;IAC5C,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACzF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,OAAO,GAAoB,EAAE;IACpE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAAC;IACtC,MAAM,MAAM,GAAW,OAAO,CAAC,MAAM,IAAI,cAAc,CAAC;IACxD,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAiB,OAAO,CAAC,YAAY,IAAI,cAAc,CAAC;IAEnE,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,WAAW,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEvD,IAAI,OAAO,GAAG,cAAc,CAAC;IAC7B,MAAM,WAAW,GAAiB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,SAAS;QACpC,IAAI,IAAI,CAAC,KAAK,KAAK,YAAY,IAAI,KAAK,KAAK,YAAY;YAAE,SAAS;QAEpE,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,KAAK,CAAC,eAAe,CAAC,CAAC;QACtC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtD,IAAI,IAAI,KAAK,CAAC,IAAI,SAAS,KAAK,OAAO;YAAE,SAAS;QAElD,MAAM,iBAAiB,GAAG,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAEnD,uEAAuE;QACvE,iDAAiD;QACjD,MAAM,aAAa,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACpF,IAAI,aAAa;YAAE,SAAS;QAE5B,MAAM,KAAK,GAAG,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACvC,yEAAyE;QACzE,oEAAoE;QACpE,MAAM,OAAO,GAAG,cAAc,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAC;QACnE,OAAO,GAAG,SAAS,CAAC;QACpB,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9B,WAAW,CAAC,IAAI,CAAC;YACf,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,IAAI;YACJ,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;YACxC,OAAO;SACR,CAAC,CAAC;IACL,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IAErC,MAAM,KAAK,GAAiB,EAAE,GAAG,aAAa,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IACnE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,iBAAiB,CAAC;IACrD,MAAM,OAAO,GAAG,cAAc,CAAC,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IACtF,MAAM,UAAU,GAAG,eAAe,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAE9F,OAAO;QACL,QAAQ,EAAE,MAAM;QAChB,SAAS;QACT,YAAY;QACZ,WAAW;QACX,WAAW,EAAE,YAAY,GAAG,WAAW;QACvC,YAAY,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,WAAW,CAAC,GAAG,YAAY,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACxF,KAAK,EAAE,WAAW;QAClB,UAAU;QACV,OAAO;QACP,KAAK;QACL,MAAM;QACN,WAAW,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW;QAC5D,aAAa,EAAE;YACb,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,WAAW,EAAE,OAAO,CAAC,WAAW;SACjC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAA0B,EAC1B,OAA0B,EAC1B,OAAO,GAAqB,iBAAiB;IAE7C,8EAA8E;IAC9E,4EAA4E;IAC5E,+EAA+E;IAC/E,+EAA+E;IAC/E,mDAAmD;IACnD,MAAM,QAAQ,GACZ,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC;QAChD,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9C,IAAI,QAAQ,IAAI,OAAO,KAAK,iBAAiB,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CACb,4EAA4E;YAC1E,6EAA6E;YAC7E,wEAAwE,CAC3E,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,YAAY,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACpD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;KAC1B,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,cAAc,CAAC,YAAY,EAAE,WAAW,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IAC7F,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,KAAK,EAAE;QAC9E,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO;KACR,CAAC,CAAC;IAEH,OAAO;QACL,GAAG,MAAM;QACT,YAAY;QACZ,WAAW;QACX,WAAW,EAAE,YAAY,GAAG,WAAW;QACvC,YAAY,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,WAAW,CAAC,GAAG,YAAY,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACxF,OAAO;QACP,UAAU;QACV,WAAW,EAAE,UAAU;QACvB,aAAa,EAAE;YACb,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,WAAW,EAAE,OAAO,CAAC,WAAW;SACjC;KACF,CAAC;AACJ,CAAC;AAED,sEAAsE;AACtE,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,aAAa,EAAE,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,CAAC"}
|
package/dist/otlp.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt cost as OpenTelemetry metrics, in the OTLP/HTTP JSON encoding.
|
|
3
|
+
*
|
|
4
|
+
* **Trazum writes the payload; it does not send it.** That is a decision, not a
|
|
5
|
+
* missing feature. Pushing to a collector would mean holding an endpoint and a
|
|
6
|
+
* credential, and this repository has twice shipped an SSRF where a URL reached
|
|
7
|
+
* `fetch` without being the URL that was checked. A command that writes a file
|
|
8
|
+
* has no such failure mode, and `curl --data-binary @metrics.json $OTLP_ENDPOINT`
|
|
9
|
+
* is one line in the pipeline that already has the credential.
|
|
10
|
+
*
|
|
11
|
+
* No dependency on `@opentelemetry/*` either — the JSON encoding is a stable,
|
|
12
|
+
* documented wire format, and this package has no runtime dependencies by design.
|
|
13
|
+
*
|
|
14
|
+
* The two encoding rules that are easy to get wrong and silently produce a
|
|
15
|
+
* payload a collector drops:
|
|
16
|
+
*
|
|
17
|
+
* - **64-bit integers are strings.** `timeUnixNano` and `asInt` are `int64` in
|
|
18
|
+
* the protobuf, and JSON numbers cannot hold one exactly, so the JSON mapping
|
|
19
|
+
* specifies strings. A collector reading `1786000000000000000` as a double
|
|
20
|
+
* loses the last digits of every timestamp.
|
|
21
|
+
* - **A gauge is `asDouble` or `asInt`, never both**, and a money figure is a
|
|
22
|
+
* double. Sending `asInt` for `$41.20` silently reports `41`.
|
|
23
|
+
*/
|
|
24
|
+
/** Attributes as OTLP encodes them: a list of key/value, not an object. */
|
|
25
|
+
export interface OtlpAttribute {
|
|
26
|
+
key: string;
|
|
27
|
+
value: {
|
|
28
|
+
stringValue: string;
|
|
29
|
+
} | {
|
|
30
|
+
intValue: string;
|
|
31
|
+
} | {
|
|
32
|
+
doubleValue: number;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export interface OtlpDataPoint {
|
|
36
|
+
attributes: OtlpAttribute[];
|
|
37
|
+
timeUnixNano: string;
|
|
38
|
+
asInt?: string;
|
|
39
|
+
asDouble?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface OtlpMetric {
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
unit: string;
|
|
45
|
+
gauge: {
|
|
46
|
+
dataPoints: OtlpDataPoint[];
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export interface OtlpPayload {
|
|
50
|
+
resourceMetrics: [
|
|
51
|
+
{
|
|
52
|
+
resource: {
|
|
53
|
+
attributes: OtlpAttribute[];
|
|
54
|
+
};
|
|
55
|
+
scopeMetrics: [{
|
|
56
|
+
scope: {
|
|
57
|
+
name: string;
|
|
58
|
+
version: string;
|
|
59
|
+
};
|
|
60
|
+
metrics: OtlpMetric[];
|
|
61
|
+
}];
|
|
62
|
+
}
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
/** What `doctor` knows, in the shape this module needs. */
|
|
66
|
+
export interface OtlpInput {
|
|
67
|
+
/** Every prompt surveyed, with the count a budget would be compared against. */
|
|
68
|
+
prompts: readonly {
|
|
69
|
+
path: string;
|
|
70
|
+
tokens: number;
|
|
71
|
+
overBudget: boolean;
|
|
72
|
+
budgeted: boolean;
|
|
73
|
+
}[];
|
|
74
|
+
/** Advisories rolled up by id, as the survey reports them. */
|
|
75
|
+
findings: readonly {
|
|
76
|
+
id: string;
|
|
77
|
+
prompts: number;
|
|
78
|
+
monthlyUsd: number | null;
|
|
79
|
+
}[];
|
|
80
|
+
model: string;
|
|
81
|
+
callsPerMonth: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* `atMillis` is a parameter rather than a call to `Date.now()`.
|
|
85
|
+
*
|
|
86
|
+
* The same reason `computeSavings` takes a `Date`: a function that reads the
|
|
87
|
+
* clock cannot be asserted against a fixed expectation, and every test of it
|
|
88
|
+
* ends up asserting only its shape. Here the timestamp *is* half the payload.
|
|
89
|
+
*/
|
|
90
|
+
export declare function toOtlpMetrics(input: OtlpInput, atMillis: number): OtlpPayload;
|
|
91
|
+
//# sourceMappingURL=otlp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otlp.d.ts","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CACjF;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QAAE,UAAU,EAAE,aAAa,EAAE,CAAA;KAAE,CAAC;CACxC;AAED,MAAM,WAAW,WAAW;IAC1B,eAAe,EAAE;QACf;YACE,QAAQ,EAAE;gBAAE,UAAU,EAAE,aAAa,EAAE,CAAA;aAAE,CAAC;YAC1C,YAAY,EAAE,CAAC;gBAAE,KAAK,EAAE;oBAAE,IAAI,EAAE,MAAM,CAAC;oBAAC,OAAO,EAAE,MAAM,CAAA;iBAAE,CAAC;gBAAC,OAAO,EAAE,UAAU,EAAE,CAAA;aAAE,CAAC,CAAC;SACrF;KACF,CAAC;CACH;AAED,2DAA2D;AAC3D,MAAM,WAAW,SAAS;IACxB,gFAAgF;IAChF,OAAO,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IAC7F,8DAA8D;IAC9D,QAAQ,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;AASD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAqG7E"}
|
package/dist/otlp.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt cost as OpenTelemetry metrics, in the OTLP/HTTP JSON encoding.
|
|
3
|
+
*
|
|
4
|
+
* **Trazum writes the payload; it does not send it.** That is a decision, not a
|
|
5
|
+
* missing feature. Pushing to a collector would mean holding an endpoint and a
|
|
6
|
+
* credential, and this repository has twice shipped an SSRF where a URL reached
|
|
7
|
+
* `fetch` without being the URL that was checked. A command that writes a file
|
|
8
|
+
* has no such failure mode, and `curl --data-binary @metrics.json $OTLP_ENDPOINT`
|
|
9
|
+
* is one line in the pipeline that already has the credential.
|
|
10
|
+
*
|
|
11
|
+
* No dependency on `@opentelemetry/*` either — the JSON encoding is a stable,
|
|
12
|
+
* documented wire format, and this package has no runtime dependencies by design.
|
|
13
|
+
*
|
|
14
|
+
* The two encoding rules that are easy to get wrong and silently produce a
|
|
15
|
+
* payload a collector drops:
|
|
16
|
+
*
|
|
17
|
+
* - **64-bit integers are strings.** `timeUnixNano` and `asInt` are `int64` in
|
|
18
|
+
* the protobuf, and JSON numbers cannot hold one exactly, so the JSON mapping
|
|
19
|
+
* specifies strings. A collector reading `1786000000000000000` as a double
|
|
20
|
+
* loses the last digits of every timestamp.
|
|
21
|
+
* - **A gauge is `asDouble` or `asInt`, never both**, and a money figure is a
|
|
22
|
+
* double. Sending `asInt` for `$41.20` silently reports `41`.
|
|
23
|
+
*/
|
|
24
|
+
const str = (key, value) => ({ key, value: { stringValue: value } });
|
|
25
|
+
/** Rounded to the nearest nanosecond as an integer string, never a float. */
|
|
26
|
+
function nanos(atMillis) {
|
|
27
|
+
return `${Math.round(atMillis)}000000`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `atMillis` is a parameter rather than a call to `Date.now()`.
|
|
31
|
+
*
|
|
32
|
+
* The same reason `computeSavings` takes a `Date`: a function that reads the
|
|
33
|
+
* clock cannot be asserted against a fixed expectation, and every test of it
|
|
34
|
+
* ends up asserting only its shape. Here the timestamp *is* half the payload.
|
|
35
|
+
*/
|
|
36
|
+
export function toOtlpMetrics(input, atMillis) {
|
|
37
|
+
const { prompts, findings, model, callsPerMonth } = input;
|
|
38
|
+
const time = nanos(atMillis);
|
|
39
|
+
const metrics = [];
|
|
40
|
+
const gauge = (name, description, unit, dataPoints) => {
|
|
41
|
+
// An empty metric is not the same as a zero, and a collector charting an
|
|
42
|
+
// empty series draws nothing rather than a flat line at zero. Omitted.
|
|
43
|
+
if (dataPoints.length > 0)
|
|
44
|
+
metrics.push({ name, description, unit, gauge: { dataPoints } });
|
|
45
|
+
};
|
|
46
|
+
gauge('trazum.prompt.tokens', 'Input tokens in a prompt as written, the figure a budget is compared against.', '{token}', prompts.map((p) => ({
|
|
47
|
+
attributes: [str('prompt.path', p.path)],
|
|
48
|
+
timeUnixNano: time,
|
|
49
|
+
asInt: `${Math.round(p.tokens)}`,
|
|
50
|
+
})));
|
|
51
|
+
// Per prompt rather than as one count, so a dashboard can name the offender
|
|
52
|
+
// instead of showing a number that went from 2 to 3.
|
|
53
|
+
gauge('trazum.prompt.over_budget', 'Whether this prompt exceeds the budget that covers it. 1 when it does.', '1', prompts
|
|
54
|
+
.filter((p) => p.budgeted)
|
|
55
|
+
.map((p) => ({
|
|
56
|
+
attributes: [str('prompt.path', p.path)],
|
|
57
|
+
timeUnixNano: time,
|
|
58
|
+
asInt: p.overBudget ? '1' : '0',
|
|
59
|
+
})));
|
|
60
|
+
gauge('trazum.prompts.unbudgeted', 'Prompts no budget pattern matches, so nothing is watching them.', '{prompt}', [
|
|
61
|
+
{
|
|
62
|
+
attributes: [],
|
|
63
|
+
timeUnixNano: time,
|
|
64
|
+
asInt: `${prompts.filter((p) => !p.budgeted).length}`,
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
// Money is a double. Sending this as asInt would report $41.20 as 41 and the
|
|
68
|
+
// chart would look plausible.
|
|
69
|
+
gauge('trazum.advisory.monthly_usd', 'Estimated monthly USD an advisory would recover, summed across the prompts raising it.', 'USD', findings
|
|
70
|
+
.filter((f) => f.monthlyUsd !== null)
|
|
71
|
+
.map((f) => ({
|
|
72
|
+
attributes: [str('advisory.id', f.id)],
|
|
73
|
+
timeUnixNano: time,
|
|
74
|
+
asDouble: f.monthlyUsd,
|
|
75
|
+
})));
|
|
76
|
+
gauge('trazum.advisory.prompts', 'How many prompts raise this advisory.', '{prompt}', findings.map((f) => ({
|
|
77
|
+
attributes: [str('advisory.id', f.id)],
|
|
78
|
+
timeUnixNano: time,
|
|
79
|
+
asInt: `${f.prompts}`,
|
|
80
|
+
})));
|
|
81
|
+
return {
|
|
82
|
+
resourceMetrics: [
|
|
83
|
+
{
|
|
84
|
+
// The model and call volume are resource attributes, not metric labels:
|
|
85
|
+
// every figure above was priced through them, and a dollar amount whose
|
|
86
|
+
// scenario is not recorded beside it is a number nobody can check later.
|
|
87
|
+
resource: {
|
|
88
|
+
attributes: [
|
|
89
|
+
str('service.name', 'trazum'),
|
|
90
|
+
str('trazum.model', model),
|
|
91
|
+
{
|
|
92
|
+
key: 'trazum.calls_per_month',
|
|
93
|
+
value: { intValue: `${Math.round(callsPerMonth)}` },
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
scopeMetrics: [{ scope: { name: 'trazum', version: '1.0.0' }, metrics }],
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=otlp.js.map
|
package/dist/otlp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otlp.js","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phrase dictionaries.
|
|
3
|
+
*
|
|
4
|
+
* These lists are DATA, not user interface: they are the vocabulary Trazum
|
|
5
|
+
* looks for inside the prompts it optimises. Real prompts mix languages, so
|
|
6
|
+
* the dictionaries deliberately cover English and Spanish at the same time and
|
|
7
|
+
* a single prompt can trigger entries from both.
|
|
8
|
+
*
|
|
9
|
+
* Adding a language here means adding entries to these lists — it is unrelated
|
|
10
|
+
* to the report language, which lives in `src/i18n/`.
|
|
11
|
+
*
|
|
12
|
+
* Every entry earns its place by preserving meaning. Anything whose removal
|
|
13
|
+
* could change what the prompt asks for belongs in the `aggressive` level, or
|
|
14
|
+
* does not belong here at all.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Languages the trimming dictionaries below actually cover.
|
|
18
|
+
*
|
|
19
|
+
* Exported because the report has to be able to say so. Until this existed,
|
|
20
|
+
* a French or German prompt came back with `No rule found anything to trim` —
|
|
21
|
+
* which reads as "your prompt is already efficient" and meant "I do not speak
|
|
22
|
+
* your language". Same class of mistake as the one `--reorder` had: the tool
|
|
23
|
+
* knew something it was not telling the reader.
|
|
24
|
+
*
|
|
25
|
+
* `phrases.test.js` asserts this list matches the languages the dictionaries are
|
|
26
|
+
* actually grouped under, so adding a dictionary without listing it — or listing
|
|
27
|
+
* one without the entries — fails rather than misleads.
|
|
28
|
+
*/
|
|
29
|
+
export declare const PHRASE_LANGUAGES: readonly string[];
|
|
30
|
+
/** Long phrases and their shorter equivalent. */
|
|
31
|
+
export declare const VERBOSE_PHRASES: ReadonlyArray<readonly [string, string]>;
|
|
32
|
+
/** Courtesy: adds nothing for the model and costs tokens on every call. */
|
|
33
|
+
export declare const POLITENESS: readonly string[];
|
|
34
|
+
/** Filler and throat-clearing with no content. */
|
|
35
|
+
export declare const FILLER: readonly string[];
|
|
36
|
+
/** Intensifiers: they rarely change the task and almost always add tokens. */
|
|
37
|
+
/**
|
|
38
|
+
* Words that add emphasis and nothing else, so dropping one cannot change what
|
|
39
|
+
* the prompt asks for.
|
|
40
|
+
*
|
|
41
|
+
* **A quantifier is not an intensifier**, and the distinction is not visible in
|
|
42
|
+
* a dictionary translated word by word. Spanish gets `muy` and deliberately not
|
|
43
|
+
* `mucho`; the first draft of the other five languages did not keep that line
|
|
44
|
+
* and shipped `muito`, `molto` and `heel`, each of which does both jobs:
|
|
45
|
+
*
|
|
46
|
+
* Hai molto tempo per rispondere. → Hai tempo per rispondere.
|
|
47
|
+
*
|
|
48
|
+
* "You have much time" became "you have time". Caught by running the five
|
|
49
|
+
* languages through the rules rather than by reading the list, and pinned by a
|
|
50
|
+
* test that keeps those three words out.
|
|
51
|
+
*/
|
|
52
|
+
export declare const INTENSIFIERS: readonly string[];
|
|
53
|
+
/** Hedges that weaken the instruction without adding information. */
|
|
54
|
+
export declare const HEDGES: readonly string[];
|
|
55
|
+
/**
|
|
56
|
+
* Self-verification instructions.
|
|
57
|
+
*
|
|
58
|
+
* On current models these trigger over-verification: the model already checks
|
|
59
|
+
* its own work, and asking explicitly fires extra steps paid for in output
|
|
60
|
+
* tokens. Aggressive level because some workflows do want explicit
|
|
61
|
+
* verification.
|
|
62
|
+
*
|
|
63
|
+
* The full forms come first on purpose: removing only the core phrase would
|
|
64
|
+
* leave a dangling fragment such as "Before answering." — worse than leaving
|
|
65
|
+
* the sentence alone.
|
|
66
|
+
*
|
|
67
|
+
* That includes whatever introduces the instruction. "You MUST double-check
|
|
68
|
+
* your answer before responding" reduced to "You must." for exactly this
|
|
69
|
+
* reason: the phrase matched, the subject and modal in front of it did not,
|
|
70
|
+
* and what survived was a sentence that says nothing. Anything that can open
|
|
71
|
+
* one of these instructions belongs in the list, ahead of the bare form.
|
|
72
|
+
*/
|
|
73
|
+
export declare const SELF_CHECK: readonly string[];
|
|
74
|
+
/** Words shouted in capitals that cost fewer tokens in lowercase. */
|
|
75
|
+
export declare const SHOUTED_WORDS: readonly string[];
|
|
76
|
+
/** Emphasis prefixes at the start of a line. */
|
|
77
|
+
export declare const EMPHASIS_PREFIXES: readonly string[];
|
|
78
|
+
/**
|
|
79
|
+
* Vocabulary suggesting the task needs a more capable model.
|
|
80
|
+
* Multilingual for the same reason as the dictionaries above.
|
|
81
|
+
*/
|
|
82
|
+
export declare const COMPLEX_SIGNALS: readonly string[];
|
|
83
|
+
/** Vocabulary suggesting a cheaper model would do. */
|
|
84
|
+
export declare const SIMPLE_SIGNALS: readonly string[];
|
|
85
|
+
/**
|
|
86
|
+
* Phrases that refer *backwards* to something earlier in the prompt, by language.
|
|
87
|
+
*
|
|
88
|
+
* These are what makes reordering unsafe. "Summarise the text above" is correct
|
|
89
|
+
* where it sits and nonsense if moved in front of the text it points at, so a
|
|
90
|
+
* block containing one of these must stay where it is — and so must everything
|
|
91
|
+
* after it, because moving a later block past a pinned one changes their order
|
|
92
|
+
* relative to each other.
|
|
93
|
+
*
|
|
94
|
+
* Deliberately generous. A false positive costs a saving that was available; a
|
|
95
|
+
* false negative silently changes what the prompt asks for, which is the one
|
|
96
|
+
* thing this project will not trade for tokens.
|
|
97
|
+
*
|
|
98
|
+
* **Grouped by language rather than kept as one flat list, because the flat list
|
|
99
|
+
* hid a hole.** It held English and Spanish and was applied to every prompt, so
|
|
100
|
+
* a French, German or Japanese author ran `--reorder` with no protection at all:
|
|
101
|
+
* every refusal this module is built on silently did not apply to them, and
|
|
102
|
+
* "Résumez le texte ci-dessus" was hoisted above the text and reported as a
|
|
103
|
+
* saving. A list per language makes the coverage a thing you can look at, and
|
|
104
|
+
* lets `UNCOVERED_SCRIPTS` below refuse what is still missing rather than
|
|
105
|
+
* pretending.
|
|
106
|
+
*
|
|
107
|
+
* Every language is matched against every prompt. Language detection would be
|
|
108
|
+
* one more thing to get wrong, and the cost of checking a French prompt for
|
|
109
|
+
* German phrases is a missed saving, which is the direction this module errs in
|
|
110
|
+
* anyway.
|
|
111
|
+
*/
|
|
112
|
+
export interface BackwardReferenceSet {
|
|
113
|
+
/**
|
|
114
|
+
* Whether a match must sit on a word boundary.
|
|
115
|
+
*
|
|
116
|
+
* True for anything written with spaces, so "aboveboard" does not pin a block.
|
|
117
|
+
* False for Japanese and Chinese, which have no word boundaries at all — the
|
|
118
|
+
* boundary test asks whether the neighbouring character is a letter, and in
|
|
119
|
+
* 上記のテキスト it always is, so a boundary-matched CJK phrase would never
|
|
120
|
+
* fire. A list that cannot match is worse than no list: it reads like cover.
|
|
121
|
+
*/
|
|
122
|
+
wordBoundaries: boolean;
|
|
123
|
+
phrases: readonly string[];
|
|
124
|
+
}
|
|
125
|
+
export declare const BACKWARD_REFERENCES_BY_LANGUAGE: Readonly<Record<string, BackwardReferenceSet>>;
|
|
126
|
+
/**
|
|
127
|
+
* Every phrase, flattened. Kept for callers that only need the words.
|
|
128
|
+
*/
|
|
129
|
+
export declare const BACKWARD_REFERENCES: readonly string[];
|
|
130
|
+
/**
|
|
131
|
+
* Scripts this module has no backward-reference phrases for.
|
|
132
|
+
*
|
|
133
|
+
* The point of naming them is that the alternative is what used to happen:
|
|
134
|
+
* rearranging a Russian or Arabic prompt with nothing to stop it, and calling
|
|
135
|
+
* the result a saving. `reorderForCache` refuses when it sees one of these,
|
|
136
|
+
* which turns a silent hazard into a message somebody can act on — and into a
|
|
137
|
+
* short list of pull requests, since adding a language is adding an array.
|
|
138
|
+
*
|
|
139
|
+
* Matched on the script, not the language: the question is not "which language
|
|
140
|
+
* is this" but "is there any chance my phrase lists apply to it".
|
|
141
|
+
*/
|
|
142
|
+
export declare const UNCOVERED_SCRIPTS: ReadonlyArray<{
|
|
143
|
+
name: string;
|
|
144
|
+
pattern: RegExp;
|
|
145
|
+
}>;
|
|
146
|
+
/**
|
|
147
|
+
* Phrases that mark a block as the *output contract* rather than as data.
|
|
148
|
+
*
|
|
149
|
+
* Needed because a fenced JSON block in a prompt is one of two completely
|
|
150
|
+
* different things, and confusing them is the one way this analysis could do
|
|
151
|
+
* harm. `Output format: {...}` describes what the model must return, and a
|
|
152
|
+
* provider that accepts a response schema takes it as a request parameter
|
|
153
|
+
* instead — those tokens leave the prompt on every call. `Input: {...}` inside a
|
|
154
|
+
* few-shot example is *data the prompt needs*, and advising anyone to move it
|
|
155
|
+
* would break their prompt.
|
|
156
|
+
*
|
|
157
|
+
* Nothing here guesses. A schema block with no such phrase before it is left
|
|
158
|
+
* alone, because "probably an output format" is not good enough when being wrong
|
|
159
|
+
* costs somebody a working prompt.
|
|
160
|
+
*
|
|
161
|
+
* Seven languages, the same set the rules cover. A prompt in a language absent
|
|
162
|
+
* here raises nothing, which is a false negative and states itself as one — the
|
|
163
|
+
* alternative is matching an English cue inside Japanese prose and calling the
|
|
164
|
+
* result a saving.
|
|
165
|
+
*/
|
|
166
|
+
export declare const OUTPUT_CUES_BY_LANGUAGE: Readonly<Record<string, readonly string[]>>;
|
|
167
|
+
/** Every output cue, flattened. Order is not significant. */
|
|
168
|
+
export declare const OUTPUT_CUES: readonly string[];
|
|
169
|
+
//# sourceMappingURL=phrases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phrases.d.ts","sourceRoot":"","sources":["../src/phrases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAA+C,CAAC;AAE9F,iDAAiD;AACjD,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CA4IpE,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,UAAU,EAAE,SAAS,MAAM,EA8DvC,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,MAAM,EAAE,SAAS,MAAM,EAiEnC,CAAC;AAEF,8EAA8E;AAC9E;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAkDzC,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,MAAM,EAAE,SAAS,MAAM,EA0CnC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,MAAM,EAqFvC,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAoB1C,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAe9C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAkC5C,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAsB3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;OAQG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED,eAAO,MAAM,+BAA+B,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAiN1F,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAElB,CAAC;AAEhC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAQ9E,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CA+G/E,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,WAAW,EAAE,SAAS,MAAM,EAAkD,CAAC"}
|