@takk/bayesoutputgate 1.0.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/LICENSE +190 -0
  3. package/NOTICE +45 -0
  4. package/README.md +403 -0
  5. package/SECURITY.md +98 -0
  6. package/SPEC.md +467 -0
  7. package/dist/adapter/index.cjs +411 -0
  8. package/dist/adapter/index.d.cts +29 -0
  9. package/dist/adapter/index.d.ts +29 -0
  10. package/dist/adapter/index.js +404 -0
  11. package/dist/audit/index.cjs +82 -0
  12. package/dist/audit/index.d.cts +40 -0
  13. package/dist/audit/index.d.ts +40 -0
  14. package/dist/audit/index.js +77 -0
  15. package/dist/bayesfactor/index.cjs +152 -0
  16. package/dist/bayesfactor/index.d.cts +15 -0
  17. package/dist/bayesfactor/index.d.ts +15 -0
  18. package/dist/bayesfactor/index.js +149 -0
  19. package/dist/beta/index.cjs +180 -0
  20. package/dist/beta/index.d.cts +45 -0
  21. package/dist/beta/index.d.ts +45 -0
  22. package/dist/beta/index.js +178 -0
  23. package/dist/calibration/index.cjs +339 -0
  24. package/dist/calibration/index.d.cts +53 -0
  25. package/dist/calibration/index.d.ts +53 -0
  26. package/dist/calibration/index.js +333 -0
  27. package/dist/cli/index.cjs +968 -0
  28. package/dist/cli/index.d.cts +1 -0
  29. package/dist/cli/index.d.ts +1 -0
  30. package/dist/cli/index.js +966 -0
  31. package/dist/dimensions/index.cjs +106 -0
  32. package/dist/dimensions/index.d.cts +33 -0
  33. package/dist/dimensions/index.d.ts +33 -0
  34. package/dist/dimensions/index.js +104 -0
  35. package/dist/edge/index.cjs +1141 -0
  36. package/dist/edge/index.d.cts +12 -0
  37. package/dist/edge/index.d.ts +12 -0
  38. package/dist/edge/index.js +1109 -0
  39. package/dist/gate/index.cjs +803 -0
  40. package/dist/gate/index.d.cts +77 -0
  41. package/dist/gate/index.d.ts +77 -0
  42. package/dist/gate/index.js +799 -0
  43. package/dist/hypothesis/index.cjs +268 -0
  44. package/dist/hypothesis/index.d.cts +38 -0
  45. package/dist/hypothesis/index.d.ts +38 -0
  46. package/dist/hypothesis/index.js +266 -0
  47. package/dist/index.cjs +1141 -0
  48. package/dist/index.d.cts +29 -0
  49. package/dist/index.d.ts +29 -0
  50. package/dist/index.js +1109 -0
  51. package/dist/likelihood/index.cjs +137 -0
  52. package/dist/likelihood/index.d.cts +23 -0
  53. package/dist/likelihood/index.d.ts +23 -0
  54. package/dist/likelihood/index.js +132 -0
  55. package/dist/node/index.cjs +1282 -0
  56. package/dist/node/index.d.cts +24 -0
  57. package/dist/node/index.d.ts +24 -0
  58. package/dist/node/index.js +1246 -0
  59. package/dist/policy/index.cjs +88 -0
  60. package/dist/policy/index.d.cts +11 -0
  61. package/dist/policy/index.d.ts +11 -0
  62. package/dist/policy/index.js +85 -0
  63. package/dist/types-bMjn1j4e.d.cts +159 -0
  64. package/dist/types-bMjn1j4e.d.ts +159 -0
  65. package/package.json +142 -0
