@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
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { normalizeForCompare } from './similarity.js';
|
|
2
|
+
import { estimateTokens } from './tokenizer.js';
|
|
3
|
+
/**
|
|
4
|
+
* The waste no single prompt can see.
|
|
5
|
+
*
|
|
6
|
+
* Every other analysis in this package reads one prompt. This one reads a
|
|
7
|
+
* workspace, because there is a kind of caching loss that only exists *between*
|
|
8
|
+
* prompts: forty prompts assembled from the same system preamble, byte-identical
|
|
9
|
+
* except that one has a trailing space, another reordered two bullets, and a
|
|
10
|
+
* third says "You are an assistant" where the rest say "You are an Assistant".
|
|
11
|
+
*
|
|
12
|
+
* Prompt caching is a byte-for-byte prefix match. Those forty prompts therefore
|
|
13
|
+
* occupy forty separate cache entries and share nothing. Unify the preamble and
|
|
14
|
+
* they occupy one. No amount of analysis of any one of those files can find that
|
|
15
|
+
* — each is individually fine — which is what makes this worth a module rather
|
|
16
|
+
* than another advisory.
|
|
17
|
+
*
|
|
18
|
+
* **It reports no dollar figure, and that is a finding rather than a gap.**
|
|
19
|
+
*
|
|
20
|
+
* The saving lives entirely in the cache hit rate: a prompt called ten times a
|
|
21
|
+
* month never warms an entry of its own and always reads a shared one that
|
|
22
|
+
* somebody else keeps hot. But `cacheHitRate` is an *input* to Trazum's cost
|
|
23
|
+
* model, not an output — `advisories.ts` takes it from `--cache-hit-rate` and
|
|
24
|
+
* applies the same value to every prompt. Under that model, splitting one prefix
|
|
25
|
+
* into forty changes nothing, because the model has no term for how many distinct
|
|
26
|
+
* cache entries exist.
|
|
27
|
+
*
|
|
28
|
+
* Pricing this would mean inventing a distribution of calls across the group,
|
|
29
|
+
* which is the one thing here that only the operator knows. So the report gives
|
|
30
|
+
* what it can establish — which prompts, how far the common prefix runs, and what
|
|
31
|
+
* stops it being byte-identical — and refuses the number. Naming a mechanism
|
|
32
|
+
* accurately beats attaching a figure to it that came from nowhere.
|
|
33
|
+
*/
|
|
34
|
+
/** Blocks are blank-line separated, the same unit `reorder.ts` moves. */
|
|
35
|
+
function blocksOf(text) {
|
|
36
|
+
return text.split(/\n[ \t]*\n/);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whitespace collapsed, and nothing else touched.
|
|
40
|
+
*
|
|
41
|
+
* Used to separate the two kinds of drift, because they are different amounts of
|
|
42
|
+
* work to fix. A prefix that differs only here is a formatter away from sharing a
|
|
43
|
+
* cache entry; one that differs in wording needs somebody to decide which wording
|
|
44
|
+
* is right.
|
|
45
|
+
*/
|
|
46
|
+
function collapseWhitespace(text) {
|
|
47
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The prefix length below which unifying buys nothing, and `Infinity` when there
|
|
51
|
+
* is no answer.
|
|
52
|
+
*
|
|
53
|
+
* Lives here rather than in the CLI because it is the gate on this whole report
|
|
54
|
+
* and a gate with no test is decoration. It was a private function in the CLI
|
|
55
|
+
* first; a mutation run deleting the `unknown` branch survived, which is what a
|
|
56
|
+
* function nothing can reach looks like from the outside.
|
|
57
|
+
*
|
|
58
|
+
* `unknown` and a missing minimum both yield `Infinity`, so nothing is reported.
|
|
59
|
+
* That is the direction to fail: telling somebody to unify a preamble across
|
|
60
|
+
* twelve files to enable caching their provider may not offer spends their
|
|
61
|
+
* afternoon, and unlike a wrong number on a report nothing later corrects it.
|
|
62
|
+
*/
|
|
63
|
+
export function cacheableMinimum(model) {
|
|
64
|
+
if (!model)
|
|
65
|
+
return Number.POSITIVE_INFINITY;
|
|
66
|
+
if (model.caching === 'none' || model.caching === 'unknown')
|
|
67
|
+
return Number.POSITIVE_INFINITY;
|
|
68
|
+
return model.cacheMinTokens ?? Number.POSITIVE_INFINITY;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Groups of prompts that could share a cache prefix and do not.
|
|
72
|
+
*
|
|
73
|
+
* Grouped by their **first** block, which is not a shortcut. Caching matches from
|
|
74
|
+
* the beginning of the request, so two prompts whose opening paragraphs differ
|
|
75
|
+
* share nothing no matter how identical the rest is — a group keyed on anything
|
|
76
|
+
* later would name prompts that cannot be made to share a prefix without
|
|
77
|
+
* reordering them, which is a different and far more dangerous change.
|
|
78
|
+
*/
|
|
79
|
+
export function sharedPrefixes(prompts, options = {}) {
|
|
80
|
+
const counter = options.countTokens ?? estimateTokens;
|
|
81
|
+
const minTokens = options.minTokens ?? 0;
|
|
82
|
+
const byOpening = new Map();
|
|
83
|
+
for (const prompt of prompts) {
|
|
84
|
+
const blocks = blocksOf(prompt.text);
|
|
85
|
+
const opening = normalizeForCompare(blocks[0] ?? '');
|
|
86
|
+
// An empty opening is not a match, it is an absence. Grouping on it would
|
|
87
|
+
// put every blank-led prompt in one group and report a prefix of nothing.
|
|
88
|
+
if (opening === '')
|
|
89
|
+
continue;
|
|
90
|
+
const group = byOpening.get(opening);
|
|
91
|
+
if (group)
|
|
92
|
+
group.push(prompt);
|
|
93
|
+
else
|
|
94
|
+
byOpening.set(opening, [prompt]);
|
|
95
|
+
}
|
|
96
|
+
const found = [];
|
|
97
|
+
for (const group of byOpening.values()) {
|
|
98
|
+
if (group.length < 2)
|
|
99
|
+
continue;
|
|
100
|
+
const blockLists = group.map((prompt) => blocksOf(prompt.text));
|
|
101
|
+
const first = blockLists[0];
|
|
102
|
+
if (!first)
|
|
103
|
+
continue;
|
|
104
|
+
// How far the agreement runs, comparing normalised blocks position by
|
|
105
|
+
// position. Stops at the first block any member disagrees on, because a
|
|
106
|
+
// prefix is contiguous by definition.
|
|
107
|
+
let shared = 0;
|
|
108
|
+
for (let index = 0; index < first.length; index++) {
|
|
109
|
+
const target = normalizeForCompare(first[index] ?? '');
|
|
110
|
+
if (target === '')
|
|
111
|
+
break;
|
|
112
|
+
const all = blockLists.every((blocks) => normalizeForCompare(blocks[index] ?? '') === target);
|
|
113
|
+
if (!all)
|
|
114
|
+
break;
|
|
115
|
+
shared = index + 1;
|
|
116
|
+
}
|
|
117
|
+
if (shared === 0)
|
|
118
|
+
continue;
|
|
119
|
+
/**
|
|
120
|
+
* The raw prefixes, joined the way they were written.
|
|
121
|
+
*
|
|
122
|
+
* `\n\n` rather than the original separator: the separator is itself part of
|
|
123
|
+
* what may differ, and reconstructing each prompt's exact bytes is not needed
|
|
124
|
+
* to answer whether they agree.
|
|
125
|
+
*/
|
|
126
|
+
const rawPrefixes = blockLists.map((blocks) => blocks.slice(0, shared).join('\n\n'));
|
|
127
|
+
const sample = rawPrefixes[0] ?? '';
|
|
128
|
+
// Already byte-identical: these prompts share a cache entry today and there
|
|
129
|
+
// is nothing to recover. Reporting them would be noise that teaches people
|
|
130
|
+
// to stop reading this section.
|
|
131
|
+
if (rawPrefixes.every((prefix) => prefix === sample))
|
|
132
|
+
continue;
|
|
133
|
+
const collapsed = rawPrefixes.map(collapseWhitespace);
|
|
134
|
+
const flat = collapseWhitespace(sample);
|
|
135
|
+
const drift = collapsed.every((prefix) => prefix === flat) ? 'whitespace' : 'wording';
|
|
136
|
+
const tokens = counter(sample);
|
|
137
|
+
if (tokens < minTokens)
|
|
138
|
+
continue;
|
|
139
|
+
found.push({
|
|
140
|
+
paths: group.map((prompt) => prompt.path),
|
|
141
|
+
blocks: shared,
|
|
142
|
+
tokens,
|
|
143
|
+
drift,
|
|
144
|
+
sample,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// Largest prefix first: it is the group where unifying recovers most, and the
|
|
148
|
+
// ordering is a total one so the output does not depend on Map iteration.
|
|
149
|
+
return found.sort((a, b) => b.tokens - a.tokens || (a.paths[0] ?? '').localeCompare(b.paths[0] ?? ''));
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=shared-prefix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared-prefix.js","sourceRoot":"","sources":["../src/shared-prefix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,yEAAyE;AACzE,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,kBAAkB,CAAC,IAAY;IACtC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAA8E;IAE9E,IAAI,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC,iBAAiB,CAAC;IAC5C,IAAI,KAAK,CAAC,OAAO,KAAK,MAAM,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,iBAAiB,CAAC;IAC7F,OAAO,KAAK,CAAC,cAAc,IAAI,MAAM,CAAC,iBAAiB,CAAC;AAC1D,CAAC;AAsCD;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAmC,EACnC,OAAO,GAAwB,EAAE;IAEjC,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,IAAI,cAAc,CAAC;IACtD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,CAAC,CAAC;IAEzC,MAAM,SAAS,GAAG,IAAI,GAAG,EAA6B,CAAC;IACvD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACrD,0EAA0E;QAC1E,0EAA0E;QAC1E,IAAI,OAAO,KAAK,EAAE;YAAE,SAAS;QAC7B,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;;YACzB,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,KAAK,GAAmB,EAAE,CAAC;IAEjC,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;QACvC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAE/B,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAChE,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,CAAC,KAAK;YAAE,SAAS;QAErB,sEAAsE;QACtE,wEAAwE;QACxE,sCAAsC;QACtC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;YAClD,MAAM,MAAM,GAAG,mBAAmB,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACvD,IAAI,MAAM,KAAK,EAAE;gBAAE,MAAM;YACzB,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC,CAAC;YAC9F,IAAI,CAAC,GAAG;gBAAE,MAAM;YAChB,MAAM,GAAG,KAAK,GAAG,CAAC,CAAC;QACrB,CAAC;QACD,IAAI,MAAM,KAAK,CAAC;YAAE,SAAS;QAE3B;;;;;;WAMG;QACH,MAAM,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACrF,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAEpC,4EAA4E;QAC5E,2EAA2E;QAC3E,gCAAgC;QAChC,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,MAAM,CAAC;YAAE,SAAS;QAE/D,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACtD,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;QAEtF,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,MAAM,GAAG,SAAS;YAAE,SAAS;QAEjC,KAAK,CAAC,IAAI,CAAC;YACT,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC;YACzC,MAAM,EAAE,MAAM;YACd,MAAM;YACN,KAAK;YACL,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,0EAA0E;IAC1E,OAAO,KAAK,CAAC,IAAI,CACf,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CACpF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-similarity helpers shared by the duplicate rules and the structural
|
|
3
|
+
* analysis.
|
|
4
|
+
*
|
|
5
|
+
* Both need to answer "are these two chunks saying the same thing?", and both
|
|
6
|
+
* need to answer it the same way — a rule that removes a near-duplicate and an
|
|
7
|
+
* advisory that reports one should never disagree about what "near" means.
|
|
8
|
+
*/
|
|
9
|
+
/** Normalises a line or paragraph for duplicate comparison. */
|
|
10
|
+
export declare function normalizeForCompare(text: string): string;
|
|
11
|
+
/** Jaccard similarity over word sets. */
|
|
12
|
+
export declare function jaccard(a: string, b: string): number;
|
|
13
|
+
//# sourceMappingURL=similarity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similarity.d.ts","sourceRoot":"","sources":["../src/similarity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+DAA+D;AAC/D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOxD;AAED,yCAAyC;AACzC,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAOpD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-similarity helpers shared by the duplicate rules and the structural
|
|
3
|
+
* analysis.
|
|
4
|
+
*
|
|
5
|
+
* Both need to answer "are these two chunks saying the same thing?", and both
|
|
6
|
+
* need to answer it the same way — a rule that removes a near-duplicate and an
|
|
7
|
+
* advisory that reports one should never disagree about what "near" means.
|
|
8
|
+
*/
|
|
9
|
+
/** Normalises a line or paragraph for duplicate comparison. */
|
|
10
|
+
export function normalizeForCompare(text) {
|
|
11
|
+
return text
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.normalize('NFD')
|
|
14
|
+
.replace(/\p{Diacritic}/gu, '')
|
|
15
|
+
.replace(/[^\p{L}\p{N}]+/gu, ' ')
|
|
16
|
+
.trim();
|
|
17
|
+
}
|
|
18
|
+
/** Jaccard similarity over word sets. */
|
|
19
|
+
export function jaccard(a, b) {
|
|
20
|
+
const setA = new Set(a.split(' ').filter(Boolean));
|
|
21
|
+
const setB = new Set(b.split(' ').filter(Boolean));
|
|
22
|
+
if (setA.size === 0 || setB.size === 0)
|
|
23
|
+
return 0;
|
|
24
|
+
let intersection = 0;
|
|
25
|
+
for (const word of setA)
|
|
26
|
+
if (setB.has(word))
|
|
27
|
+
intersection++;
|
|
28
|
+
return intersection / (setA.size + setB.size - intersection);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=similarity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similarity.js","sourceRoot":"","sources":["../src/similarity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+DAA+D;AAC/D,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI;SACR,WAAW,EAAE;SACb,SAAS,CAAC,KAAK,CAAC;SAChB,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;SAC9B,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC;SAChC,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,OAAO,CAAC,CAAS,EAAE,CAAS;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACjD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,IAAI,IAAI,IAAI;QAAE,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,YAAY,EAAE,CAAC;IAC5D,OAAO,YAAY,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { ContradictionAxisId, ContradictionValueId } from './i18n/types.js';
|
|
2
|
+
import type { TokenCounter } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Structural analysis.
|
|
5
|
+
*
|
|
6
|
+
* Every rule in `rules.ts` matches a phrase. The waste this module looks for
|
|
7
|
+
* is a relationship between two places in the prompt — an instruction that
|
|
8
|
+
* contradicts another, an example that teaches what an earlier one already
|
|
9
|
+
* taught. No dictionary can see either, because neither is wrong on its own.
|
|
10
|
+
*
|
|
11
|
+
* Everything here is advisory. A contradiction has a right answer only the
|
|
12
|
+
* author knows, and an example that looks redundant may be demonstrating a
|
|
13
|
+
* boundary case on purpose. Trazum points; it does not cut.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Sourced from the i18n contract rather than declared here, so adding an axis
|
|
17
|
+
* fails to compile until every catalogue can name it.
|
|
18
|
+
*/
|
|
19
|
+
export type ContradictionAxis = ContradictionAxisId;
|
|
20
|
+
export interface ContradictionSide {
|
|
21
|
+
/** Which end of the axis this instruction sits on. */
|
|
22
|
+
value: ContradictionValueId;
|
|
23
|
+
/** The sentence it was found in, trimmed for display. */
|
|
24
|
+
snippet: string;
|
|
25
|
+
}
|
|
26
|
+
export interface Contradiction {
|
|
27
|
+
axis: ContradictionAxis;
|
|
28
|
+
a: ContradictionSide;
|
|
29
|
+
b: ContradictionSide;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Finds instructions that pull in opposite directions on the same axis.
|
|
33
|
+
*
|
|
34
|
+
* Only the first occurrence of each value is kept, and an axis reports at most
|
|
35
|
+
* one contradiction: a prompt that says "be concise" four times and "be
|
|
36
|
+
* thorough" once has one problem, not four.
|
|
37
|
+
*
|
|
38
|
+
* Two matches inside the same sentence are ignored. "Be concise but complete"
|
|
39
|
+
* is a deliberate trade-off an author wrote on purpose; flagging it would
|
|
40
|
+
* train people to ignore the advisory.
|
|
41
|
+
*/
|
|
42
|
+
export declare function findContradictions(prompt: string): Contradiction[];
|
|
43
|
+
/**
|
|
44
|
+
* A labelled field inside a few-shot example — `Input:`, `Output:`, `A:`…
|
|
45
|
+
*
|
|
46
|
+
* Exported because the duplicate-line rule needs it: two examples that share
|
|
47
|
+
* an output line are demonstrating that two different inputs map to the *same*
|
|
48
|
+
* answer, which is often the whole point of including both. Deduplicating
|
|
49
|
+
* those lines leaves an example with no output at all — worse than the
|
|
50
|
+
* repetition it removed.
|
|
51
|
+
*/
|
|
52
|
+
export declare const EXAMPLE_FIELD_LINE: RegExp;
|
|
53
|
+
export interface ExampleBlock {
|
|
54
|
+
text: string;
|
|
55
|
+
tokens: number;
|
|
56
|
+
}
|
|
57
|
+
export interface RedundantExample {
|
|
58
|
+
/** Index of the earlier example that already teaches this. */
|
|
59
|
+
duplicateOf: number;
|
|
60
|
+
index: number;
|
|
61
|
+
similarity: number;
|
|
62
|
+
tokens: number;
|
|
63
|
+
}
|
|
64
|
+
export interface ExampleAnalysis {
|
|
65
|
+
examples: ExampleBlock[];
|
|
66
|
+
redundant: RedundantExample[];
|
|
67
|
+
/** Tokens held by examples that repeat an earlier one. */
|
|
68
|
+
redundantTokens: number;
|
|
69
|
+
}
|
|
70
|
+
/** Splits the prompt into labelled example blocks. */
|
|
71
|
+
export declare function findExamples(prompt: string, count: TokenCounter): ExampleBlock[];
|
|
72
|
+
/**
|
|
73
|
+
* Reports examples that repeat what an earlier example already demonstrates.
|
|
74
|
+
*
|
|
75
|
+
* Each example is compared against every earlier one, and blamed on the first
|
|
76
|
+
* match: in a run of three near-identical examples the second and third are
|
|
77
|
+
* both reported against the first, rather than chaining.
|
|
78
|
+
*/
|
|
79
|
+
export declare function analyzeExamples(prompt: string, count: TokenCounter): ExampleAnalysis;
|
|
80
|
+
/**
|
|
81
|
+
* A prompt that shows its output schema in a code block, and then describes
|
|
82
|
+
* the same fields again in prose, is paying for the schema twice.
|
|
83
|
+
*
|
|
84
|
+
* The block is the version that survives: it is unambiguous, and the
|
|
85
|
+
* protection pass already guarantees Trazum will not touch it. The prose
|
|
86
|
+
* restatement is what can go — but only a human can tell a restatement from a
|
|
87
|
+
* clarification that happens to name the same fields, so this reports and does
|
|
88
|
+
* not cut.
|
|
89
|
+
*/
|
|
90
|
+
export interface RestatedFormat {
|
|
91
|
+
/** Top-level keys found in the fenced schema. */
|
|
92
|
+
keys: string[];
|
|
93
|
+
/** Those keys that are also named in the prose outside the block. */
|
|
94
|
+
restatedKeys: string[];
|
|
95
|
+
/** Tokens held by the prose sentences that restate them. */
|
|
96
|
+
restatedTokens: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Finds an output schema that the prose repeats.
|
|
100
|
+
*
|
|
101
|
+
* The threshold is deliberately blunt: naming one or two fields in prose is
|
|
102
|
+
* ordinary ("set `escalate` to true when the customer asks for a human"), and
|
|
103
|
+
* flagging that would make the advisory noise. Three or more, and the prose is
|
|
104
|
+
* walking the schema.
|
|
105
|
+
*/
|
|
106
|
+
export declare function findRestatedFormat(prompt: string, count: TokenCounter): RestatedFormat | null;
|
|
107
|
+
/**
|
|
108
|
+
* A schema shown in the prompt that the request could carry as a parameter.
|
|
109
|
+
*
|
|
110
|
+
* Every provider worth naming now accepts a response schema alongside the
|
|
111
|
+
* message — `output_config.format`, `response_format`, `responseSchema`,
|
|
112
|
+
* whatever it is called this quarter. A prompt that spells the same shape out in
|
|
113
|
+
* a fenced block pays for it in input tokens on **every call**, and gets a
|
|
114
|
+
* weaker guarantee for the money: prose asks the model to comply, a parameter
|
|
115
|
+
* makes the decoder comply.
|
|
116
|
+
*
|
|
117
|
+
* So this is the rare finding that is not a trade-off. Moving a schema out is
|
|
118
|
+
* cheaper *and* stricter. What stops it being a rule is that Trazum cannot make
|
|
119
|
+
* the change: it edits prompts, and this is a change to the call around the
|
|
120
|
+
* prompt. It reports, names the tokens, and leaves the edit to whoever owns the
|
|
121
|
+
* client code.
|
|
122
|
+
*
|
|
123
|
+
* **The one way this could do harm, and what prevents it.** A fenced JSON block
|
|
124
|
+
* in a prompt is one of two completely different things. `Output format: {...}`
|
|
125
|
+
* is a contract and moving it is free. `Input: {...}` inside a few-shot example
|
|
126
|
+
* is *data the prompt needs*, and moving it breaks the prompt. Nothing here
|
|
127
|
+
* guesses which: a block counts only when a phrase from `OUTPUT_CUES_BY_LANGUAGE`
|
|
128
|
+
* appears in the text immediately before it. A schema with no such phrase is
|
|
129
|
+
* left alone, and a prompt in a language those dictionaries do not cover raises
|
|
130
|
+
* nothing at all — a false negative, stated as one, rather than an English cue
|
|
131
|
+
* matched inside Japanese prose and called a saving.
|
|
132
|
+
*/
|
|
133
|
+
export interface MovableSchema {
|
|
134
|
+
/** Fenced blocks that an output cue introduces. */
|
|
135
|
+
blocks: number;
|
|
136
|
+
/** Top-level keys across all of them, deduplicated. */
|
|
137
|
+
keys: string[];
|
|
138
|
+
/** Tokens the blocks hold, fences included, since all of it leaves the prompt. */
|
|
139
|
+
tokens: number;
|
|
140
|
+
/** The cue that identified the first block, so the report can quote it. */
|
|
141
|
+
cue: string;
|
|
142
|
+
}
|
|
143
|
+
export declare function findMovableSchema(prompt: string, count: TokenCounter): MovableSchema | null;
|
|
144
|
+
//# sourceMappingURL=structure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../src/structure.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;;;;;;GAWG;AAMH;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,KAAK,EAAE,oBAAoB,CAAC;IAC5B,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,iBAAiB,CAAC;IACxB,CAAC,EAAE,iBAAiB,CAAC;IACrB,CAAC,EAAE,iBAAiB,CAAC;CACtB;AAoID;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CA6BlE;AA6CD;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,QAG9B,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,0DAA0D;IAC1D,eAAe,EAAE,MAAM,CAAC;CACzB;AAqED,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,YAAY,EAAE,CAWhF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,eAAe,CAyBpF;AAMD;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,qEAAqE;IACrE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,4DAA4D;IAC5D,cAAc,EAAE,MAAM,CAAC;CACxB;AAmED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,cAAc,GAAG,IAAI,CAuC7F;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;CACb;AAqBD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,aAAa,GAAG,IAAI,CA4C3F"}
|