@botpress/evals 1.19.0-beta.14 → 1.19.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/client.d.ts +9 -9
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +74 -20
  4. package/dist/client.js.map +4 -4
  5. package/dist/definition.d.ts +2 -3
  6. package/dist/definition.d.ts.map +1 -1
  7. package/dist/definition.js.map +2 -2
  8. package/dist/graders/index.js +74 -21
  9. package/dist/graders/index.js.map +2 -2
  10. package/dist/graders/llm.d.ts +12 -12
  11. package/dist/graders/llm.d.ts.map +1 -1
  12. package/dist/graders/llm.js +75 -30
  13. package/dist/graders/llm.js.map +2 -2
  14. package/dist/graders/response.js +65 -16
  15. package/dist/graders/response.js.map +2 -2
  16. package/dist/index.d.ts +1 -3
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +0 -10
  19. package/dist/index.js.map +3 -3
  20. package/dist/loader.js +39 -1
  21. package/dist/loader.js.map +3 -3
  22. package/dist/runner.d.ts +1 -5
  23. package/dist/runner.d.ts.map +1 -1
  24. package/dist/runner.js +217 -141
  25. package/dist/runner.js.map +4 -4
  26. package/dist/{spans/sse-collector.d.ts → sse-collector.d.ts} +4 -6
  27. package/dist/sse-collector.d.ts.map +1 -0
  28. package/dist/{spans/sse-collector.js → sse-collector.js} +49 -17
  29. package/dist/sse-collector.js.map +7 -0
  30. package/dist/trace.d.ts.map +1 -0
  31. package/dist/transformer.d.ts +2 -2
  32. package/dist/transformer.d.ts.map +1 -1
  33. package/dist/transformer.js +1 -1
  34. package/dist/transformer.js.map +2 -2
  35. package/dist/types.d.ts +33 -38
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/types.js.map +2 -2
  38. package/package.json +6 -66
  39. package/dist/manifest.d.ts +0 -13
  40. package/dist/manifest.d.ts.map +0 -1
  41. package/dist/spans/index.d.ts +0 -5
  42. package/dist/spans/index.d.ts.map +0 -1
  43. package/dist/spans/index.js +0 -642
  44. package/dist/spans/index.js.map +0 -7
  45. package/dist/spans/local-span-source.d.ts +0 -16
  46. package/dist/spans/local-span-source.d.ts.map +0 -1
  47. package/dist/spans/local-span-source.js +0 -478
  48. package/dist/spans/local-span-source.js.map +0 -7
  49. package/dist/spans/span-source.d.ts +0 -20
  50. package/dist/spans/span-source.d.ts.map +0 -1
  51. package/dist/spans/span-source.js +0 -1
  52. package/dist/spans/span-source.js.map +0 -7
  53. package/dist/spans/sse-collector.d.ts.map +0 -1
  54. package/dist/spans/sse-collector.js.map +0 -7
  55. package/dist/spans/trace.d.ts.map +0 -1
  56. package/dist/spans/vortex-span-source.d.ts +0 -34
  57. package/dist/spans/vortex-span-source.d.ts.map +0 -1
  58. package/dist/spans/vortex-span-source.js +0 -166
  59. package/dist/spans/vortex-span-source.js.map +0 -7
  60. package/dist/stores/eval-definition-loader.d.ts +0 -4
  61. package/dist/stores/eval-definition-loader.d.ts.map +0 -1
  62. package/dist/stores/eval-definition-loader.js +0 -104
  63. package/dist/stores/eval-definition-loader.js.map +0 -7
  64. package/dist/stores/eval-store.d.ts +0 -61
  65. package/dist/stores/eval-store.d.ts.map +0 -1
  66. package/dist/stores/eval-store.js +0 -1
  67. package/dist/stores/eval-store.js.map +0 -7
  68. package/dist/stores/index.d.ts +0 -8
  69. package/dist/stores/index.d.ts.map +0 -1
  70. package/dist/stores/index.js +0 -1118
  71. package/dist/stores/index.js.map +0 -7
  72. package/dist/stores/local-eval-store.d.ts +0 -66
  73. package/dist/stores/local-eval-store.d.ts.map +0 -1
  74. package/dist/stores/local-eval-store.js +0 -557
  75. package/dist/stores/local-eval-store.js.map +0 -7
  76. package/dist/stores/vortex-eval-store-entry.d.ts +0 -6
  77. package/dist/stores/vortex-eval-store-entry.d.ts.map +0 -1
  78. package/dist/stores/vortex-eval-store-entry.js +0 -664
  79. package/dist/stores/vortex-eval-store-entry.js.map +0 -7
  80. package/dist/stores/vortex-eval-store.d.ts +0 -78
  81. package/dist/stores/vortex-eval-store.d.ts.map +0 -1
  82. package/dist/stores/vortex-eval-store.js +0 -562
  83. package/dist/stores/vortex-eval-store.js.map +0 -7
  84. /package/dist/{spans/trace.d.ts → trace.d.ts} +0 -0
  85. /package/dist/{spans/trace.js → trace.js} +0 -0
  86. /package/dist/{spans/trace.js.map → trace.js.map} +0 -0
