@botpress/evals 1.19.0-beta.9 → 2.0.0-alpha.1
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 +21 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +103 -36
- package/dist/client.js.map +4 -4
- package/dist/definition.d.ts +9 -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.map +2 -2
- 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 +316 -183
- 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 +725 -0
- package/dist/spans/index.js.map +7 -0
- package/dist/spans/local-span-source.d.ts +19 -0
- package/dist/spans/local-span-source.d.ts.map +1 -0
- package/dist/spans/local-span-source.js +543 -0
- package/dist/spans/local-span-source.js.map +7 -0
- package/dist/spans/span-source.d.ts +40 -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} +44 -5
- package/dist/spans/sse-collector.d.ts.map +1 -0
- package/dist/{sse-collector.js → spans/sse-collector.js} +97 -20
- 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 +42 -0
- package/dist/spans/vortex-span-source.d.ts.map +1 -0
- package/dist/spans/vortex-span-source.js +184 -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 +142 -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 +1156 -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 +595 -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 +702 -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,10 +23,6 @@ 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
26
|
// ../analytics/src/errors.ts
|
|
34
27
|
var AdkError = class extends Error {
|
|
35
28
|
/**
|
|
@@ -79,30 +72,27 @@ var EvalRunnerError = class extends AdkError {
|
|
|
79
72
|
};
|
|
80
73
|
|
|
81
74
|
// src/client.ts
|
|
82
|
-
var
|
|
83
|
-
function getDefaultChatClient() {
|
|
84
|
-
if (defaultChatClient) return defaultChatClient;
|
|
85
|
-
const require2 = createRequire(import.meta.url);
|
|
86
|
-
const mod = require2("@botpress/chat");
|
|
87
|
-
const ChatClient = mod.default?.Client ?? mod.Client;
|
|
88
|
-
if (!ChatClient) {
|
|
89
|
-
throw new EvalRunnerError({
|
|
90
|
-
code: "CHAT_CLIENT_MISSING",
|
|
91
|
-
message: "@botpress/chat did not expose a Client export. Pass EvalRunnerConfig.chatClient explicitly."
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
return ChatClient;
|
|
95
|
-
}
|
|
75
|
+
var TRACE_STREAM_PREFLIGHT_TIMEOUT_MS = 1e4;
|
|
96
76
|
var ChatSession = class {
|
|
97
|
-
constructor(
|
|
98
|
-
this.
|
|
99
|
-
this.
|
|
77
|
+
constructor(bpClient, botId, _chatWebhookId, _chatBaseUrl, _chatClient) {
|
|
78
|
+
this.bpClient = bpClient;
|
|
79
|
+
this.botId = botId;
|
|
80
|
+
this._chatWebhookId = _chatWebhookId;
|
|
81
|
+
this._chatBaseUrl = _chatBaseUrl;
|
|
82
|
+
this._chatClient = _chatClient;
|
|
100
83
|
}
|
|
101
84
|
client = null;
|
|
102
85
|
conversationId = null;
|
|
103
86
|
async connect() {
|
|
104
|
-
const
|
|
105
|
-
this.
|
|
87
|
+
const webhookId = this._chatWebhookId ?? await discoverWebhookId(this.bpClient, this.botId);
|
|
88
|
+
const ChatClientCtor = this._chatClient ?? (await import(
|
|
89
|
+
/* webpackIgnore: true */
|
|
90
|
+
"@botpress/chat"
|
|
91
|
+
)).Client;
|
|
92
|
+
this.client = await ChatClientCtor.connect({
|
|
93
|
+
webhookId,
|
|
94
|
+
...this._chatBaseUrl ? { baseApiUrl: this._chatBaseUrl } : {}
|
|
95
|
+
});
|
|
106
96
|
}
|
|
107
97
|
/** Invariant guard — calling any session method before connect() is a runner bug. */
|
|
108
98
|
assertConnected() {
|
|
@@ -126,6 +116,16 @@ var ChatSession = class {
|
|
|
126
116
|
}
|
|
127
117
|
return this.conversationId;
|
|
128
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Open a fresh conversation under the same user (the user is fixed at
|
|
121
|
+
* connect()) and make it active. Returns the new conversation id.
|
|
122
|
+
*/
|
|
123
|
+
async newConversation() {
|
|
124
|
+
const client = this.assertConnected();
|
|
125
|
+
const conv = await client.createConversation({});
|
|
126
|
+
this.conversationId = conv.conversation.id;
|
|
127
|
+
return conv.conversation.id;
|
|
128
|
+
}
|
|
129
129
|
/**
|
|
130
130
|
* Send a user message. Fire-and-forget — observation is via SSECollector.
|
|
131
131
|
*/
|
|
@@ -134,17 +134,13 @@ var ChatSession = class {
|
|
|
134
134
|
const conversationId = await this.ensureConversation();
|
|
135
135
|
await client.createMessage({ conversationId, payload: { type: "text", text: message } });
|
|
136
136
|
}
|
|
137
|
-
|
|
138
|
-
* Send an event. Fire-and-forget — observation is via SSECollector.
|
|
139
|
-
*/
|
|
140
|
-
async sendEvent(type, payload) {
|
|
137
|
+
async sendEvent(payload) {
|
|
141
138
|
const client = this.assertConnected();
|
|
142
139
|
const conversationId = await this.ensureConversation();
|
|
143
|
-
await client.createEvent({
|
|
140
|
+
await client.createEvent({ payload, conversationId });
|
|
144
141
|
}
|
|
145
142
|
};
|
|
146
|
-
async function discoverWebhookId(
|
|
147
|
-
const client = new BpClient({ token, botId, apiUrl });
|
|
143
|
+
async function discoverWebhookId(client, botId) {
|
|
148
144
|
const { bot } = await client.getBot({ id: botId });
|
|
149
145
|
const integrations = bot.integrations || {};
|
|
150
146
|
const chat = Object.values(integrations).find((int) => int.name === "chat");
|
|
@@ -156,7 +152,7 @@ async function discoverWebhookId(botId, token, apiUrl) {
|
|
|
156
152
|
"The `chat` integration is not installed on this bot \u2014 evals require it.",
|
|
157
153
|
"",
|
|
158
154
|
"To fix:",
|
|
159
|
-
" 1. Install it: adk add chat",
|
|
155
|
+
" 1. Install it: adk integrations add chat",
|
|
160
156
|
" 2. Redeploy: adk dev (or: adk deploy)"
|
|
161
157
|
].join("\n"),
|
|
162
158
|
expected: true
|
|
@@ -164,6 +160,77 @@ async function discoverWebhookId(botId, token, apiUrl) {
|
|
|
164
160
|
}
|
|
165
161
|
return webhookId;
|
|
166
162
|
}
|
|
163
|
+
async function assertTraceStreamReadable(devServerUrl, headers = {}, timeoutMs = TRACE_STREAM_PREFLIGHT_TIMEOUT_MS) {
|
|
164
|
+
const url = `${devServerUrl.replace(/\/$/, "")}/api/traces/stream?count=1`;
|
|
165
|
+
const abortController = new AbortController();
|
|
166
|
+
let timedOut = false;
|
|
167
|
+
let reader = null;
|
|
168
|
+
const timer = setTimeout(() => {
|
|
169
|
+
timedOut = true;
|
|
170
|
+
abortController.abort();
|
|
171
|
+
}, timeoutMs);
|
|
172
|
+
try {
|
|
173
|
+
const res = await fetch(url, {
|
|
174
|
+
headers: { ...headers, Accept: "text/event-stream" },
|
|
175
|
+
signal: abortController.signal
|
|
176
|
+
});
|
|
177
|
+
if (!res.ok) {
|
|
178
|
+
throw traceStreamPreflightError(`Trace stream pre-flight failed: ${res.status} ${res.statusText}`);
|
|
179
|
+
}
|
|
180
|
+
if (!res.body) {
|
|
181
|
+
throw traceStreamPreflightError("Trace stream pre-flight failed: response had no body.");
|
|
182
|
+
}
|
|
183
|
+
reader = res.body.getReader();
|
|
184
|
+
const decoder = new TextDecoder();
|
|
185
|
+
let buffer = "";
|
|
186
|
+
while (true) {
|
|
187
|
+
const { done, value } = await reader.read();
|
|
188
|
+
if (done) {
|
|
189
|
+
throw traceStreamPreflightError("Trace stream pre-flight failed: stream closed before snapshot.");
|
|
190
|
+
}
|
|
191
|
+
buffer += decoder.decode(value, { stream: true });
|
|
192
|
+
const parts = buffer.split("\n\n");
|
|
193
|
+
buffer = parts.pop() ?? "";
|
|
194
|
+
for (const part of parts) {
|
|
195
|
+
if (parseSSEEventName(part) === "snapshot") {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
if (err instanceof EvalRunnerError) {
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
if (timedOut) {
|
|
205
|
+
throw traceStreamPreflightError(
|
|
206
|
+
`Trace stream pre-flight timed out after ${timeoutMs}ms before receiving a snapshot.`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
throw traceStreamPreflightError(
|
|
210
|
+
`Trace stream pre-flight failed: ${err instanceof Error ? err.message : String(err)}`
|
|
211
|
+
);
|
|
212
|
+
} finally {
|
|
213
|
+
clearTimeout(timer);
|
|
214
|
+
reader?.cancel().catch(() => {
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function parseSSEEventName(block) {
|
|
219
|
+
for (const line of block.split("\n")) {
|
|
220
|
+
if (line.startsWith("event:")) {
|
|
221
|
+
return line.slice("event:".length).trim();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
function traceStreamPreflightError(message) {
|
|
227
|
+
return new EvalRunnerError({
|
|
228
|
+
code: "SSE_CONNECT_FAILED",
|
|
229
|
+
message,
|
|
230
|
+
expected: true,
|
|
231
|
+
suggestion: "Make sure the dev server is running (`adk dev`) and trace streaming is available."
|
|
232
|
+
});
|
|
233
|
+
}
|
|
167
234
|
async function assertChatChannelBound(devServerUrl, headers = {}, logger = defaultLogger) {
|
|
168
235
|
let conversations = [];
|
|
169
236
|
try {
|
|
@@ -201,13 +268,17 @@ async function assertChatChannelBound(devServerUrl, headers = {}, logger = defau
|
|
|
201
268
|
});
|
|
202
269
|
}
|
|
203
270
|
|
|
204
|
-
// src/sse-collector.ts
|
|
271
|
+
// src/spans/sse-collector.ts
|
|
205
272
|
function spanKey(span) {
|
|
206
273
|
return `${span.id.trace}:${span.id.span}`;
|
|
207
274
|
}
|
|
208
275
|
function spanData(span) {
|
|
209
276
|
return span.data && typeof span.data === "object" ? span.data : {};
|
|
210
277
|
}
|
|
278
|
+
var TURN_HANDLER_SPAN_NAMES = /* @__PURE__ */ new Set(["handler.conversation", "handler.event"]);
|
|
279
|
+
function isTurnHandlerSpan(span) {
|
|
280
|
+
return TURN_HANDLER_SPAN_NAMES.has(span.name);
|
|
281
|
+
}
|
|
211
282
|
var SSECollector = class _SSECollector {
|
|
212
283
|
devServerUrl;
|
|
213
284
|
headers;
|
|
@@ -225,6 +296,10 @@ var SSECollector = class _SSECollector {
|
|
|
225
296
|
connected = false;
|
|
226
297
|
/** Error from the SSE stream if it died unexpectedly. */
|
|
227
298
|
streamError = null;
|
|
299
|
+
/** Passive counters used only to explain timeout failures. */
|
|
300
|
+
updateEventCount = 0;
|
|
301
|
+
keepaliveEventCount = 0;
|
|
302
|
+
lastEvent = null;
|
|
228
303
|
/** Set while a settle phase is in flight; called by disconnect() to short-circuit. */
|
|
229
304
|
_cancelSettle = null;
|
|
230
305
|
constructor(devServerUrl, headers = {}) {
|
|
@@ -240,9 +315,10 @@ var SSECollector = class _SSECollector {
|
|
|
240
315
|
const attributeValue = "conversationId" in filter ? filter.conversationId : filter.workflowId;
|
|
241
316
|
const url = `${this.devServerUrl}/api/traces/stream?attributeName=${encodeURIComponent(attributeName)}&attributeValue=${encodeURIComponent(attributeValue)}`;
|
|
242
317
|
this.abortController = new AbortController();
|
|
318
|
+
const myController = this.abortController;
|
|
243
319
|
const response = await fetch(url, {
|
|
244
320
|
headers: { ...this.headers, Accept: "text/event-stream" },
|
|
245
|
-
signal:
|
|
321
|
+
signal: myController.signal
|
|
246
322
|
});
|
|
247
323
|
if (!response.ok) {
|
|
248
324
|
throw new EvalRunnerError({
|
|
@@ -257,28 +333,48 @@ var SSECollector = class _SSECollector {
|
|
|
257
333
|
}
|
|
258
334
|
this.connected = true;
|
|
259
335
|
this._consumeStream(response.body).catch((err) => {
|
|
260
|
-
if (this.connected) {
|
|
336
|
+
if (this.connected && this.abortController === myController) {
|
|
261
337
|
this.streamError = err instanceof Error ? err : new Error(String(err));
|
|
262
338
|
}
|
|
263
339
|
});
|
|
264
340
|
await this._waitForSnapshot();
|
|
265
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Re-point at a new conversation, keeping `spanMap` and `seenHandlerKeys` (so
|
|
344
|
+
* the earlier conversation stays visible and its completed handler can't
|
|
345
|
+
* satisfy the next turn). Resets the snapshot latch so connect() awaits the
|
|
346
|
+
* new conversation's snapshot.
|
|
347
|
+
*/
|
|
348
|
+
async repoint(filter) {
|
|
349
|
+
this.connected = false;
|
|
350
|
+
this.abortController?.abort();
|
|
351
|
+
this.abortController = null;
|
|
352
|
+
this._cancelSettle?.();
|
|
353
|
+
this._cancelSettle = null;
|
|
354
|
+
this.waiters = [];
|
|
355
|
+
this.snapshotReceived = false;
|
|
356
|
+
this.snapshotResolvers = [];
|
|
357
|
+
this.streamError = null;
|
|
358
|
+
await this.connect(filter);
|
|
359
|
+
}
|
|
266
360
|
/**
|
|
267
361
|
* Mark the beginning of a new turn.
|
|
268
|
-
* Records which handler
|
|
269
|
-
* so waitForTurnComplete() only resolves on NEW
|
|
362
|
+
* Records which turn-handler span IDs (handler.conversation / handler.event)
|
|
363
|
+
* have been seen so far, so waitForTurnComplete() only resolves on NEW
|
|
364
|
+
* handler completions.
|
|
270
365
|
*/
|
|
271
366
|
startTurn() {
|
|
272
367
|
this.turnStartKeys = new Set(this.spanMap.keys());
|
|
273
368
|
for (const [key, span] of this.spanMap) {
|
|
274
|
-
if (span
|
|
369
|
+
if (isTurnHandlerSpan(span)) {
|
|
275
370
|
this.seenHandlerKeys.add(key);
|
|
276
371
|
}
|
|
277
372
|
}
|
|
278
373
|
}
|
|
279
374
|
static SETTLE_MS = 100;
|
|
280
375
|
/**
|
|
281
|
-
* Wait for a handler
|
|
376
|
+
* Wait for a turn-handler span (handler.conversation or handler.event) to
|
|
377
|
+
* reach terminal status (ok or error).
|
|
282
378
|
* Only considers spans NOT seen before the last startTurn() call.
|
|
283
379
|
*
|
|
284
380
|
* After the handler closes, waits for a quiet period (no new spans for SETTLE_MS)
|
|
@@ -287,8 +383,10 @@ var SSECollector = class _SSECollector {
|
|
|
287
383
|
* spans may arrive slightly after the handler close event.
|
|
288
384
|
*/
|
|
289
385
|
async waitForTurnComplete(opts) {
|
|
290
|
-
|
|
291
|
-
|
|
386
|
+
const accept = opts.acceptSpanNames;
|
|
387
|
+
const settle = { quietMs: opts.settleQuietMs, maxMs: opts.settleMaxMs };
|
|
388
|
+
if (this._hasNewCompletedHandler(accept)) {
|
|
389
|
+
return this._settleAfterHandler(settle);
|
|
292
390
|
}
|
|
293
391
|
if (opts.abortSignal?.aborted) {
|
|
294
392
|
throw new Error("waitForTurnComplete aborted");
|
|
@@ -297,8 +395,13 @@ var SSECollector = class _SSECollector {
|
|
|
297
395
|
const timer = setTimeout(() => {
|
|
298
396
|
this._removeWaiter(waiter);
|
|
299
397
|
opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
300
|
-
const
|
|
301
|
-
|
|
398
|
+
const expected = accept ? [...accept].join(" or ") : "handler.conversation or handler.event";
|
|
399
|
+
const reason = this.streamError ? `SSE stream died: ${this.streamError.message}` : `no ${expected} span completed`;
|
|
400
|
+
reject(
|
|
401
|
+
new Error(
|
|
402
|
+
`waitForTurnComplete timed out after ${opts.timeout}ms \u2014 ${reason}; diagnostics: ${this._formatTurnDiagnostics()}`
|
|
403
|
+
)
|
|
404
|
+
);
|
|
302
405
|
}, opts.timeout);
|
|
303
406
|
const onAbort = () => {
|
|
304
407
|
clearTimeout(timer);
|
|
@@ -307,11 +410,11 @@ var SSECollector = class _SSECollector {
|
|
|
307
410
|
};
|
|
308
411
|
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
309
412
|
const waiter = (_span) => {
|
|
310
|
-
if (this._hasNewCompletedHandler()) {
|
|
413
|
+
if (this._hasNewCompletedHandler(accept)) {
|
|
311
414
|
clearTimeout(timer);
|
|
312
415
|
this._removeWaiter(waiter);
|
|
313
416
|
opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
314
|
-
this._settleAfterHandler().then(resolve2, reject);
|
|
417
|
+
this._settleAfterHandler(settle).then(resolve2, reject);
|
|
315
418
|
}
|
|
316
419
|
};
|
|
317
420
|
this.waiters.push(waiter);
|
|
@@ -325,7 +428,9 @@ var SSECollector = class _SSECollector {
|
|
|
325
428
|
* Exposes a cancellation hook via _cancelSettle so disconnect() can short-
|
|
326
429
|
* circuit an in-flight settle phase and avoid leaking timers for up to 2s.
|
|
327
430
|
*/
|
|
328
|
-
_settleAfterHandler() {
|
|
431
|
+
_settleAfterHandler(settle) {
|
|
432
|
+
const quietMs = settle?.quietMs ?? _SSECollector.SETTLE_MS;
|
|
433
|
+
const maxMs = settle?.maxMs ?? _SSECollector.MAX_SETTLE_MS;
|
|
329
434
|
return new Promise((resolve2) => {
|
|
330
435
|
let resolved = false;
|
|
331
436
|
const done = () => {
|
|
@@ -337,12 +442,12 @@ var SSECollector = class _SSECollector {
|
|
|
337
442
|
this._cancelSettle = null;
|
|
338
443
|
resolve2();
|
|
339
444
|
};
|
|
340
|
-
const hardCap = setTimeout(done,
|
|
341
|
-
let settleTimer = setTimeout(done,
|
|
445
|
+
const hardCap = setTimeout(done, maxMs);
|
|
446
|
+
let settleTimer = setTimeout(done, quietMs);
|
|
342
447
|
const settleWaiter = () => {
|
|
343
448
|
if (resolved) return;
|
|
344
449
|
clearTimeout(settleTimer);
|
|
345
|
-
settleTimer = setTimeout(done,
|
|
450
|
+
settleTimer = setTimeout(done, quietMs);
|
|
346
451
|
};
|
|
347
452
|
this.waiters.push(settleWaiter);
|
|
348
453
|
this._cancelSettle = done;
|
|
@@ -507,6 +612,7 @@ ${value}` : value;
|
|
|
507
612
|
* Dispatch a parsed SSE event to the appropriate handler.
|
|
508
613
|
*/
|
|
509
614
|
_handleEvent(eventName, payload) {
|
|
615
|
+
this._recordEvent(eventName);
|
|
510
616
|
if (eventName === "snapshot") {
|
|
511
617
|
const data2 = payload;
|
|
512
618
|
const spans = Array.isArray(data2.spans) ? data2.spans : [];
|
|
@@ -526,6 +632,44 @@ ${value}` : value;
|
|
|
526
632
|
}
|
|
527
633
|
}
|
|
528
634
|
}
|
|
635
|
+
_recordEvent(eventName) {
|
|
636
|
+
this.lastEvent = { name: eventName, at: Date.now() };
|
|
637
|
+
if (eventName === "update") {
|
|
638
|
+
this.updateEventCount++;
|
|
639
|
+
} else if (eventName === "keepalive") {
|
|
640
|
+
this.keepaliveEventCount++;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
_formatTurnDiagnostics() {
|
|
644
|
+
const turnSpans = this.getTurnSpans();
|
|
645
|
+
const spanNames = /* @__PURE__ */ new Set();
|
|
646
|
+
const handlerStatuses = /* @__PURE__ */ new Set();
|
|
647
|
+
for (const span of turnSpans) {
|
|
648
|
+
spanNames.add(span.name);
|
|
649
|
+
if (isTurnHandlerSpan(span)) {
|
|
650
|
+
handlerStatuses.add(span.status);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const lastEvent = this.lastEvent ? `${this.lastEvent.name} ${Date.now() - this.lastEvent.at}ms ago` : "none";
|
|
654
|
+
return [
|
|
655
|
+
`snapshotReceived=${this.snapshotReceived}`,
|
|
656
|
+
`totalUpdates=${this.updateEventCount}`,
|
|
657
|
+
`totalKeepalives=${this.keepaliveEventCount}`,
|
|
658
|
+
`lastEvent=${lastEvent}`,
|
|
659
|
+
`totalSpans=${this.spanMap.size}`,
|
|
660
|
+
`turnSpans=${turnSpans.length}`,
|
|
661
|
+
`turnSpanNames=${this._formatSet(spanNames)}`,
|
|
662
|
+
`turnHandlerStatuses=${this._formatSet(handlerStatuses)}`
|
|
663
|
+
].join(", ");
|
|
664
|
+
}
|
|
665
|
+
_formatSet(values, limit = 12) {
|
|
666
|
+
if (values.size === 0) {
|
|
667
|
+
return "none";
|
|
668
|
+
}
|
|
669
|
+
const items = Array.from(values);
|
|
670
|
+
const suffix = items.length > limit ? `|+${items.length - limit} more` : "";
|
|
671
|
+
return `${items.slice(0, limit).join("|")}${suffix}`;
|
|
672
|
+
}
|
|
529
673
|
/**
|
|
530
674
|
* Insert or replace a span in the map (dedup by trace+span ID).
|
|
531
675
|
*/
|
|
@@ -551,7 +695,7 @@ ${value}` : value;
|
|
|
551
695
|
}
|
|
552
696
|
}
|
|
553
697
|
/**
|
|
554
|
-
* Check if there is a handler.conversation
|
|
698
|
+
* Check if there is a turn-handler span (handler.conversation / handler.event) that:
|
|
555
699
|
* 1. Was NOT seen before startTurn()
|
|
556
700
|
* 2. Has terminal status (ok or error)
|
|
557
701
|
*
|
|
@@ -560,9 +704,9 @@ ${value}` : value;
|
|
|
560
704
|
* waitForTurnComplete check (which would otherwise resolve instantly
|
|
561
705
|
* against stale data instead of waiting for the new turn's handler).
|
|
562
706
|
*/
|
|
563
|
-
_hasNewCompletedHandler() {
|
|
707
|
+
_hasNewCompletedHandler(acceptSpanNames) {
|
|
564
708
|
for (const [key, span] of this.spanMap) {
|
|
565
|
-
if (span
|
|
709
|
+
if (isTurnHandlerSpan(span) && (!acceptSpanNames || acceptSpanNames.has(span.name)) && !this.seenHandlerKeys.has(key) && (span.status === "ok" || span.status === "error")) {
|
|
566
710
|
this.seenHandlerKeys.add(key);
|
|
567
711
|
return true;
|
|
568
712
|
}
|
|
@@ -589,6 +733,38 @@ ${value}` : value;
|
|
|
589
733
|
}
|
|
590
734
|
};
|
|
591
735
|
|
|
736
|
+
// src/spans/local-span-source.ts
|
|
737
|
+
var LocalSpanSource = class {
|
|
738
|
+
collector;
|
|
739
|
+
constructor(devServerUrl, headers = {}) {
|
|
740
|
+
this.collector = new SSECollector(devServerUrl, headers);
|
|
741
|
+
}
|
|
742
|
+
async connect(filter) {
|
|
743
|
+
await this.collector.connect(filter);
|
|
744
|
+
}
|
|
745
|
+
async repoint(filter) {
|
|
746
|
+
await this.collector.repoint(filter);
|
|
747
|
+
}
|
|
748
|
+
startTurn() {
|
|
749
|
+
this.collector.startTurn();
|
|
750
|
+
}
|
|
751
|
+
async waitForTurnComplete(opts) {
|
|
752
|
+
await this.collector.waitForTurnComplete(opts);
|
|
753
|
+
}
|
|
754
|
+
async waitForWorkflow(name, opts) {
|
|
755
|
+
await this.collector.waitForWorkflow(name, opts);
|
|
756
|
+
}
|
|
757
|
+
getTurnSpans() {
|
|
758
|
+
return this.collector.getTurnSpans();
|
|
759
|
+
}
|
|
760
|
+
getAllSpans() {
|
|
761
|
+
return this.collector.getAllSpans();
|
|
762
|
+
}
|
|
763
|
+
disconnect() {
|
|
764
|
+
this.collector.disconnect();
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
|
|
592
768
|
// src/transformer.ts
|
|
593
769
|
function data(span) {
|
|
594
770
|
return span.data && typeof span.data === "object" ? span.data : {};
|
|
@@ -676,7 +852,7 @@ function extractWorkflowSpans(spans) {
|
|
|
676
852
|
}
|
|
677
853
|
function extractHandler(spans) {
|
|
678
854
|
const handler = spans.find(
|
|
679
|
-
(s) => (s.name === "handler.conversation" || s.name === "handler.workflow") && s.status !== "running"
|
|
855
|
+
(s) => (s.name === "handler.conversation" || s.name === "handler.event" || s.name === "handler.workflow") && s.status !== "running"
|
|
680
856
|
);
|
|
681
857
|
if (!handler) {
|
|
682
858
|
return { handlerDuration: 0, handlerStatus: "ok" };
|
|
@@ -688,55 +864,26 @@ function extractHandler(spans) {
|
|
|
688
864
|
}
|
|
689
865
|
|
|
690
866
|
// src/graders/llm.ts
|
|
691
|
-
import { Client } from "@botpress/client";
|
|
692
867
|
import { Cognitive } from "@botpress/cognitive";
|
|
693
|
-
|
|
694
|
-
function buildJudgeSystemPrompt(passThreshold) {
|
|
695
|
-
return `You are an evaluation judge for a chatbot. You will be given:
|
|
696
|
-
- The user's message
|
|
697
|
-
- The bot's response
|
|
698
|
-
- Grading criteria
|
|
699
|
-
|
|
700
|
-
Your job is to determine if the bot's response meets the criteria.
|
|
701
|
-
|
|
702
|
-
Respond with a JSON object:
|
|
703
|
-
{
|
|
704
|
-
"pass": true/false,
|
|
705
|
-
"score": 1-5,
|
|
706
|
-
"reason": "brief explanation"
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
Scoring guide:
|
|
710
|
-
- 5: Fully meets criteria, excellent response
|
|
711
|
-
- 4: Mostly meets criteria, minor issues
|
|
712
|
-
- 3: Partially meets criteria, notable gaps
|
|
713
|
-
- 2: Barely meets criteria, significant issues
|
|
714
|
-
- 1: Does not meet criteria
|
|
715
|
-
|
|
716
|
-
A score of ${passThreshold} or above is a pass.`;
|
|
717
|
-
}
|
|
868
|
+
import { Zai } from "@botpress/zai";
|
|
718
869
|
var MODEL_ALIASES = ["fast", "best"];
|
|
719
|
-
var
|
|
870
|
+
var _zai = null;
|
|
720
871
|
var _availableModels = [];
|
|
721
872
|
var _judgeModel = "fast";
|
|
873
|
+
var _failClosed = true;
|
|
722
874
|
var _log = defaultLogger;
|
|
723
875
|
function _isModelValid(model) {
|
|
724
876
|
if (MODEL_ALIASES.includes(model)) return true;
|
|
725
877
|
if (_availableModels.length === 0) return false;
|
|
726
878
|
return _availableModels.includes(model);
|
|
727
879
|
}
|
|
728
|
-
async function initLLMJudge(
|
|
880
|
+
async function initLLMJudge(client, options) {
|
|
729
881
|
const log = options?.logger ?? defaultLogger;
|
|
730
882
|
_log = log;
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
apiUrl: credentials.apiUrl,
|
|
734
|
-
botId: credentials.botId,
|
|
735
|
-
...credentials.workspaceId !== void 0 ? { workspaceId: credentials.workspaceId } : {}
|
|
736
|
-
});
|
|
737
|
-
_cognitive = new Cognitive({ client, __experimental_beta: true });
|
|
883
|
+
_failClosed = options?.failClosed ?? true;
|
|
884
|
+
const cognitive = new Cognitive({ client, __experimental_beta: true });
|
|
738
885
|
try {
|
|
739
|
-
_availableModels = Array.from((await
|
|
886
|
+
_availableModels = Array.from((await cognitive.fetchRemoteModels()).keys());
|
|
740
887
|
} catch (err) {
|
|
741
888
|
log.warn(`Failed to fetch available models: ${err.message}. LLM judge will be unavailable.`);
|
|
742
889
|
_availableModels = [];
|
|
@@ -747,66 +894,42 @@ async function initLLMJudge(credentials, options) {
|
|
|
747
894
|
} else if (!_isModelValid(_judgeModel)) {
|
|
748
895
|
log.warn(`Configured LLM judge model "${_judgeModel}" is invalid. Run "adk models" to list available models.`);
|
|
749
896
|
}
|
|
897
|
+
_zai = new Zai({ client: cognitive, modelId: _judgeModel });
|
|
750
898
|
}
|
|
751
899
|
async function gradeLLMJudge(botResponse, criteria, context) {
|
|
752
|
-
const
|
|
900
|
+
const assertion = `llm_judge: "${criteria}"`;
|
|
753
901
|
try {
|
|
754
|
-
if (!
|
|
902
|
+
if (!_zai) {
|
|
755
903
|
_log.warn(`LLM judge unavailable for "${criteria}": no credentials configured (initLLMJudge not called)`);
|
|
756
904
|
return {
|
|
757
|
-
assertion
|
|
758
|
-
pass:
|
|
905
|
+
assertion,
|
|
906
|
+
pass: !_failClosed,
|
|
907
|
+
skipped: true,
|
|
759
908
|
expected: criteria,
|
|
760
909
|
actual: "FAILED \u2014 LLM judge unavailable: no credentials configured"
|
|
761
910
|
};
|
|
762
911
|
}
|
|
763
912
|
if (!_isModelValid(_judgeModel)) {
|
|
764
913
|
return {
|
|
765
|
-
assertion
|
|
914
|
+
assertion,
|
|
766
915
|
pass: false,
|
|
767
916
|
expected: criteria,
|
|
768
917
|
actual: "FAILED \u2014 invalid LLM judge model"
|
|
769
918
|
};
|
|
770
919
|
}
|
|
771
|
-
const { output } = await
|
|
772
|
-
model: _judgeModel,
|
|
773
|
-
temperature: 0,
|
|
774
|
-
responseFormat: "json_object",
|
|
775
|
-
systemPrompt: buildJudgeSystemPrompt(threshold),
|
|
776
|
-
messages: [
|
|
777
|
-
{
|
|
778
|
-
role: "user",
|
|
779
|
-
content: `User message: ${context.userMessage}
|
|
780
|
-
|
|
781
|
-
Bot response: ${botResponse}
|
|
782
|
-
|
|
783
|
-
Criteria: ${criteria}`
|
|
784
|
-
}
|
|
785
|
-
]
|
|
786
|
-
});
|
|
787
|
-
const rawContent = output?.choices?.[0]?.content;
|
|
788
|
-
const content = typeof rawContent === "string" ? rawContent : null;
|
|
789
|
-
if (!content) {
|
|
790
|
-
_log.warn(`LLM judge returned an empty response for "${criteria}"`);
|
|
791
|
-
return {
|
|
792
|
-
assertion: `llm_judge: "${criteria}"`,
|
|
793
|
-
pass: false,
|
|
794
|
-
expected: criteria,
|
|
795
|
-
actual: "FAILED \u2014 LLM judge returned empty response"
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
const verdict = JSON.parse(content);
|
|
920
|
+
const { output } = await _zai.check({ userMessage: context.userMessage, botResponse }, criteria).result();
|
|
799
921
|
return {
|
|
800
|
-
assertion
|
|
801
|
-
pass:
|
|
922
|
+
assertion,
|
|
923
|
+
pass: output.value,
|
|
802
924
|
expected: criteria,
|
|
803
|
-
actual:
|
|
925
|
+
actual: `${output.value ? "Pass" : "Fail"} \u2014 ${output.explanation}`
|
|
804
926
|
};
|
|
805
927
|
} catch (err) {
|
|
806
928
|
_log.warn(`LLM judge failed for "${criteria}": ${err.message}`);
|
|
807
929
|
return {
|
|
808
|
-
assertion
|
|
809
|
-
pass:
|
|
930
|
+
assertion,
|
|
931
|
+
pass: !_failClosed,
|
|
932
|
+
skipped: true,
|
|
810
933
|
expected: criteria,
|
|
811
934
|
actual: `FAILED \u2014 LLM judge unavailable: ${err.message}`
|
|
812
935
|
};
|
|
@@ -1315,27 +1438,22 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1315
1438
|
const start = Date.now();
|
|
1316
1439
|
const turns = [];
|
|
1317
1440
|
let outcomeAssertions = [];
|
|
1318
|
-
const collector = new
|
|
1441
|
+
const collector = options.spanSource ?? new LocalSpanSource(devServerUrl, devServerHeaders);
|
|
1319
1442
|
try {
|
|
1320
|
-
const session = new ChatSession(
|
|
1443
|
+
const session = new ChatSession(
|
|
1444
|
+
connection.client,
|
|
1445
|
+
connection.botId,
|
|
1446
|
+
options.chatWebhookId,
|
|
1447
|
+
options.chatBaseUrl,
|
|
1448
|
+
options.chatClient
|
|
1449
|
+
);
|
|
1321
1450
|
await session.connect();
|
|
1322
|
-
let bpClient = null;
|
|
1323
|
-
const getBpClient = () => {
|
|
1324
|
-
if (!bpClient) {
|
|
1325
|
-
bpClient = new BpClient2({
|
|
1326
|
-
token: connection.token,
|
|
1327
|
-
botId: connection.botId,
|
|
1328
|
-
apiUrl: connection.apiUrl
|
|
1329
|
-
});
|
|
1330
|
-
}
|
|
1331
|
-
return bpClient;
|
|
1332
|
-
};
|
|
1333
1451
|
let conversationId = "";
|
|
1334
1452
|
if (evalDef.setup?.state?.conversation || evalDef.setup?.workflow) {
|
|
1335
1453
|
conversationId = await session.ensureConversation();
|
|
1336
1454
|
}
|
|
1337
1455
|
await seedEvalState(
|
|
1338
|
-
|
|
1456
|
+
connection.client,
|
|
1339
1457
|
{
|
|
1340
1458
|
botId: connection.botId,
|
|
1341
1459
|
userId: session.userId,
|
|
@@ -1344,7 +1462,7 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1344
1462
|
evalDef.setup
|
|
1345
1463
|
);
|
|
1346
1464
|
await triggerEvalWorkflow(
|
|
1347
|
-
|
|
1465
|
+
connection.client,
|
|
1348
1466
|
{
|
|
1349
1467
|
userId: session.userId,
|
|
1350
1468
|
...conversationId ? { conversationId } : {}
|
|
@@ -1380,24 +1498,34 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1380
1498
|
if (!turn.user && !turn.event) {
|
|
1381
1499
|
throw turnConfigError(`must have either 'user' or 'event'.`);
|
|
1382
1500
|
}
|
|
1383
|
-
const
|
|
1384
|
-
options.onProgress?.({
|
|
1501
|
+
const turnLabel = turn.event ? "[event]" : turn.user;
|
|
1502
|
+
await options.onProgress?.({
|
|
1385
1503
|
type: "turn_start",
|
|
1386
1504
|
evalName: evalDef.name,
|
|
1387
1505
|
evalIndex: options.evalIndex ?? 0,
|
|
1388
|
-
|
|
1506
|
+
turnIndex: i,
|
|
1389
1507
|
totalTurns: evalDef.conversation.length,
|
|
1390
|
-
userMessage:
|
|
1508
|
+
userMessage: turnLabel
|
|
1391
1509
|
});
|
|
1510
|
+
if (turn.newConversation && i > 0) {
|
|
1511
|
+
const newConversationId = await session.newConversation();
|
|
1512
|
+
await collector.repoint({ conversationId: newConversationId });
|
|
1513
|
+
}
|
|
1392
1514
|
collector.startTurn();
|
|
1393
1515
|
if (turn.event) {
|
|
1394
|
-
await session.sendEvent(turn.event.
|
|
1516
|
+
await session.sendEvent(turn.event.payload);
|
|
1395
1517
|
} else {
|
|
1396
1518
|
await session.sendMessage(turn.user);
|
|
1397
1519
|
}
|
|
1398
1520
|
try {
|
|
1399
1521
|
await collector.waitForTurnComplete({
|
|
1400
1522
|
timeout: idleTimeout,
|
|
1523
|
+
acceptSpanNames: turn.event ? /* @__PURE__ */ new Set(["handler.event"]) : /* @__PURE__ */ new Set(["handler.conversation"]),
|
|
1524
|
+
// Long quiet window: a tool-started workflow and its callback only run
|
|
1525
|
+
// AFTER the handler closes; sending the next message into that window
|
|
1526
|
+
// can get it absorbed by their transcript saves and silently skipped.
|
|
1527
|
+
settleQuietMs: 1500,
|
|
1528
|
+
settleMaxMs: 15e3,
|
|
1401
1529
|
...options.signal !== void 0 ? { abortSignal: options.signal } : {}
|
|
1402
1530
|
});
|
|
1403
1531
|
} catch (err) {
|
|
@@ -1421,7 +1549,6 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1421
1549
|
const turnData = transformSpans(turnSpans);
|
|
1422
1550
|
const botResponse = turnData.messages.join("\n");
|
|
1423
1551
|
const botDuration = turnData.handlerDuration;
|
|
1424
|
-
const turnLabel = turn.event ? `[event: ${turn.event.type}]` : turn.user;
|
|
1425
1552
|
const evalStart = Date.now();
|
|
1426
1553
|
let assertions = [];
|
|
1427
1554
|
const noResponse = !turn.expectSilence && turnData.messages.length === 0;
|
|
@@ -1476,7 +1603,7 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1476
1603
|
const turnPass = assertions.every((a) => a.pass);
|
|
1477
1604
|
const evalDuration = Date.now() - evalStart;
|
|
1478
1605
|
const turnReport = {
|
|
1479
|
-
|
|
1606
|
+
turnIndex: i,
|
|
1480
1607
|
userMessage: turnLabel,
|
|
1481
1608
|
botResponse,
|
|
1482
1609
|
assertions,
|
|
@@ -1485,11 +1612,11 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1485
1612
|
evalDuration
|
|
1486
1613
|
};
|
|
1487
1614
|
turns.push(turnReport);
|
|
1488
|
-
options.onProgress?.({
|
|
1615
|
+
await options.onProgress?.({
|
|
1489
1616
|
type: "turn_complete",
|
|
1490
1617
|
evalName: evalDef.name,
|
|
1491
1618
|
evalIndex: options.evalIndex ?? 0,
|
|
1492
|
-
|
|
1619
|
+
turnIndex: i,
|
|
1493
1620
|
totalTurns: evalDef.conversation.length,
|
|
1494
1621
|
turnReport
|
|
1495
1622
|
});
|
|
@@ -1536,7 +1663,15 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1536
1663
|
} catch (err) {
|
|
1537
1664
|
const adkErr = isAdkError(err) ? err : void 0;
|
|
1538
1665
|
if (!adkErr || !adkErr.expected) {
|
|
1539
|
-
|
|
1666
|
+
const errMsg = err.message ?? String(err);
|
|
1667
|
+
const errStack = err.stack ?? "";
|
|
1668
|
+
logger.error(`Eval "${evalDef.name}" failed unexpectedly: ${errMsg}`);
|
|
1669
|
+
logger.error(` stack: ${errStack}`);
|
|
1670
|
+
logger.error(` eval: ${evalDef.name}, turn: ${turns.length}/${evalDef.conversation.length}`);
|
|
1671
|
+
logger.error(` connection: botId=${connection.botId}`);
|
|
1672
|
+
logger.error(
|
|
1673
|
+
` spanSource: ${options.spanSource?.constructor?.name ?? "default"}, devServerUrl: ${options.devServerUrl ?? "n/a"}`
|
|
1674
|
+
);
|
|
1540
1675
|
options.onException?.(err, { source: "evals", eval_name: evalDef.name });
|
|
1541
1676
|
}
|
|
1542
1677
|
return {
|
|
@@ -1554,9 +1689,15 @@ async function runEval(evalDef, connection, options = {}) {
|
|
|
1554
1689
|
}
|
|
1555
1690
|
async function runEvalSuite(config, filter) {
|
|
1556
1691
|
const start = Date.now();
|
|
1557
|
-
const runId = randomUUID().replace(/-/g, "").slice(0, 26);
|
|
1558
|
-
|
|
1559
|
-
|
|
1692
|
+
const runId = config.runId ?? randomUUID().replace(/-/g, "").slice(0, 26);
|
|
1693
|
+
let allEvals;
|
|
1694
|
+
if (config.definitions) {
|
|
1695
|
+
allEvals = config.definitions;
|
|
1696
|
+
} else if (config.agentPath) {
|
|
1697
|
+
allEvals = await loadEvalsFromDir(`${config.agentPath}/evals`);
|
|
1698
|
+
} else {
|
|
1699
|
+
throw new Error("Either `definitions` or `agentPath` must be provided in EvalRunnerConfig");
|
|
1700
|
+
}
|
|
1560
1701
|
const evals = filterEvals(allEvals, filter);
|
|
1561
1702
|
if (evals.length === 0) {
|
|
1562
1703
|
return {
|
|
@@ -1570,37 +1711,26 @@ async function runEvalSuite(config, filter) {
|
|
|
1570
1711
|
...filter !== void 0 ? { filter } : {}
|
|
1571
1712
|
};
|
|
1572
1713
|
}
|
|
1573
|
-
await initLLMJudge(
|
|
1574
|
-
{
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
botId: config.credentials.botId,
|
|
1578
|
-
...config.credentials.workspaceId !== void 0 ? { workspaceId: config.credentials.workspaceId } : {}
|
|
1579
|
-
},
|
|
1580
|
-
{
|
|
1581
|
-
...config.evalOptions?.judgeModel !== void 0 ? { model: config.evalOptions.judgeModel } : {},
|
|
1582
|
-
...config.logger !== void 0 ? { logger: config.logger } : {}
|
|
1583
|
-
}
|
|
1584
|
-
);
|
|
1585
|
-
let webhookId = config.credentials.webhookId;
|
|
1586
|
-
if (!webhookId) {
|
|
1587
|
-
webhookId = await discoverWebhookId(config.credentials.botId, config.credentials.token, config.credentials.apiUrl);
|
|
1588
|
-
}
|
|
1714
|
+
await initLLMJudge(config.client, {
|
|
1715
|
+
...config.evalOptions?.judgeModel !== void 0 ? { model: config.evalOptions.judgeModel } : {},
|
|
1716
|
+
...config.logger !== void 0 ? { logger: config.logger } : {}
|
|
1717
|
+
});
|
|
1589
1718
|
const connection = {
|
|
1590
|
-
|
|
1591
|
-
botId: config.
|
|
1592
|
-
token: config.credentials.token,
|
|
1593
|
-
apiUrl: config.credentials.apiUrl
|
|
1719
|
+
client: config.client,
|
|
1720
|
+
botId: config.botId
|
|
1594
1721
|
};
|
|
1595
1722
|
const devServerUrl = config.devServerUrl || DEFAULT_DEV_SERVER_URL;
|
|
1596
1723
|
const devServerHeaders = config.devServerHeaders ?? {};
|
|
1597
|
-
|
|
1724
|
+
if (!config.createSpanSource) {
|
|
1725
|
+
await assertChatChannelBound(devServerUrl, devServerHeaders, config.logger);
|
|
1726
|
+
await assertTraceStreamReadable(devServerUrl, devServerHeaders);
|
|
1727
|
+
}
|
|
1598
1728
|
const reports = [];
|
|
1599
|
-
config.onProgress?.({ type: "suite_start", totalEvals: evals.length });
|
|
1729
|
+
await config.onProgress?.({ type: "suite_start", totalEvals: evals.length });
|
|
1600
1730
|
for (let i = 0; i < evals.length; i++) {
|
|
1601
1731
|
if (config.signal?.aborted) break;
|
|
1602
1732
|
const evalDef = evals[i];
|
|
1603
|
-
config.onProgress?.({
|
|
1733
|
+
await config.onProgress?.({
|
|
1604
1734
|
type: "eval_start",
|
|
1605
1735
|
evalName: evalDef.name,
|
|
1606
1736
|
index: i,
|
|
@@ -1616,10 +1746,13 @@ async function runEvalSuite(config, filter) {
|
|
|
1616
1746
|
...config.signal !== void 0 ? { signal: config.signal } : {},
|
|
1617
1747
|
...config.chatClient !== void 0 ? { chatClient: config.chatClient } : {},
|
|
1618
1748
|
...config.logger !== void 0 ? { logger: config.logger } : {},
|
|
1619
|
-
...config.onException !== void 0 ? { onException: config.onException } : {}
|
|
1749
|
+
...config.onException !== void 0 ? { onException: config.onException } : {},
|
|
1750
|
+
...config.createSpanSource !== void 0 ? { spanSource: config.createSpanSource() } : {},
|
|
1751
|
+
...config.chatWebhookId !== void 0 ? { chatWebhookId: config.chatWebhookId } : {},
|
|
1752
|
+
...config.chatBaseUrl !== void 0 ? { chatBaseUrl: config.chatBaseUrl } : {}
|
|
1620
1753
|
});
|
|
1621
1754
|
reports.push(report);
|
|
1622
|
-
config.onProgress?.({ type: "eval_complete", evalName: evalDef.name, index: i, report });
|
|
1755
|
+
await config.onProgress?.({ type: "eval_complete", evalName: evalDef.name, index: i, report });
|
|
1623
1756
|
}
|
|
1624
1757
|
const runReport = {
|
|
1625
1758
|
id: runId,
|
|
@@ -1634,7 +1767,7 @@ async function runEvalSuite(config, filter) {
|
|
|
1634
1767
|
if (config.signal?.aborted) {
|
|
1635
1768
|
runReport.aborted = true;
|
|
1636
1769
|
}
|
|
1637
|
-
config.onProgress?.({ type: "suite_complete", report: runReport });
|
|
1770
|
+
await config.onProgress?.({ type: "suite_complete", report: runReport });
|
|
1638
1771
|
return runReport;
|
|
1639
1772
|
}
|
|
1640
1773
|
export {
|