@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.
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 +20 -74
  4. package/dist/client.js.map +4 -4
  5. package/dist/definition.d.ts +3 -2
  6. package/dist/definition.d.ts.map +1 -1
  7. package/dist/definition.js.map +2 -2
  8. package/dist/graders/index.js +21 -74
  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 +30 -75
  13. package/dist/graders/llm.js.map +2 -2
  14. package/dist/graders/response.js +16 -65
  15. package/dist/graders/response.js.map +2 -2
  16. package/dist/index.d.ts +3 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +10 -0
  19. package/dist/index.js.map +3 -3
  20. package/dist/loader.js +1 -39
  21. package/dist/loader.js.map +3 -3
  22. package/dist/manifest.d.ts +13 -0
  23. package/dist/manifest.d.ts.map +1 -0
  24. package/dist/runner.d.ts +5 -1
  25. package/dist/runner.d.ts.map +1 -1
  26. package/dist/runner.js +141 -217
  27. package/dist/runner.js.map +4 -4
  28. package/dist/spans/index.d.ts +5 -0
  29. package/dist/spans/index.d.ts.map +1 -0
  30. package/dist/spans/index.js +642 -0
  31. package/dist/spans/index.js.map +7 -0
  32. package/dist/spans/local-span-source.d.ts +16 -0
  33. package/dist/spans/local-span-source.d.ts.map +1 -0
  34. package/dist/spans/local-span-source.js +478 -0
  35. package/dist/spans/local-span-source.js.map +7 -0
  36. package/dist/spans/span-source.d.ts +20 -0
  37. package/dist/spans/span-source.d.ts.map +1 -0
  38. package/dist/spans/span-source.js +1 -0
  39. package/dist/{sse-collector.d.ts → spans/sse-collector.d.ts} +6 -4
  40. package/dist/spans/sse-collector.d.ts.map +1 -0
  41. package/dist/{sse-collector.js → spans/sse-collector.js} +17 -49
  42. package/dist/spans/sse-collector.js.map +7 -0
  43. package/dist/spans/trace.d.ts.map +1 -0
  44. package/dist/spans/trace.js.map +7 -0
  45. package/dist/spans/vortex-span-source.d.ts +34 -0
  46. package/dist/spans/vortex-span-source.d.ts.map +1 -0
  47. package/dist/spans/vortex-span-source.js +166 -0
  48. package/dist/spans/vortex-span-source.js.map +7 -0
  49. package/dist/stores/eval-definition-loader.d.ts +4 -0
  50. package/dist/stores/eval-definition-loader.d.ts.map +1 -0
  51. package/dist/stores/eval-definition-loader.js +104 -0
  52. package/dist/stores/eval-definition-loader.js.map +7 -0
  53. package/dist/stores/eval-store.d.ts +61 -0
  54. package/dist/stores/eval-store.d.ts.map +1 -0
  55. package/dist/stores/eval-store.js +1 -0
  56. package/dist/stores/eval-store.js.map +7 -0
  57. package/dist/stores/index.d.ts +8 -0
  58. package/dist/stores/index.d.ts.map +1 -0
  59. package/dist/stores/index.js +1118 -0
  60. package/dist/stores/index.js.map +7 -0
  61. package/dist/stores/local-eval-store.d.ts +66 -0
  62. package/dist/stores/local-eval-store.d.ts.map +1 -0
  63. package/dist/stores/local-eval-store.js +557 -0
  64. package/dist/stores/local-eval-store.js.map +7 -0
  65. package/dist/stores/vortex-eval-store-entry.d.ts +6 -0
  66. package/dist/stores/vortex-eval-store-entry.d.ts.map +1 -0
  67. package/dist/stores/vortex-eval-store-entry.js +664 -0
  68. package/dist/stores/vortex-eval-store-entry.js.map +7 -0
  69. package/dist/stores/vortex-eval-store.d.ts +78 -0
  70. package/dist/stores/vortex-eval-store.d.ts.map +1 -0
  71. package/dist/stores/vortex-eval-store.js +562 -0
  72. package/dist/stores/vortex-eval-store.js.map +7 -0
  73. package/dist/transformer.d.ts +2 -2
  74. package/dist/transformer.d.ts.map +1 -1
  75. package/dist/transformer.js +1 -1
  76. package/dist/transformer.js.map +2 -2
  77. package/dist/types.d.ts +38 -33
  78. package/dist/types.d.ts.map +1 -1
  79. package/dist/types.js.map +2 -2
  80. package/package.json +66 -6
  81. package/dist/sse-collector.d.ts.map +0 -1
  82. package/dist/sse-collector.js.map +0 -7
  83. package/dist/trace.d.ts.map +0 -1
  84. /package/dist/{trace.js.map → spans/span-source.js.map} +0 -0
  85. /package/dist/{trace.d.ts → spans/trace.d.ts} +0 -0
  86. /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(webhookId, chatClient) {
99
- this.webhookId = webhookId;
100
- this.chatClient = chatClient;
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 ChatClient = this.chatClient ?? getDefaultChatClient();
106
- this.client = await ChatClient.connect({ webhookId: this.webhookId });
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({ type, payload, conversationId });
87
+ await client.createEvent({ payload, conversationId });
145
88
  }
