@botpress/evals 1.19.0-beta.12 → 1.19.0-beta.14
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/client.d.ts +9 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +20 -74
- package/dist/client.js.map +4 -4
- package/dist/definition.d.ts +3 -2
- package/dist/definition.d.ts.map +1 -1
- package/dist/definition.js.map +2 -2
- package/dist/graders/index.js +21 -74
- package/dist/graders/index.js.map +2 -2
- package/dist/graders/llm.d.ts +12 -12
- package/dist/graders/llm.d.ts.map +1 -1
- package/dist/graders/llm.js +30 -75
- package/dist/graders/llm.js.map +2 -2
- package/dist/graders/response.js +16 -65
- package/dist/graders/response.js.map +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +3 -3
- package/dist/loader.js +1 -39
- package/dist/loader.js.map +3 -3
- package/dist/manifest.d.ts +13 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/runner.d.ts +5 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +141 -217
- package/dist/runner.js.map +4 -4
- package/dist/spans/index.d.ts +5 -0
- package/dist/spans/index.d.ts.map +1 -0
- package/dist/spans/index.js +642 -0
- package/dist/spans/index.js.map +7 -0
- package/dist/spans/local-span-source.d.ts +16 -0
- package/dist/spans/local-span-source.d.ts.map +1 -0
- package/dist/spans/local-span-source.js +478 -0
- package/dist/spans/local-span-source.js.map +7 -0
- package/dist/spans/span-source.d.ts +20 -0
- package/dist/spans/span-source.d.ts.map +1 -0
- package/dist/spans/span-source.js +1 -0
- package/dist/{sse-collector.d.ts → spans/sse-collector.d.ts} +6 -4
- package/dist/spans/sse-collector.d.ts.map +1 -0
- package/dist/{sse-collector.js → spans/sse-collector.js} +17 -49
- package/dist/spans/sse-collector.js.map +7 -0
- package/dist/spans/trace.d.ts.map +1 -0
- package/dist/spans/trace.js.map +7 -0
- package/dist/spans/vortex-span-source.d.ts +34 -0
- package/dist/spans/vortex-span-source.d.ts.map +1 -0
- package/dist/spans/vortex-span-source.js +166 -0
- package/dist/spans/vortex-span-source.js.map +7 -0
- package/dist/stores/eval-definition-loader.d.ts +4 -0
- package/dist/stores/eval-definition-loader.d.ts.map +1 -0
- package/dist/stores/eval-definition-loader.js +104 -0
- package/dist/stores/eval-definition-loader.js.map +7 -0
- package/dist/stores/eval-store.d.ts +61 -0
- package/dist/stores/eval-store.d.ts.map +1 -0
- package/dist/stores/eval-store.js +1 -0
- package/dist/stores/eval-store.js.map +7 -0
- package/dist/stores/index.d.ts +8 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +1118 -0
- package/dist/stores/index.js.map +7 -0
- package/dist/stores/local-eval-store.d.ts +66 -0
- package/dist/stores/local-eval-store.d.ts.map +1 -0
- package/dist/stores/local-eval-store.js +557 -0
- package/dist/stores/local-eval-store.js.map +7 -0
- package/dist/stores/vortex-eval-store-entry.d.ts +6 -0
- package/dist/stores/vortex-eval-store-entry.d.ts.map +1 -0
- package/dist/stores/vortex-eval-store-entry.js +664 -0
- package/dist/stores/vortex-eval-store-entry.js.map +7 -0
- package/dist/stores/vortex-eval-store.d.ts +78 -0
- package/dist/stores/vortex-eval-store.d.ts.map +1 -0
- package/dist/stores/vortex-eval-store.js +562 -0
- package/dist/stores/vortex-eval-store.js.map +7 -0
- package/dist/transformer.d.ts +2 -2
- package/dist/transformer.d.ts.map +1 -1
- package/dist/transformer.js +1 -1
- package/dist/transformer.js.map +2 -2
- package/dist/types.d.ts +38 -33
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +2 -2
- package/package.json +66 -6
- package/dist/sse-collector.d.ts.map +0 -1
- package/dist/sse-collector.js.map +0 -7
- package/dist/trace.d.ts.map +0 -1
- /package/dist/{trace.js.map → spans/span-source.js.map} +0 -0
- /package/dist/{trace.d.ts → spans/trace.d.ts} +0 -0
- /package/dist/{trace.js → spans/trace.js} +0 -0
package/dist/runner.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// src/runner.ts
|
|
2
|
-
import { Client as BpClient2 } from "@botpress/client";
|
|
3
|
-
|
|
4
1
|
// src/definition.ts
|
|
5
2
|
var Eval = class {
|
|
6
3
|
name;
|
|
@@ -26,84 +23,33 @@ var Eval = class {
|
|
|
26
23
|
// src/types.ts
|
|
27
24
|
var defaultLogger = console;
|
|
28
25
|
|
|
29
|
-
// src/client.ts
|
|
30
|
-
import { Client as BpClient } from "@botpress/client";
|
|
31
|
-
import { createRequire } from "module";
|
|
32
|
-
|
|
33
|
-
// ../analytics/src/errors.ts
|
|
34
|
-
var AdkError = class extends Error {
|
|
35
|
-
/**
|
|
36
|
-
* Marker for cross-module-instance checks — `instanceof` breaks when a
|
|
37
|
-
* package is duplicated in the dependency graph.
|
|
38
|
-
* @internal
|
|
39
|
-
*/
|
|
40
|
-
static __IS_ADK_BASE_ERROR = true;
|
|
41
|
-
/**
|
|
42
|
-
* Stable SCREAMING_SNAKE_CASE identifier, e.g. 'BOT_NOT_FOUND'. Subclasses
|
|
43
|
-
* pin `Code` to a literal union (`class EvalRunnerError extends
|
|
44
|
-
* AdkError<EvalErrorCode>`) so typos are compile errors and `code` narrows
|
|
45
|
-
* in switches.
|
|
46
|
-
*/
|
|
47
|
-
code;
|
|
48
|
-
/**
|
|
49
|
-
* True for user/environment conditions the developer can fix themselves
|
|
50
|
-
* (validation, auth, connectivity). False for internal bugs. Defaults to
|
|
51
|
-
* false so unclassified errors surface as bugs until someone triages them.
|
|
52
|
-
*/
|
|
53
|
-
expected;
|
|
54
|
-
/** Structured context. Sanitized before any telemetry leaves the machine. */
|
|
55
|
-
details;
|
|
56
|
-
/** Actionable next step shown to the user, e.g. "Run 'adk login' first." */
|
|
57
|
-
suggestion;
|
|
58
|
-
constructor(opts) {
|
|
59
|
-
super(opts.message, opts.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
60
|
-
this.name = this.constructor.name;
|
|
61
|
-
this.code = opts.code;
|
|
62
|
-
this.expected = opts.expected ?? false;
|
|
63
|
-
if (opts.details !== void 0) {
|
|
64
|
-
this.details = opts.details;
|
|
65
|
-
}
|
|
66
|
-
if (opts.suggestion !== void 0) {
|
|
67
|
-
this.suggestion = opts.suggestion;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
function isAdkError(error) {
|
|
72
|
-
if (error instanceof AdkError) return true;
|
|
73
|
-
if (!(error instanceof Error)) return false;
|
|
74
|
-
return error.constructor?.__IS_ADK_BASE_ERROR === true;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
26
|
// src/errors.ts
|
|
27
|
+
import { AdkError } from "@botpress/analytics";
|
|
78
28
|
var EvalRunnerError = class extends AdkError {
|
|
79
29
|
};
|
|
80
30
|
|
|
81
31
|
// src/client.ts
|
|
82
32
|
var TRACE_STREAM_PREFLIGHT_TIMEOUT_MS = 1e4;
|
|
83
|
-
var defaultChatClient = null;
|
|
84
|
-
function getDefaultChatClient() {
|
|
85
|
-
if (defaultChatClient) return defaultChatClient;
|
|
86
|
-
const require2 = createRequire(import.meta.url);
|
|
87
|
-
const mod = require2("@botpress/chat");
|
|
88
|
-
const ChatClient = mod.default?.Client ?? mod.Client;
|
|
89
|
-
if (!ChatClient) {
|
|
90
|
-
throw new EvalRunnerError({
|
|
91
|
-
code: "CHAT_CLIENT_MISSING",
|
|
92
|
-
message: "@botpress/chat did not expose a Client export. Pass EvalRunnerConfig.chatClient explicitly."
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
return ChatClient;
|
|
96
|
-
}
|
|
97
33
|
var ChatSession = class {
|
|
98
|
-
constructor(
|
|
99
|
-
this.
|
|
100
|
-
this.
|
|
34
|
+
constructor(bpClient, botId, _chatWebhookId, _chatBaseUrl, _chatClient) {
|
|
35
|
+
this.bpClient = bpClient;
|
|
36
|
+
this.botId = botId;
|
|
37
|
+
this._chatWebhookId = _chatWebhookId;
|
|
38
|
+
this._chatBaseUrl = _chatBaseUrl;
|
|
39
|
+
this._chatClient = _chatClient;
|
|
101
40
|
}
|
|
102
41
|
client = null;
|
|
103
42
|
conversationId = null;
|
|
104
43
|
async connect() {
|
|
105
|
-
const
|
|
106
|
-
this.
|
|
44
|
+
const webhookId = this._chatWebhookId ?? await discoverWebhookId(this.bpClient, this.botId);
|
|
45
|
+
const ChatClientCtor = this._chatClient ?? (await import(
|
|
46
|
+
/* webpackIgnore: true */
|
|
47
|
+
"@botpress/chat"
|
|
48
|
+
)).Client;
|
|
49
|
+
this.client = await ChatClientCtor.connect({
|
|
50
|
+
webhookId,
|
|
51
|
+
...this._chatBaseUrl ? { baseApiUrl: this._chatBaseUrl } : {}
|
|
52
|
+
});
|
|
107
53
|
}
|
|
108
54
|
/** Invariant guard — calling any session method before connect() is a runner bug. */
|
|
109
55
|
assertConnected() {
|
|
@@ -135,17 +81,13 @@ var ChatSession = class {
|
|
|
135
81
|
const conversationId = await this.ensureConversation();
|
|
136
82
|
await client.createMessage({ conversationId, payload: { type: "text", text: message } });
|
|
137
83
|
}
|
|
138
|
-
|
|
139
|
-
* Send an event. Fire-and-forget — observation is via SSECollector.
|
|
140
|
-
*/
|
|
141
|
-
async sendEvent(type, payload) {
|
|
84
|
+
async sendEvent(payload) {
|
|
142
85
|
const client = this.assertConnected();
|
|
143
86
|
const conversationId = await this.ensureConversation();
|
|
144
|
-
await client.createEvent({
|
|
87
|
+
await client.createEvent({ payload, conversationId });
|
|
145
88
|
}
|
|
146
89
|
};
|
|
147
|
-
async function discoverWebhookId(
|
|
148
|
-
const client = new BpClient({ token, botId, apiUrl });
|
|
90
|
+
async function discoverWebhookId(client, botId) {
|
|
149
91
|
const { bot } = await client.getBot({ id: botId });
|
|
150
92
|
const integrations = bot.integrations || {};
|
|
151
93
|
const chat = Object.values(integrations).find((int) => int.name === "chat");
|
|
@@ -273,13 +215,17 @@ async function assertChatChannelBound(devServerUrl, headers = {}, logger = defau
|
|
|
273
215
|
});
|
|
274
216
|
}
|
|
275
217
|
|
|
276
|
-
// src/sse-collector.ts
|
|
218
|
+
// src/spans/sse-collector.ts
|
|
277
219
|
function spanKey(span) {
|
|
278
220
|
return `${span.id.trace}:${span.id.span}`;
|
|
279
221
|
}
|
|
280
222
|
function spanData(span) {
|
|
281
223
|
return span.data && typeof span.data === "object" ? span.data : {};
|
|
282
224
|
}
|
|
225
|
+
var TURN_HANDLER_SPAN_NAMES = /* @__PURE__ */ new Set(["handler.conversation", "handler.event"]);
|
|
226
|
+
function isTurnHandlerSpan(span) {
|
|
227
|
+
return TURN_HANDLER_SPAN_NAMES.has(span.name);
|
|
228
|
+
}
|
|
283
229
|
var SSECollector = class _SSECollector {
|
|
284
230
|
devServerUrl;
|
|
285
231
|
headers;
|
|
@@ -341,20 +287,22 @@ var SSECollector = class _SSECollector {
|
|
|
341
287
|
}
|
|
342
288
|
/**
|
|
343
289
|
* Mark the beginning of a new turn.
|
|
344
|
-
* Records which handler
|
|
345
|
-
* so waitForTurnComplete() only resolves on NEW
|
|
290
|
+
* Records which turn-handler span IDs (handler.conversation / handler.event)
|
|
291
|
+
* have been seen so far, so waitForTurnComplete() only resolves on NEW
|
|
292
|
+
* handler completions.
|
|
346
293
|
*/
|
|
347
294
|
startTurn() {
|
|
348
295
|
this.turnStartKeys = new Set(this.spanMap.keys());
|
|
349
296
|
for (const [key, span] of this.spanMap) {
|
|
350
|
-
if (span
|
|
297
|
+
if (isTurnHandlerSpan(span)) {
|
|
351
298
|
this.seenHandlerKeys.add(key);
|
|
352
299
|
}
|
|
353
300
|
}
|
|
354
301
|
}
|
|
355
302
|
static SETTLE_MS = 100;
|
|
356
303
|
/**
|
|
357
|
-
* Wait for a handler
|
|
304
|
+
* Wait for a turn-handler span (handler.conversation or handler.event) to
|
|
305
|
+
* reach terminal status (ok or error).
|
|
358
306
|
* Only considers spans NOT seen before the last startTurn() call.
|
|
359
307
|
*
|
|
360
308
|
* After the handler closes, waits for a quiet period (no new spans for SETTLE_MS)
|
|
@@ -373,7 +321,7 @@ var SSECollector = class _SSECollector {
|
|
|
373
321
|
const timer = setTimeout(() => {
|
|
374
322
|
this._removeWaiter(waiter);
|
|
375
323
|
opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
376
|
-
const reason = this.streamError ? `SSE stream died: ${this.streamError.message}` : "no handler.conversation span completed";
|
|
324
|
+
const reason = this.streamError ? `SSE stream died: ${this.streamError.message}` : "no handler.conversation or handler.event span completed";
|
|
377
325
|
reject(
|
|
378
326
|
new Error(
|
|
379
327
|
`waitForTurnComplete timed out after ${opts.timeout}ms \u2014 ${reason}; diagnostics: ${this._formatTurnDiagnostics()}`
|
|
@@ -621,7 +569,7 @@ ${value}` : value;
|
|
|
621
569
|
const handlerStatuses = /* @__PURE__ */ new Set();
|
|
622
570
|
for (const span of turnSpans) {
|
|
623
571
|
spanNames.add(span.name);
|
|
624
|
-
if (span
|
|
572
|
+
if (isTurnHandlerSpan(span)) {
|
|
625
573
|
handlerStatuses.add(span.status);
|
|
626
574
|
}
|
|
627
575
|
}
|
|
@@ -634,7 +582,7 @@ ${value}` : value;
|
|
|
634
582
|
`totalSpans=${this.spanMap.size}`,
|
|
635
583
|
`turnSpans=${turnSpans.length}`,
|
|
636
584
|
`turnSpanNames=${this._formatSet(spanNames)}`,
|
|
637
|
-
`
|
|
585
|
+
`turnHandlerStatuses=${this._formatSet(handlerStatuses)}`
|
|
638
586
|
].join(", ");
|
|
639
587
|
}
|
|
640
588
|
_formatSet(values, limit = 12) {
|
|
@@ -670,7 +618,7 @@ ${value}` : value;
|
|
|
670
618
|
}
|
|
671
619
|
}
|
|
672
620
|
/**
|
|
673
|
-
* Check if there is a handler.conversation
|
|
621
|
+
* Check if there is a turn-handler span (handler.conversation / handler.event) that:
|
|
674
622
|
* 1. Was NOT seen before startTurn()
|
|
675
623
|
* 2. Has terminal status (ok or error)
|
|
676
624
|
*
|
|
@@ -681,7 +629,7 @@ ${value}` : value;
|
|
|
681
629
|
*/
|
|
682
630
|
_hasNewCompletedHandler() {
|
|
683
631
|
for (const [key, span] of this.spanMap) {
|
|
684
|
-
if (span
|
|
632
|
+
if (isTurnHandlerSpan(span) && !this.seenHandlerKeys.has(key) && (span.status === "ok" || span.status === "error")) {
|
|
685
633
|
this.seenHandlerKeys.add(key);
|
|
686
634
|
return true;
|
|
687
635
|
}
|
|
@@ -708,6 +656,35 @@ ${value}` : value;
|
|
|
708
656
|
}
|
|
709
657
|
};
|
|
710
658
|
|
|
659
|
+
// src/spans/local-span-source.ts
|
|
660
|
+
var LocalSpanSource = class {
|
|
661
|
+
collector;
|
|
662
|
+
constructor(devServerUrl, headers = {}) {
|
|
663
|
+
this.collector = new SSECollector(devServerUrl, headers);
|
|
664
|
+
}
|
|
665
|
+
async connect(filter) {
|
|
666
|
+
await this.collector.connect(filter);
|
|
667
|
+
}
|
|
668
|
+
startTurn() {
|
|
669
|
+
this.collector.startTurn();
|
|
670
|
+
}
|
|
671
|
+
async waitForTurnComplete(opts) {
|
|
672
|
+
await this.collector.waitForTurnComplete(opts);
|
|
673
|
+
}
|
|
674
|
+
async waitForWorkflow(name, opts) {
|
|
675
|
+
await this.collector.waitForWorkflow(name, opts);
|
|
676
|
+
}
|
|
677
|
+
getTurnSpans() {
|
|
678
|
+
return this.collector.getTurnSpans();
|
|
679
|
+
}
|
|
680
|
+
getAllSpans() {
|
|
681
|
+
return this.collector.getAllSpans();
|
|
682
|
+
}
|
|
683
|
+
disconnect() {
|
|
684
|
+
this.collector.disconnect();
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
|
|
711
688
|
// src/transformer.ts
|
|
712
689
|
function data(span) {
|
|
713
690
|
return span.data && typeof span.data === "object" ? span.data : {};
|
|
@@ -795,7 +772,7 @@ function extractWorkflowSpans(spans) {
|
|
|
795
772
|
}
|
|
796
773
|
function extractHandler(spans) {
|
|
797
774
|
const handler = spans.find(
|
|
798
|
-
(s) => (s.name === "handler.conversation" || s.name === "handler.workflow") && s.status !== "running"
|
|
775
|
+
(s) => (s.name === "handler.conversation" || s.name === "handler.event" || s.name === "handler.workflow") && s.status !== "running"
|
|
799
776
|
);
|
|
800
777
|
if (!handler) {
|
|
801
778
|
return { handlerDuration: 0, handlerStatus: "ok" };
|
|
@@ -807,55 +784,26 @@ function extractHandler(spans) {
|
|
|
807
784
|
}
|
|
808
785
|
|
|
809
786
|
// src/graders/llm.ts
|
|
810
|
-
import { Client } from "@botpress/client";
|
|
811
787
|
import { Cognitive } from "@botpress/cognitive";
|
|
812
|
-
|
|
813
|
-
function buildJudgeSystemPrompt(passThreshold) {
|
|
814
|
-
return `You are an evaluation judge for a chatbot. You will be given:
|
|
815
|
-
- The user's message
|
|
816
|
-
- The bot's response
|
|
817
|
-
- Grading criteria
|
|
818
|
-
|
|
819
|
-
Your job is to determine if the bot's response meets the criteria.
|
|
820
|
-
|
|
821
|
-
Respond with a JSON object:
|
|
822
|
-
{
|
|
823
|
-
"pass": true/false,
|
|
824
|
-
"score": 1-5,
|
|
825
|
-
"reason": "brief explanation"
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
Scoring guide:
|
|
829
|
-
- 5: Fully meets criteria, excellent response
|
|
830
|
-
- 4: Mostly meets criteria, minor issues
|
|
831
|
-
- 3: Partially meets criteria, notable gaps
|
|
832
|
-
- 2: Barely meets criteria, significant issues
|
|
833
|
-
- 1: Does not meet criteria
|
|
834
|
-
|
|
835
|
-
A score of ${passThreshold} or above is a pass.`;
|
|
836
|
-
}
|
|
788
|
+
import { Zai } from "@botpress/zai";
|
|
837
789
|
var MODEL_ALIASES = ["fast", "best"];
|
|
838
|
-
var
|
|
790
|
+
var _zai = null;
|
|
839
791
|
var _availableModels = [];
|
|
840
792
|
var _judgeModel = "fast";
|
|
793
|
+
var _failClosed = true;
|
|
841
794
|
var _log = defaultLogger;
|
|
842
795
|
function _isModelValid(model) {
|
|
843
796
|
if (MODEL_ALIASES.includes(model)) return true;
|
|
844
797
|
if (_availableModels.length === 0) return false;
|
|
845
798
|
return _availableModels.includes(model);
|
|
846
799
|
}
|
|
847
|
-
async function initLLMJudge(
|
|
800
|
+
async function initLLMJudge(client, options) {
|
|
848
801
|
const log = options?.logger ?? defaultLogger;
|
|
849
802
|
_log = log;
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
apiUrl: credentials.apiUrl,
|
|
853
|
-
botId: credentials.botId,
|
|
854
|
-
...credentials.workspaceId !== void 0 ? { workspaceId: credentials.workspaceId } : {}
|
|
855
|
-
});
|
|
856
|
-
_cognitive = new Cognitive({ client, __experimental_beta: true });
|
|
803
|
+
_failClosed = options?.failClosed ?? true;
|
|
804
|
+
const cognitive = new Cognitive({ client, __experimental_beta: true });
|
|
857
805
|
try {
|
|
858
|
-
_availableModels = Array.from((await
|
|
806
|
+
_availableModels = Array.from((await cognitive.fetchRemoteModels()).keys());
|
|
859
807
|
} catch (err) {
|
|
860
808
|
log.warn(`Failed to fetch available models: ${err.message}. LLM judge will be unavailable.`);
|
|
861
809
|
_availableModels = [];
|
|
@@ -866,66 +814,42 @@ async function initLLMJudge(credentials, options) {
|
|
|
866
814
|
} else if (!_isModelValid(_judgeModel)) {
|
|
867
815
|
log.warn(`Configured LLM judge model "${_judgeModel}" is invalid. Run "adk models" to list available models.`);
|
|
868
816
|
}
|
|
817
|
+
_zai = new Zai({ client: cognitive, modelId: _judgeModel });
|
|
869
818
|
}
|
|
870
819
|
async function gradeLLMJudge(botResponse, criteria, context) {
|
|
871
|
-
const
|
|
820
|
+
const assertion = `llm_judge: "${criteria}"`;
|
|
872
821
|
try {
|
|
873
|
-
if (!
|
|
822
|
+
if (!_zai) {
|
|
874
823
|
_log.warn(`LLM judge unavailable for "${criteria}": no credentials configured (initLLMJudge not called)`);
|
|
875
824
|
return {
|
|
876
|
-
assertion
|
|
877
|
-
pass:
|
|
825
|
+
assertion,
|
|
826
|
+
pass: !_failClosed,
|
|
827
|
+
skipped: true,
|
|
878
828
|
expected: criteria,
|
|
879
829
|
actual: "FAILED \u2014 LLM judge unavailable: no credentials configured"
|
|
880
830
|
};
|
|
881
831
|
}
|
|
882
832
|
if (!_isModelValid(_judgeModel)) {
|
|
883
833
|
return {
|
|
884
|
-
assertion
|
|
834
|
+
assertion,
|
|
885
835
|
pass: false,
|
|
886
836
|
expected: criteria,
|
|
887
837
|
actual: "FAILED \u2014 invalid LLM judge model"
|
|
888
838
|
};
|
|
889
839
|
}
|
|
890
|
-
const { output } = await
|
|
891
|
-
model: _judgeModel,
|
|
892
|
-
temperature: 0,
|
|
893
|
-
responseFormat: "json_object",
|
|
894
|
-
systemPrompt: buildJudgeSystemPrompt(threshold),
|
|
895
|
-
messages: [
|
|
896
|
-
{
|
|
897
|
-
role: "user",
|
|
898
|
-
content: `User message: ${context.userMessage}
|
|
899
|
-
|
|
900
|
-
Bot response: ${botResponse}
|
|
901
|
-
|
|
902
|
-
Criteria: ${criteria}`
|
|
903
|
-
}
|
|
904
|
-
]
|
|
905
|
-
});
|
|
906
|
-
const rawContent = output?.choices?.[0]?.content;
|
|
907
|
-
const content = typeof rawContent === "string" ? rawContent : null;
|
|
908
|
-
if (!content) {
|
|
909
|
-
_log.warn(`LLM judge returned an empty response for "${criteria}"`);
|
|
910
|
-
return {
|
|
911
|
-
assertion: `llm_judge: "${criteria}"`,
|
|
912
|
-
pass: false,
|
|
913
|
-
expected: criteria,
|
|
914
|
-
actual: "FAILED \u2014 LLM judge returned empty response"
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
const verdict = JSON.parse(content);
|
|
840
|
+
const { output } = await _zai.check({ userMessage: context.userMessage, botResponse }, criteria).result();
|
|
918
841
|
return {
|
|
919
|
-
assertion
|
|
920
|
-
pass:
|
|
842
|
+
assertion,
|
|
843
|
+
pass: output.value,
|
|
921
844
|
expected: criteria,
|
|
922
|
-
actual:
|
|
845
|
+
actual: `${output.value ? "Pass" : "Fail"} \u2014 ${output.explanation}`
|
|
923
846
|
};
|
|
924
847
|
} catch (err) {
|
|
925
848
|
_log.warn(`LLM judge failed for "${criteria}": ${err.message}`);
|
|
926
849
|
return {
|
|
927
|
-
assertion
|
|
928
|
-
pass:
|
|
850
|
+
assertion,
|
|
851
|
+
pass: !_failClosed,
|
|
852
|
+
skipped: true,
|
|
929
853
|
expected: criteria,
|
|
930
854
|
actual: `FAILED \u2014 LLM judge unavailable: ${err.message}`
|
|
931
855
|
};
|
|
@@ -1348,6 +1272,7 @@ function filterEvals(evals, filter) {
|
|
|
1348
1272
|
}
|
|
1349
1273
|
|
|
1350
1274
|
// src/runner.ts
|
|
1275
|
+
import { isAdkError } from "@botpress/analytics";
|
|
1351
1276
|
import { randomUUID } from "crypto";
|
|
1352
1277
|
var DEFAULT_IDLE_TIMEOUT = 3e4;
|
|
1353
1278
|
var DEFAULT_DEV_SERVER_URL = "http://localhost:3001";
|
|
@@ -1434,27 +1359,22 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1434
1359
|
const start = Date.now();
|
|
1435
1360
|
const turns = [];
|
|
1436
1361
|
let outcomeAssertions = [];
|
|
1437
|
-
const collector = new
|
|
1362
|
+
const collector = options.spanSource ?? new LocalSpanSource(devServerUrl, devServerHeaders);
|
|
1438
1363
|
try {
|
|
1439
|
-
const session = new ChatSession(
|
|
1364
|
+
const session = new ChatSession(
|
|
1365
|
+
connection.client,
|
|
1366
|
+
connection.botId,
|
|
1367
|
+
options.chatWebhookId,
|
|
1368
|
+
options.chatBaseUrl,
|
|
1369
|
+
options.chatClient
|
|
1370
|
+
);
|
|
1440
1371
|
await session.connect();
|
|
1441
|
-
let bpClient = null;
|
|
1442
|
-
const getBpClient = () => {
|
|
1443
|
-
if (!bpClient) {
|
|
1444
|
-
bpClient = new BpClient2({
|
|
1445
|
-
token: connection.token,
|
|
1446
|
-
botId: connection.botId,
|
|
1447
|
-
apiUrl: connection.apiUrl
|
|
1448
|
-
});
|
|
1449
|
-
}
|
|
1450
|
-
return bpClient;
|
|
1451
|
-
};
|
|
1452
1372
|
let conversationId = "";
|
|
1453
1373
|
if (evalDef.setup?.state?.conversation || evalDef.setup?.workflow) {
|
|
1454
1374
|
conversationId = await session.ensureConversation();
|
|
1455
1375
|
}
|
|
1456
1376
|
await seedEvalState(
|
|
1457
|
-
|
|
1377
|
+
connection.client,
|
|
1458
1378
|
{
|
|
1459
1379
|
botId: connection.botId,
|
|
1460
1380
|
userId: session.userId,
|
|
@@ -1463,7 +1383,7 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1463
1383
|
evalDef.setup
|
|
1464
1384
|
);
|
|
1465
1385
|
await triggerEvalWorkflow(
|
|
1466
|
-
|
|
1386
|
+
connection.client,
|
|
1467
1387
|
{
|
|
1468
1388
|
userId: session.userId,
|
|
1469
1389
|
...conversationId ? { conversationId } : {}
|
|
@@ -1499,18 +1419,18 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1499
1419
|
if (!turn.user && !turn.event) {
|
|
1500
1420
|
throw turnConfigError(`must have either 'user' or 'event'.`);
|
|
1501
1421
|
}
|
|
1502
|
-
const
|
|
1503
|
-
options.onProgress?.({
|
|
1422
|
+
const turnLabel = turn.event ? "[event]" : turn.user;
|
|
1423
|
+
await options.onProgress?.({
|
|
1504
1424
|
type: "turn_start",
|
|
1505
1425
|
evalName: evalDef.name,
|
|
1506
1426
|
evalIndex: options.evalIndex ?? 0,
|
|
1507
|
-
|
|
1427
|
+
turnIndex: i,
|
|
1508
1428
|
totalTurns: evalDef.conversation.length,
|
|
1509
|
-
userMessage:
|
|
1429
|
+
userMessage: turnLabel
|
|
1510
1430
|
});
|
|
1511
1431
|
collector.startTurn();
|
|
1512
1432
|
if (turn.event) {
|
|
1513
|
-
await session.sendEvent(turn.event.
|
|
1433
|
+
await session.sendEvent(turn.event.payload);
|
|
1514
1434
|
} else {
|
|
1515
1435
|
await session.sendMessage(turn.user);
|
|
1516
1436
|
}
|
|
@@ -1540,7 +1460,6 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1540
1460
|
const turnData = transformSpans(turnSpans);
|
|
1541
1461
|
const botResponse = turnData.messages.join("\n");
|
|
1542
1462
|
const botDuration = turnData.handlerDuration;
|
|
1543
|
-
const turnLabel = turn.event ? `[event: ${turn.event.type}]` : turn.user;
|
|
1544
1463
|
const evalStart = Date.now();
|
|
1545
1464
|
let assertions = [];
|
|
1546
1465
|
const noResponse = !turn.expectSilence && turnData.messages.length === 0;
|
|
@@ -1595,7 +1514,7 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1595
1514
|
const turnPass = assertions.every((a) => a.pass);
|
|
1596
1515
|
const evalDuration = Date.now() - evalStart;
|
|
1597
1516
|
const turnReport = {
|
|
1598
|
-
|
|
1517
|
+
turnIndex: i,
|
|
1599
1518
|
userMessage: turnLabel,
|
|
1600
1519
|
botResponse,
|
|
1601
1520
|
assertions,
|
|
@@ -1604,11 +1523,11 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1604
1523
|
evalDuration
|
|
1605
1524
|
};
|
|
1606
1525
|
turns.push(turnReport);
|
|
1607
|
-
options.onProgress?.({
|
|
1526
|
+
await options.onProgress?.({
|
|
1608
1527
|
type: "turn_complete",
|
|
1609
1528
|
evalName: evalDef.name,
|
|
1610
1529
|
evalIndex: options.evalIndex ?? 0,
|
|
1611
|
-
|
|
1530
|
+
turnIndex: i,
|
|
1612
1531
|
totalTurns: evalDef.conversation.length,
|
|
1613
1532
|
turnReport
|
|
1614
1533
|
});
|
|
@@ -1655,7 +1574,15 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1655
1574
|
} catch (err) {
|
|
1656
1575
|
const adkErr = isAdkError(err) ? err : void 0;
|
|
1657
1576
|
if (!adkErr || !adkErr.expected) {
|
|
1658
|
-
|
|
1577
|
+
const errMsg = err.message ?? String(err);
|
|
1578
|
+
const errStack = err.stack ?? "";
|
|
1579
|
+
logger.error(`Eval "${evalDef.name}" failed unexpectedly: ${errMsg}`);
|
|
1580
|
+
logger.error(` stack: ${errStack}`);
|
|
1581
|
+
logger.error(` eval: ${evalDef.name}, turn: ${turns.length}/${evalDef.conversation.length}`);
|
|
1582
|
+
logger.error(` connection: botId=${connection.botId}`);
|
|
1583
|
+
logger.error(
|
|
1584
|
+
` spanSource: ${options.spanSource?.constructor?.name ?? "default"}, devServerUrl: ${options.devServerUrl ?? "n/a"}`
|
|
1585
|
+
);
|
|
1659
1586
|
options.onException?.(err, { source: "evals", eval_name: evalDef.name });
|
|
1660
1587
|
}
|
|
1661
1588
|
return {
|
|
@@ -1673,9 +1600,15 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1673
1600
|
}
|
|
1674
1601
|
async function runEvalSuite(config, filter) {
|
|
1675
1602
|
const start = Date.now();
|
|
1676
|
-
const runId = randomUUID().replace(/-/g, "").slice(0, 26);
|
|
1677
|
-
|
|
1678
|
-
|
|
1603
|
+
const runId = config.runId ?? randomUUID().replace(/-/g, "").slice(0, 26);
|
|
1604
|
+
let allEvals;
|
|
1605
|
+
if (config.definitions) {
|
|
1606
|
+
allEvals = config.definitions;
|
|
1607
|
+
} else if (config.agentPath) {
|
|
1608
|
+
allEvals = await loadEvalsFromDir(`${config.agentPath}/evals`);
|
|
1609
|
+
} else {
|
|
1610
|
+
throw new Error("Either `definitions` or `agentPath` must be provided in EvalRunnerConfig");
|
|
1611
|
+
}
|
|
1679
1612
|
const evals = filterEvals(allEvals, filter);
|
|
1680
1613
|
if (evals.length === 0) {
|
|
1681
1614
|
return {
|
|
@@ -1689,38 +1622,26 @@ async function runEvalSuite(config, filter) {
|
|
|
1689
1622
|
...filter !== void 0 ? { filter } : {}
|
|
1690
1623
|
};
|
|
1691
1624
|
}
|
|
1692
|
-
await initLLMJudge(
|
|
1693
|
-
{
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
botId: config.credentials.botId,
|
|
1697
|
-
...config.credentials.workspaceId !== void 0 ? { workspaceId: config.credentials.workspaceId } : {}
|
|
1698
|
-
},
|
|
1699
|
-
{
|
|
1700
|
-
...config.evalOptions?.judgeModel !== void 0 ? { model: config.evalOptions.judgeModel } : {},
|
|
1701
|
-
...config.logger !== void 0 ? { logger: config.logger } : {}
|
|
1702
|
-
}
|
|
1703
|
-
);
|
|
1704
|
-
let webhookId = config.credentials.webhookId;
|
|
1705
|
-
if (!webhookId) {
|
|
1706
|
-
webhookId = await discoverWebhookId(config.credentials.botId, config.credentials.token, config.credentials.apiUrl);
|
|
1707
|
-
}
|
|
1625
|
+
await initLLMJudge(config.client, {
|
|
1626
|
+
...config.evalOptions?.judgeModel !== void 0 ? { model: config.evalOptions.judgeModel } : {},
|
|
1627
|
+
...config.logger !== void 0 ? { logger: config.logger } : {}
|
|
1628
|
+
});
|
|
1708
1629
|
const connection = {
|
|
1709
|
-
|
|
1710
|
-
botId: config.
|
|
1711
|
-
token: config.credentials.token,
|
|
1712
|
-
apiUrl: config.credentials.apiUrl
|
|
1630
|
+
client: config.client,
|
|
1631
|
+
botId: config.botId
|
|
1713
1632
|
};
|
|
1714
1633
|
const devServerUrl = config.devServerUrl || DEFAULT_DEV_SERVER_URL;
|
|
1715
1634
|
const devServerHeaders = config.devServerHeaders ?? {};
|
|
1716
|
-
|
|
1717
|
-
|
|
1635
|
+
if (!config.createSpanSource) {
|
|
1636
|
+
await assertChatChannelBound(devServerUrl, devServerHeaders, config.logger);
|
|
1637
|
+
await assertTraceStreamReadable(devServerUrl, devServerHeaders);
|
|
1638
|
+
}
|
|
1718
1639
|
const reports = [];
|
|
1719
|
-
config.onProgress?.({ type: "suite_start", totalEvals: evals.length });
|
|
1640
|
+
await config.onProgress?.({ type: "suite_start", totalEvals: evals.length });
|
|
1720
1641
|
for (let i = 0; i < evals.length; i++) {
|
|
1721
1642
|
if (config.signal?.aborted) break;
|
|
1722
1643
|
const evalDef = evals[i];
|
|
1723
|
-
config.onProgress?.({
|
|
1644
|
+
await config.onProgress?.({
|
|
1724
1645
|
type: "eval_start",
|
|
1725
1646
|
evalName: evalDef.name,
|
|
1726
1647
|
index: i,
|
|
@@ -1736,10 +1657,13 @@ async function runEvalSuite(config, filter) {
|
|
|
1736
1657
|
...config.signal !== void 0 ? { signal: config.signal } : {},
|
|
1737
1658
|
...config.chatClient !== void 0 ? { chatClient: config.chatClient } : {},
|
|
1738
1659
|
...config.logger !== void 0 ? { logger: config.logger } : {},
|
|
1739
|
-
...config.onException !== void 0 ? { onException: config.onException } : {}
|
|
1660
|
+
...config.onException !== void 0 ? { onException: config.onException } : {},
|
|
1661
|
+
...config.createSpanSource !== void 0 ? { spanSource: config.createSpanSource() } : {},
|
|
1662
|
+
...config.chatWebhookId !== void 0 ? { chatWebhookId: config.chatWebhookId } : {},
|
|
1663
|
+
...config.chatBaseUrl !== void 0 ? { chatBaseUrl: config.chatBaseUrl } : {}
|
|
1740
1664
|
});
|
|
1741
1665
|
reports.push(report);
|
|
1742
|
-
config.onProgress?.({ type: "eval_complete", evalName: evalDef.name, index: i, report });
|
|
1666
|
+
await config.onProgress?.({ type: "eval_complete", evalName: evalDef.name, index: i, report });
|
|
1743
1667
|
}
|
|
1744
1668
|
const runReport = {
|
|
1745
1669
|
id: runId,
|
|
@@ -1754,7 +1678,7 @@ async function runEvalSuite(config, filter) {
|
|
|
1754
1678
|
if (config.signal?.aborted) {
|
|
1755
1679
|
runReport.aborted = true;
|
|
1756
1680
|
}
|
|
1757
|
-
config.onProgress?.({ type: "suite_complete", report: runReport });
|
|
1681
|
+
await config.onProgress?.({ type: "suite_complete", report: runReport });
|
|
1758
1682
|
return runReport;
|
|
1759
1683
|
}
|
|
1760
1684
|
export {
|