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,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* margin-meter/braintrust — turn a Braintrust eval result file into outcomes.
|
|
3
|
+
*
|
|
4
|
+
* The TypeScript twin of `sdk/python/margin_meter/braintrust.py`. Braintrust is
|
|
5
|
+
* the eval platform funded AI teams most often already pay for, and the nearest
|
|
6
|
+
* competitor Margin has (`docs/growth/POSITIONING.md`): it ships "cost per
|
|
7
|
+
* successful eval" — the metric — but has no automated parity-gated
|
|
8
|
+
* promote/revert — the action. So this adapter is the neutral-auditor wedge in
|
|
9
|
+
* code: it hands a Braintrust customer the action layer they lack, over the
|
|
10
|
+
* evals they already run. Rung 2 of `docs/INTEGRATION_LADDER.md`.
|
|
11
|
+
*
|
|
12
|
+
* import { MarginMeter, braintrust } from "margin-meter";
|
|
13
|
+
* const meter = new MarginMeter();
|
|
14
|
+
* const data = JSON.parse(await readFile("results.json", "utf8"));
|
|
15
|
+
* await braintrust.recordResults(meter, data, "my-suite", { threshold: 0.7 });
|
|
16
|
+
*
|
|
17
|
+
* The mapping is identical to the Python adapter's, on purpose — one Braintrust
|
|
18
|
+
* result → one outcome:
|
|
19
|
+
*
|
|
20
|
+
* - `qualityScore` ← the mean of the case's numeric scores in [0, 1] (Braintrust
|
|
21
|
+
* reports a named score per scorer, not one per case).
|
|
22
|
+
* - `passed` ← Braintrust has NO intrinsic pass/fail (its scores are
|
|
23
|
+
* continuous), so it is derived HONESTLY, never fabricated: (1) `threshold`
|
|
24
|
+
* given → mean ≥ threshold; (2) no threshold but every scorer is binary
|
|
25
|
+
* (0.0/1.0) → every scorer passed; (3) no threshold and continuous scores →
|
|
26
|
+
* the row is SKIPPED with a warning.
|
|
27
|
+
* - `qualityMethod`/`graderKind` ← DERIVED from the scorer names, never assumed
|
|
28
|
+
* (weaker-claim-wins, see the honesty rule below).
|
|
29
|
+
* - `judgeModelId` ← an opt-in `margin_judge_model` field, ridden only when the
|
|
30
|
+
* grade is an LLM judge (rung 6 provenance). A bare `model` field is NOT used —
|
|
31
|
+
* in a Braintrust record it is usually the generation model under test.
|
|
32
|
+
* - `link` ← the case id/span_id/root_span_id, else `braintrust:<index>` —
|
|
33
|
+
* never its `input` (MAR-1548).
|
|
34
|
+
* - `valueUsd` ← opt-in via `metadata.margin_value` (rung 8); absent is the
|
|
35
|
+
* cost-per-outcome default, never 0.
|
|
36
|
+
* - a result with no numeric score at all is SKIPPED — recording a non-verdict
|
|
37
|
+
* would fabricate an outcome the eval did not produce.
|
|
38
|
+
*
|
|
39
|
+
* ## The honesty rule
|
|
40
|
+
*
|
|
41
|
+
* Braintrust grades two ways and the adapter must not conflate them: code-graded
|
|
42
|
+
* scorers (ExactMatch, Levenshtein, NumericDiff, JSONDiff, …) are
|
|
43
|
+
* `ground_truth`/`code`; model-graded autoevals (Factuality, ClosedQA,
|
|
44
|
+
* AnswerRelevancy, Moderation, …) are `llm_judge`/`judge`. The WEAKER honest
|
|
45
|
+
* claim wins: any model-graded scorer makes the whole outcome a judge grade; a
|
|
46
|
+
* code scorer mixed with an unrecognised one is downgraded to `unknown`; nothing
|
|
47
|
+
* recognisable is `unknown`.
|
|
48
|
+
*
|
|
49
|
+
* Same two promises as the promptfoo adapter: it consumes a file the customer
|
|
50
|
+
* already produces, and it never fabricates and is fail-safe.
|
|
51
|
+
*/
|
|
52
|
+
import type { RecordOutcomeInput } from "./index.ts";
|
|
53
|
+
/** Where a warning goes. Defaults to `process.emitWarning`; a test injects a
|
|
54
|
+
* collector. */
|
|
55
|
+
export type WarnFn = (message: string) => void;
|
|
56
|
+
/** Grading provenance for one outcome. */
|
|
57
|
+
export interface GraderSignature {
|
|
58
|
+
qualityMethod: string;
|
|
59
|
+
graderKind: string;
|
|
60
|
+
}
|
|
61
|
+
/** Normalise a scorer name for set membership: lower-case, keep only
|
|
62
|
+
* alphanumerics (so `Answer Relevancy`/`answer_relevancy` collapse), strip a
|
|
63
|
+
* leading `not` negation. Returns undefined for a missing/blank name. */
|
|
64
|
+
export declare function normScorerName(raw: unknown): string | undefined;
|
|
65
|
+
/** The per-scorer numeric scores of a case, as `{ [scorer]: score }`. Braintrust
|
|
66
|
+
* records `scores` either as a mapping (`{name: score|null}`) or a list of
|
|
67
|
+
* `{name, score}`. Both are read; a null/non-numeric/out-of-[0,1] score is
|
|
68
|
+
* dropped (the ingest layer rejects a score outside [0,1] and a lost decimal
|
|
69
|
+
* beats a lost row). */
|
|
70
|
+
export declare function scoresOf(result: unknown): Record<string, number>;
|
|
71
|
+
/** Derive `(qualityMethod, graderKind)` from the scorer names, weaker-claim-wins:
|
|
72
|
+
* - any model-graded scorer → llm_judge/judge
|
|
73
|
+
* - only recognised code scorers → ground_truth/code
|
|
74
|
+
* - anything else → unknown/unknown */
|
|
75
|
+
export declare function scorerSignature(scorerNames: Iterable<unknown>): GraderSignature;
|
|
76
|
+
/** The judging model, from an explicit `margin_judge_model` opt-in on the result
|
|
77
|
+
* or its metadata, or undefined. A bare `model` field is deliberately NOT used. */
|
|
78
|
+
export declare function judgeModelFromResult(result: unknown): string | undefined;
|
|
79
|
+
/** Read an opt-in `margin_value` off a result's `metadata` (or a top-level key)
|
|
80
|
+
* — the value-per-outcome numerator (rung 8). Returns the USD amount, or
|
|
81
|
+
* undefined when absent. Throws on a malformed/implausible value so the
|
|
82
|
+
* caller can warn and drop it. */
|
|
83
|
+
export declare function valueFromResult(result: unknown): number | undefined;
|
|
84
|
+
/** A traceable link back to the exact eval case: its id/span id, else the row
|
|
85
|
+
* index. ⛔ Never the case's `input` (MAR-1548): /security says no prompt
|
|
86
|
+
* reaches us. */
|
|
87
|
+
export declare function linkFromResult(result: unknown, index: number): string;
|
|
88
|
+
/** Derive the pass/fail for a case, or undefined when it cannot be derived
|
|
89
|
+
* honestly (no scores, or continuous scores with no threshold). */
|
|
90
|
+
export declare function passedFromScores(scores: Record<string, number>, threshold: number | undefined): boolean | undefined;
|
|
91
|
+
/** Fields for {@link outcomeInput}. */
|
|
92
|
+
export interface OutcomeInputArgs {
|
|
93
|
+
workflowId: string;
|
|
94
|
+
passed: boolean;
|
|
95
|
+
qualityScore: number;
|
|
96
|
+
scorerNames: Iterable<unknown>;
|
|
97
|
+
link: string;
|
|
98
|
+
judgeModelId?: string;
|
|
99
|
+
valueUsd?: number;
|
|
100
|
+
isSimulated?: boolean;
|
|
101
|
+
}
|
|
102
|
+
/** Translate one graded Braintrust result into `MarginMeter.recordOutcome` input.
|
|
103
|
+
* `judgeModelId` rides only when the grade is an LLM judge and a model was named;
|
|
104
|
+
* `valueUsd` rides only when a value was supplied. */
|
|
105
|
+
export declare function outcomeInput(args: OutcomeInputArgs): RecordOutcomeInput;
|
|
106
|
+
/** Options shared by the result-level translators. */
|
|
107
|
+
export interface TranslateOptions {
|
|
108
|
+
workflowId: string;
|
|
109
|
+
index?: number;
|
|
110
|
+
threshold?: number;
|
|
111
|
+
isSimulated?: boolean;
|
|
112
|
+
onWarn?: WarnFn;
|
|
113
|
+
}
|
|
114
|
+
/** Full translation of one Braintrust result, or undefined if it is not a real
|
|
115
|
+
* verdict (no numeric score, or an ambiguous continuous score with no
|
|
116
|
+
* threshold). A malformed `margin_value` warns and drops the value while keeping
|
|
117
|
+
* the pass/fail. */
|
|
118
|
+
export declare function resultToOutcomeInput(result: unknown, opts: TranslateOptions): RecordOutcomeInput | undefined;
|
|
119
|
+
/** Pull the flat list of per-case results out of parsed Braintrust output. It
|
|
120
|
+
* reaches a file several ways: a bare list, the `EvalResultWithSummary` envelope
|
|
121
|
+
* (`{summary, results}`), a `{results}` wrapper, or the experiment-fetch export
|
|
122
|
+
* (`{events}`). This walks the known shapes rather than pinning one. */
|
|
123
|
+
export declare function extractResults(data: unknown): Record<string, unknown>[];
|
|
124
|
+
/** Yield `recordOutcome` inputs for every real verdict in parsed Braintrust
|
|
125
|
+
* output (any envelope shape {@link extractResults} understands). */
|
|
126
|
+
export declare function iterOutcomes(data: unknown, opts: {
|
|
127
|
+
workflowId: string;
|
|
128
|
+
threshold?: number;
|
|
129
|
+
isSimulated?: boolean;
|
|
130
|
+
onWarn?: WarnFn;
|
|
131
|
+
}): RecordOutcomeInput[];
|
|
132
|
+
/** The minimal meter surface {@link recordResults} needs — satisfied by
|
|
133
|
+
* `MarginMeter`, kept as a structural type so this module imports no runtime
|
|
134
|
+
* value from `index.ts`. */
|
|
135
|
+
export interface OutcomeSink {
|
|
136
|
+
recordOutcome(input: RecordOutcomeInput): Promise<unknown>;
|
|
137
|
+
flush(): Promise<void>;
|
|
138
|
+
}
|
|
139
|
+
/** How one `recordResults` batch landed. */
|
|
140
|
+
export interface RecordSummary {
|
|
141
|
+
recorded: number;
|
|
142
|
+
errors: number;
|
|
143
|
+
skipped: number;
|
|
144
|
+
}
|
|
145
|
+
/** Emit an outcome per verdict in parsed Braintrust output through `meter`.
|
|
146
|
+
* Fail-safe: a delivery fault on one row warns and counts an error; it never
|
|
147
|
+
* aborts the batch. Non-verdict rows are counted as skipped. */
|
|
148
|
+
export declare function recordResults(meter: OutcomeSink, data: unknown, workflowId: string, opts?: {
|
|
149
|
+
threshold?: number;
|
|
150
|
+
isSimulated?: boolean;
|
|
151
|
+
onWarn?: WarnFn;
|
|
152
|
+
}): Promise<RecordSummary>;
|
|
153
|
+
//# sourceMappingURL=braintrust.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"braintrust.d.ts","sourceRoot":"","sources":["../src/braintrust.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* margin-meter/braintrust — turn a Braintrust eval result file into outcomes.
|
|
3
|
+
*
|
|
4
|
+
* The TypeScript twin of `sdk/python/margin_meter/braintrust.py`. Braintrust is
|
|
5
|
+
* the eval platform funded AI teams most often already pay for, and the nearest
|
|
6
|
+
* competitor Margin has (`docs/growth/POSITIONING.md`): it ships "cost per
|
|
7
|
+
* successful eval" — the metric — but has no automated parity-gated
|
|
8
|
+
* promote/revert — the action. So this adapter is the neutral-auditor wedge in
|
|
9
|
+
* code: it hands a Braintrust customer the action layer they lack, over the
|
|
10
|
+
* evals they already run. Rung 2 of `docs/INTEGRATION_LADDER.md`.
|
|
11
|
+
*
|
|
12
|
+
* import { MarginMeter, braintrust } from "margin-meter";
|
|
13
|
+
* const meter = new MarginMeter();
|
|
14
|
+
* const data = JSON.parse(await readFile("results.json", "utf8"));
|
|
15
|
+
* await braintrust.recordResults(meter, data, "my-suite", { threshold: 0.7 });
|
|
16
|
+
*
|
|
17
|
+
* The mapping is identical to the Python adapter's, on purpose — one Braintrust
|
|
18
|
+
* result → one outcome:
|
|
19
|
+
*
|
|
20
|
+
* - `qualityScore` ← the mean of the case's numeric scores in [0, 1] (Braintrust
|
|
21
|
+
* reports a named score per scorer, not one per case).
|
|
22
|
+
* - `passed` ← Braintrust has NO intrinsic pass/fail (its scores are
|
|
23
|
+
* continuous), so it is derived HONESTLY, never fabricated: (1) `threshold`
|
|
24
|
+
* given → mean ≥ threshold; (2) no threshold but every scorer is binary
|
|
25
|
+
* (0.0/1.0) → every scorer passed; (3) no threshold and continuous scores →
|
|
26
|
+
* the row is SKIPPED with a warning.
|
|
27
|
+
* - `qualityMethod`/`graderKind` ← DERIVED from the scorer names, never assumed
|
|
28
|
+
* (weaker-claim-wins, see the honesty rule below).
|
|
29
|
+
* - `judgeModelId` ← an opt-in `margin_judge_model` field, ridden only when the
|
|
30
|
+
* grade is an LLM judge (rung 6 provenance). A bare `model` field is NOT used —
|
|
31
|
+
* in a Braintrust record it is usually the generation model under test.
|
|
32
|
+
* - `link` ← the case id/span_id/root_span_id, else `braintrust:<index>` —
|
|
33
|
+
* never its `input` (MAR-1548).
|
|
34
|
+
* - `valueUsd` ← opt-in via `metadata.margin_value` (rung 8); absent is the
|
|
35
|
+
* cost-per-outcome default, never 0.
|
|
36
|
+
* - a result with no numeric score at all is SKIPPED — recording a non-verdict
|
|
37
|
+
* would fabricate an outcome the eval did not produce.
|
|
38
|
+
*
|
|
39
|
+
* ## The honesty rule
|
|
40
|
+
*
|
|
41
|
+
* Braintrust grades two ways and the adapter must not conflate them: code-graded
|
|
42
|
+
* scorers (ExactMatch, Levenshtein, NumericDiff, JSONDiff, …) are
|
|
43
|
+
* `ground_truth`/`code`; model-graded autoevals (Factuality, ClosedQA,
|
|
44
|
+
* AnswerRelevancy, Moderation, …) are `llm_judge`/`judge`. The WEAKER honest
|
|
45
|
+
* claim wins: any model-graded scorer makes the whole outcome a judge grade; a
|
|
46
|
+
* code scorer mixed with an unrecognised one is downgraded to `unknown`; nothing
|
|
47
|
+
* recognisable is `unknown`.
|
|
48
|
+
*
|
|
49
|
+
* Same two promises as the promptfoo adapter: it consumes a file the customer
|
|
50
|
+
* already produces, and it never fabricates and is fail-safe.
|
|
51
|
+
*/
|
|
52
|
+
// Grading provenance strings — mirror `src/margin/grading.py`, kept as literals.
|
|
53
|
+
const GROUND_TRUTH = "ground_truth";
|
|
54
|
+
const LLM_JUDGE = "llm_judge";
|
|
55
|
+
const UNKNOWN_METHOD = "unknown";
|
|
56
|
+
const GRADER_CODE = "code";
|
|
57
|
+
const GRADER_JUDGE = "judge";
|
|
58
|
+
const GRADER_UNKNOWN = "unknown";
|
|
59
|
+
const MAX_LINK_LEN = 512;
|
|
60
|
+
const MAX_JUDGE_ID_LEN = 128;
|
|
61
|
+
const VALUE_KEY = "margin_value";
|
|
62
|
+
// The explicit opt-in key for the judging model. A bare `model` is NOT used: in a
|
|
63
|
+
// Braintrust record it is usually the GENERATION model under test, not the judge,
|
|
64
|
+
// and labelling it `judgeModelId` would misstate the provenance (rung 6).
|
|
65
|
+
const JUDGE_MODEL_KEY = "margin_judge_model";
|
|
66
|
+
// Mirror `src/margin/ingest.MAX_VALUE_USD`. A value above this is rejected by
|
|
67
|
+
// ingest and would take the whole row down with it, so the adapter drops the
|
|
68
|
+
// value and keeps the row — the same treatment a malformed value gets.
|
|
69
|
+
const MAX_VALUE_USD = 10_000_000.0;
|
|
70
|
+
// Model ids that name no real model — a custom/deterministic scorer leaves the
|
|
71
|
+
// field empty or a placeholder. Normalised lower-case for the check.
|
|
72
|
+
const NON_MODELS = new Set(["", "n/a", "na", "none", "null"]);
|
|
73
|
+
// A finite DECIMAL number — the only string shape accepted for a value tag.
|
|
74
|
+
// Deliberately narrower than JS `Number()` (which parses `0x10`/`Infinity`/`""`):
|
|
75
|
+
// neither SDK lets `0x10`/`0b1` through, and this is stricter than Python
|
|
76
|
+
// `float()` in the honest direction — it also rejects `inf`/`nan`, which Python
|
|
77
|
+
// would coerce into a garbage numerator.
|
|
78
|
+
const DECIMAL_RE = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
|
|
79
|
+
/** Braintrust autoeval scorers that are pure code: a deterministic comparison
|
|
80
|
+
* with no model in the loop. Normalised lower-case, non-alphanumerics stripped
|
|
81
|
+
* so `exact-match`/`ExactMatch`/`exact_match` collapse together. */
|
|
82
|
+
const CODE_SCORERS = new Set([
|
|
83
|
+
"exactmatch",
|
|
84
|
+
"equals",
|
|
85
|
+
"levenshtein",
|
|
86
|
+
"numericdiff",
|
|
87
|
+
"jsondiff",
|
|
88
|
+
"listcontains",
|
|
89
|
+
"validjson",
|
|
90
|
+
"jsonvalidity",
|
|
91
|
+
"isjson",
|
|
92
|
+
"containsjson",
|
|
93
|
+
"regex",
|
|
94
|
+
"contains",
|
|
95
|
+
]);
|
|
96
|
+
/** Braintrust autoeval scorers that put a MODEL in the grading loop. Any one
|
|
97
|
+
* present means the strongest honest claim is `llm_judge`/`judge`. */
|
|
98
|
+
const MODEL_SCORERS = new Set([
|
|
99
|
+
"battle",
|
|
100
|
+
"closedqa",
|
|
101
|
+
"humor",
|
|
102
|
+
"factuality",
|
|
103
|
+
"possible",
|
|
104
|
+
"security",
|
|
105
|
+
"sql",
|
|
106
|
+
"summary",
|
|
107
|
+
"translation",
|
|
108
|
+
"fluency",
|
|
109
|
+
"toxicity",
|
|
110
|
+
"moderation",
|
|
111
|
+
"answerrelevancy",
|
|
112
|
+
"answerrelevance",
|
|
113
|
+
"answercorrectness",
|
|
114
|
+
"answersimilarity",
|
|
115
|
+
"contextrelevancy",
|
|
116
|
+
"contextrelevance",
|
|
117
|
+
"contextrecall",
|
|
118
|
+
"contextprecision",
|
|
119
|
+
"contextentityrecall",
|
|
120
|
+
"faithfulness",
|
|
121
|
+
"llmclassifier",
|
|
122
|
+
"modelgradedqa",
|
|
123
|
+
]);
|
|
124
|
+
const defaultWarn = (message) => {
|
|
125
|
+
const p = globalThis
|
|
126
|
+
.process;
|
|
127
|
+
if (p && typeof p.emitWarning === "function")
|
|
128
|
+
p.emitWarning(message);
|
|
129
|
+
else
|
|
130
|
+
console.warn(message);
|
|
131
|
+
};
|
|
132
|
+
function isObject(x) {
|
|
133
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
134
|
+
}
|
|
135
|
+
/** Normalise a scorer name for set membership: lower-case, keep only
|
|
136
|
+
* alphanumerics (so `Answer Relevancy`/`answer_relevancy` collapse), strip a
|
|
137
|
+
* leading `not` negation. Returns undefined for a missing/blank name. */
|
|
138
|
+
export function normScorerName(raw) {
|
|
139
|
+
if (typeof raw !== "string")
|
|
140
|
+
return undefined;
|
|
141
|
+
let t = raw
|
|
142
|
+
.trim()
|
|
143
|
+
.toLowerCase()
|
|
144
|
+
.replace(/[^a-z0-9]/g, "");
|
|
145
|
+
if (!t)
|
|
146
|
+
return undefined;
|
|
147
|
+
if (t.startsWith("not") && t !== "not")
|
|
148
|
+
t = t.slice(3);
|
|
149
|
+
return t || undefined;
|
|
150
|
+
}
|
|
151
|
+
/** The per-scorer numeric scores of a case, as `{ [scorer]: score }`. Braintrust
|
|
152
|
+
* records `scores` either as a mapping (`{name: score|null}`) or a list of
|
|
153
|
+
* `{name, score}`. Both are read; a null/non-numeric/out-of-[0,1] score is
|
|
154
|
+
* dropped (the ingest layer rejects a score outside [0,1] and a lost decimal
|
|
155
|
+
* beats a lost row). */
|
|
156
|
+
export function scoresOf(result) {
|
|
157
|
+
const out = {};
|
|
158
|
+
if (!isObject(result))
|
|
159
|
+
return out;
|
|
160
|
+
const raw = result.scores;
|
|
161
|
+
const pairs = [];
|
|
162
|
+
if (isObject(raw)) {
|
|
163
|
+
for (const [k, v] of Object.entries(raw))
|
|
164
|
+
pairs.push([k, v]);
|
|
165
|
+
}
|
|
166
|
+
else if (Array.isArray(raw)) {
|
|
167
|
+
for (const item of raw) {
|
|
168
|
+
if (isObject(item))
|
|
169
|
+
pairs.push([item.name, item.score]);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (const [name, score] of pairs) {
|
|
173
|
+
if (typeof score === "boolean" || typeof score !== "number" || Number.isNaN(score))
|
|
174
|
+
continue;
|
|
175
|
+
if (!(score >= 0.0 && score <= 1.0))
|
|
176
|
+
continue;
|
|
177
|
+
const key = typeof name === "string" && name.trim() ? name : `scorer_${Object.keys(out).length}`;
|
|
178
|
+
out[key] = score;
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
/** Derive `(qualityMethod, graderKind)` from the scorer names, weaker-claim-wins:
|
|
183
|
+
* - any model-graded scorer → llm_judge/judge
|
|
184
|
+
* - only recognised code scorers → ground_truth/code
|
|
185
|
+
* - anything else → unknown/unknown */
|
|
186
|
+
export function scorerSignature(scorerNames) {
|
|
187
|
+
let hasModel = false;
|
|
188
|
+
let hasCode = false;
|
|
189
|
+
let hasUnknown = false;
|
|
190
|
+
for (const raw of scorerNames) {
|
|
191
|
+
const t = normScorerName(raw);
|
|
192
|
+
if (t === undefined)
|
|
193
|
+
continue;
|
|
194
|
+
if (MODEL_SCORERS.has(t))
|
|
195
|
+
hasModel = true;
|
|
196
|
+
else if (CODE_SCORERS.has(t))
|
|
197
|
+
hasCode = true;
|
|
198
|
+
else
|
|
199
|
+
hasUnknown = true;
|
|
200
|
+
}
|
|
201
|
+
if (hasModel)
|
|
202
|
+
return { qualityMethod: LLM_JUDGE, graderKind: GRADER_JUDGE };
|
|
203
|
+
if (hasCode && !hasUnknown)
|
|
204
|
+
return { qualityMethod: GROUND_TRUTH, graderKind: GRADER_CODE };
|
|
205
|
+
return { qualityMethod: UNKNOWN_METHOD, graderKind: GRADER_UNKNOWN };
|
|
206
|
+
}
|
|
207
|
+
function metadataOf(result) {
|
|
208
|
+
const meta = result.metadata;
|
|
209
|
+
return isObject(meta) ? meta : {};
|
|
210
|
+
}
|
|
211
|
+
/** The judging model, from an explicit `margin_judge_model` opt-in on the result
|
|
212
|
+
* or its metadata, or undefined. A bare `model` field is deliberately NOT used. */
|
|
213
|
+
export function judgeModelFromResult(result) {
|
|
214
|
+
if (!isObject(result))
|
|
215
|
+
return undefined;
|
|
216
|
+
const meta = metadataOf(result);
|
|
217
|
+
let raw = meta[JUDGE_MODEL_KEY];
|
|
218
|
+
if (typeof raw !== "string")
|
|
219
|
+
raw = result[JUDGE_MODEL_KEY];
|
|
220
|
+
if (typeof raw !== "string")
|
|
221
|
+
return undefined;
|
|
222
|
+
if (NON_MODELS.has(raw.trim().toLowerCase()))
|
|
223
|
+
return undefined;
|
|
224
|
+
return raw.trim().slice(0, MAX_JUDGE_ID_LEN);
|
|
225
|
+
}
|
|
226
|
+
function coerceValue(raw) {
|
|
227
|
+
if (typeof raw === "boolean")
|
|
228
|
+
throw new Error(`${VALUE_KEY} must be a number, got ${JSON.stringify(raw)}`);
|
|
229
|
+
let val;
|
|
230
|
+
if (typeof raw === "number") {
|
|
231
|
+
val = raw;
|
|
232
|
+
}
|
|
233
|
+
else if (typeof raw === "string") {
|
|
234
|
+
const s = raw.trim();
|
|
235
|
+
if (!DECIMAL_RE.test(s))
|
|
236
|
+
throw new Error(`${VALUE_KEY} must be a number, got ${JSON.stringify(raw)}`);
|
|
237
|
+
val = Number(s);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
throw new Error(`${VALUE_KEY} must be a number, got ${JSON.stringify(raw)}`);
|
|
241
|
+
}
|
|
242
|
+
if (!Number.isFinite(val))
|
|
243
|
+
throw new Error(`${VALUE_KEY} must be a finite number, got ${JSON.stringify(raw)}`);
|
|
244
|
+
// A loss is a real value (MAR-1198): ingest takes any finite amount within +/-MAX.
|
|
245
|
+
if (Math.abs(val) > MAX_VALUE_USD)
|
|
246
|
+
throw new Error(`${VALUE_KEY} implausible (|value| > ${MAX_VALUE_USD}), got ${val}`);
|
|
247
|
+
return val;
|
|
248
|
+
}
|
|
249
|
+
/** Read an opt-in `margin_value` off a result's `metadata` (or a top-level key)
|
|
250
|
+
* — the value-per-outcome numerator (rung 8). Returns the USD amount, or
|
|
251
|
+
* undefined when absent. Throws on a malformed/implausible value so the
|
|
252
|
+
* caller can warn and drop it. */
|
|
253
|
+
export function valueFromResult(result) {
|
|
254
|
+
if (!isObject(result))
|
|
255
|
+
return undefined;
|
|
256
|
+
const meta = metadataOf(result);
|
|
257
|
+
let raw;
|
|
258
|
+
// Key ABSENT → undefined (the normal case). Key PRESENT but null/malformed →
|
|
259
|
+
// coerceValue throws, so the caller warns and drops it — matching the Python
|
|
260
|
+
// twin, which raises on a present-but-null value rather than silently dropping.
|
|
261
|
+
if (VALUE_KEY in meta)
|
|
262
|
+
raw = meta[VALUE_KEY];
|
|
263
|
+
else if (VALUE_KEY in result)
|
|
264
|
+
raw = result[VALUE_KEY];
|
|
265
|
+
else
|
|
266
|
+
return undefined;
|
|
267
|
+
return coerceValue(raw);
|
|
268
|
+
}
|
|
269
|
+
/** A traceable link back to the exact eval case: its id/span id, else the row
|
|
270
|
+
* index. ⛔ Never the case's `input` (MAR-1548): /security says no prompt
|
|
271
|
+
* reaches us. */
|
|
272
|
+
export function linkFromResult(result, index) {
|
|
273
|
+
if (isObject(result)) {
|
|
274
|
+
for (const key of ["id", "span_id", "root_span_id"]) {
|
|
275
|
+
const val = result[key];
|
|
276
|
+
if (typeof val === "string" && val.trim())
|
|
277
|
+
return val.trim().slice(0, MAX_LINK_LEN);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return `braintrust:${index}`;
|
|
281
|
+
}
|
|
282
|
+
/** Derive the pass/fail for a case, or undefined when it cannot be derived
|
|
283
|
+
* honestly (no scores, or continuous scores with no threshold). */
|
|
284
|
+
export function passedFromScores(scores, threshold) {
|
|
285
|
+
const values = Object.values(scores);
|
|
286
|
+
if (values.length === 0)
|
|
287
|
+
return undefined;
|
|
288
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
289
|
+
if (threshold !== undefined)
|
|
290
|
+
return mean >= threshold;
|
|
291
|
+
if (values.every((v) => v === 0.0 || v === 1.0))
|
|
292
|
+
return values.every((v) => v === 1.0);
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
/** Translate one graded Braintrust result into `MarginMeter.recordOutcome` input.
|
|
296
|
+
* `judgeModelId` rides only when the grade is an LLM judge and a model was named;
|
|
297
|
+
* `valueUsd` rides only when a value was supplied. */
|
|
298
|
+
export function outcomeInput(args) {
|
|
299
|
+
const { qualityMethod, graderKind } = scorerSignature(args.scorerNames);
|
|
300
|
+
const input = {
|
|
301
|
+
workflowId: args.workflowId,
|
|
302
|
+
passed: Boolean(args.passed),
|
|
303
|
+
qualityScore: Number(args.qualityScore),
|
|
304
|
+
qualityMethod,
|
|
305
|
+
graderKind,
|
|
306
|
+
link: String(args.link).slice(0, MAX_LINK_LEN),
|
|
307
|
+
isSimulated: Boolean(args.isSimulated),
|
|
308
|
+
};
|
|
309
|
+
if (qualityMethod === LLM_JUDGE && args.judgeModelId)
|
|
310
|
+
input.judgeModelId = args.judgeModelId.slice(0, MAX_JUDGE_ID_LEN);
|
|
311
|
+
if (args.valueUsd !== undefined)
|
|
312
|
+
input.valueUsd = Number(args.valueUsd);
|
|
313
|
+
return input;
|
|
314
|
+
}
|
|
315
|
+
/** Full translation of one Braintrust result, or undefined if it is not a real
|
|
316
|
+
* verdict (no numeric score, or an ambiguous continuous score with no
|
|
317
|
+
* threshold). A malformed `margin_value` warns and drops the value while keeping
|
|
318
|
+
* the pass/fail. */
|
|
319
|
+
export function resultToOutcomeInput(result, opts) {
|
|
320
|
+
const onWarn = opts.onWarn ?? defaultWarn;
|
|
321
|
+
const index = opts.index ?? 0;
|
|
322
|
+
const scores = scoresOf(result);
|
|
323
|
+
const passed = passedFromScores(scores, opts.threshold);
|
|
324
|
+
if (passed === undefined) {
|
|
325
|
+
if (Object.keys(scores).length > 0) {
|
|
326
|
+
onWarn(`${linkFromResult(result, index)}: continuous scores and no threshold; ` +
|
|
327
|
+
"skipped (no verdict to record)");
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
const values = Object.values(scores);
|
|
332
|
+
const quality = values.reduce((a, b) => a + b, 0) / values.length;
|
|
333
|
+
let value;
|
|
334
|
+
try {
|
|
335
|
+
value = valueFromResult(result);
|
|
336
|
+
}
|
|
337
|
+
catch (exc) {
|
|
338
|
+
value = undefined;
|
|
339
|
+
onWarn(`${linkFromResult(result, index)}: ${exc.message}`);
|
|
340
|
+
}
|
|
341
|
+
return outcomeInput({
|
|
342
|
+
workflowId: opts.workflowId,
|
|
343
|
+
passed,
|
|
344
|
+
qualityScore: quality,
|
|
345
|
+
scorerNames: Object.keys(scores),
|
|
346
|
+
link: linkFromResult(result, index),
|
|
347
|
+
judgeModelId: judgeModelFromResult(result),
|
|
348
|
+
valueUsd: value,
|
|
349
|
+
isSimulated: opts.isSimulated,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
/** Pull the flat list of per-case results out of parsed Braintrust output. It
|
|
353
|
+
* reaches a file several ways: a bare list, the `EvalResultWithSummary` envelope
|
|
354
|
+
* (`{summary, results}`), a `{results}` wrapper, or the experiment-fetch export
|
|
355
|
+
* (`{events}`). This walks the known shapes rather than pinning one. */
|
|
356
|
+
export function extractResults(data) {
|
|
357
|
+
if (Array.isArray(data))
|
|
358
|
+
return data.filter(isObject);
|
|
359
|
+
if (!isObject(data))
|
|
360
|
+
return [];
|
|
361
|
+
for (const key of ["results", "events"]) {
|
|
362
|
+
const block = data[key];
|
|
363
|
+
if (Array.isArray(block))
|
|
364
|
+
return block.filter(isObject);
|
|
365
|
+
}
|
|
366
|
+
return [];
|
|
367
|
+
}
|
|
368
|
+
/** Yield `recordOutcome` inputs for every real verdict in parsed Braintrust
|
|
369
|
+
* output (any envelope shape {@link extractResults} understands). */
|
|
370
|
+
export function iterOutcomes(data, opts) {
|
|
371
|
+
const out = [];
|
|
372
|
+
const results = extractResults(data);
|
|
373
|
+
for (let i = 0; i < results.length; i++) {
|
|
374
|
+
const input = resultToOutcomeInput(results[i], {
|
|
375
|
+
workflowId: opts.workflowId,
|
|
376
|
+
index: i,
|
|
377
|
+
threshold: opts.threshold,
|
|
378
|
+
isSimulated: opts.isSimulated,
|
|
379
|
+
onWarn: opts.onWarn,
|
|
380
|
+
});
|
|
381
|
+
if (input !== undefined)
|
|
382
|
+
out.push(input);
|
|
383
|
+
}
|
|
384
|
+
return out;
|
|
385
|
+
}
|
|
386
|
+
/** Emit an outcome per verdict in parsed Braintrust output through `meter`.
|
|
387
|
+
* Fail-safe: a delivery fault on one row warns and counts an error; it never
|
|
388
|
+
* aborts the batch. Non-verdict rows are counted as skipped. */
|
|
389
|
+
export async function recordResults(meter, data, workflowId, opts = {}) {
|
|
390
|
+
const onWarn = opts.onWarn ?? defaultWarn;
|
|
391
|
+
const results = extractResults(data);
|
|
392
|
+
let recorded = 0;
|
|
393
|
+
let errors = 0;
|
|
394
|
+
let skipped = 0;
|
|
395
|
+
for (let i = 0; i < results.length; i++) {
|
|
396
|
+
const input = resultToOutcomeInput(results[i], {
|
|
397
|
+
workflowId,
|
|
398
|
+
index: i,
|
|
399
|
+
threshold: opts.threshold,
|
|
400
|
+
isSimulated: opts.isSimulated,
|
|
401
|
+
onWarn,
|
|
402
|
+
});
|
|
403
|
+
if (input === undefined) {
|
|
404
|
+
skipped++;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
await meter.recordOutcome(input);
|
|
409
|
+
recorded++;
|
|
410
|
+
}
|
|
411
|
+
catch (exc) {
|
|
412
|
+
errors++;
|
|
413
|
+
onWarn(`failed to record outcome for ${input.link}: ${exc.message}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
await meter.flush();
|
|
418
|
+
}
|
|
419
|
+
catch (exc) {
|
|
420
|
+
onWarn(`flush failed: ${exc.message}`);
|
|
421
|
+
}
|
|
422
|
+
return { recorded, errors, skipped };
|
|
423
|
+
}
|
|
424
|
+
//# sourceMappingURL=braintrust.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"braintrust.js","sourceRoot":"","sources":["../src/braintrust.ts"],"names":[],"mappings":"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"}
|