margin-meter 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +292 -2
- package/dist/batching.d.ts +94 -0
- package/dist/batching.d.ts.map +1 -0
- package/dist/batching.js +181 -0
- package/dist/batching.js.map +1 -0
- package/dist/braintrust.d.ts +153 -0
- package/dist/braintrust.d.ts.map +1 -0
- package/dist/braintrust.js +424 -0
- package/dist/braintrust.js.map +1 -0
- package/dist/index.d.ts +304 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +628 -23
- package/dist/index.js.map +1 -1
- package/dist/instrument.d.ts +8 -7
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +8 -7
- package/dist/instrument.js.map +1 -1
- package/dist/langsmith.d.ts +150 -0
- package/dist/langsmith.d.ts.map +1 -0
- package/dist/langsmith.js +415 -0
- package/dist/langsmith.js.map +1 -0
- package/dist/promptfoo.d.ts +161 -0
- package/dist/promptfoo.d.ts.map +1 -0
- package/dist/promptfoo.js +446 -0
- package/dist/promptfoo.js.map +1 -0
- package/dist/providers.d.ts +32 -0
- package/dist/providers.d.ts.map +1 -1
- package/dist/providers.js +217 -3
- package/dist/providers.js.map +1 -1
- package/dist/vitest.d.ts +232 -0
- package/dist/vitest.d.ts.map +1 -0
- package/dist/vitest.js +502 -0
- package/dist/vitest.js.map +1 -0
- package/package.json +24 -6
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* margin-meter/langsmith — turn a LangSmith `evaluate()` result into outcomes.
|
|
3
|
+
*
|
|
4
|
+
* The TypeScript twin of `sdk/python/margin_meter/langsmith.py`. LangSmith is the
|
|
5
|
+
* eval/observability layer of the LangChain ecosystem — one of the largest
|
|
6
|
+
* install bases in production LLM apps, and it ships a first-class JS/TS SDK, so a
|
|
7
|
+
* LangSmith customer is squarely in the TypeScript toolchain. `evaluate()`
|
|
8
|
+
* attaches one or more feedback scores to each example; a customer serializes that
|
|
9
|
+
* result — iterating `ExperimentResults` (each row carries
|
|
10
|
+
* `evaluation_results.results`) or via `result.to_pandas().to_dict(...)` (feedback
|
|
11
|
+
* lands in `feedback.<key>` columns) — and both are a match for
|
|
12
|
+
* `POST /api/ingest/outcomes` (`docs/INTEGRATION_LADDER.md` rung 2):
|
|
13
|
+
*
|
|
14
|
+
* import { MarginMeter, langsmith } from "margin-meter";
|
|
15
|
+
* const meter = new MarginMeter();
|
|
16
|
+
* const data = JSON.parse(await readFile("ls.json", "utf8"));
|
|
17
|
+
* await langsmith.recordResults(meter, data, "my-app", { threshold: 0.7 });
|
|
18
|
+
*
|
|
19
|
+
* The mapping is identical to the Python adapter's, on purpose — one LangSmith
|
|
20
|
+
* row → one outcome:
|
|
21
|
+
*
|
|
22
|
+
* - `qualityScore` ← the mean of the row's numeric feedback scores in [0, 1]
|
|
23
|
+
* (booleans count as 1.0 / 0.0). LangSmith attaches a score per evaluator, not
|
|
24
|
+
* one per row, so they are aggregated — the same rule the DeepEval, Braintrust
|
|
25
|
+
* and Ragas adapters apply. Both serializations are read: the object form
|
|
26
|
+
* (`evaluation_results.results = [{key, score}]`) and the pandas form
|
|
27
|
+
* (`feedback.<key>` columns).
|
|
28
|
+
* - `passed` ← LangSmith has NO single pass/fail verdict, so it is derived
|
|
29
|
+
* HONESTLY, never fabricated: (1) `threshold` given → mean ≥ threshold; (2) no
|
|
30
|
+
* threshold but every feedback is binary (0.0/1.0) → every evaluator passed;
|
|
31
|
+
* (3) no threshold and continuous scores → the row is SKIPPED with a warning.
|
|
32
|
+
* - `qualityMethod`/`graderKind` ← DERIVED from the evaluator KEYS, never assumed:
|
|
33
|
+
* a known LangChain LLM-judge evaluator (`correctness`, `qa`, `criteria`,
|
|
34
|
+
* `score_string`, …) → `llm_judge`/`judge`; a known code evaluator
|
|
35
|
+
* (`string_distance`, `exact_match`, `json_validity`, …) → `ground_truth`/`code`;
|
|
36
|
+
* anything else → `unknown`/`unknown` (the weaker-claim-wins honesty rule).
|
|
37
|
+
* Most LangSmith keys are user-named, so `unknown` is the honest common default.
|
|
38
|
+
* - `judgeModelId` ← an opt-in `margin_judge_model` field on the ROW (LangSmith
|
|
39
|
+
* rows have no metadata envelope), ridden only when the grade is an LLM judge
|
|
40
|
+
* (rung 6 provenance). A bare `model` field is NOT used.
|
|
41
|
+
* - `link` ← the row's `example_id`/`id`/`run_id`, else
|
|
42
|
+
* `langsmith:<index>` — never the input question (MAR-1548).
|
|
43
|
+
* - `valueUsd` ← opt-in `margin_value` on the ROW (rung 8); absent is the
|
|
44
|
+
* cost-per-outcome default, never 0.
|
|
45
|
+
*
|
|
46
|
+
* Same two promises as the other adapters: it consumes output the customer already
|
|
47
|
+
* produces (no new harness), and it never fabricates and is fail-safe.
|
|
48
|
+
*/
|
|
49
|
+
// Grading provenance strings — mirror `src/margin/grading.py`, kept as literals.
|
|
50
|
+
const GROUND_TRUTH = "ground_truth";
|
|
51
|
+
const LLM_JUDGE = "llm_judge";
|
|
52
|
+
const UNKNOWN_METHOD = "unknown";
|
|
53
|
+
const GRADER_CODE = "code";
|
|
54
|
+
const GRADER_JUDGE = "judge";
|
|
55
|
+
const GRADER_UNKNOWN = "unknown";
|
|
56
|
+
const MAX_LINK_LEN = 512;
|
|
57
|
+
const MAX_JUDGE_ID_LEN = 128;
|
|
58
|
+
const VALUE_KEY = "margin_value";
|
|
59
|
+
// The explicit opt-in key for the judging model. A bare `model` is NOT used: on a
|
|
60
|
+
// LangSmith row it is the target model under evaluation, not the judge, and
|
|
61
|
+
// labelling it `judgeModelId` would misstate the provenance (rung 6).
|
|
62
|
+
const JUDGE_MODEL_KEY = "margin_judge_model";
|
|
63
|
+
// Mirror `src/margin/ingest.MAX_VALUE_USD`. A value above this is rejected by
|
|
64
|
+
// ingest and would take the whole row down with it, so the adapter drops the
|
|
65
|
+
// value and keeps the row — the same treatment a malformed value gets.
|
|
66
|
+
const MAX_VALUE_USD = 10_000_000.0;
|
|
67
|
+
// Model ids that name no real model. Normalised lower-case for the check.
|
|
68
|
+
const NON_MODELS = new Set(["", "n/a", "na", "none", "null"]);
|
|
69
|
+
// A finite DECIMAL number — the only string shape accepted for a value tag.
|
|
70
|
+
// Deliberately narrower than JS `Number()` (which parses `0x10`/`Infinity`/`""`):
|
|
71
|
+
// this is stricter than Python `float()` in the honest direction — it also
|
|
72
|
+
// rejects `inf`/`nan`, which Python would coerce into a garbage numerator.
|
|
73
|
+
const DECIMAL_RE = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
|
|
74
|
+
// Feedback columns in the pandas serialization are prefixed with this.
|
|
75
|
+
const FEEDBACK_PREFIX = "feedback.";
|
|
76
|
+
/** LangChain's built-in LLM-as-judge string evaluators, plus common judge keys.
|
|
77
|
+
* Normalised lower-case, non-alphanumerics stripped so `score_string`/`scoreString`
|
|
78
|
+
* collapse together. */
|
|
79
|
+
const MODEL_EVALUATORS = new Set([
|
|
80
|
+
"correctness",
|
|
81
|
+
"qa",
|
|
82
|
+
"cotqa",
|
|
83
|
+
"contextqa",
|
|
84
|
+
"criteria",
|
|
85
|
+
"labeledcriteria",
|
|
86
|
+
"scorestring",
|
|
87
|
+
"labeledscorestring",
|
|
88
|
+
"embeddingdistance",
|
|
89
|
+
"helpfulness",
|
|
90
|
+
"relevance",
|
|
91
|
+
"coherence",
|
|
92
|
+
"conciseness",
|
|
93
|
+
"harmfulness",
|
|
94
|
+
"maliciousness",
|
|
95
|
+
"faithfulness",
|
|
96
|
+
"hallucination",
|
|
97
|
+
]);
|
|
98
|
+
/** Deterministic, code-graded evaluators (no model in the grading loop). */
|
|
99
|
+
const CODE_EVALUATORS = new Set([
|
|
100
|
+
"stringdistance",
|
|
101
|
+
"exactmatch",
|
|
102
|
+
"regex",
|
|
103
|
+
"jsonvalidity",
|
|
104
|
+
"jsonequality",
|
|
105
|
+
"jsonedit",
|
|
106
|
+
"jsonschema",
|
|
107
|
+
"levenshtein",
|
|
108
|
+
]);
|
|
109
|
+
const defaultWarn = (message) => {
|
|
110
|
+
const p = globalThis
|
|
111
|
+
.process;
|
|
112
|
+
if (p && typeof p.emitWarning === "function")
|
|
113
|
+
p.emitWarning(message);
|
|
114
|
+
else
|
|
115
|
+
console.warn(message);
|
|
116
|
+
};
|
|
117
|
+
function isObject(x) {
|
|
118
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
119
|
+
}
|
|
120
|
+
/** Normalise an evaluator key for set membership: lower-case, keep only
|
|
121
|
+
* alphanumerics (so `string_distance`/`String Distance` collapse), strip a
|
|
122
|
+
* leading `not` negation. Returns undefined for a missing/blank name. */
|
|
123
|
+
export function normKey(raw) {
|
|
124
|
+
if (typeof raw !== "string")
|
|
125
|
+
return undefined;
|
|
126
|
+
let t = raw
|
|
127
|
+
.trim()
|
|
128
|
+
.toLowerCase()
|
|
129
|
+
.replace(/[^a-z0-9]/g, "");
|
|
130
|
+
if (!t)
|
|
131
|
+
return undefined;
|
|
132
|
+
if (t.startsWith("not") && t !== "not")
|
|
133
|
+
t = t.slice(3);
|
|
134
|
+
return t || undefined;
|
|
135
|
+
}
|
|
136
|
+
/** A score coerced to [0, 1]: a bool becomes 1.0/0.0, a number is kept when in
|
|
137
|
+
* range, anything else (or out of range) is undefined (dropped). */
|
|
138
|
+
function numInRange(value) {
|
|
139
|
+
if (typeof value === "boolean")
|
|
140
|
+
return value ? 1.0 : 0.0;
|
|
141
|
+
if (typeof value === "number" && !Number.isNaN(value)) {
|
|
142
|
+
if (value >= 0.0 && value <= 1.0)
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
/** The per-evaluator feedback scores of a row, as `{ [key]: score }`.
|
|
148
|
+
*
|
|
149
|
+
* Reads both real serializations: the object form
|
|
150
|
+
* (`evaluation_results.results = [{key, score}]`, or a bare top-level `results`
|
|
151
|
+
* list) and the pandas form (`feedback.<key>` columns). A null/non-numeric/
|
|
152
|
+
* out-of-[0,1] score is dropped so the ingest layer never rejects the row for a
|
|
153
|
+
* bad decimal. */
|
|
154
|
+
export function feedbackScores(row) {
|
|
155
|
+
const out = {};
|
|
156
|
+
if (!isObject(row))
|
|
157
|
+
return out;
|
|
158
|
+
// Object form.
|
|
159
|
+
let results = undefined;
|
|
160
|
+
const er = row.evaluation_results;
|
|
161
|
+
if (isObject(er))
|
|
162
|
+
results = er.results;
|
|
163
|
+
// Python tests `results is None`, which is true both when `evaluation_results`
|
|
164
|
+
// was absent AND when its `results` was explicitly null — so a null there must
|
|
165
|
+
// also fall through to a top-level `results` list, not just an absent one.
|
|
166
|
+
if ((results === undefined || results === null) && Array.isArray(row.results))
|
|
167
|
+
results = row.results;
|
|
168
|
+
if (Array.isArray(results)) {
|
|
169
|
+
for (let i = 0; i < results.length; i++) {
|
|
170
|
+
const item = results[i];
|
|
171
|
+
if (!isObject(item))
|
|
172
|
+
continue;
|
|
173
|
+
const score = numInRange(item.score);
|
|
174
|
+
if (score === undefined)
|
|
175
|
+
continue;
|
|
176
|
+
const key = typeof item.key === "string" && item.key.trim() ? item.key : `eval_${i}`;
|
|
177
|
+
out[key] = score;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Pandas form: feedback.<key> columns.
|
|
181
|
+
for (const [name, value] of Object.entries(row)) {
|
|
182
|
+
if (!name.startsWith(FEEDBACK_PREFIX))
|
|
183
|
+
continue;
|
|
184
|
+
const score = numInRange(value);
|
|
185
|
+
if (score === undefined)
|
|
186
|
+
continue;
|
|
187
|
+
out[name.slice(FEEDBACK_PREFIX.length) || name] = score;
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
/** Derive `(qualityMethod, graderKind)` from the evaluator keys behind a row,
|
|
192
|
+
* weaker-claim-wins:
|
|
193
|
+
* - any model evaluator → llm_judge/judge
|
|
194
|
+
* - only recognised code evaluators → ground_truth/code
|
|
195
|
+
* - anything else → unknown/unknown */
|
|
196
|
+
export function evaluatorSignature(keys) {
|
|
197
|
+
let hasModel = false;
|
|
198
|
+
let hasCode = false;
|
|
199
|
+
let hasUnknown = false;
|
|
200
|
+
for (const raw of keys) {
|
|
201
|
+
const t = normKey(raw);
|
|
202
|
+
if (t === undefined)
|
|
203
|
+
continue;
|
|
204
|
+
if (MODEL_EVALUATORS.has(t))
|
|
205
|
+
hasModel = true;
|
|
206
|
+
else if (CODE_EVALUATORS.has(t))
|
|
207
|
+
hasCode = true;
|
|
208
|
+
else
|
|
209
|
+
hasUnknown = true;
|
|
210
|
+
}
|
|
211
|
+
if (hasModel)
|
|
212
|
+
return { qualityMethod: LLM_JUDGE, graderKind: GRADER_JUDGE };
|
|
213
|
+
if (hasCode && !hasUnknown)
|
|
214
|
+
return { qualityMethod: GROUND_TRUTH, graderKind: GRADER_CODE };
|
|
215
|
+
return { qualityMethod: UNKNOWN_METHOD, graderKind: GRADER_UNKNOWN };
|
|
216
|
+
}
|
|
217
|
+
/** The judging model, from an explicit `margin_judge_model` opt-in on the row, or
|
|
218
|
+
* undefined. A bare `model` field is deliberately NOT used. */
|
|
219
|
+
export function judgeModelFromRow(row) {
|
|
220
|
+
if (!isObject(row))
|
|
221
|
+
return undefined;
|
|
222
|
+
const raw = row[JUDGE_MODEL_KEY];
|
|
223
|
+
if (typeof raw !== "string")
|
|
224
|
+
return undefined;
|
|
225
|
+
if (NON_MODELS.has(raw.trim().toLowerCase()))
|
|
226
|
+
return undefined;
|
|
227
|
+
return raw.trim().slice(0, MAX_JUDGE_ID_LEN);
|
|
228
|
+
}
|
|
229
|
+
function coerceValue(raw) {
|
|
230
|
+
if (typeof raw === "boolean")
|
|
231
|
+
throw new Error(`${VALUE_KEY} must be a number, got ${JSON.stringify(raw)}`);
|
|
232
|
+
let val;
|
|
233
|
+
if (typeof raw === "number") {
|
|
234
|
+
val = raw;
|
|
235
|
+
}
|
|
236
|
+
else if (typeof raw === "string") {
|
|
237
|
+
const s = raw.trim();
|
|
238
|
+
if (!DECIMAL_RE.test(s))
|
|
239
|
+
throw new Error(`${VALUE_KEY} must be a number, got ${JSON.stringify(raw)}`);
|
|
240
|
+
val = Number(s);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
throw new Error(`${VALUE_KEY} must be a number, got ${JSON.stringify(raw)}`);
|
|
244
|
+
}
|
|
245
|
+
if (!Number.isFinite(val))
|
|
246
|
+
throw new Error(`${VALUE_KEY} must be a finite number, got ${JSON.stringify(raw)}`);
|
|
247
|
+
// A loss is a real value (MAR-1198): ingest takes any finite amount within +/-MAX.
|
|
248
|
+
if (Math.abs(val) > MAX_VALUE_USD)
|
|
249
|
+
throw new Error(`${VALUE_KEY} implausible (|value| > ${MAX_VALUE_USD}), got ${val}`);
|
|
250
|
+
return val;
|
|
251
|
+
}
|
|
252
|
+
/** Read an opt-in `margin_value` off a row — the value-per-outcome numerator
|
|
253
|
+
* (rung 8). Returns the USD amount, or undefined when absent. Throws on a
|
|
254
|
+
* malformed/implausible value (a present-but-null value included) so the
|
|
255
|
+
* caller can warn and drop it, matching the Python twin. */
|
|
256
|
+
export function valueFromRow(row) {
|
|
257
|
+
if (!isObject(row))
|
|
258
|
+
return undefined;
|
|
259
|
+
if (!(VALUE_KEY in row))
|
|
260
|
+
return undefined;
|
|
261
|
+
return coerceValue(row[VALUE_KEY]);
|
|
262
|
+
}
|
|
263
|
+
/** A traceable link back to the example: its id, else the row index.
|
|
264
|
+
* ⛔ Never the input question (MAR-1548): /security says no prompt reaches us. */
|
|
265
|
+
export function linkFromRow(row, index) {
|
|
266
|
+
if (isObject(row)) {
|
|
267
|
+
for (const key of ["example_id", "id", "run_id"]) {
|
|
268
|
+
const val = row[key];
|
|
269
|
+
if (typeof val === "string" && val.trim())
|
|
270
|
+
return val.trim().slice(0, MAX_LINK_LEN);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return `langsmith:${index}`;
|
|
274
|
+
}
|
|
275
|
+
/** Derive the pass/fail for a row, or undefined when it cannot be derived
|
|
276
|
+
* honestly (no scores, or continuous scores with no threshold). */
|
|
277
|
+
export function passedFromScores(scores, threshold) {
|
|
278
|
+
const values = Object.values(scores);
|
|
279
|
+
if (values.length === 0)
|
|
280
|
+
return undefined;
|
|
281
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
282
|
+
if (threshold !== undefined)
|
|
283
|
+
return mean >= threshold;
|
|
284
|
+
if (values.every((v) => v === 0.0 || v === 1.0))
|
|
285
|
+
return values.every((v) => v === 1.0);
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
/** Translate one graded LangSmith row into `MarginMeter.recordOutcome` input.
|
|
289
|
+
* `judgeModelId` rides only when the grade is an LLM judge and a model was named;
|
|
290
|
+
* `valueUsd` rides only when a value was supplied. */
|
|
291
|
+
export function outcomeInput(args) {
|
|
292
|
+
const { qualityMethod, graderKind } = evaluatorSignature(args.evaluatorKeys);
|
|
293
|
+
const input = {
|
|
294
|
+
workflowId: args.workflowId,
|
|
295
|
+
passed: Boolean(args.passed),
|
|
296
|
+
qualityScore: Number(args.qualityScore),
|
|
297
|
+
qualityMethod,
|
|
298
|
+
graderKind,
|
|
299
|
+
link: String(args.link).slice(0, MAX_LINK_LEN),
|
|
300
|
+
isSimulated: Boolean(args.isSimulated),
|
|
301
|
+
};
|
|
302
|
+
if (qualityMethod === LLM_JUDGE && args.judgeModelId)
|
|
303
|
+
input.judgeModelId = args.judgeModelId.slice(0, MAX_JUDGE_ID_LEN);
|
|
304
|
+
if (args.valueUsd !== undefined)
|
|
305
|
+
input.valueUsd = Number(args.valueUsd);
|
|
306
|
+
return input;
|
|
307
|
+
}
|
|
308
|
+
/** Full translation of one LangSmith row, or undefined if it is not a real
|
|
309
|
+
* verdict (no feedback score, or an ambiguous continuous score with no
|
|
310
|
+
* threshold). A malformed `margin_value` warns and drops the value while keeping
|
|
311
|
+
* the pass/fail. */
|
|
312
|
+
export function rowToOutcomeInput(row, opts) {
|
|
313
|
+
const onWarn = opts.onWarn ?? defaultWarn;
|
|
314
|
+
const index = opts.index ?? 0;
|
|
315
|
+
const scores = feedbackScores(row);
|
|
316
|
+
const passed = passedFromScores(scores, opts.threshold);
|
|
317
|
+
if (passed === undefined) {
|
|
318
|
+
if (Object.keys(scores).length > 0) {
|
|
319
|
+
onWarn(`${linkFromRow(row, index)}: continuous scores and no threshold; ` +
|
|
320
|
+
"skipped (no verdict to record)");
|
|
321
|
+
}
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
const values = Object.values(scores);
|
|
325
|
+
const quality = values.reduce((a, b) => a + b, 0) / values.length;
|
|
326
|
+
let value;
|
|
327
|
+
try {
|
|
328
|
+
value = valueFromRow(row);
|
|
329
|
+
}
|
|
330
|
+
catch (exc) {
|
|
331
|
+
value = undefined;
|
|
332
|
+
onWarn(`${linkFromRow(row, index)}: ${exc.message}`);
|
|
333
|
+
}
|
|
334
|
+
return outcomeInput({
|
|
335
|
+
workflowId: opts.workflowId,
|
|
336
|
+
passed,
|
|
337
|
+
qualityScore: quality,
|
|
338
|
+
evaluatorKeys: Object.keys(scores),
|
|
339
|
+
link: linkFromRow(row, index),
|
|
340
|
+
judgeModelId: judgeModelFromRow(row),
|
|
341
|
+
valueUsd: value,
|
|
342
|
+
isSimulated: opts.isSimulated,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
/** Pull the flat list of per-example rows out of parsed LangSmith output — a bare
|
|
346
|
+
* list, or a `{results|rows|records: [...]}` envelope. */
|
|
347
|
+
export function extractRows(data) {
|
|
348
|
+
if (Array.isArray(data))
|
|
349
|
+
return data.filter(isObject);
|
|
350
|
+
if (!isObject(data))
|
|
351
|
+
return [];
|
|
352
|
+
for (const key of ["results", "rows", "records"]) {
|
|
353
|
+
const block = data[key];
|
|
354
|
+
if (Array.isArray(block))
|
|
355
|
+
return block.filter(isObject);
|
|
356
|
+
}
|
|
357
|
+
return [];
|
|
358
|
+
}
|
|
359
|
+
/** Yield `recordOutcome` inputs for every real verdict in parsed LangSmith output
|
|
360
|
+
* (any envelope shape {@link extractRows} understands). */
|
|
361
|
+
export function iterOutcomes(data, opts) {
|
|
362
|
+
const out = [];
|
|
363
|
+
const rows = extractRows(data);
|
|
364
|
+
for (let i = 0; i < rows.length; i++) {
|
|
365
|
+
const input = rowToOutcomeInput(rows[i], {
|
|
366
|
+
workflowId: opts.workflowId,
|
|
367
|
+
index: i,
|
|
368
|
+
threshold: opts.threshold,
|
|
369
|
+
isSimulated: opts.isSimulated,
|
|
370
|
+
onWarn: opts.onWarn,
|
|
371
|
+
});
|
|
372
|
+
if (input !== undefined)
|
|
373
|
+
out.push(input);
|
|
374
|
+
}
|
|
375
|
+
return out;
|
|
376
|
+
}
|
|
377
|
+
/** Emit an outcome per verdict in parsed LangSmith output through `meter`.
|
|
378
|
+
* Fail-safe: a delivery fault on one row warns and counts an error; it never
|
|
379
|
+
* aborts the batch. Non-verdict rows are counted as skipped. */
|
|
380
|
+
export async function recordResults(meter, data, workflowId, opts = {}) {
|
|
381
|
+
const onWarn = opts.onWarn ?? defaultWarn;
|
|
382
|
+
const rows = extractRows(data);
|
|
383
|
+
let recorded = 0;
|
|
384
|
+
let errors = 0;
|
|
385
|
+
let skipped = 0;
|
|
386
|
+
for (let i = 0; i < rows.length; i++) {
|
|
387
|
+
const input = rowToOutcomeInput(rows[i], {
|
|
388
|
+
workflowId,
|
|
389
|
+
index: i,
|
|
390
|
+
threshold: opts.threshold,
|
|
391
|
+
isSimulated: opts.isSimulated,
|
|
392
|
+
onWarn,
|
|
393
|
+
});
|
|
394
|
+
if (input === undefined) {
|
|
395
|
+
skipped++;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
await meter.recordOutcome(input);
|
|
400
|
+
recorded++;
|
|
401
|
+
}
|
|
402
|
+
catch (exc) {
|
|
403
|
+
errors++;
|
|
404
|
+
onWarn(`failed to record outcome for ${input.link}: ${exc.message}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
await meter.flush();
|
|
409
|
+
}
|
|
410
|
+
catch (exc) {
|
|
411
|
+
onWarn(`flush failed: ${exc.message}`);
|
|
412
|
+
}
|
|
413
|
+
return { recorded, errors, skipped };
|
|
414
|
+
}
|
|
415
|
+
//# sourceMappingURL=langsmith.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langsmith.js","sourceRoot":"","sources":["../src/langsmith.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* margin-meter/promptfoo — turn a `promptfoo eval` result file into outcomes.
|
|
3
|
+
*
|
|
4
|
+
* The TypeScript twin of `sdk/python/margin_meter/promptfoo.py`. promptfoo is a
|
|
5
|
+
* Node tool, so a promptfoo user is already in the JS toolchain and this SDK is
|
|
6
|
+
* their natural bridge. `promptfoo eval -o results.json` writes a code-graded
|
|
7
|
+
* pass/fail file that maps one-to-one onto `POST /api/ingest/outcomes`
|
|
8
|
+
* (`docs/INTEGRATION_LADDER.md` rung 2), so the whole of rung 2 — "their evals
|
|
9
|
+
* report pass/fail" — is available with no hand-written POST:
|
|
10
|
+
*
|
|
11
|
+
* promptfoo eval -o results.json
|
|
12
|
+
*
|
|
13
|
+
* import { MarginMeter, promptfoo } from "margin-meter";
|
|
14
|
+
* const meter = new MarginMeter(); // MARGIN_INGEST_URL/KEY from env
|
|
15
|
+
* const data = JSON.parse(await readFile("results.json", "utf8"));
|
|
16
|
+
* await promptfoo.recordResults(meter, data, "my-eval-suite");
|
|
17
|
+
*
|
|
18
|
+
* The mapping is identical to the Python adapter's, on purpose — the two SDKs
|
|
19
|
+
* must not drift:
|
|
20
|
+
*
|
|
21
|
+
* - `passed` ← `success` (the top-level aggregate verdict).
|
|
22
|
+
* - `qualityScore` ← `score` passed through when in [0, 1]; a score outside
|
|
23
|
+
* that range (a raw metric from a custom assert) falls back to the coherent
|
|
24
|
+
* binary, because the ingest layer rejects an out-of-range score and losing
|
|
25
|
+
* the row is worse than losing the decimal. Always supplied (1.0/0.0 at
|
|
26
|
+
* least): the ingest layer discards `quality_method`/`grader_kind` on a
|
|
27
|
+
* scoreless outcome, so the score is what lights the grading provenance.
|
|
28
|
+
* - `qualityMethod`/`graderKind` ← DERIVED from the assert types, never
|
|
29
|
+
* assumed (see the honesty rule below).
|
|
30
|
+
* - `link` ← `testCase.description`, else the prompt id + index — never
|
|
31
|
+
* `prompt.label`, which promptfoo defaults to the prompt text (MAR-1548).
|
|
32
|
+
* - `valueUsd` ← opt-in via `vars.margin_value` or
|
|
33
|
+
* `testCase.metadata.margin_value` — the only route to value-per-outcome
|
|
34
|
+
* (rung 8). Absent is the cost-per-outcome default, never coerced to 0.
|
|
35
|
+
* - a result with no boolean `success` is not a verdict and is SKIPPED —
|
|
36
|
+
* recording a non-verdict would fabricate an outcome the eval did not
|
|
37
|
+
* produce.
|
|
38
|
+
*
|
|
39
|
+
* ## The honesty rule
|
|
40
|
+
*
|
|
41
|
+
* promptfoo grades two different ways and the adapter must not conflate them:
|
|
42
|
+
* code-graded asserts (`contains`, `equals`, `is-json`, `javascript`, `regex`,
|
|
43
|
+
* …) are `ground_truth`/`code`; model-graded asserts (`llm-rubric`,
|
|
44
|
+
* `factuality`, `similar`, `classifier`, `g-eval`, …) are `llm_judge`/`judge`.
|
|
45
|
+
* The WEAKER honest claim wins: any model-graded assert makes the whole outcome
|
|
46
|
+
* a judge grade; a code assert mixed with an assert type we do not recognise is
|
|
47
|
+
* downgraded to `unknown` (we cannot prove the unknown one was code); nothing
|
|
48
|
+
* recognisable at all is `unknown`. A rubric-graded eval reported as a
|
|
49
|
+
* ground-truth code grade would be the exact productivity illusion Margin
|
|
50
|
+
* exists to puncture.
|
|
51
|
+
*
|
|
52
|
+
* Two design promises, same as the Python adapter: it consumes the file the
|
|
53
|
+
* customer already produces (no new harness), and it never fabricates and is
|
|
54
|
+
* fail-safe — a delivery fault on one row warns and is counted, never aborting
|
|
55
|
+
* the batch.
|
|
56
|
+
*/
|
|
57
|
+
import type { RecordOutcomeInput } from "./index.ts";
|
|
58
|
+
/** Where a warning goes. Defaults to `process.emitWarning` so a delivery or
|
|
59
|
+
* tagging problem is visible without crashing the host; a test injects a
|
|
60
|
+
* collector. */
|
|
61
|
+
export type WarnFn = (message: string) => void;
|
|
62
|
+
/** The graded verdict behind one promptfoo result. */
|
|
63
|
+
export interface Verdict {
|
|
64
|
+
passed: boolean;
|
|
65
|
+
score: number;
|
|
66
|
+
}
|
|
67
|
+
/** Grading provenance for one outcome. */
|
|
68
|
+
export interface GraderSignature {
|
|
69
|
+
qualityMethod: string;
|
|
70
|
+
graderKind: string;
|
|
71
|
+
}
|
|
72
|
+
/** Normalise a promptfoo assert type for set membership: lower-case, drop
|
|
73
|
+
* dashes/underscores/spaces, strip a leading `not` negation (`not-contains`
|
|
74
|
+
* grades the same WAY as `contains`). Returns undefined for a missing/blank
|
|
75
|
+
* type. */
|
|
76
|
+
export declare function normAssertType(raw: unknown): string | undefined;
|
|
77
|
+
/** Derive `(qualityMethod, graderKind)` from the assert types behind a result,
|
|
78
|
+
* applying the weaker-claim-wins honesty rule:
|
|
79
|
+
* - any model-graded assert → llm_judge/judge
|
|
80
|
+
* - only recognised code asserts → ground_truth/code
|
|
81
|
+
* - anything else (no asserts, or a code assert mixed with an unrecognised
|
|
82
|
+
* type we cannot vouch for) → unknown/unknown */
|
|
83
|
+
export declare function gradingSignature(assertTypes: Iterable<unknown>): GraderSignature;
|
|
84
|
+
/** Collect the assert types behind one promptfoo result. Prefers
|
|
85
|
+
* `gradingResult.componentResults[].assertion.type` (what actually ran,
|
|
86
|
+
* including expanded asserts) and its single top-level `assertion`; falls back
|
|
87
|
+
* to the configured `testCase.assert[].type`. */
|
|
88
|
+
export declare function assertTypesFromResult(result: unknown): unknown[];
|
|
89
|
+
/** Read an opt-in `margin_value` off a result's `vars` or its test case's
|
|
90
|
+
* `metadata`/`vars` — the value-per-outcome numerator (rung 8). Returns the USD
|
|
91
|
+
* amount, or undefined when absent (the normal cost-per-outcome case). Throws
|
|
92
|
+
* on a malformed value so the caller can warn and drop it. */
|
|
93
|
+
export declare function valueFromResult(result: unknown): number | undefined;
|
|
94
|
+
/** A traceable link back to the exact eval case: the test description if the
|
|
95
|
+
* author wrote one, else the prompt id plus the row index. ⛔ Never
|
|
96
|
+
* `prompt.label` (MAR-1548): promptfoo defaults it to the raw prompt text. */
|
|
97
|
+
export declare function linkFromResult(result: unknown, index: number): string;
|
|
98
|
+
/** Return the `{ passed, score }` verdict for a real result, else undefined.
|
|
99
|
+
* `passed` comes from a boolean `success`; a result without one is not a
|
|
100
|
+
* verdict. `score` is passed through when in [0, 1]; out of that range (a raw
|
|
101
|
+
* metric) falls back to the coherent binary and warns, because the ingest layer
|
|
102
|
+
* rejects an out-of-range score and a lost row is worse than a lost decimal. */
|
|
103
|
+
export declare function passedAndScore(result: unknown, onWarn?: WarnFn): Verdict | undefined;
|
|
104
|
+
/** Fields for {@link outcomeInput}. */
|
|
105
|
+
export interface OutcomeInputArgs {
|
|
106
|
+
workflowId: string;
|
|
107
|
+
passed: boolean;
|
|
108
|
+
qualityScore: number;
|
|
109
|
+
assertTypes: Iterable<unknown>;
|
|
110
|
+
link: string;
|
|
111
|
+
valueUsd?: number;
|
|
112
|
+
isSimulated?: boolean;
|
|
113
|
+
}
|
|
114
|
+
/** Translate one graded promptfoo result into `MarginMeter.recordOutcome` input.
|
|
115
|
+
* The grader signature is derived from `assertTypes` (never assumed);
|
|
116
|
+
* `valueUsd` rides only when a value was supplied. */
|
|
117
|
+
export declare function outcomeInput(args: OutcomeInputArgs): RecordOutcomeInput;
|
|
118
|
+
/** Options shared by the result-level translators. */
|
|
119
|
+
export interface TranslateOptions {
|
|
120
|
+
workflowId: string;
|
|
121
|
+
index?: number;
|
|
122
|
+
isSimulated?: boolean;
|
|
123
|
+
onWarn?: WarnFn;
|
|
124
|
+
}
|
|
125
|
+
/** Full translation of one promptfoo result, or undefined if it is not a real
|
|
126
|
+
* verdict. A malformed `margin_value` warns and drops the value (keeping the
|
|
127
|
+
* pass/fail), never throwing into the caller. */
|
|
128
|
+
export declare function resultToOutcomeInput(result: unknown, opts: TranslateOptions): RecordOutcomeInput | undefined;
|
|
129
|
+
/** Pull the flat list of per-test results out of a parsed promptfoo output.
|
|
130
|
+
* promptfoo has shipped several envelope shapes (the top-level OutputFile with
|
|
131
|
+
* `results.results`, an older `results` list, a bare list); this walks the
|
|
132
|
+
* known shapes rather than pinning one version. */
|
|
133
|
+
export declare function extractResults(data: unknown): Record<string, unknown>[];
|
|
134
|
+
/** Yield `recordOutcome` inputs for every real verdict in a parsed promptfoo
|
|
135
|
+
* output (any envelope shape {@link extractResults} understands). */
|
|
136
|
+
export declare function iterOutcomes(data: unknown, opts: {
|
|
137
|
+
workflowId: string;
|
|
138
|
+
isSimulated?: boolean;
|
|
139
|
+
onWarn?: WarnFn;
|
|
140
|
+
}): RecordOutcomeInput[];
|
|
141
|
+
/** The minimal meter surface {@link recordResults} needs — satisfied by
|
|
142
|
+
* `MarginMeter`, kept as a structural type so this module imports no runtime
|
|
143
|
+
* value from `index.ts`. */
|
|
144
|
+
export interface OutcomeSink {
|
|
145
|
+
recordOutcome(input: RecordOutcomeInput): Promise<unknown>;
|
|
146
|
+
flush(): Promise<void>;
|
|
147
|
+
}
|
|
148
|
+
/** How one `recordResults` batch landed. */
|
|
149
|
+
export interface RecordSummary {
|
|
150
|
+
recorded: number;
|
|
151
|
+
errors: number;
|
|
152
|
+
skipped: number;
|
|
153
|
+
}
|
|
154
|
+
/** Emit an outcome per verdict in a parsed promptfoo output through `meter`.
|
|
155
|
+
* Fail-safe: a delivery fault on one row warns and counts an error; it never
|
|
156
|
+
* aborts the batch. Non-verdict rows are counted as skipped. */
|
|
157
|
+
export declare function recordResults(meter: OutcomeSink, data: unknown, workflowId: string, opts?: {
|
|
158
|
+
isSimulated?: boolean;
|
|
159
|
+
onWarn?: WarnFn;
|
|
160
|
+
}): Promise<RecordSummary>;
|
|
161
|
+
//# sourceMappingURL=promptfoo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promptfoo.d.ts","sourceRoot":"","sources":["../src/promptfoo.ts"],"names":[],"mappings":"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"}
|