@@ -0,0 +1,411 @@
1
+ 'use strict';
2
+
3
+ // src/errors.ts
4
+ var BayesOutputGateError = class _BayesOutputGateError extends Error {
5
+ code;
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.name = "BayesOutputGateError";
9
+ this.code = code;
10
+ Object.setPrototypeOf(this, _BayesOutputGateError.prototype);
11
+ }
12
+ };
13
+ function invariant(condition, code, message) {
14
+ if (!condition) {
15
+ throw new BayesOutputGateError(code, message);
16
+ }
17
+ }
18
+
19
+ // src/mathspecial.ts
20
+ var LN_SQRT_2PI = 0.9189385332046728;
21
+ var LANCZOS_G = 7;
22
+ var LANCZOS_COEFFICIENTS = [
23
+ 0.9999999999998099,
24
+ 676.5203681218851,
25
+ -1259.1392167224028,
26
+ 771.3234287776531,
27
+ -176.6150291621406,
28
+ 12.507343278686905,
29
+ -0.13857109526572012,
30
+ 9984369578019572e-21,
31
+ 15056327351493116e-23
32
+ ];
33
+ function lgamma(x) {
34
+ if (!Number.isFinite(x) || x <= 0) {
35
+ throw new BayesOutputGateError(
36
+ "NUMERIC",
37
+ `lgamma requires a positive finite argument, got ${x}`
38
+ );
39
+ }
40
+ const z = x - 1;
41
+ let acc = LANCZOS_COEFFICIENTS[0];
42
+ for (let i = 1; i < LANCZOS_COEFFICIENTS.length; i++) {
43
+ acc += LANCZOS_COEFFICIENTS[i] / (z + i);
44
+ }
45
+ const t = z + LANCZOS_G + 0.5;
46
+ return LN_SQRT_2PI + (z + 0.5) * Math.log(t) - t + Math.log(acc);
47
+ }
48
+ function lbeta(a, b) {
49
+ return lgamma(a) + lgamma(b) - lgamma(a + b);
50
+ }
51
+ function betaLogDensity(x, a, b) {
52
+ if (a <= 0 || b <= 0) {
53
+ throw new BayesOutputGateError(
54
+ "NUMERIC",
55
+ `Beta shape parameters must be positive, got a=${a}, b=${b}`
56
+ );
57
+ }
58
+ if (x < 0 || x > 1 || !Number.isFinite(x)) {
59
+ return Number.NEGATIVE_INFINITY;
60
+ }
61
+ if (x === 0) {
62
+ if (a < 1) return Number.POSITIVE_INFINITY;
63
+ if (a > 1) return Number.NEGATIVE_INFINITY;
64
+ return -lbeta(a, b) + (b - 1) * Math.log(1);
65
+ }
66
+ if (x === 1) {
67
+ if (b < 1) return Number.POSITIVE_INFINITY;
68
+ if (b > 1) return Number.NEGATIVE_INFINITY;
69
+ return -lbeta(a, b);
70
+ }
71
+ return (a - 1) * Math.log(x) + (b - 1) * Math.log1p(-x) - lbeta(a, b);
72
+ }
73
+ function clamp(x, lo, hi) {
74
+ if (x < lo) return lo;
75
+ if (x > hi) return hi;
76
+ return x;
77
+ }
78
+
79
+ // src/bayesfactor/index.ts
80
+ var LN_3 = Math.log(3);
81
+ var LN_10 = Math.log(10);
82
+ var LN_100 = Math.log(100);
83
+ var DENSITY_EPSILON = 1e-6;
84
+ function jeffreysStrength(logBayesFactor) {
85
+ const x = logBayesFactor;
86
+ if (x >= LN_100) return "decisive-high";
87
+ if (x >= LN_10) return "strong-high";
88
+ if (x >= LN_3) return "substantial-high";
89
+ if (x > -LN_3) return "inconclusive";
90
+ if (x > -LN_10) return "substantial-low";
91
+ if (x > -LN_100) return "strong-low";
92
+ return "decisive-low";
93
+ }
94
+ function bayesFactor(scores, models) {
95
+ invariant(models.length > 0, "INVALID_CONFIG", "at least one dimension model is required");
96
+ const map = /* @__PURE__ */ new Map();
97
+ for (const s of scores) {
98
+ invariant(
99
+ Number.isFinite(s.value) && s.value >= 0 && s.value <= 1,
100
+ "INVALID_SCORE",
101
+ `score for "${s.dimension}" must be a finite number in [0, 1], got ${s.value}`
102
+ );
103
+ invariant(
104
+ !map.has(s.dimension),
105
+ "INVALID_SCORE",
106
+ `duplicate score for dimension "${s.dimension}"`
107
+ );
108
+ map.set(s.dimension, s.value);
109
+ }
110
+ const contributions = [];
111
+ let logBayesFactor = 0;
112
+ for (const model of models) {
113
+ const value = map.get(model.dimension);
114
+ if (value === void 0) {
115
+ continue;
116
+ }
117
+ invariant(
118
+ Number.isFinite(model.weight) && model.weight >= 0,
119
+ "INVALID_CONFIG",
120
+ `weight for "${model.dimension}" must be a non-negative finite number, got ${model.weight}`
121
+ );
122
+ const x = clamp(value, DENSITY_EPSILON, 1 - DENSITY_EPSILON);
123
+ const logHigh = betaLogDensity(x, model.high.a, model.high.b);
124
+ const logLow = betaLogDensity(x, model.low.a, model.low.b);
125
+ const weighted = model.weight * (logHigh - logLow);
126
+ logBayesFactor += weighted;
127
+ contributions.push({
128
+ dimension: model.dimension,
129
+ score: value,
130
+ logBayesFactor: weighted,
131
+ weight: model.weight
132
+ });
133
+ }
134
+ invariant(
135
+ contributions.length > 0,
136
+ "INVALID_SCORE",
137
+ "no scored dimension matched a configured dimension model"
138
+ );
139
+ invariant(Number.isFinite(logBayesFactor), "NUMERIC", "combined log-Bayes-Factor is not finite");
140
+ const strength = jeffreysStrength(logBayesFactor);
141
+ const favored = strength.endsWith("high") ? "high" : strength.endsWith("low") ? "low" : "inconclusive";
142
+ return {
143
+ logBayesFactor,
144
+ bayesFactor: Math.exp(logBayesFactor),
145
+ favored,
146
+ strength,
147
+ contributions
148
+ };
149
+ }
150
+
151
+ // src/policy/index.ts
152
+ function posteriorHighQuality(logBayesFactor, priorHighQuality) {
153
+ invariant(
154
+ priorHighQuality > 0 && priorHighQuality < 1,
155
+ "INVALID_CONFIG",
156
+ `priorHighQuality must be in (0, 1), got ${priorHighQuality}`
157
+ );
158
+ invariant(
159
+ Number.isFinite(logBayesFactor),
160
+ "NUMERIC",
161
+ `logBayesFactor must be finite, got ${logBayesFactor}`
162
+ );
163
+ const logPosteriorOdds = logBayesFactor + Math.log(priorHighQuality / (1 - priorHighQuality));
164
+ if (logPosteriorOdds >= 0) {
165
+ return 1 / (1 + Math.exp(-logPosteriorOdds));
166
+ }
167
+ const odds = Math.exp(logPosteriorOdds);
168
+ return odds / (1 + odds);
169
+ }
170
+ function decide(logBayesFactor, policy) {
171
+ if (policy.kind === "bayes-factor") {
172
+ invariant(
173
+ policy.passAbove >= 1,
174
+ "INVALID_CONFIG",
175
+ `passAbove must be >= 1, got ${policy.passAbove}`
176
+ );
177
+ invariant(
178
+ policy.failBelow > 0 && policy.failBelow <= 1,
179
+ "INVALID_CONFIG",
180
+ `failBelow must be in (0, 1], got ${policy.failBelow}`
181
+ );
182
+ invariant(
183
+ policy.passAbove > policy.failBelow,
184
+ "INVALID_CONFIG",
185
+ "passAbove must be greater than failBelow"
186
+ );
187
+ const bayesFactor2 = Math.exp(logBayesFactor);
188
+ const action2 = bayesFactor2 >= policy.passAbove ? "pass" : bayesFactor2 <= policy.failBelow ? "fail" : "escalate";
189
+ return { action: action2, rationale: "bayes-factor" };
190
+ }
191
+ invariant(
192
+ policy.lossFalsePass >= 0 && policy.lossFalseFail >= 0 && policy.escalationCost >= 0,
193
+ "INVALID_CONFIG",
194
+ "decision-theoretic losses must be non-negative"
195
+ );
196
+ invariant(
197
+ Number.isFinite(policy.lossFalsePass) && Number.isFinite(policy.lossFalseFail) && Number.isFinite(policy.escalationCost),
198
+ "INVALID_CONFIG",
199
+ "decision-theoretic losses must be finite"
200
+ );
201
+ const posterior = posteriorHighQuality(logBayesFactor, policy.priorHighQuality);
202
+ const expectedLoss = {
203
+ pass: (1 - posterior) * policy.lossFalsePass,
204
+ fail: posterior * policy.lossFalseFail,
205
+ escalate: policy.escalationCost
206
+ };
207
+ let action = "pass";
208
+ let best = expectedLoss.pass;
209
+ if (expectedLoss.fail < best) {
210
+ action = "fail";
211
+ best = expectedLoss.fail;
212
+ }
213
+ if (expectedLoss.escalate < best) {
214
+ action = "escalate";
215
+ }
216
+ return { action, rationale: "expected-loss", posteriorHighQuality: posterior, expectedLoss };
217
+ }
218
+
219
+ // src/gate/index.ts
220
+ function toDecision(factor, policy, guards) {
221
+ const base = {
222
+ action: policy.action,
223
+ bayesFactor: factor.bayesFactor,
224
+ logBayesFactor: factor.logBayesFactor,
225
+ strength: factor.strength,
226
+ rationale: policy.rationale,
227
+ contributions: factor.contributions,
228
+ ...policy.posteriorHighQuality !== void 0 ? { posteriorHighQuality: policy.posteriorHighQuality } : {},
229
+ ...policy.expectedLoss !== void 0 ? { expectedLoss: policy.expectedLoss } : {}
230
+ };
231
+ {
232
+ return base;
233
+ }
234
+ }
235
+ function evaluate(scores, models, policy, guards) {
236
+ const factor = bayesFactor(scores, models);
237
+ const decision = decide(factor.logBayesFactor, policy);
238
+ return toDecision(factor, decision);
239
+ }
240
+
241
+ // src/adapter/index.ts
242
+ var TOOL_NAME = "bayes_output_gate";
243
+ var DEFAULT_POLICY = { kind: "bayes-factor", passAbove: 10, failBelow: 0.1 };
244
+ function toJsonSafe(value) {
245
+ if (typeof value === "number") {
246
+ return Number.isFinite(value) ? value : null;
247
+ }
248
+ if (Array.isArray(value)) {
249
+ return value.map((item) => toJsonSafe(item));
250
+ }
251
+ if (value !== null && typeof value === "object") {
252
+ const out = {};
253
+ for (const [key, item] of Object.entries(value)) {
254
+ out[key] = toJsonSafe(item);
255
+ }
256
+ return out;
257
+ }
258
+ return value;
259
+ }
260
+ function asObject(value, message) {
261
+ invariant(
262
+ typeof value === "object" && value !== null && !Array.isArray(value),
263
+ "INVALID_CONFIG",
264
+ message
265
+ );
266
+ return value;
267
+ }
268
+ function parseBeta(value, where) {
269
+ const obj = asObject(value, `${where} must be an object with positive a and b`);
270
+ const a = obj["a"];
271
+ const b = obj["b"];
272
+ invariant(
273
+ typeof a === "number" && typeof b === "number" && a > 0 && b > 0,
274
+ "INVALID_CONFIG",
275
+ `${where} must have positive numeric a and b`
276
+ );
277
+ return { a, b };
278
+ }
279
+ function parseScore(value) {
280
+ const obj = asObject(value, "each score must be an object with dimension and value");
281
+ const dimension = obj["dimension"];
282
+ const score = obj["value"];
283
+ invariant(
284
+ typeof dimension === "string" && dimension.length > 0,
285
+ "INVALID_SCORE",
286
+ "score dimension must be a non-empty string"
287
+ );
288
+ invariant(
289
+ typeof score === "number" && Number.isFinite(score) && score >= 0 && score <= 1,
290
+ "INVALID_SCORE",
291
+ `score value for "${dimension}" must be a finite number in [0, 1]`
292
+ );
293
+ return { dimension, value: score };
294
+ }
295
+ function parseModel(value) {
296
+ const obj = asObject(value, "each model must be an object");
297
+ const dimension = obj["dimension"];
298
+ invariant(
299
+ typeof dimension === "string" && dimension.length > 0,
300
+ "INVALID_CONFIG",
301
+ "model dimension must be a non-empty string"
302
+ );
303
+ const rawWeight = obj["weight"];
304
+ const weight = rawWeight === void 0 ? 1 : rawWeight;
305
+ invariant(
306
+ typeof weight === "number" && Number.isFinite(weight) && weight >= 0,
307
+ "INVALID_CONFIG",
308
+ `model weight for "${dimension}" must be a non-negative number`
309
+ );
310
+ return {
311
+ dimension,
312
+ high: parseBeta(obj["high"], `model "${dimension}" high`),
313
+ low: parseBeta(obj["low"], `model "${dimension}" low`),
314
+ weight
315
+ };
316
+ }
317
+ function parsePolicy(value) {
318
+ if (value === void 0) {
319
+ return DEFAULT_POLICY;
320
+ }
321
+ const obj = asObject(value, "policy must be an object");
322
+ const kind = obj["kind"];
323
+ if (kind === "decision-theoretic") {
324
+ const prior = obj["priorHighQuality"];
325
+ const lossFalsePass = obj["lossFalsePass"];
326
+ const lossFalseFail = obj["lossFalseFail"];
327
+ const escalationCost = obj["escalationCost"];
328
+ invariant(
329
+ typeof prior === "number" && typeof lossFalsePass === "number" && typeof lossFalseFail === "number" && typeof escalationCost === "number",
330
+ "INVALID_CONFIG",
331
+ "decision-theoretic policy requires numeric priorHighQuality, lossFalsePass, lossFalseFail, escalationCost"
332
+ );
333
+ return {
334
+ kind: "decision-theoretic",
335
+ priorHighQuality: prior,
336
+ lossFalsePass,
337
+ lossFalseFail,
338
+ escalationCost
339
+ };
340
+ }
341
+ invariant(kind === "bayes-factor", "INVALID_CONFIG", `unknown policy kind ${String(kind)}`);
342
+ const passAbove = obj["passAbove"];
343
+ const failBelow = obj["failBelow"];
344
+ invariant(
345
+ typeof passAbove === "number" && typeof failBelow === "number",
346
+ "INVALID_CONFIG",
347
+ "bayes-factor policy requires numeric passAbove and failBelow"
348
+ );
349
+ return { kind: "bayes-factor", passAbove, failBelow };
350
+ }
351
+ function parseGateInput(input) {
352
+ const obj = asObject(input, "tool input must be an object");
353
+ invariant(Array.isArray(obj["scores"]), "INVALID_SCORE", "scores must be an array");
354
+ invariant(Array.isArray(obj["models"]), "INVALID_CONFIG", "models must be an array");
355
+ const scores = obj["scores"].map(parseScore);
356
+ const models = obj["models"].map(parseModel);
357
+ invariant(models.length > 0, "INVALID_CONFIG", "at least one model is required");
358
+ return { scores, models, policy: parsePolicy(obj["policy"]) };
359
+ }
360
+ function runTool(input) {
361
+ const { scores, models, policy } = parseGateInput(input);
362
+ return evaluate(scores, models, policy);
363
+ }
364
+ function describeTool() {
365
+ return {
366
+ name: TOOL_NAME,
367
+ description: "Weigh an output's per-dimension quality scores against a high-quality and a low-quality hypothesis with a calibrated Bayes Factor, and return a pass, fail, or escalate decision on the Jeffreys scale.",
368
+ inputSchema: {
369
+ type: "object",
370
+ required: ["scores", "models"],
371
+ properties: {
372
+ scores: {
373
+ type: "array",
374
+ items: {
375
+ type: "object",
376
+ required: ["dimension", "value"],
377
+ properties: {
378
+ dimension: { type: "string" },
379
+ value: { type: "number", minimum: 0, maximum: 1 }
380
+ }
381
+ }
382
+ },
383
+ models: {
384
+ type: "array",
385
+ items: {
386
+ type: "object",
387
+ required: ["dimension", "high", "low"],
388
+ properties: {
389
+ dimension: { type: "string" },
390
+ high: { type: "object", required: ["a", "b"] },
391
+ low: { type: "object", required: ["a", "b"] },
392
+ weight: { type: "number", minimum: 0 }
393
+ }
394
+ }
395
+ },
396
+ policy: { type: "object" }
397
+ }
398
+ }
399
+ };
400
+ }
401
+ var bayesOutputGateTool = {
402
+ ...describeTool(),
403
+ handler: (input) => toJsonSafe(runTool(input))
404
+ };
405
+
406
+ exports.TOOL_NAME = TOOL_NAME;
407
+ exports.bayesOutputGateTool = bayesOutputGateTool;
408
+ exports.describeTool = describeTool;
409
+ exports.parseGateInput = parseGateInput;
410
+ exports.runTool = runTool;
411
+ exports.toJsonSafe = toJsonSafe;
@@ -0,0 +1,29 @@
1
+ import { Q as QualityScore, e as DimensionModel, P as PolicyConfig, g as GateDecision } from '../types-bMjn1j4e.cjs';
2
+
3
+ /** The tool name an MCP server or LLM tool-calling API registers. */
4
+ declare const TOOL_NAME = "bayes_output_gate";
5
+ /** Recursively replace non-finite numbers with null so the value is safe to JSON-serialize. */
6
+ declare function toJsonSafe(value: unknown): unknown;
7
+ /** Defensively parse tool input from a model into validated scores, models, and policy. */
8
+ declare function parseGateInput(input: unknown): {
9
+ scores: QualityScore[];
10
+ models: DimensionModel[];
11
+ policy: PolicyConfig;
12
+ };
13
+ /** Run the gate from raw tool input and return the decision. */
14
+ declare function runTool(input: unknown): GateDecision;
15
+ /** The JSON Schema descriptor an MCP server or tool-calling API registers. */
16
+ declare function describeTool(): {
17
+ name: string;
18
+ description: string;
19
+ inputSchema: Record<string, unknown>;
20
+ };
21
+ /** The framework-agnostic tool: descriptor plus a JSON-safe handler. */
22
+ declare const bayesOutputGateTool: {
23
+ handler: (input: unknown) => unknown;
24
+ name: string;
25
+ description: string;
26
+ inputSchema: Record<string, unknown>;
27
+ };
28
+
29
+ export { TOOL_NAME, bayesOutputGateTool, describeTool, parseGateInput, runTool, toJsonSafe };
@@ -0,0 +1,29 @@
1
+ import { Q as QualityScore, e as DimensionModel, P as PolicyConfig, g as GateDecision } from '../types-bMjn1j4e.js';
2
+
3
+ /** The tool name an MCP server or LLM tool-calling API registers. */
4
+ declare const TOOL_NAME = "bayes_output_gate";
5
+ /** Recursively replace non-finite numbers with null so the value is safe to JSON-serialize. */
6
+ declare function toJsonSafe(value: unknown): unknown;
7
+ /** Defensively parse tool input from a model into validated scores, models, and policy. */
8
+ declare function parseGateInput(input: unknown): {
9
+ scores: QualityScore[];
10
+ models: DimensionModel[];
11
+ policy: PolicyConfig;
12
+ };
13
+ /** Run the gate from raw tool input and return the decision. */
14
+ declare function runTool(input: unknown): GateDecision;
15
+ /** The JSON Schema descriptor an MCP server or tool-calling API registers. */
16
+ declare function describeTool(): {
17
+ name: string;
18
+ description: string;
19
+ inputSchema: Record<string, unknown>;
20
+ };
21
+ /** The framework-agnostic tool: descriptor plus a JSON-safe handler. */
22
+ declare const bayesOutputGateTool: {
23
+ handler: (input: unknown) => unknown;
24
+ name: string;
25
+ description: string;
26
+ inputSchema: Record<string, unknown>;
27
+ };
28
+
29
+ export { TOOL_NAME, bayesOutputGateTool, describeTool, parseGateInput, runTool, toJsonSafe };