package/dist/runner.js CHANGED
@@ -1,3 +1,6 @@
1
+ // src/runner.ts
2
+ import { Client as BpClient2 } from "@botpress/client";
3
+
1
4
  // src/definition.ts
2
5
  var Eval = class {
3
6
  name;
@@ -23,33 +26,84 @@ var Eval = class {
23
26
  // src/types.ts
24
27
  var defaultLogger = console;
25
28
 
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
+
26
77
  // src/errors.ts
27
- import { AdkError } from "@botpress/analytics";
28
78
  var EvalRunnerError = class extends AdkError {
29
79
  };
30
80
 
31
81
  // src/client.ts
32
82
  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
+ }
33
97
  var ChatSession = class {
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;
98
+ constructor(webhookId, chatClient) {
99
+ this.webhookId = webhookId;
100
+ this.chatClient = chatClient;
40
101
  }
41
102
  client = null;
42
103
  conversationId = null;
43
104
  async connect() {
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
- });
105
+ const ChatClient = this.chatClient ?? getDefaultChatClient();
106
+ this.client = await ChatClient.connect({ webhookId: this.webhookId });
53
107
  }
54
108
  /** Invariant guard — calling any session method before connect() is a runner bug. */
55
109
  assertConnected() {
@@ -81,13 +135,17 @@ var ChatSession = class {
81
135
  const conversationId = await this.ensureConversation();
82
136
  await client.createMessage({ conversationId, payload: { type: "text", text: message } });
83
137
  }
84
- async sendEvent(payload) {
138
+ /**
139
+ * Send an event. Fire-and-forget — observation is via SSECollector.
140
+ */
141
+ async sendEvent(type, payload) {
85
142
  const client = this.assertConnected();
86
143
  const conversationId = await this.ensureConversation();
87
- await client.createEvent({ payload, conversationId });
144
+ await client.createEvent({ type, payload, conversationId });
88
145
  }
89
146
  };
90
- async function discoverWebhookId(client, botId) {
147
+ async function discoverWebhookId(botId, token, apiUrl) {
148
+ const client = new BpClient({ token, botId, apiUrl });
91
149
  const { bot } = await client.getBot({ id: botId });
92
150
  const integrations = bot.integrations || {};
93
151
  const chat = Object.values(integrations).find((int) => int.name === "chat");
@@ -215,17 +273,13 @@ async function assertChatChannelBound(devServerUrl, headers = {}, logger = defau
215
273
  });
216
274
  }
217
275
 
218
- // src/spans/sse-collector.ts
276
+ // src/sse-collector.ts
219
277
  function spanKey(span) {
220
278
  return `${span.id.trace}:${span.id.span}`;
221
279
  }
222
280
  function spanData(span) {
223
281
  return span.data && typeof span.data === "object" ? span.data : {};
224
282
  }
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
- }
229
283
  var SSECollector = class _SSECollector {
230
284
  devServerUrl;
231
285
  headers;
@@ -287,22 +341,20 @@ var SSECollector = class _SSECollector {
287
341
  }
288
342
  /**
289
343
  * Mark the beginning of a new turn.
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.
344
+ * Records which handler.conversation span IDs have been seen so far,
345
+ * so waitForTurnComplete() only resolves on NEW handler completions.
293
346
  */
294
347
  startTurn() {
295
348
  this.turnStartKeys = new Set(this.spanMap.keys());
296
349
  for (const [key, span] of this.spanMap) {
297
- if (isTurnHandlerSpan(span)) {
350
+ if (span.name === "handler.conversation") {
298
351
  this.seenHandlerKeys.add(key);
299
352
  }
300
353
  }
301
354
  }
302
355
  static SETTLE_MS = 100;
303
356
  /**
304
- * Wait for a turn-handler span (handler.conversation or handler.event) to
305
- * reach terminal status (ok or error).
357
+ * Wait for a handler.conversation span to reach terminal status (ok or error).
306
358
  * Only considers spans NOT seen before the last startTurn() call.
307
359
  *
308
360
  * After the handler closes, waits for a quiet period (no new spans for SETTLE_MS)
@@ -321,7 +373,7 @@ var SSECollector = class _SSECollector {
321
373
  const timer = setTimeout(() => {
322
374
  this._removeWaiter(waiter);
323
375
  opts.abortSignal?.removeEventListener("abort", onAbort);
324
- const reason = this.streamError ? `SSE stream died: ${this.streamError.message}` : "no handler.conversation or handler.event span completed";
376
+ const reason = this.streamError ? `SSE stream died: ${this.streamError.message}` : "no handler.conversation span completed";
325
377
  reject(
326
378
  new Error(
327
379
  `waitForTurnComplete timed out after ${opts.timeout}ms \u2014 ${reason}; diagnostics: ${this._formatTurnDiagnostics()}`
@@ -569,7 +621,7 @@ ${value}` : value;
569
621
  const handlerStatuses = /* @__PURE__ */ new Set();
570
622
  for (const span of turnSpans) {
571
623
  spanNames.add(span.name);
572
- if (isTurnHandlerSpan(span)) {
624
+ if (span.name === "handler.conversation") {
573
625
  handlerStatuses.add(span.status);
574
626
  }
575
627
  }
@@ -582,7 +634,7 @@ ${value}` : value;
582
634
  `totalSpans=${this.spanMap.size}`,
583
635
  `turnSpans=${turnSpans.length}`,
584
636
  `turnSpanNames=${this._formatSet(spanNames)}`,
585
- `turnHandlerStatuses=${this._formatSet(handlerStatuses)}`
637
+ `handlerConversationStatuses=${this._formatSet(handlerStatuses)}`
586
638
  ].join(", ");
587
639
  }
588
640
  _formatSet(values, limit = 12) {
@@ -618,7 +670,7 @@ ${value}` : value;
618
670
  }
619
671
  }
620
672
  /**
621
- * Check if there is a turn-handler span (handler.conversation / handler.event) that:
673
+ * Check if there is a handler.conversation span that:
622
674
  * 1. Was NOT seen before startTurn()
623
675
  * 2. Has terminal status (ok or error)
624
676
  *
@@ -629,7 +681,7 @@ ${value}` : value;
629
681
  */
630
682
  _hasNewCompletedHandler() {
631
683
  for (const [key, span] of this.spanMap) {
632
- if (isTurnHandlerSpan(span) && !this.seenHandlerKeys.has(key) && (span.status === "ok" || span.status === "error")) {
684
+ if (span.name === "handler.conversation" && !this.seenHandlerKeys.has(key) && (span.status === "ok" || span.status === "error")) {
633
685
  this.seenHandlerKeys.add(key);
634
686
  return true;
635
687
  }
@@ -656,35 +708,6 @@ ${value}` : value;
656
708
  }
657
709
  };
658
710
 
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
-
688
711
  // src/transformer.ts
689
712
  function data(span) {
690
713
  return span.data && typeof span.data === "object" ? span.data : {};
@@ -772,7 +795,7 @@ function extractWorkflowSpans(spans) {
772
795
  }
773
796
  function extractHandler(spans) {
774
797
  const handler = spans.find(
775
- (s) => (s.name === "handler.conversation" || s.name === "handler.event" || s.name === "handler.workflow") && s.status !== "running"
798
+ (s) => (s.name === "handler.conversation" || s.name === "handler.workflow") && s.status !== "running"
776
799
  );
777
800
  if (!handler) {
778
801
  return { handlerDuration: 0, handlerStatus: "ok" };
@@ -784,26 +807,55 @@ function extractHandler(spans) {
784
807
  }
785
808
 
786
809
  // src/graders/llm.ts
810
+ import { Client } from "@botpress/client";
787
811
  import { Cognitive } from "@botpress/cognitive";
788
- import { Zai } from "@botpress/zai";
812
+ var DEFAULT_PASS_THRESHOLD = 3;
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
+ }
789
837
  var MODEL_ALIASES = ["fast", "best"];
790
- var _zai = null;
838
+ var _cognitive = null;
791
839
  var _availableModels = [];
792
840
  var _judgeModel = "fast";
793
- var _failClosed = true;
794
841
  var _log = defaultLogger;
795
842
  function _isModelValid(model) {
796
843
  if (MODEL_ALIASES.includes(model)) return true;
797
844
  if (_availableModels.length === 0) return false;
798
845
  return _availableModels.includes(model);
799
846
  }
800
- async function initLLMJudge(client, options) {
847
+ async function initLLMJudge(credentials, options) {
801
848
  const log = options?.logger ?? defaultLogger;
802
849
  _log = log;
803
- _failClosed = options?.failClosed ?? true;
804
- const cognitive = new Cognitive({ client, __experimental_beta: true });
850
+ const client = new Client({
851
+ token: credentials.token,
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 });
805
857
  try {
806
- _availableModels = Array.from((await cognitive.fetchRemoteModels()).keys());
858
+ _availableModels = Array.from((await _cognitive.fetchRemoteModels()).keys());
807
859
  } catch (err) {
808
860
  log.warn(`Failed to fetch available models: ${err.message}. LLM judge will be unavailable.`);
809
861
  _availableModels = [];
@@ -814,42 +866,66 @@ async function initLLMJudge(client, options) {
814
866
  } else if (!_isModelValid(_judgeModel)) {
815
867
  log.warn(`Configured LLM judge model "${_judgeModel}" is invalid. Run "adk models" to list available models.`);
816
868
  }
817
- _zai = new Zai({ client: cognitive, modelId: _judgeModel });
818
869
  }
819
870
  async function gradeLLMJudge(botResponse, criteria, context) {
820
- const assertion = `llm_judge: "${criteria}"`;
871
+ const threshold = Math.max(1, Math.min(5, context.passThreshold ?? DEFAULT_PASS_THRESHOLD));
821
872
  try {
822
- if (!_zai) {
873
+ if (!_cognitive) {
823
874
  _log.warn(`LLM judge unavailable for "${criteria}": no credentials configured (initLLMJudge not called)`);
824
875
  return {
825
- assertion,
826
- pass: !_failClosed,
827
- skipped: true,
876
+ assertion: `llm_judge: "${criteria}"`,
877
+ pass: false,
828
878
  expected: criteria,
829
879
  actual: "FAILED \u2014 LLM judge unavailable: no credentials configured"
830
880
  };
831
881
  }
832
882
  if (!_isModelValid(_judgeModel)) {
833
883
  return {
834
- assertion,
884
+ assertion: `llm_judge: "${criteria}"`,
835
885
  pass: false,
836
886
  expected: criteria,
837
887
  actual: "FAILED \u2014 invalid LLM judge model"
838
888
  };
839
889
  }
840
- const { output } = await _zai.check({ userMessage: context.userMessage, botResponse }, criteria).result();
890
+ const { output } = await _cognitive.generateContent({
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);
841
918
  return {
842
- assertion,
843
- pass: output.value,
919
+ assertion: `llm_judge: "${criteria}"`,
920
+ pass: verdict.score >= threshold,
844
921
  expected: criteria,
845
- actual: `${output.value ? "Pass" : "Fail"} \u2014 ${output.explanation}`
922
+ actual: `Score ${verdict.score}/5 (threshold: ${threshold}) \u2014 ${verdict.reason}`
846
923
  };
847
924
  } catch (err) {
848
925
  _log.warn(`LLM judge failed for "${criteria}": ${err.message}`);
849
926
  return {
850
- assertion,
851
- pass: !_failClosed,
852
- skipped: true,
927
+ assertion: `llm_judge: "${criteria}"`,
928
+ pass: false,
853
929
  expected: criteria,
854
930
  actual: `FAILED \u2014 LLM judge unavailable: ${err.message}`
855
931
  };
@@ -1272,7 +1348,6 @@ function filterEvals(evals, filter) {
1272
1348
  }
1273
1349
 
1274
1350
  // src/runner.ts
1275
- import { isAdkError } from "@botpress/analytics";
1276
1351
  import { randomUUID } from "crypto";
1277
1352
  var DEFAULT_IDLE_TIMEOUT = 3e4;
1278
1353
  var DEFAULT_DEV_SERVER_URL = "http://localhost:3001";
@@ -1359,22 +1434,27 @@ async function runEval(evalDef, connection, options = {}) {
1359
1434
  const start = Date.now();
1360
1435
  const turns = [];
1361
1436
  let outcomeAssertions = [];
1362
- const collector = options.spanSource ?? new LocalSpanSource(devServerUrl, devServerHeaders);
1437
+ const collector = new SSECollector(devServerUrl, devServerHeaders);
1363
1438
  try {
1364
- const session = new ChatSession(
1365
- connection.client,
1366
- connection.botId,
1367
- options.chatWebhookId,
1368
- options.chatBaseUrl,
1369
- options.chatClient
1370
- );
1439
+ const session = new ChatSession(connection.webhookId, options.chatClient);
1371
1440
  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
+ };
1372
1452
  let conversationId = "";
1373
1453
  if (evalDef.setup?.state?.conversation || evalDef.setup?.workflow) {
1374
1454
  conversationId = await session.ensureConversation();
1375
1455
  }
1376
1456
  await seedEvalState(
1377
- connection.client,
1457
+ getBpClient(),
1378
1458
  {
1379
1459
  botId: connection.botId,
1380
1460
  userId: session.userId,
@@ -1383,7 +1463,7 @@ async function runEval(evalDef, connection, options = {}) {
1383
1463
  evalDef.setup
1384
1464
  );
1385
1465
  await triggerEvalWorkflow(
1386
- connection.client,
1466
+ getBpClient(),
1387
1467
  {
1388
1468
  userId: session.userId,
1389
1469
  ...conversationId ? { conversationId } : {}
@@ -1419,18 +1499,18 @@ async function runEval(evalDef, connection, options = {}) {
1419
1499
  if (!turn.user && !turn.event) {
1420
1500
  throw turnConfigError(`must have either 'user' or 'event'.`);
1421
1501
  }
1422
- const turnLabel = turn.event ? "[event]" : turn.user;
1423
- await options.onProgress?.({
1502
+ const turnLabelForEvent = turn.event ? `[event: ${turn.event.type}]` : turn.user;
1503
+ options.onProgress?.({
1424
1504
  type: "turn_start",
1425
1505
  evalName: evalDef.name,
1426
1506
  evalIndex: options.evalIndex ?? 0,
1427
- turnIndex: i,
1507
+ turnNumber: i + 1,
1428
1508
  totalTurns: evalDef.conversation.length,
1429
- userMessage: turnLabel
1509
+ userMessage: turnLabelForEvent
1430
1510
  });
1431
1511
  collector.startTurn();
1432
1512
  if (turn.event) {
1433
- await session.sendEvent(turn.event.payload);
1513
+ await session.sendEvent(turn.event.type, turn.event.payload ?? {});
1434
1514
  } else {
1435
1515
  await session.sendMessage(turn.user);
1436
1516
  }
@@ -1460,6 +1540,7 @@ async function runEval(evalDef, connection, options = {}) {
1460
1540
  const turnData = transformSpans(turnSpans);
1461
1541
  const botResponse = turnData.messages.join("\n");
1462
1542
  const botDuration = turnData.handlerDuration;
1543
+ const turnLabel = turn.event ? `[event: ${turn.event.type}]` : turn.user;
1463
1544
  const evalStart = Date.now();
1464
1545
  let assertions = [];
1465
1546
  const noResponse = !turn.expectSilence && turnData.messages.length === 0;
@@ -1514,7 +1595,7 @@ async function runEval(evalDef, connection, options = {}) {
1514
1595
  const turnPass = assertions.every((a) => a.pass);
1515
1596
  const evalDuration = Date.now() - evalStart;
1516
1597
  const turnReport = {
1517
- turnIndex: i,
1598
+ turnNumber: i + 1,
1518
1599
  userMessage: turnLabel,
1519
1600
  botResponse,
1520
1601
  assertions,
@@ -1523,11 +1604,11 @@ async function runEval(evalDef, connection, options = {}) {
1523
1604
  evalDuration
1524
1605
  };
1525
1606
  turns.push(turnReport);
1526
- await options.onProgress?.({
1607
+ options.onProgress?.({
1527
1608
  type: "turn_complete",
1528
1609
  evalName: evalDef.name,
1529
1610
  evalIndex: options.evalIndex ?? 0,
1530
- turnIndex: i,
1611
+ turnNumber: i + 1,
1531
1612
  totalTurns: evalDef.conversation.length,
1532
1613
  turnReport
1533
1614
  });
@@ -1574,15 +1655,7 @@ async function runEval(evalDef, connection, options = {}) {
1574
1655
  } catch (err) {
1575
1656
  const adkErr = isAdkError(err) ? err : void 0;
1576
1657
  if (!adkErr || !adkErr.expected) {
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
- );
1658
+ logger.error(`Eval "${evalDef.name}" failed unexpectedly: ${err.message}`);
1586
1659
  options.onException?.(err, { source: "evals", eval_name: evalDef.name });
1587
1660
  }
1588
1661
  return {
@@ -1600,15 +1673,9 @@ async function runEval(evalDef, connection, options = {}) {
1600
1673
  }
1601
1674
  async function runEvalSuite(config, filter) {
1602
1675
  const start = Date.now();
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
- }
1676
+ const runId = randomUUID().replace(/-/g, "").slice(0, 26);
1677
+ const evalsDir = `${config.agentPath}/evals`;
1678
+ const allEvals = await loadEvalsFromDir(evalsDir);
1612
1679
  const evals = filterEvals(allEvals, filter);
1613
1680
  if (evals.length === 0) {
1614
1681
  return {
@@ -1622,26 +1689,38 @@ async function runEvalSuite(config, filter) {
1622
1689
  ...filter !== void 0 ? { filter } : {}
1623
1690
  };
1624
1691
  }
1625
- await initLLMJudge(config.client, {
1626
- ...config.evalOptions?.judgeModel !== void 0 ? { model: config.evalOptions.judgeModel } : {},
1627
- ...config.logger !== void 0 ? { logger: config.logger } : {}
1628
- });
1692
+ await initLLMJudge(
1693
+ {
1694
+ token: config.credentials.token,
1695
+ apiUrl: config.credentials.apiUrl,
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
+ }
1629
1708
  const connection = {
1630
- client: config.client,
1631
- botId: config.botId
1709
+ webhookId,
1710
+ botId: config.credentials.botId,
1711
+ token: config.credentials.token,
1712
+ apiUrl: config.credentials.apiUrl
1632
1713
  };
1633
1714
  const devServerUrl = config.devServerUrl || DEFAULT_DEV_SERVER_URL;
1634
1715
  const devServerHeaders = config.devServerHeaders ?? {};
1635
- if (!config.createSpanSource) {
1636
- await assertChatChannelBound(devServerUrl, devServerHeaders, config.logger);
1637
- await assertTraceStreamReadable(devServerUrl, devServerHeaders);
1638
- }
1716
+ await assertChatChannelBound(devServerUrl, devServerHeaders, config.logger);
1717
+ await assertTraceStreamReadable(devServerUrl, devServerHeaders);
1639
1718
  const reports = [];
1640
- await config.onProgress?.({ type: "suite_start", totalEvals: evals.length });
1719
+ config.onProgress?.({ type: "suite_start", totalEvals: evals.length });
1641
1720
  for (let i = 0; i < evals.length; i++) {
1642
1721
  if (config.signal?.aborted) break;
1643
1722
  const evalDef = evals[i];
1644
- await config.onProgress?.({
1723
+ config.onProgress?.({
1645
1724
  type: "eval_start",
1646
1725
  evalName: evalDef.name,
1647
1726
  index: i,
@@ -1657,13 +1736,10 @@ async function runEvalSuite(config, filter) {
1657
1736
  ...config.signal !== void 0 ? { signal: config.signal } : {},
1658
1737
  ...config.chatClient !== void 0 ? { chatClient: config.chatClient } : {},
1659
1738
  ...config.logger !== void 0 ? { logger: config.logger } : {},
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 } : {}
1739
+ ...config.onException !== void 0 ? { onException: config.onException } : {}
1664
1740
  });
1665
1741
  reports.push(report);
1666
- await config.onProgress?.({ type: "eval_complete", evalName: evalDef.name, index: i, report });
1742
+ config.onProgress?.({ type: "eval_complete", evalName: evalDef.name, index: i, report });
1667
1743
  }
1668
1744
  const runReport = {
1669
1745
  id: runId,
@@ -1678,7 +1754,7 @@ async function runEvalSuite(config, filter) {
1678
1754
  if (config.signal?.aborted) {
1679
1755
  runReport.aborted = true;
1680
1756
  }
1681
- await config.onProgress?.({ type: "suite_complete", report: runReport });
1757
+ config.onProgress?.({ type: "suite_complete", report: runReport });
1682
1758
  return runReport;
1683
1759
  }
1684
1760
  export {