@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/detect.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How each provider announces itself in source.
|
|
3
|
+
*
|
|
4
|
+
* Data rather than branches, so adding a provider is a row. Matched as plain
|
|
5
|
+
* substrings on a lowercased copy — no regex, because this runs over files a
|
|
6
|
+
* contributor supplies and the pattern-goes-quadratic lesson has been paid for
|
|
7
|
+
* three times this week already.
|
|
8
|
+
*
|
|
9
|
+
* A quoted package name will also match the same string appearing in a prompt's
|
|
10
|
+
* own text. That is accepted rather than fixed: an import is the *weakest* kind
|
|
11
|
+
* of evidence here, so a model literal or a base URL overrides it, and the
|
|
12
|
+
* alternative — enumerating every import spelling in two languages — is how the
|
|
13
|
+
* single-quoted `require` form came to be missing.
|
|
14
|
+
*/
|
|
15
|
+
const SIGNATURES = [
|
|
16
|
+
{
|
|
17
|
+
provider: 'anthropic',
|
|
18
|
+
imports: ['@anthropic-ai/sdk', 'anthropic-sdk', 'from anthropic', 'import anthropic'],
|
|
19
|
+
urls: ['api.anthropic.com'],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
provider: 'openai',
|
|
23
|
+
// The quoted package name covers `from 'openai'`, `require("openai")` and
|
|
24
|
+
// every other JS spelling without enumerating them — which is how the
|
|
25
|
+
// single-quoted `require` form got missed. The bare Python forms need their
|
|
26
|
+
// own entries because Python does not quote its imports.
|
|
27
|
+
imports: ["'openai'", '"openai"', 'from openai', 'import openai'],
|
|
28
|
+
urls: ['api.openai.com'],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
provider: 'google',
|
|
32
|
+
imports: [
|
|
33
|
+
'@google/genai',
|
|
34
|
+
'@google/generative-ai',
|
|
35
|
+
'google.generativeai',
|
|
36
|
+
'from google import genai',
|
|
37
|
+
],
|
|
38
|
+
urls: ['generativelanguage.googleapis.com', 'aiplatform.googleapis.com'],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
provider: 'moonshot',
|
|
42
|
+
imports: ['moonshot'],
|
|
43
|
+
urls: ['api.moonshot.cn', 'api.moonshot.ai'],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
provider: 'deepseek',
|
|
47
|
+
imports: ['deepseek'],
|
|
48
|
+
urls: ['api.deepseek.com'],
|
|
49
|
+
},
|
|
50
|
+
{ provider: 'xai', imports: ['@ai-sdk/xai', 'xai-sdk'], urls: ['api.x.ai'] },
|
|
51
|
+
{ provider: 'mistral', imports: ['@mistralai/mistralai', 'from mistralai'], urls: ['api.mistral.ai'] },
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* 1-based line lookup, built once per file.
|
|
55
|
+
*
|
|
56
|
+
* The obvious version counts newlines from zero on every call, which is
|
|
57
|
+
* quadratic in the number of matches — 36 seconds on a file repeating a model
|
|
58
|
+
* id. That is the third time this week the same shape has appeared, and the
|
|
59
|
+
* second time I have written it after fixing it in `extract.ts`; the difference
|
|
60
|
+
* here is that matches arrive out of order across the signature loop, so a
|
|
61
|
+
* forward-only counter does not work and this is a binary search instead.
|
|
62
|
+
*/
|
|
63
|
+
function lineIndex(source) {
|
|
64
|
+
const newlines = [];
|
|
65
|
+
for (let i = 0; i < source.length; i++)
|
|
66
|
+
if (source[i] === '\n')
|
|
67
|
+
newlines.push(i);
|
|
68
|
+
return (offset) => {
|
|
69
|
+
let low = 0;
|
|
70
|
+
let high = newlines.length;
|
|
71
|
+
while (low < high) {
|
|
72
|
+
const mid = (low + high) >> 1;
|
|
73
|
+
if (newlines[mid] < offset)
|
|
74
|
+
low = mid + 1;
|
|
75
|
+
else
|
|
76
|
+
high = mid;
|
|
77
|
+
}
|
|
78
|
+
return low + 1;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Model names written as string literals next to a `model` key.
|
|
83
|
+
*
|
|
84
|
+
* Bounded deliberately: `model` then up to a few characters of punctuation and
|
|
85
|
+
* whitespace, then a quoted value. A looser search would match the word "model"
|
|
86
|
+
* in a prompt's own text and price the prompt against something it merely
|
|
87
|
+
* mentions.
|
|
88
|
+
*/
|
|
89
|
+
function modelLiterals(source, known, lineAt) {
|
|
90
|
+
const found = [];
|
|
91
|
+
const haystack = source.toLowerCase();
|
|
92
|
+
for (const model of known) {
|
|
93
|
+
let from = 0;
|
|
94
|
+
for (;;) {
|
|
95
|
+
const at = haystack.indexOf(model.id.toLowerCase(), from);
|
|
96
|
+
if (at === -1)
|
|
97
|
+
break;
|
|
98
|
+
from = at + model.id.length;
|
|
99
|
+
// It has to be a quoted value: a model id in prose is a mention, not a
|
|
100
|
+
// call. Checking the character either side is enough and costs nothing.
|
|
101
|
+
const before = source[at - 1];
|
|
102
|
+
const after = source[at + model.id.length];
|
|
103
|
+
const quoted = (before === "'" || before === '"' || before === '`') &&
|
|
104
|
+
(after === "'" || after === '"' || after === '`');
|
|
105
|
+
if (!quoted)
|
|
106
|
+
continue;
|
|
107
|
+
found.push({
|
|
108
|
+
kind: 'model-literal',
|
|
109
|
+
detail: model.id,
|
|
110
|
+
line: lineAt(at),
|
|
111
|
+
provider: model.provider,
|
|
112
|
+
model: model.id,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return found;
|
|
117
|
+
}
|
|
118
|
+
/** `model=gpt-5` on a `trazum:prompt` marker line. */
|
|
119
|
+
function markerModels(source, known, lineAt) {
|
|
120
|
+
const found = [];
|
|
121
|
+
let from = 0;
|
|
122
|
+
for (;;) {
|
|
123
|
+
const at = source.indexOf('trazum:prompt', from);
|
|
124
|
+
if (at === -1)
|
|
125
|
+
break;
|
|
126
|
+
from = at + 13;
|
|
127
|
+
const lineEnd = source.indexOf('\n', at);
|
|
128
|
+
const rest = source.slice(from, lineEnd === -1 ? source.length : lineEnd);
|
|
129
|
+
const key = rest.indexOf('model=');
|
|
130
|
+
if (key === -1)
|
|
131
|
+
continue;
|
|
132
|
+
const value = rest.slice(key + 6).trim().split(/[\s>]/)[0] ?? '';
|
|
133
|
+
const model = known.find((m) => m.id === value);
|
|
134
|
+
if (!model)
|
|
135
|
+
continue;
|
|
136
|
+
found.push({
|
|
137
|
+
kind: 'marker',
|
|
138
|
+
detail: `model=${value}`,
|
|
139
|
+
line: lineAt(at),
|
|
140
|
+
provider: model.provider,
|
|
141
|
+
model: model.id,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return found;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Works out which provider a source file talks to, and says why.
|
|
148
|
+
*
|
|
149
|
+
* Returns `provider: null` both when nothing was found and when the file named
|
|
150
|
+
* more than one — the caller cannot act on either, and `conflicts` distinguishes
|
|
151
|
+
* them for the reader.
|
|
152
|
+
*/
|
|
153
|
+
export function detectFromSource(source, options = {}) {
|
|
154
|
+
const known = options.models ?? [];
|
|
155
|
+
const haystack = source.toLowerCase();
|
|
156
|
+
const lineAt = lineIndex(source);
|
|
157
|
+
const evidence = [
|
|
158
|
+
// Strongest first: the author naming a model beats the code naming one,
|
|
159
|
+
// which beats the code naming only a provider.
|
|
160
|
+
...markerModels(source, known, lineAt),
|
|
161
|
+
...modelLiterals(source, known, lineAt),
|
|
162
|
+
];
|
|
163
|
+
for (const signature of SIGNATURES) {
|
|
164
|
+
for (const needle of signature.imports) {
|
|
165
|
+
const at = haystack.indexOf(needle.toLowerCase());
|
|
166
|
+
if (at === -1)
|
|
167
|
+
continue;
|
|
168
|
+
evidence.push({
|
|
169
|
+
kind: 'sdk-import',
|
|
170
|
+
detail: needle,
|
|
171
|
+
line: lineAt(at),
|
|
172
|
+
provider: signature.provider,
|
|
173
|
+
});
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
for (const url of signature.urls) {
|
|
177
|
+
const at = haystack.indexOf(url);
|
|
178
|
+
if (at === -1)
|
|
179
|
+
continue;
|
|
180
|
+
evidence.push({
|
|
181
|
+
kind: 'base-url',
|
|
182
|
+
detail: url,
|
|
183
|
+
line: lineAt(at),
|
|
184
|
+
provider: signature.provider,
|
|
185
|
+
});
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (evidence.length === 0) {
|
|
190
|
+
return { provider: null, model: null, evidence: [], conflicts: [] };
|
|
191
|
+
}
|
|
192
|
+
// A stronger kind overrides a weaker one; only equals can disagree.
|
|
193
|
+
//
|
|
194
|
+
// Getting this wrong the first time broke the most common non-OpenAI setup
|
|
195
|
+
// there is. Moonshot, DeepSeek, xAI, Groq and Together all ship an
|
|
196
|
+
// OpenAI-compatible endpoint, so their documented usage is the OpenAI SDK
|
|
197
|
+
// pointed at a different `base_url`. Treating that as a contradiction —
|
|
198
|
+
// "this file says both openai and deepseek" — would refuse to price a
|
|
199
|
+
// perfectly ordinary DeepSeek client. The base URL is the specific fact and
|
|
200
|
+
// the SDK import is the generic one, so the URL wins rather than ties.
|
|
201
|
+
//
|
|
202
|
+
// Likewise a `model=` on the marker is the author stating the answer, which
|
|
203
|
+
// cannot be contradicted by an import they wrote for another reason.
|
|
204
|
+
const RANK = {
|
|
205
|
+
marker: 3,
|
|
206
|
+
'model-literal': 2,
|
|
207
|
+
'base-url': 1,
|
|
208
|
+
'sdk-import': 0,
|
|
209
|
+
};
|
|
210
|
+
const ranked = [...evidence].sort((a, b) => RANK[b.kind] - RANK[a.kind]);
|
|
211
|
+
const best = ranked[0];
|
|
212
|
+
const topRank = RANK[best.kind];
|
|
213
|
+
// Only evidence of the same strength can conflict. Anything weaker is
|
|
214
|
+
// context, not contradiction.
|
|
215
|
+
const conflicts = ranked.filter((e) => RANK[e.kind] === topRank && e.provider !== undefined && e.provider !== best.provider);
|
|
216
|
+
if (conflicts.length > 0) {
|
|
217
|
+
return { provider: null, model: null, evidence: ranked, conflicts };
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
provider: best.provider ?? null,
|
|
221
|
+
// A model only comes from something that named one, and only from the
|
|
222
|
+
// winning provider: an import tells us who, never which of their models.
|
|
223
|
+
model: ranked.find((e) => e.model !== undefined && e.provider === best.provider)?.model ?? null,
|
|
224
|
+
evidence: ranked,
|
|
225
|
+
conflicts: [],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.js","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { LlmProvider } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Golden-set evaluation.
|
|
4
|
+
*
|
|
5
|
+
* Everything else Trazum reports is arithmetic: tokens, prices, multiplication.
|
|
6
|
+
* This is the one question arithmetic cannot answer — does the shorter prompt
|
|
7
|
+
* still do the job? — and the README has been answering it with a caveat
|
|
8
|
+
* ("the aggressive level can change nuance; read the diff") because a rules
|
|
9
|
+
* engine genuinely cannot know.
|
|
10
|
+
*
|
|
11
|
+
* The trap here is comparing the two prompts' outputs and calling the
|
|
12
|
+
* difference a regression. A model asked the same question twice does not
|
|
13
|
+
* answer identically, so "the optimised prompt diverged on 3 of 10 cases" is
|
|
14
|
+
* meaningless on its own — it might be better than the original manages
|
|
15
|
+
* against itself.
|
|
16
|
+
*
|
|
17
|
+
* So the original is run twice per case first, and that self-agreement is the
|
|
18
|
+
* yardstick. The optimised prompt is judged against the model's own variance,
|
|
19
|
+
* not against an imaginary determinism it never had. It costs a third call per
|
|
20
|
+
* case and it is the only reason the number means anything.
|
|
21
|
+
*/
|
|
22
|
+
export interface EvalCase {
|
|
23
|
+
/** The input this case feeds the prompt. */
|
|
24
|
+
input: string;
|
|
25
|
+
/** The original prompt's two answers, used to measure its own variance. */
|
|
26
|
+
baseline: [string, string];
|
|
27
|
+
/** The optimised prompt's answer. */
|
|
28
|
+
optimized: string;
|
|
29
|
+
/** How closely the original agreed with itself (0-1). */
|
|
30
|
+
selfSimilarity: number;
|
|
31
|
+
/** How closely the optimised answer matched the original's first (0-1). */
|
|
32
|
+
crossSimilarity: number;
|
|
33
|
+
}
|
|
34
|
+
export type EvalVerdict = 'indistinguishable' | 'within-noise' | 'diverges' | 'inconclusive';
|
|
35
|
+
export interface EvalReport {
|
|
36
|
+
provider: string;
|
|
37
|
+
model: string;
|
|
38
|
+
cases: EvalCase[];
|
|
39
|
+
/** Mean agreement of the original prompt with itself. The yardstick. */
|
|
40
|
+
selfAgreement: number;
|
|
41
|
+
/** Mean agreement between the original and the optimised prompt. */
|
|
42
|
+
crossAgreement: number;
|
|
43
|
+
verdict: EvalVerdict;
|
|
44
|
+
/** Total provider calls made, so the cost is never a surprise. */
|
|
45
|
+
callsMade: number;
|
|
46
|
+
}
|
|
47
|
+
export interface EvaluateOptions {
|
|
48
|
+
/**
|
|
49
|
+
* How many cases to run at once. Kept low by default: this hammers someone
|
|
50
|
+
* else's endpoint, and a rate limit tripped halfway through wastes every
|
|
51
|
+
* call already paid for.
|
|
52
|
+
*/
|
|
53
|
+
concurrency?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Builds the prompt for one case.
|
|
57
|
+
*
|
|
58
|
+
* A template gets its first placeholder filled; anything else gets the input
|
|
59
|
+
* appended. Substituting is the honest reading of a prompt written with
|
|
60
|
+
* `{{query}}` — appending would test a prompt nobody runs.
|
|
61
|
+
*/
|
|
62
|
+
export declare function fillPrompt(prompt: string, input: string): string;
|
|
63
|
+
/** Agreement between two answers, 0-1. */
|
|
64
|
+
/**
|
|
65
|
+
* How closely two answers agree, 0 to 1.
|
|
66
|
+
*
|
|
67
|
+
* Exported because `prune.ts` measures the same thing and must measure it the
|
|
68
|
+
* same way. It was a private function here first, and the copy that appeared in
|
|
69
|
+
* `prune.ts` was a bag-of-words F1 while this is Jaccard over normalised text —
|
|
70
|
+
* two different numbers under one name, with a comment in the copy claiming they
|
|
71
|
+
* were the same measure. Sharing the function is what makes that comment true.
|
|
72
|
+
*/
|
|
73
|
+
export declare function agreement(a: string, b: string): number;
|
|
74
|
+
/**
|
|
75
|
+
* Turns the two agreement figures into a verdict.
|
|
76
|
+
*
|
|
77
|
+
* The comparison is always relative. An optimised prompt agreeing with the
|
|
78
|
+
* original 0.85 of the time looks alarming until you see the original agrees
|
|
79
|
+
* with itself 0.86 — at which point the optimisation changed nothing the model
|
|
80
|
+
* was not already doing on its own.
|
|
81
|
+
*
|
|
82
|
+
* `inconclusive` exists because a model that is wildly inconsistent with itself
|
|
83
|
+
* cannot be used to judge anything. Reporting a confident verdict off that
|
|
84
|
+
* would be worse than admitting the test does not work here.
|
|
85
|
+
*/
|
|
86
|
+
export declare function verdictFor(selfAgreement: number, crossAgreement: number): EvalVerdict;
|
|
87
|
+
/** Runs `tasks` with a bounded number in flight, preserving order. */
|
|
88
|
+
export declare function pooled<T>(tasks: Array<() => Promise<T>>, limit: number): Promise<T[]>;
|
|
89
|
+
/**
|
|
90
|
+
* Runs both prompt versions over a set of inputs and reports whether the
|
|
91
|
+
* optimisation changed the answers.
|
|
92
|
+
*
|
|
93
|
+
* Costs **three provider calls per case**: the original twice, the optimised
|
|
94
|
+
* once. The doubled original is what makes the result interpretable, and
|
|
95
|
+
* `callsMade` reports the total so the bill is never a surprise.
|
|
96
|
+
*/
|
|
97
|
+
export declare function evaluate(originalPrompt: string, optimizedPrompt: string, inputs: readonly string[], provider: LlmProvider, options?: EvaluateOptions): Promise<EvalReport>;
|
|
98
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../src/evaluate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,QAAQ;IACvB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,WAAW,GAAG,mBAAmB,GAAG,cAAc,GAAG,UAAU,GAAG,cAAc,CAAC;AAE7F,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,wEAAwE;IACxE,aAAa,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,WAAW,CAAC;IACrB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED,0CAA0C;AAC1C;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAKtD;AAKD;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,WAAW,CAOrF;AAED,sEAAsE;AACtE,wBAAsB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAc3F;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAC5B,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,QAAQ,EAAE,WAAW,EACrB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,UAAU,CAAC,CAsCrB"}
|
package/dist/evaluate.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { segment } from './segment.js';
|
|
2
|
+
import { jaccard, normalizeForCompare } from './similarity.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the prompt for one case.
|
|
5
|
+
*
|
|
6
|
+
* A template gets its first placeholder filled; anything else gets the input
|
|
7
|
+
* appended. Substituting is the honest reading of a prompt written with
|
|
8
|
+
* `{{query}}` — appending would test a prompt nobody runs.
|
|
9
|
+
*/
|
|
10
|
+
export function fillPrompt(prompt, input) {
|
|
11
|
+
const placeholder = segment(prompt).find((s) => s.kind === 'protected' && s.protection === 'placeholder');
|
|
12
|
+
if (!placeholder)
|
|
13
|
+
return `${prompt.trimEnd()}\n\n${input}`;
|
|
14
|
+
return prompt.replace(placeholder.text, input);
|
|
15
|
+
}
|
|
16
|
+
/** Agreement between two answers, 0-1. */
|
|
17
|
+
/**
|
|
18
|
+
* How closely two answers agree, 0 to 1.
|
|
19
|
+
*
|
|
20
|
+
* Exported because `prune.ts` measures the same thing and must measure it the
|
|
21
|
+
* same way. It was a private function here first, and the copy that appeared in
|
|
22
|
+
* `prune.ts` was a bag-of-words F1 while this is Jaccard over normalised text —
|
|
23
|
+
* two different numbers under one name, with a comment in the copy claiming they
|
|
24
|
+
* were the same measure. Sharing the function is what makes that comment true.
|
|
25
|
+
*/
|
|
26
|
+
export function agreement(a, b) {
|
|
27
|
+
const left = normalizeForCompare(a);
|
|
28
|
+
const right = normalizeForCompare(b);
|
|
29
|
+
if (left === right)
|
|
30
|
+
return 1;
|
|
31
|
+
return jaccard(left, right);
|
|
32
|
+
}
|
|
33
|
+
const mean = (values) => values.length === 0 ? 0 : values.reduce((sum, v) => sum + v, 0) / values.length;
|
|
34
|
+
/**
|
|
35
|
+
* Turns the two agreement figures into a verdict.
|
|
36
|
+
*
|
|
37
|
+
* The comparison is always relative. An optimised prompt agreeing with the
|
|
38
|
+
* original 0.85 of the time looks alarming until you see the original agrees
|
|
39
|
+
* with itself 0.86 — at which point the optimisation changed nothing the model
|
|
40
|
+
* was not already doing on its own.
|
|
41
|
+
*
|
|
42
|
+
* `inconclusive` exists because a model that is wildly inconsistent with itself
|
|
43
|
+
* cannot be used to judge anything. Reporting a confident verdict off that
|
|
44
|
+
* would be worse than admitting the test does not work here.
|
|
45
|
+
*/
|
|
46
|
+
export function verdictFor(selfAgreement, crossAgreement) {
|
|
47
|
+
if (crossAgreement >= 0.999)
|
|
48
|
+
return 'indistinguishable';
|
|
49
|
+
if (selfAgreement < 0.5)
|
|
50
|
+
return 'inconclusive';
|
|
51
|
+
// Within a small margin of the model's own noise floor, the difference is
|
|
52
|
+
// not attributable to the prompt.
|
|
53
|
+
if (crossAgreement >= selfAgreement - 0.05)
|
|
54
|
+
return 'within-noise';
|
|
55
|
+
return 'diverges';
|
|
56
|
+
}
|
|
57
|
+
/** Runs `tasks` with a bounded number in flight, preserving order. */
|
|
58
|
+
export async function pooled(tasks, limit) {
|
|
59
|
+
const results = new Array(tasks.length);
|
|
60
|
+
let next = 0;
|
|
61
|
+
const workers = Array.from({ length: Math.min(limit, tasks.length) }, async () => {
|
|
62
|
+
for (;;) {
|
|
63
|
+
const index = next++;
|
|
64
|
+
if (index >= tasks.length)
|
|
65
|
+
return;
|
|
66
|
+
results[index] = await tasks[index]();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
await Promise.all(workers);
|
|
70
|
+
return results;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Runs both prompt versions over a set of inputs and reports whether the
|
|
74
|
+
* optimisation changed the answers.
|
|
75
|
+
*
|
|
76
|
+
* Costs **three provider calls per case**: the original twice, the optimised
|
|
77
|
+
* once. The doubled original is what makes the result interpretable, and
|
|
78
|
+
* `callsMade` reports the total so the bill is never a surprise.
|
|
79
|
+
*/
|
|
80
|
+
export async function evaluate(originalPrompt, optimizedPrompt, inputs, provider, options = {}) {
|
|
81
|
+
const concurrency = Math.max(1, options.concurrency ?? 3);
|
|
82
|
+
const run = (prompt, input) => provider.complete({ system: fillPrompt(prompt, input), user: input });
|
|
83
|
+
const cases = await pooled(inputs.map((input) => async () => {
|
|
84
|
+
// Sequential within a case: the two baseline runs exist to measure the
|
|
85
|
+
// model's variance, and issuing them together invites a provider to
|
|
86
|
+
// serve one from a cache and report a variance of zero.
|
|
87
|
+
const baselineA = await run(originalPrompt, input);
|
|
88
|
+
const baselineB = await run(originalPrompt, input);
|
|
89
|
+
const optimized = await run(optimizedPrompt, input);
|
|
90
|
+
return {
|
|
91
|
+
input,
|
|
92
|
+
baseline: [baselineA, baselineB],
|
|
93
|
+
optimized,
|
|
94
|
+
selfSimilarity: agreement(baselineA, baselineB),
|
|
95
|
+
crossSimilarity: agreement(baselineA, optimized),
|
|
96
|
+
};
|
|
97
|
+
}), concurrency);
|
|
98
|
+
const selfAgreement = mean(cases.map((c) => c.selfSimilarity));
|
|
99
|
+
const crossAgreement = mean(cases.map((c) => c.crossSimilarity));
|
|
100
|
+
return {
|
|
101
|
+
provider: provider.name,
|
|
102
|
+
model: provider.model,
|
|
103
|
+
cases,
|
|
104
|
+
selfAgreement,
|
|
105
|
+
crossAgreement,
|
|
106
|
+
verdict: verdictFor(selfAgreement, crossAgreement),
|
|
107
|
+
callsMade: cases.length * 3,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../src/evaluate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompts embedded in source files.
|
|
3
|
+
*
|
|
4
|
+
* `check` and `diff` read `.txt`, `.md`, `.prompt` and `.tmpl`. Real prompts live
|
|
5
|
+
* in TypeScript template literals, Python triple-quoted strings and YAML blocks,
|
|
6
|
+
* so adopting Trazum has meant first refactoring them out into standalone files —
|
|
7
|
+
* a change to somebody's application as the price of admission.
|
|
8
|
+
*
|
|
9
|
+
* **It reads a marker, it does not guess.** `// trazum:prompt` on the line before
|
|
10
|
+
* the literal, and then the literal by delimiter matching. Guessing which string
|
|
11
|
+
* in a file is a prompt is a heuristic, and a heuristic inside a tool used as a
|
|
12
|
+
* CI gate produces failures on strings nobody meant to govern. A marker is one
|
|
13
|
+
* line of noise in exchange for never being wrong about what it picked up.
|
|
14
|
+
*
|
|
15
|
+
* **Interpolation already works and is not a special case.** `${x}` inside a
|
|
16
|
+
* template literal is exactly the placeholder shape `segment.ts` protects, so an
|
|
17
|
+
* embedded prompt gets the same cache-prefix analysis, the same protection from
|
|
18
|
+
* the rules, and the same `--reorder` treatment as a `{{x}}` template. Nothing
|
|
19
|
+
* here has to know about it.
|
|
20
|
+
*
|
|
21
|
+
* **The honest limit:** a prompt assembled from concatenated pieces cannot be
|
|
22
|
+
* read this way. `` `You are ${role}.` + rules.join('\n') `` is a prompt whose
|
|
23
|
+
* text does not exist until it runs. This module declines it and says so rather
|
|
24
|
+
* than governing the half it can see — a budget enforced against a fragment is a
|
|
25
|
+
* green build for a prompt nobody measured.
|
|
26
|
+
*
|
|
27
|
+
* Scanned character by character rather than with a regex. The module it most
|
|
28
|
+
* resembles shipped two quadratic patterns this week, and delimiter matching over
|
|
29
|
+
* untrusted source is exactly the shape that goes wrong.
|
|
30
|
+
*/
|
|
31
|
+
export interface ExtractedPrompt {
|
|
32
|
+
/** Name from `trazum:prompt <name>`, or undefined when the marker was bare. */
|
|
33
|
+
name?: string;
|
|
34
|
+
/** 1-based line of the marker comment, which is what an error should cite. */
|
|
35
|
+
line: number;
|
|
36
|
+
/** The prompt text, delimiters removed and escapes resolved. */
|
|
37
|
+
text: string;
|
|
38
|
+
/** The delimiter it was written with, so a writer could put it back. */
|
|
39
|
+
quote: '`' | '"' | "'" | '"""' | "'''";
|
|
40
|
+
/** Offsets of the text within the source, exclusive of the delimiters. */
|
|
41
|
+
start: number;
|
|
42
|
+
end: number;
|
|
43
|
+
}
|
|
44
|
+
export interface DeclinedPrompt {
|
|
45
|
+
line: number;
|
|
46
|
+
/**
|
|
47
|
+
* Why this marker produced nothing.
|
|
48
|
+
*
|
|
49
|
+
* `concatenated` is the interesting one: the text exists only at runtime.
|
|
50
|
+
* `no-literal` means the marker was not followed by a string at all.
|
|
51
|
+
* `unterminated` means the file ends inside the literal, which is a syntax
|
|
52
|
+
* error in the source rather than a Trazum problem, but worth naming.
|
|
53
|
+
*/
|
|
54
|
+
reason: 'concatenated' | 'no-literal' | 'unterminated';
|
|
55
|
+
detail: string;
|
|
56
|
+
}
|
|
57
|
+
export interface ExtractionResult {
|
|
58
|
+
prompts: ExtractedPrompt[];
|
|
59
|
+
declined: DeclinedPrompt[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Finds every marked prompt in a source file.
|
|
63
|
+
*
|
|
64
|
+
* Returns what it found and what it refused, because a marker that produced
|
|
65
|
+
* nothing is the case the author most needs to hear about: they asked for the
|
|
66
|
+
* prompt to be governed and it is not being governed.
|
|
67
|
+
*/
|
|
68
|
+
export declare function extractPrompts(source: string): ExtractionResult;
|
|
69
|
+
/**
|
|
70
|
+
* A stable identifier for an embedded prompt, for budgets and reports.
|
|
71
|
+
*
|
|
72
|
+
* `src/prompts.ts#support` when the marker was named, `src/prompts.ts:12` when it
|
|
73
|
+
* was not. Both are path-prefixed so the glob patterns in `trazum.config.json`
|
|
74
|
+
* cover embedded prompts without learning a new syntax — `src/**` matches either.
|
|
75
|
+
*/
|
|
76
|
+
export declare function promptId(path: string, prompt: ExtractedPrompt): string;
|
|
77
|
+
/** Whether a file is worth opening for extraction at all. */
|
|
78
|
+
export declare const SOURCE_EXTENSIONS: string[];
|
|
79
|
+
/** Cheap pre-filter: a source file with no marker has nothing to extract. */
|
|
80
|
+
export declare const hasMarker: (source: string) => boolean;
|
|
81
|
+
//# sourceMappingURL=extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,KAAK,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,KAAK,GAAG,KAAK,CAAC;IACvC,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,MAAM,EAAE,cAAc,GAAG,YAAY,GAAG,cAAc,CAAC;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAkJD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAuF/D;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,MAAM,CAEtE;AAED,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,UAgB7B,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,SAAS,WAAY,MAAM,KAAG,OAA+B,CAAC"}
|