@attest-ai/core 0.6.0 → 0.9.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/dist/cjs/index.cjs +630 -25
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +117 -6
- package/dist/cjs/proto/index.cjs +81 -0
- package/dist/cjs/proto/index.cjs.map +1 -1
- package/dist/cjs/proto/index.d.cts +102 -1
- package/dist/esm/cli.js +321 -2
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/index.d.ts +117 -6
- package/dist/esm/index.js +619 -25
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/proto/index.d.ts +102 -1
- package/dist/esm/proto/index.js +77 -0
- package/dist/esm/proto/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cjs/index.cjs
CHANGED
|
@@ -57,6 +57,8 @@ __export(index_exports, {
|
|
|
57
57
|
OllamaAdapter: () => OllamaAdapter,
|
|
58
58
|
OpenAIAdapter: () => OpenAIAdapter,
|
|
59
59
|
PluginRegistry: () => PluginRegistry,
|
|
60
|
+
ProtocolDesyncError: () => ProtocolDesyncError,
|
|
61
|
+
ProtocolDiagnosticBuffer: () => ProtocolDiagnosticBuffer,
|
|
60
62
|
ProtocolError: () => ProtocolError,
|
|
61
63
|
STATUS_HARD_FAIL: () => STATUS_HARD_FAIL,
|
|
62
64
|
STATUS_PASS: () => STATUS_PASS,
|
|
@@ -85,6 +87,7 @@ __export(index_exports, {
|
|
|
85
87
|
agentId: () => agentId,
|
|
86
88
|
assertionId: () => assertionId,
|
|
87
89
|
attestExpect: () => attestExpect,
|
|
90
|
+
computeAgreement: () => computeAgreement,
|
|
88
91
|
config: () => config,
|
|
89
92
|
decodeResponse: () => decodeResponse,
|
|
90
93
|
delegate: () => delegate,
|
|
@@ -94,6 +97,14 @@ __export(index_exports, {
|
|
|
94
97
|
extractResult: () => extractResult,
|
|
95
98
|
getSampleRate: () => getSampleRate,
|
|
96
99
|
isSimulationMode: () => isSimulationMode,
|
|
100
|
+
layerForType: () => layerForType,
|
|
101
|
+
loadLabelsCSV: () => loadLabelsCSV,
|
|
102
|
+
loadLabelsJSONL: () => loadLabelsJSONL,
|
|
103
|
+
previewLine: () => previewLine,
|
|
104
|
+
promptHash: () => promptHash,
|
|
105
|
+
renderAssertionFailure: () => renderAssertionFailure,
|
|
106
|
+
renderDiagnostics: () => renderDiagnostics,
|
|
107
|
+
renderSummary: () => renderSummary,
|
|
97
108
|
resetConfig: () => resetConfig,
|
|
98
109
|
simulationEvaluateBatch: () => simulationEvaluateBatch,
|
|
99
110
|
tier: () => tier,
|
|
@@ -134,6 +145,27 @@ function assertionId(id) {
|
|
|
134
145
|
function agentId(id) {
|
|
135
146
|
return id;
|
|
136
147
|
}
|
|
148
|
+
function layerForType(assertionType) {
|
|
149
|
+
switch (assertionType) {
|
|
150
|
+
case "schema":
|
|
151
|
+
return 1;
|
|
152
|
+
case "constraint":
|
|
153
|
+
return 2;
|
|
154
|
+
case "trace":
|
|
155
|
+
case "trace_tree":
|
|
156
|
+
return 3;
|
|
157
|
+
case "content":
|
|
158
|
+
return 4;
|
|
159
|
+
case "embedding":
|
|
160
|
+
return 5;
|
|
161
|
+
case "llm_judge":
|
|
162
|
+
return 6;
|
|
163
|
+
case "plugin":
|
|
164
|
+
return 8;
|
|
165
|
+
default:
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
137
169
|
|
|
138
170
|
// src/proto/errors.ts
|
|
139
171
|
var ProtocolError = class extends Error {
|
|
@@ -160,6 +192,58 @@ var EngineTimeoutError = class extends Error {
|
|
|
160
192
|
this.timeoutMs = timeoutMs;
|
|
161
193
|
}
|
|
162
194
|
};
|
|
195
|
+
var ProtocolDesyncError = class extends Error {
|
|
196
|
+
diagnostics;
|
|
197
|
+
constructor(message, diagnostics) {
|
|
198
|
+
super(message);
|
|
199
|
+
this.name = "ProtocolDesyncError";
|
|
200
|
+
this.diagnostics = diagnostics;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// src/proto/diagnostics.ts
|
|
205
|
+
var DEFAULT_BUFFER_SIZE = 32;
|
|
206
|
+
var MAX_LINE_PREVIEW = 512;
|
|
207
|
+
var ProtocolDiagnosticBuffer = class {
|
|
208
|
+
capacity;
|
|
209
|
+
entries = [];
|
|
210
|
+
constructor(capacity = DEFAULT_BUFFER_SIZE) {
|
|
211
|
+
if (capacity <= 0) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
`ProtocolDiagnosticBuffer capacity must be positive, got ${capacity}`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
this.capacity = capacity;
|
|
217
|
+
}
|
|
218
|
+
push(diagnostic) {
|
|
219
|
+
if (this.entries.length >= this.capacity) {
|
|
220
|
+
this.entries.shift();
|
|
221
|
+
}
|
|
222
|
+
this.entries.push(diagnostic);
|
|
223
|
+
}
|
|
224
|
+
snapshot() {
|
|
225
|
+
return this.entries.slice();
|
|
226
|
+
}
|
|
227
|
+
countWithin(windowMs, nowMs) {
|
|
228
|
+
const cutoff = nowMs - windowMs;
|
|
229
|
+
let count = 0;
|
|
230
|
+
for (let i = this.entries.length - 1; i >= 0; i -= 1) {
|
|
231
|
+
if (this.entries[i].timestampMs >= cutoff) {
|
|
232
|
+
count += 1;
|
|
233
|
+
} else {
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return count;
|
|
238
|
+
}
|
|
239
|
+
clear() {
|
|
240
|
+
this.entries.length = 0;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
function previewLine(line) {
|
|
244
|
+
if (line.length <= MAX_LINE_PREVIEW) return line;
|
|
245
|
+
return line.slice(0, MAX_LINE_PREVIEW) + "\u2026";
|
|
246
|
+
}
|
|
163
247
|
|
|
164
248
|
// src/proto/codec.ts
|
|
165
249
|
function encodeRequest(id, method, params) {
|
|
@@ -222,8 +306,8 @@ function extractId(response) {
|
|
|
222
306
|
}
|
|
223
307
|
|
|
224
308
|
// src/version.ts
|
|
225
|
-
var VERSION = "0.
|
|
226
|
-
var ENGINE_VERSION = "0.
|
|
309
|
+
var VERSION = "0.9.0";
|
|
310
|
+
var ENGINE_VERSION = "0.9.0";
|
|
227
311
|
|
|
228
312
|
// src/engine-manager.ts
|
|
229
313
|
var import_node_child_process = require("child_process");
|
|
@@ -576,12 +660,16 @@ function simulationEvaluateBatch(assertions) {
|
|
|
576
660
|
return {
|
|
577
661
|
results,
|
|
578
662
|
total_cost: 0,
|
|
579
|
-
total_duration_ms: 0
|
|
663
|
+
total_duration_ms: 0,
|
|
664
|
+
simulated: true
|
|
580
665
|
};
|
|
581
666
|
}
|
|
582
667
|
|
|
583
668
|
// src/client.ts
|
|
584
669
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
670
|
+
var DEFAULT_DIAGNOSTIC_BUFFER = 32;
|
|
671
|
+
var DEFAULT_DESYNC_THRESHOLD = 3;
|
|
672
|
+
var DEFAULT_DESYNC_WINDOW_MS = 1e3;
|
|
585
673
|
function resolveTimeoutMs(override) {
|
|
586
674
|
if (override !== void 0) return override;
|
|
587
675
|
const env = process.env["ATTEST_ENGINE_TIMEOUT"];
|
|
@@ -603,6 +691,24 @@ function makeTimeoutPromise(method, timeoutMs) {
|
|
|
603
691
|
}, timeoutMs);
|
|
604
692
|
});
|
|
605
693
|
}
|
|
694
|
+
function debugProtocolEnabled() {
|
|
695
|
+
const flag = process.env["ATTEST_DEBUG_PROTOCOL"];
|
|
696
|
+
return flag === "1" || flag === "true" || flag === "yes";
|
|
697
|
+
}
|
|
698
|
+
function envLoggerDefault() {
|
|
699
|
+
if (debugProtocolEnabled()) {
|
|
700
|
+
return {
|
|
701
|
+
warn: (msg, ...args) => console.warn(msg, ...args),
|
|
702
|
+
error: (msg, ...args) => console.error(msg, ...args)
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
warn: () => {
|
|
707
|
+
},
|
|
708
|
+
error: () => {
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
}
|
|
606
712
|
var AttestClient = class {
|
|
607
713
|
engine;
|
|
608
714
|
requestId = 0;
|
|
@@ -610,8 +716,34 @@ var AttestClient = class {
|
|
|
610
716
|
writeChain = Promise.resolve();
|
|
611
717
|
readerActive = false;
|
|
612
718
|
lineHandler = null;
|
|
613
|
-
|
|
719
|
+
logger;
|
|
720
|
+
diagnosticBuffer;
|
|
721
|
+
diagnosticListeners = /* @__PURE__ */ new Set();
|
|
722
|
+
desyncThreshold;
|
|
723
|
+
desyncWindowMs;
|
|
724
|
+
desynced = false;
|
|
725
|
+
constructor(engine, options) {
|
|
614
726
|
this.engine = engine;
|
|
727
|
+
this.logger = options?.logger ?? envLoggerDefault();
|
|
728
|
+
this.diagnosticBuffer = new ProtocolDiagnosticBuffer(
|
|
729
|
+
options?.diagnosticBufferSize ?? DEFAULT_DIAGNOSTIC_BUFFER
|
|
730
|
+
);
|
|
731
|
+
this.desyncThreshold = options?.desyncThreshold ?? DEFAULT_DESYNC_THRESHOLD;
|
|
732
|
+
this.desyncWindowMs = options?.desyncWindowMs ?? DEFAULT_DESYNC_WINDOW_MS;
|
|
733
|
+
if (options?.onDiagnostic !== void 0) {
|
|
734
|
+
this.diagnosticListeners.add(options.onDiagnostic);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
/** Subscribe to protocol diagnostics. Returns an unsubscribe function. */
|
|
738
|
+
onProtocolDiagnostic(listener) {
|
|
739
|
+
this.diagnosticListeners.add(listener);
|
|
740
|
+
return () => {
|
|
741
|
+
this.diagnosticListeners.delete(listener);
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
/** Snapshot of the diagnostic ring buffer (most recent last). */
|
|
745
|
+
protocolDiagnostics() {
|
|
746
|
+
return this.diagnosticBuffer.snapshot();
|
|
615
747
|
}
|
|
616
748
|
startReader() {
|
|
617
749
|
if (this.readerActive) return;
|
|
@@ -636,35 +768,101 @@ var AttestClient = class {
|
|
|
636
768
|
this.lineHandler = null;
|
|
637
769
|
this.readerActive = false;
|
|
638
770
|
}
|
|
771
|
+
recordDiagnostic(kind, message, rawLine) {
|
|
772
|
+
const diagnostic = {
|
|
773
|
+
kind,
|
|
774
|
+
message,
|
|
775
|
+
rawLine: previewLine(rawLine),
|
|
776
|
+
timestampMs: Date.now()
|
|
777
|
+
};
|
|
778
|
+
this.diagnosticBuffer.push(diagnostic);
|
|
779
|
+
this.logger.warn(`[attest.protocol] ${kind}: ${message}`);
|
|
780
|
+
for (const listener of this.diagnosticListeners) {
|
|
781
|
+
try {
|
|
782
|
+
listener(diagnostic);
|
|
783
|
+
} catch (err) {
|
|
784
|
+
this.logger.error(
|
|
785
|
+
`[attest.protocol] diagnostic listener threw: ${err instanceof Error ? err.message : String(err)}`
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
this.maybeTriggerDesync(diagnostic.timestampMs);
|
|
790
|
+
}
|
|
791
|
+
maybeTriggerDesync(nowMs) {
|
|
792
|
+
if (this.desynced) return;
|
|
793
|
+
const recent = this.diagnosticBuffer.countWithin(this.desyncWindowMs, nowMs);
|
|
794
|
+
if (recent >= this.desyncThreshold) {
|
|
795
|
+
this.desynced = true;
|
|
796
|
+
const snapshot = this.diagnosticBuffer.snapshot();
|
|
797
|
+
const err = new ProtocolDesyncError(
|
|
798
|
+
`Engine protocol desync: ${recent} unroutable response lines within ${this.desyncWindowMs}ms.`,
|
|
799
|
+
snapshot
|
|
800
|
+
);
|
|
801
|
+
this.logger.error(`[attest.protocol] ${err.message}`);
|
|
802
|
+
this.failAll(err);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
639
805
|
handleLine(line) {
|
|
806
|
+
let response;
|
|
807
|
+
try {
|
|
808
|
+
response = decodeResponse(line);
|
|
809
|
+
} catch (err) {
|
|
810
|
+
this.handleDecodeFailure(line, err);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
640
813
|
let reqId;
|
|
641
814
|
try {
|
|
642
|
-
const response = decodeResponse(line);
|
|
643
815
|
reqId = extractId(response);
|
|
644
|
-
const pending = this.pending.get(reqId);
|
|
645
|
-
if (pending === void 0) return;
|
|
646
|
-
this.pending.delete(reqId);
|
|
647
|
-
try {
|
|
648
|
-
const result = extractResult(response);
|
|
649
|
-
pending.resolve(result);
|
|
650
|
-
} catch (err) {
|
|
651
|
-
pending.reject(err);
|
|
652
|
-
}
|
|
653
816
|
} catch (err) {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
817
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
818
|
+
this.recordDiagnostic("missing_id", message, line);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
const pending = this.pending.get(reqId);
|
|
822
|
+
if (pending === void 0) {
|
|
823
|
+
this.recordDiagnostic(
|
|
824
|
+
"unknown_id",
|
|
825
|
+
`no pending request for id=${reqId}`,
|
|
826
|
+
line
|
|
827
|
+
);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
this.pending.delete(reqId);
|
|
831
|
+
try {
|
|
832
|
+
const result = extractResult(response);
|
|
833
|
+
pending.resolve(result);
|
|
834
|
+
} catch (err) {
|
|
835
|
+
pending.reject(err);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
handleDecodeFailure(line, err) {
|
|
839
|
+
if (err instanceof ProtocolError) {
|
|
840
|
+
let reqId = -1;
|
|
841
|
+
try {
|
|
842
|
+
const raw = JSON.parse(line.trim());
|
|
843
|
+
const idCandidate = raw["id"];
|
|
844
|
+
if (typeof idCandidate === "number" && Number.isFinite(idCandidate)) {
|
|
845
|
+
reqId = idCandidate;
|
|
665
846
|
}
|
|
847
|
+
} catch {
|
|
848
|
+
reqId = -1;
|
|
849
|
+
}
|
|
850
|
+
const pending = this.pending.get(reqId);
|
|
851
|
+
if (pending !== void 0) {
|
|
852
|
+
this.pending.delete(reqId);
|
|
853
|
+
pending.reject(err);
|
|
854
|
+
return;
|
|
666
855
|
}
|
|
856
|
+
this.recordDiagnostic(
|
|
857
|
+
"non_routable_error",
|
|
858
|
+
`engine error code=${err.code} (${err.errorMessage}) had no matching pending id`,
|
|
859
|
+
line
|
|
860
|
+
);
|
|
861
|
+
return;
|
|
667
862
|
}
|
|
863
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
864
|
+
const kind = classifyDecodeError(message);
|
|
865
|
+
this.recordDiagnostic(kind, message, line);
|
|
668
866
|
}
|
|
669
867
|
failAll(err) {
|
|
670
868
|
for (const pending of this.pending.values()) {
|
|
@@ -673,6 +871,12 @@ var AttestClient = class {
|
|
|
673
871
|
this.pending.clear();
|
|
674
872
|
}
|
|
675
873
|
async sendRequest(method, params, timeoutMs) {
|
|
874
|
+
if (this.desynced) {
|
|
875
|
+
throw new ProtocolDesyncError(
|
|
876
|
+
"Engine protocol is desynced; AttestClient will not send further requests.",
|
|
877
|
+
this.diagnosticBuffer.snapshot()
|
|
878
|
+
);
|
|
879
|
+
}
|
|
676
880
|
const resolvedTimeout = resolveTimeoutMs(timeoutMs);
|
|
677
881
|
if (!this.readerActive) {
|
|
678
882
|
return Promise.race([
|
|
@@ -783,6 +987,13 @@ var AttestClient = class {
|
|
|
783
987
|
return String(raw.message);
|
|
784
988
|
}
|
|
785
989
|
};
|
|
990
|
+
function classifyDecodeError(message) {
|
|
991
|
+
if (message.startsWith("malformed JSON")) return "malformed_json";
|
|
992
|
+
if (message.startsWith("expected JSON object")) return "non_object_response";
|
|
993
|
+
if (message.startsWith("invalid jsonrpc version")) return "invalid_jsonrpc_version";
|
|
994
|
+
if (message.startsWith("empty response line")) return "malformed_json";
|
|
995
|
+
return "malformed_json";
|
|
996
|
+
}
|
|
786
997
|
|
|
787
998
|
// src/trace.ts
|
|
788
999
|
var import_node_crypto = require("crypto");
|
|
@@ -904,6 +1115,173 @@ var AgentResult = class {
|
|
|
904
1115
|
}
|
|
905
1116
|
};
|
|
906
1117
|
|
|
1118
|
+
// src/diagnostics.ts
|
|
1119
|
+
var ANSI_RESET = "\x1B[0m";
|
|
1120
|
+
var ANSI_RED = "\x1B[31m";
|
|
1121
|
+
var ANSI_YELLOW = "\x1B[33m";
|
|
1122
|
+
var ANSI_GREEN = "\x1B[32m";
|
|
1123
|
+
var ANSI_DIM = "\x1B[2m";
|
|
1124
|
+
var ANSI_BOLD = "\x1B[1m";
|
|
1125
|
+
var LAYER_NAMES = {
|
|
1126
|
+
1: "schema",
|
|
1127
|
+
2: "constraint",
|
|
1128
|
+
3: "trace",
|
|
1129
|
+
4: "content",
|
|
1130
|
+
5: "embedding",
|
|
1131
|
+
6: "llm_judge",
|
|
1132
|
+
7: "trace_tree",
|
|
1133
|
+
8: "plugin"
|
|
1134
|
+
};
|
|
1135
|
+
function layerLabel(layer, assertionType) {
|
|
1136
|
+
if (layer === 0 && assertionType) {
|
|
1137
|
+
return `L? ${assertionType}`;
|
|
1138
|
+
}
|
|
1139
|
+
const name = LAYER_NAMES[layer] ?? assertionType ?? "uncategorized";
|
|
1140
|
+
return `L${layer} ${name}`;
|
|
1141
|
+
}
|
|
1142
|
+
function statusGlyph(status, color) {
|
|
1143
|
+
let glyph;
|
|
1144
|
+
let c;
|
|
1145
|
+
if (status === STATUS_PASS) {
|
|
1146
|
+
glyph = "PASS";
|
|
1147
|
+
c = ANSI_GREEN;
|
|
1148
|
+
} else if (status === STATUS_SOFT_FAIL) {
|
|
1149
|
+
glyph = "SOFT";
|
|
1150
|
+
c = ANSI_YELLOW;
|
|
1151
|
+
} else if (status === STATUS_HARD_FAIL) {
|
|
1152
|
+
glyph = "FAIL";
|
|
1153
|
+
c = ANSI_RED;
|
|
1154
|
+
} else {
|
|
1155
|
+
glyph = status.toUpperCase() || "????";
|
|
1156
|
+
c = ANSI_DIM;
|
|
1157
|
+
}
|
|
1158
|
+
return color ? `${c}${glyph}${ANSI_RESET}` : glyph;
|
|
1159
|
+
}
|
|
1160
|
+
function bold(s, color) {
|
|
1161
|
+
return color ? `${ANSI_BOLD}${s}${ANSI_RESET}` : s;
|
|
1162
|
+
}
|
|
1163
|
+
function dim(s, color) {
|
|
1164
|
+
return color ? `${ANSI_DIM}${s}${ANSI_RESET}` : s;
|
|
1165
|
+
}
|
|
1166
|
+
function truncate(value, limit = 280) {
|
|
1167
|
+
if (value.length <= limit) {
|
|
1168
|
+
return value;
|
|
1169
|
+
}
|
|
1170
|
+
return value.slice(0, limit - 3) + "...";
|
|
1171
|
+
}
|
|
1172
|
+
function renderAssertionFailure(result, options = {}) {
|
|
1173
|
+
const color = options.color ?? false;
|
|
1174
|
+
const testFile = options.testFile;
|
|
1175
|
+
const layer = result.layer ?? 0;
|
|
1176
|
+
const label = layerLabel(layer, result.type);
|
|
1177
|
+
const lines = [];
|
|
1178
|
+
lines.push(
|
|
1179
|
+
` ${statusGlyph(result.status, color)} ${bold(result.assertion_id, color)} ${dim(`\u2014 ${label}`, color)}`
|
|
1180
|
+
);
|
|
1181
|
+
if (result.trace_node_path) {
|
|
1182
|
+
lines.push(` trace path: ${result.trace_node_path}`);
|
|
1183
|
+
}
|
|
1184
|
+
lines.push(` score: ${result.score.toFixed(3)}`);
|
|
1185
|
+
if (result.expected) {
|
|
1186
|
+
lines.push(` expected: ${truncate(result.expected)}`);
|
|
1187
|
+
}
|
|
1188
|
+
if (result.actual) {
|
|
1189
|
+
lines.push(` actual: ${truncate(result.actual)}`);
|
|
1190
|
+
}
|
|
1191
|
+
if (result.explanation) {
|
|
1192
|
+
lines.push(` detail: ${truncate(result.explanation)}`);
|
|
1193
|
+
}
|
|
1194
|
+
if (result.threshold_source && result.threshold_source !== "static") {
|
|
1195
|
+
lines.push(` threshold: ${result.threshold_source}`);
|
|
1196
|
+
}
|
|
1197
|
+
if (result.judge_metadata) {
|
|
1198
|
+
appendJudgeMetadata(lines, result.judge_metadata);
|
|
1199
|
+
}
|
|
1200
|
+
if (result.suggested_action) {
|
|
1201
|
+
lines.push(` hint: ${truncate(result.suggested_action)}`);
|
|
1202
|
+
}
|
|
1203
|
+
if ((result.cost ?? 0) > 0 || (result.duration_ms ?? 0) > 0) {
|
|
1204
|
+
const cost = result.cost ?? 0;
|
|
1205
|
+
const durMs = result.duration_ms ?? 0;
|
|
1206
|
+
lines.push(` cost/lat: $${cost.toFixed(6)} / ${durMs}ms`);
|
|
1207
|
+
}
|
|
1208
|
+
if (testFile) {
|
|
1209
|
+
lines.push(` source: ${testFile}`);
|
|
1210
|
+
}
|
|
1211
|
+
return lines.join("\n") + "\n";
|
|
1212
|
+
}
|
|
1213
|
+
function appendJudgeMetadata(lines, meta) {
|
|
1214
|
+
if (meta.model) {
|
|
1215
|
+
let rubric = meta.rubric_name ?? "default";
|
|
1216
|
+
if (meta.rubric_version) {
|
|
1217
|
+
rubric = `${rubric} @ ${meta.rubric_version}`;
|
|
1218
|
+
}
|
|
1219
|
+
lines.push(` judge: ${meta.model} / ${rubric}`);
|
|
1220
|
+
}
|
|
1221
|
+
if (meta.prompt_hash) {
|
|
1222
|
+
lines.push(` prompt: #${meta.prompt_hash}`);
|
|
1223
|
+
}
|
|
1224
|
+
const samples = meta.sample_scores ?? [];
|
|
1225
|
+
if (samples.length > 1) {
|
|
1226
|
+
const formatted = samples.map((s) => s.toFixed(2)).join(", ");
|
|
1227
|
+
const mean = (meta.score_mean ?? 0).toFixed(2);
|
|
1228
|
+
const stddev = (meta.score_stddev ?? 0).toFixed(2);
|
|
1229
|
+
const flag = meta.high_variance ? " \u26A0 HIGH VARIANCE" : "";
|
|
1230
|
+
lines.push(
|
|
1231
|
+
` samples: [${formatted}] mean=${mean} stddev=${stddev}${flag}`
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
const probes = meta.bias_probes ?? [];
|
|
1235
|
+
if (probes.length > 0) {
|
|
1236
|
+
const formatted = probes.map((p) => `${p.name} \u0394${p.delta >= 0 ? "+" : ""}${p.delta.toFixed(2)}`).join(", ");
|
|
1237
|
+
lines.push(` bias: ${formatted}`);
|
|
1238
|
+
}
|
|
1239
|
+
if (meta.calibration) {
|
|
1240
|
+
const c = meta.calibration;
|
|
1241
|
+
lines.push(
|
|
1242
|
+
` calibrated: ${c.label_count} labels, agreement=${c.agreement.toFixed(2)}, \u03BA=${c.cohen_kappa.toFixed(2)}`
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
function renderSummary(result, options = {}) {
|
|
1247
|
+
const color = options.color ?? false;
|
|
1248
|
+
const passN = result.passCount;
|
|
1249
|
+
const soft = result.assertionResults.filter(
|
|
1250
|
+
(r) => r.status === STATUS_SOFT_FAIL
|
|
1251
|
+
).length;
|
|
1252
|
+
const hard = result.assertionResults.filter(
|
|
1253
|
+
(r) => r.status === STATUS_HARD_FAIL
|
|
1254
|
+
).length;
|
|
1255
|
+
const parts = [
|
|
1256
|
+
`${statusGlyph(STATUS_PASS, color)} ${passN}`,
|
|
1257
|
+
`${statusGlyph(STATUS_SOFT_FAIL, color)} ${soft}`,
|
|
1258
|
+
`${statusGlyph(STATUS_HARD_FAIL, color)} ${hard}`,
|
|
1259
|
+
`cost $${result.totalCost.toFixed(6)}`,
|
|
1260
|
+
`dur ${result.totalDurationMs}ms`
|
|
1261
|
+
];
|
|
1262
|
+
return parts.join(" | ");
|
|
1263
|
+
}
|
|
1264
|
+
function renderDiagnostics(result, options = {}) {
|
|
1265
|
+
const color = options.color ?? false;
|
|
1266
|
+
const includePassing = options.includePassing ?? false;
|
|
1267
|
+
const rows = includePassing ? result.assertionResults : result.failedAssertions;
|
|
1268
|
+
if (rows.length === 0) {
|
|
1269
|
+
return dim(" (no failures)\n", color);
|
|
1270
|
+
}
|
|
1271
|
+
const out = [""];
|
|
1272
|
+
out.push(
|
|
1273
|
+
bold(
|
|
1274
|
+
`Attest diagnostic \u2014 ${rows.length} of ${result.assertionResults.length} assertions failed:`,
|
|
1275
|
+
color
|
|
1276
|
+
)
|
|
1277
|
+
);
|
|
1278
|
+
for (const r of rows) {
|
|
1279
|
+
out.push(renderAssertionFailure(r, options));
|
|
1280
|
+
}
|
|
1281
|
+
out.push(dim(` Summary: ${renderSummary(result, { color })}`, color));
|
|
1282
|
+
return out.join("\n");
|
|
1283
|
+
}
|
|
1284
|
+
|
|
907
1285
|
// src/trace-tree.ts
|
|
908
1286
|
var TraceTree = class {
|
|
909
1287
|
root;
|
|
@@ -2108,6 +2486,222 @@ var ContinuousEvalRunner = class {
|
|
|
2108
2486
|
}
|
|
2109
2487
|
};
|
|
2110
2488
|
|
|
2489
|
+
// src/calibration.ts
|
|
2490
|
+
var import_node_crypto3 = require("crypto");
|
|
2491
|
+
function computeAgreement(pairs, threshold) {
|
|
2492
|
+
if (pairs.length === 0) {
|
|
2493
|
+
throw new Error("no labeled pairs available");
|
|
2494
|
+
}
|
|
2495
|
+
if (threshold <= 0 || threshold >= 1) {
|
|
2496
|
+
throw new Error("threshold must be in (0, 1)");
|
|
2497
|
+
}
|
|
2498
|
+
const n = pairs.length;
|
|
2499
|
+
let humanPos = 0;
|
|
2500
|
+
let judgePos = 0;
|
|
2501
|
+
let bothPos = 0;
|
|
2502
|
+
let bothNeg = 0;
|
|
2503
|
+
for (const p of pairs) {
|
|
2504
|
+
const hPos = p.human >= threshold;
|
|
2505
|
+
const jPos = p.judge >= threshold;
|
|
2506
|
+
if (hPos) humanPos++;
|
|
2507
|
+
if (jPos) judgePos++;
|
|
2508
|
+
if (hPos && jPos) bothPos++;
|
|
2509
|
+
if (!hPos && !jPos) bothNeg++;
|
|
2510
|
+
}
|
|
2511
|
+
const humanNeg = n - humanPos;
|
|
2512
|
+
const judgeNeg = n - judgePos;
|
|
2513
|
+
const agreement = (bothPos + bothNeg) / n;
|
|
2514
|
+
const expected = (humanPos * judgePos + humanNeg * judgeNeg) / (n * n);
|
|
2515
|
+
const cohenKappa = expected < 1 ? (agreement - expected) / (1 - expected) : 0;
|
|
2516
|
+
const rocAuc = rocAUC(pairs, threshold);
|
|
2517
|
+
return { threshold, n, agreement, cohenKappa, rocAuc };
|
|
2518
|
+
}
|
|
2519
|
+
function rocAUC(pairs, threshold) {
|
|
2520
|
+
const rows = pairs.map((p) => ({
|
|
2521
|
+
score: p.judge,
|
|
2522
|
+
pos: p.human >= threshold
|
|
2523
|
+
}));
|
|
2524
|
+
const pos = rows.filter((r) => r.pos).length;
|
|
2525
|
+
const neg = rows.length - pos;
|
|
2526
|
+
if (pos === 0 || neg === 0) return 0;
|
|
2527
|
+
rows.sort((a, b) => a.score - b.score);
|
|
2528
|
+
let rankSumPos = 0;
|
|
2529
|
+
let i = 0;
|
|
2530
|
+
while (i < rows.length) {
|
|
2531
|
+
let j = i;
|
|
2532
|
+
while (j < rows.length && rows[j].score === rows[i].score) {
|
|
2533
|
+
j++;
|
|
2534
|
+
}
|
|
2535
|
+
const avgRank = (i + j + 1) / 2;
|
|
2536
|
+
for (let k = i; k < j; k++) {
|
|
2537
|
+
if (rows[k].pos) rankSumPos += avgRank;
|
|
2538
|
+
}
|
|
2539
|
+
i = j;
|
|
2540
|
+
}
|
|
2541
|
+
const auc = (rankSumPos - pos * (pos + 1) / 2) / (pos * neg);
|
|
2542
|
+
if (Number.isNaN(auc) || !Number.isFinite(auc)) return 0;
|
|
2543
|
+
return auc;
|
|
2544
|
+
}
|
|
2545
|
+
function loadLabelsCSV(text) {
|
|
2546
|
+
const rows = parseCSV(text);
|
|
2547
|
+
if (rows.length === 0) {
|
|
2548
|
+
throw new Error("empty CSV");
|
|
2549
|
+
}
|
|
2550
|
+
let start = 0;
|
|
2551
|
+
if (rows[0].length >= 2) {
|
|
2552
|
+
const firstHuman = parseFloatStrict(rows[0][1]);
|
|
2553
|
+
if (firstHuman === null) start = 1;
|
|
2554
|
+
}
|
|
2555
|
+
const out = [];
|
|
2556
|
+
for (let idx = start; idx < rows.length; idx++) {
|
|
2557
|
+
const row = rows[idx];
|
|
2558
|
+
const lineNum = idx + 1;
|
|
2559
|
+
if (row.length === 0) continue;
|
|
2560
|
+
if (row[0].trimStart().startsWith("#")) continue;
|
|
2561
|
+
if (row.length < 2) {
|
|
2562
|
+
throw new Error(
|
|
2563
|
+
`CSV line ${lineNum}: want at least 2 columns (input, human_label)`
|
|
2564
|
+
);
|
|
2565
|
+
}
|
|
2566
|
+
const human = parseFloatStrict(row[1]);
|
|
2567
|
+
if (human === null) {
|
|
2568
|
+
throw new Error(`CSV line ${lineNum}: human_label not a float: ${row[1]}`);
|
|
2569
|
+
}
|
|
2570
|
+
const rec = {
|
|
2571
|
+
input: row[0],
|
|
2572
|
+
humanLabel: human,
|
|
2573
|
+
judgeScore: 0,
|
|
2574
|
+
judgeKnown: false
|
|
2575
|
+
};
|
|
2576
|
+
if (row.length >= 3 && row[2].trim() !== "") {
|
|
2577
|
+
const judge = parseFloatStrict(row[2]);
|
|
2578
|
+
if (judge === null) {
|
|
2579
|
+
throw new Error(
|
|
2580
|
+
`CSV line ${lineNum}: judge_score not a float: ${row[2]}`
|
|
2581
|
+
);
|
|
2582
|
+
}
|
|
2583
|
+
rec.judgeScore = judge;
|
|
2584
|
+
rec.judgeKnown = true;
|
|
2585
|
+
}
|
|
2586
|
+
out.push(rec);
|
|
2587
|
+
}
|
|
2588
|
+
if (out.length === 0) {
|
|
2589
|
+
throw new Error("CSV contained no labeled rows");
|
|
2590
|
+
}
|
|
2591
|
+
return out;
|
|
2592
|
+
}
|
|
2593
|
+
function loadLabelsJSONL(text) {
|
|
2594
|
+
const out = [];
|
|
2595
|
+
const lines = text.split(/\r?\n/);
|
|
2596
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2597
|
+
const stripped = lines[i].trim();
|
|
2598
|
+
if (!stripped) continue;
|
|
2599
|
+
let obj;
|
|
2600
|
+
try {
|
|
2601
|
+
obj = JSON.parse(stripped);
|
|
2602
|
+
} catch (err) {
|
|
2603
|
+
throw new Error(`JSONL line ${i + 1}: ${err.message}`);
|
|
2604
|
+
}
|
|
2605
|
+
if (Object.keys(obj).length === 0) continue;
|
|
2606
|
+
if (obj["human_label"] === void 0) {
|
|
2607
|
+
throw new Error(`JSONL line ${i + 1}: missing human_label`);
|
|
2608
|
+
}
|
|
2609
|
+
const human = parseJSONNumber(obj["human_label"]);
|
|
2610
|
+
if (human === null) {
|
|
2611
|
+
throw new Error(`JSONL line ${i + 1}: human_label is not a number`);
|
|
2612
|
+
}
|
|
2613
|
+
const rec = {
|
|
2614
|
+
input: typeof obj["input"] === "string" ? obj["input"] : "",
|
|
2615
|
+
humanLabel: human,
|
|
2616
|
+
judgeScore: 0,
|
|
2617
|
+
judgeKnown: false
|
|
2618
|
+
};
|
|
2619
|
+
if (obj["judge_score"] !== void 0 && obj["judge_score"] !== null) {
|
|
2620
|
+
const judge = parseJSONNumber(obj["judge_score"]);
|
|
2621
|
+
if (judge === null) {
|
|
2622
|
+
throw new Error(`JSONL line ${i + 1}: judge_score is not a number`);
|
|
2623
|
+
}
|
|
2624
|
+
rec.judgeScore = judge;
|
|
2625
|
+
rec.judgeKnown = true;
|
|
2626
|
+
}
|
|
2627
|
+
out.push(rec);
|
|
2628
|
+
}
|
|
2629
|
+
if (out.length === 0) {
|
|
2630
|
+
throw new Error("JSONL contained no labeled rows");
|
|
2631
|
+
}
|
|
2632
|
+
return out;
|
|
2633
|
+
}
|
|
2634
|
+
function promptHash(text) {
|
|
2635
|
+
return (0, import_node_crypto3.createHash)("sha256").update(text).digest("hex").slice(0, 16);
|
|
2636
|
+
}
|
|
2637
|
+
function parseFloatStrict(raw) {
|
|
2638
|
+
const trimmed = raw.trim();
|
|
2639
|
+
if (trimmed === "") return null;
|
|
2640
|
+
const n = Number(trimmed);
|
|
2641
|
+
if (Number.isNaN(n)) return null;
|
|
2642
|
+
return n;
|
|
2643
|
+
}
|
|
2644
|
+
function parseJSONNumber(value) {
|
|
2645
|
+
if (typeof value !== "number") return null;
|
|
2646
|
+
if (Number.isNaN(value)) return null;
|
|
2647
|
+
return value;
|
|
2648
|
+
}
|
|
2649
|
+
function parseCSV(text) {
|
|
2650
|
+
const rows = [];
|
|
2651
|
+
let current = [];
|
|
2652
|
+
let field = "";
|
|
2653
|
+
let inQuotes = false;
|
|
2654
|
+
let i = 0;
|
|
2655
|
+
while (i < text.length) {
|
|
2656
|
+
const ch = text[i];
|
|
2657
|
+
if (inQuotes) {
|
|
2658
|
+
if (ch === '"') {
|
|
2659
|
+
if (text[i + 1] === '"') {
|
|
2660
|
+
field += '"';
|
|
2661
|
+
i += 2;
|
|
2662
|
+
continue;
|
|
2663
|
+
}
|
|
2664
|
+
inQuotes = false;
|
|
2665
|
+
i++;
|
|
2666
|
+
continue;
|
|
2667
|
+
}
|
|
2668
|
+
field += ch;
|
|
2669
|
+
i++;
|
|
2670
|
+
continue;
|
|
2671
|
+
}
|
|
2672
|
+
if (ch === '"') {
|
|
2673
|
+
inQuotes = true;
|
|
2674
|
+
i++;
|
|
2675
|
+
continue;
|
|
2676
|
+
}
|
|
2677
|
+
if (ch === ",") {
|
|
2678
|
+
current.push(field);
|
|
2679
|
+
field = "";
|
|
2680
|
+
i++;
|
|
2681
|
+
continue;
|
|
2682
|
+
}
|
|
2683
|
+
if (ch === "\n") {
|
|
2684
|
+
current.push(field);
|
|
2685
|
+
rows.push(current);
|
|
2686
|
+
current = [];
|
|
2687
|
+
field = "";
|
|
2688
|
+
i++;
|
|
2689
|
+
continue;
|
|
2690
|
+
}
|
|
2691
|
+
if (ch === "\r") {
|
|
2692
|
+
i++;
|
|
2693
|
+
continue;
|
|
2694
|
+
}
|
|
2695
|
+
field += ch;
|
|
2696
|
+
i++;
|
|
2697
|
+
}
|
|
2698
|
+
if (field.length > 0 || current.length > 0) {
|
|
2699
|
+
current.push(field);
|
|
2700
|
+
rows.push(current);
|
|
2701
|
+
}
|
|
2702
|
+
return rows;
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2111
2705
|
// src/personas.ts
|
|
2112
2706
|
var FRIENDLY_USER = {
|
|
2113
2707
|
name: "FriendlyUser",
|
|
@@ -2550,6 +3144,8 @@ var LangChainAdapter = class {
|
|
|
2550
3144
|
OllamaAdapter,
|
|
2551
3145
|
OpenAIAdapter,
|
|
2552
3146
|
PluginRegistry,
|
|
3147
|
+
ProtocolDesyncError,
|
|
3148
|
+
ProtocolDiagnosticBuffer,
|
|
2553
3149
|
ProtocolError,
|
|
2554
3150
|
STATUS_HARD_FAIL,
|
|
2555
3151
|
STATUS_PASS,
|
|
@@ -2578,6 +3174,7 @@ var LangChainAdapter = class {
|
|
|
2578
3174
|
agentId,
|
|
2579
3175
|
assertionId,
|
|
2580
3176
|
attestExpect,
|
|
3177
|
+
computeAgreement,
|
|
2581
3178
|
config,
|
|
2582
3179
|
decodeResponse,
|
|
2583
3180
|
delegate,
|
|
@@ -2587,6 +3184,14 @@ var LangChainAdapter = class {
|
|
|
2587
3184
|
extractResult,
|
|
2588
3185
|
getSampleRate,
|
|
2589
3186
|
isSimulationMode,
|
|
3187
|
+
layerForType,
|
|
3188
|
+
loadLabelsCSV,
|
|
3189
|
+
loadLabelsJSONL,
|
|
3190
|
+
previewLine,
|
|
3191
|
+
promptHash,
|
|
3192
|
+
renderAssertionFailure,
|
|
3193
|
+
renderDiagnostics,
|
|
3194
|
+
renderSummary,
|
|
2590
3195
|
resetConfig,
|
|
2591
3196
|
simulationEvaluateBatch,
|
|
2592
3197
|
tier,
|