146
89
  };
147
- async function discoverWebhookId(botId, token, apiUrl) {
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.conversation span IDs have been seen so far,
345
- * so waitForTurnComplete() only resolves on NEW handler completions.
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.name === "handler.conversation") {
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.conversation span to reach terminal status (ok or error).
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.name === "handler.conversation") {
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
- `handlerConversationStatuses=${this._formatSet(handlerStatuses)}`
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 span that:
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.name === "handler.conversation" && !this.seenHandlerKeys.has(key) && (span.status === "ok" || span.status === "error")) {
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
- 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
- }
788
+ import { Zai } from "@botpress/zai";
837
789
  var MODEL_ALIASES = ["fast", "best"];
838
- var _cognitive = null;
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(credentials, options) {
800
+ async function initLLMJudge(client, options) {
848
801
  const log = options?.logger ?? defaultLogger;
849
802
  _log = log;
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 });
803
+ _failClosed = options?.failClosed ?? true;
804
+ const cognitive = new Cognitive({ client, __experimental_beta: true });
857
805
  try {
858
- _availableModels = Array.from((await _cognitive.fetchRemoteModels()).keys());
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 threshold = Math.max(1, Math.min(5, context.passThreshold ?? DEFAULT_PASS_THRESHOLD));
820
+ const assertion = `llm_judge: "${criteria}"`;
872
821
  try {
873
- if (!_cognitive) {
822
+ if (!_zai) {
874
823
  _log.warn(`LLM judge unavailable for "${criteria}": no credentials configured (initLLMJudge not called)`);
875
824
  return {
876
- assertion: `llm_judge: "${criteria}"`,
877
- pass: false,
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: `llm_judge: "${criteria}"`,
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 _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);
840
+ const { output } = await _zai.check({ userMessage: context.userMessage, botResponse }, criteria).result();
918
841
  return {
919
- assertion: `llm_judge: "${criteria}"`,
920
- pass: verdict.score >= threshold,
842
+ assertion,
843
+ pass: output.value,
921
844
  expected: criteria,
922
- actual: `Score ${verdict.score}/5 (threshold: ${threshold}) \u2014 ${verdict.reason}`
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: `llm_judge: "${criteria}"`,
928
- pass: false,
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 SSECollector(devServerUrl, devServerHeaders);
1362
+ const collector = options.spanSource ?? new LocalSpanSource(devServerUrl, devServerHeaders);
1438
1363
  try {
1439
- const session = new ChatSession(connection.webhookId, options.chatClient);
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
- getBpClient(),
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
- getBpClient(),
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 turnLabelForEvent = turn.event ? `[event: ${turn.event.type}]` : turn.user;
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
- turnNumber: i + 1,
1427
+ turnIndex: i,
1508
1428
  totalTurns: evalDef.conversation.length,
1509
- userMessage: turnLabelForEvent
1429
+ userMessage: turnLabel
1510
1430
  });
1511
1431
  collector.startTurn();
1512
1432
  if (turn.event) {
1513
- await session.sendEvent(turn.event.type, turn.event.payload ?? {});
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
- turnNumber: i + 1,
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
- turnNumber: i + 1,
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
- logger.error(`Eval "${evalDef.name}" failed unexpectedly: ${err.message}`);
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
- const evalsDir = `${config.agentPath}/evals`;
1678
- const allEvals = await loadEvalsFromDir(evalsDir);
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
- 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
- }
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
- webhookId,
1710
- botId: config.credentials.botId,
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
- await assertChatChannelBound(devServerUrl, devServerHeaders, config.logger);
1717
- await assertTraceStreamReadable(devServerUrl, devServerHeaders);
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 {