@botpress/evals 1.18.3 → 1.19.0-beta.10

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 CHANGED
@@ -5,7 +5,7 @@
5
5
  * This is a send-only client. Observation (bot responses, tool calls, state)
6
6
  * is handled by the SSECollector via trace spans — not via WebSocket listeners.
7
7
  */
8
- import type { ChatClient } from './types';
8
+ import type { ChatClient, EvalLogger } from './types';
9
9
  export declare function setDefaultChatClient(chatClient: ChatClient | null): void;
10
10
  /**
11
11
  * A send-only chat session that maintains a single client connection across turns.
@@ -18,6 +18,8 @@ export declare class ChatSession {
18
18
  private conversationId;
19
19
  constructor(webhookId: string, chatClient?: ChatClient);
20
20
  connect(): Promise<void>;
21
+ /** Invariant guard — calling any session method before connect() is a runner bug. */
22
+ private assertConnected;
21
23
  get userId(): string;
22
24
  ensureConversation(): Promise<string>;
23
25
  /**
@@ -36,6 +38,13 @@ export declare class ChatSession {
36
38
  * If the bot doesn't have that integration installed, evals can't run.
37
39
  */
38
40
  export declare function discoverWebhookId(botId: string, token: string, apiUrl: string): Promise<string>;
41
+ /**
42
+ * Verify the dev server's trace stream can be read before eval turns start.
43
+ *
44
+ * This only checks that `/api/traces/stream` is reachable and emits its initial
45
+ * snapshot. It does not prove the private runtime span-ingest path is healthy.
46
+ */
47
+ export declare function assertTraceStreamReadable(devServerUrl: string, headers?: Record<string, string>, timeoutMs?: number): Promise<void>;
39
48
  /**
40
49
  * Verify the bot has at least one Conversation bound to `chat.channel`.
41
50
  *
@@ -46,5 +55,5 @@ export declare function discoverWebhookId(botId: string, token: string, apiUrl:
46
55
  * Uses the dev server's `/api/agent` endpoint, so this is a no-op against
47
56
  * production bots (no devServerUrl).
48
57
  */
49
- export declare function assertChatChannelBound(devServerUrl: string, headers?: Record<string, string>): Promise<void>;
58
+ export declare function assertChatChannelBound(devServerUrl: string, headers?: Record<string, string>, logger?: EvalLogger): Promise<void>;
50
59
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAwB,MAAM,SAAS,CAAA;AAI/D,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAExE;AAcD;;;GAGG;AACH,qBAAa,WAAW;IAKpB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,UAAU,CAAC;IALrB,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,cAAc,CAAsB;gBAGlC,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,UAAU;IAG3B,OAAO;IAKb,IAAI,MAAM,IAAI,MAAM,CAKnB;IAEK,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;IAc3C;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASjD;;OAEG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAQ/E;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqBrG;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,OAAO,CAAC,IAAI,CAAC,CAsCf"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAwB,UAAU,EAAE,MAAM,SAAS,CAAA;AAQ3E,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAExE;AAiBD;;;GAGG;AACH,qBAAa,WAAW;IAKpB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,UAAU,CAAC;IALrB,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,cAAc,CAAsB;gBAGlC,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,UAAU;IAG3B,OAAO;IAKb,qFAAqF;IACrF,OAAO,CAAC,eAAe;IAUvB,IAAI,MAAM,IAAI,MAAM,CAEnB;IAEK,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;IAY3C;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjD;;OAEG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAK/E;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBrG;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACpC,SAAS,SAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CA+Df;AAoBD;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACpC,MAAM,GAAE,UAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
package/dist/client.js CHANGED
@@ -1,6 +1,55 @@
1
1
  // src/client.ts
2
2
  import { Client as BpClient } from "@botpress/client";
3
3
  import { createRequire } from "module";
4
+
5
+ // src/types.ts
6
+ var defaultLogger = console;
7
+
8
+ // ../analytics/src/errors.ts
9
+ var AdkError = class extends Error {
10
+ /**
11
+ * Marker for cross-module-instance checks — `instanceof` breaks when a
12
+ * package is duplicated in the dependency graph.
13
+ * @internal
14
+ */
15
+ static __IS_ADK_BASE_ERROR = true;
16
+ /**
17
+ * Stable SCREAMING_SNAKE_CASE identifier, e.g. 'BOT_NOT_FOUND'. Subclasses
18
+ * pin `Code` to a literal union (`class EvalRunnerError extends
19
+ * AdkError<EvalErrorCode>`) so typos are compile errors and `code` narrows
20
+ * in switches.
21
+ */
22
+ code;
23
+ /**
24
+ * True for user/environment conditions the developer can fix themselves
25
+ * (validation, auth, connectivity). False for internal bugs. Defaults to
26
+ * false so unclassified errors surface as bugs until someone triages them.
27
+ */
28
+ expected;
29
+ /** Structured context. Sanitized before any telemetry leaves the machine. */
30
+ details;
31
+ /** Actionable next step shown to the user, e.g. "Run 'adk login' first." */
32
+ suggestion;
33
+ constructor(opts) {
34
+ super(opts.message, opts.cause !== void 0 ? { cause: opts.cause } : void 0);
35
+ this.name = this.constructor.name;
36
+ this.code = opts.code;
37
+ this.expected = opts.expected ?? false;
38
+ if (opts.details !== void 0) {
39
+ this.details = opts.details;
40
+ }
41
+ if (opts.suggestion !== void 0) {
42
+ this.suggestion = opts.suggestion;
43
+ }
44
+ }
45
+ };
46
+
47
+ // src/errors.ts
48
+ var EvalRunnerError = class extends AdkError {
49
+ };
50
+
51
+ // src/client.ts
52
+ var TRACE_STREAM_PREFLIGHT_TIMEOUT_MS = 1e4;
4
53
  var defaultChatClient = null;
5
54
  function setDefaultChatClient(chatClient) {
6
55
  defaultChatClient = chatClient;
@@ -11,7 +60,10 @@ function getDefaultChatClient() {
11
60
  const mod = require2("@botpress/chat");
12
61
  const ChatClient = mod.default?.Client ?? mod.Client;
13
62
  if (!ChatClient) {
14
- throw new Error("@botpress/chat did not expose a Client export. Pass EvalRunnerConfig.chatClient explicitly.");
63
+ throw new EvalRunnerError({
64
+ code: "CHAT_CLIENT_MISSING",
65
+ message: "@botpress/chat did not expose a Client export. Pass EvalRunnerConfig.chatClient explicitly."
66
+ });
15
67
  }
16
68
  return ChatClient;
17
69
  }
@@ -26,18 +78,23 @@ var ChatSession = class {
26
78
  const ChatClient = this.chatClient ?? getDefaultChatClient();
27
79
  this.client = await ChatClient.connect({ webhookId: this.webhookId });
28
80
  }
29
- get userId() {
81
+ /** Invariant guard — calling any session method before connect() is a runner bug. */
82
+ assertConnected() {
30
83
  if (!this.client) {
31
- throw new Error("ChatSession not connected. Call connect() first.");
84
+ throw new EvalRunnerError({
85
+ code: "CHAT_NOT_CONNECTED",
86
+ message: "ChatSession not connected. Call connect() first."
87
+ });
32
88
  }
33
- return this.client.user.id;
89
+ return this.client;
90
+ }
91
+ get userId() {
92
+ return this.assertConnected().user.id;
34
93
  }
35
94
  async ensureConversation() {
36
- if (!this.client) {
37
- throw new Error("ChatSession not connected. Call connect() first.");
38
- }
95
+ const client = this.assertConnected();
39
96
  if (!this.conversationId) {
40
- const conv = await this.client.createConversation({});
97
+ const conv = await client.createConversation({});
41
98
  this.conversationId = conv.conversation.id;
42
99
  return conv.conversation.id;
43
100
  }
@@ -47,21 +104,17 @@ var ChatSession = class {
47
104
  * Send a user message. Fire-and-forget — observation is via SSECollector.
48
105
  */
49
106
  async sendMessage(message) {
50
- if (!this.client) {
51
- throw new Error("ChatSession not connected. Call connect() first.");
52
- }
107
+ const client = this.assertConnected();
53
108
  const conversationId = await this.ensureConversation();
54
- await this.client.createMessage({ conversationId, payload: { type: "text", text: message } });
109
+ await client.createMessage({ conversationId, payload: { type: "text", text: message } });
55
110
  }
56
111
  /**
57
112
  * Send an event. Fire-and-forget — observation is via SSECollector.
58
113
  */
59
114
  async sendEvent(type, payload) {
60
- if (!this.client) {
61
- throw new Error("ChatSession not connected. Call connect() first.");
62
- }
115
+ const client = this.assertConnected();
63
116
  const conversationId = await this.ensureConversation();
64
- await this.client.createEvent({ type, payload, conversationId });
117
+ await client.createEvent({ type, payload, conversationId });
65
118
  }
66
119
  };
67
120
  async function discoverWebhookId(botId, token, apiUrl) {
@@ -71,26 +124,102 @@ async function discoverWebhookId(botId, token, apiUrl) {
71
124
  const chat = Object.values(integrations).find((int) => int.name === "chat");
72
125
  const webhookId = chat?.webhookId;
73
126
  if (!webhookId || typeof webhookId !== "string") {
74
- throw new Error(
75
- [
127
+ throw new EvalRunnerError({
128
+ code: "CHAT_INTEGRATION_MISSING",
129
+ message: [
76
130
  "The `chat` integration is not installed on this bot \u2014 evals require it.",
77
131
  "",
78
132
  "To fix:",
79
133
  " 1. Install it: adk add chat",
80
134
  " 2. Redeploy: adk dev (or: adk deploy)"
81
- ].join("\n")
82
- );
135
+ ].join("\n"),
136
+ expected: true
137
+ });
83
138
  }
84
139
  return webhookId;
85
140
  }
86
- async function assertChatChannelBound(devServerUrl, headers = {}) {
141
+ async function assertTraceStreamReadable(devServerUrl, headers = {}, timeoutMs = TRACE_STREAM_PREFLIGHT_TIMEOUT_MS) {
142
+ const url = `${devServerUrl.replace(/\/$/, "")}/api/traces/stream?count=1`;
143
+ const abortController = new AbortController();
144
+ let timedOut = false;
145
+ let reader = null;
146
+ const timer = setTimeout(() => {
147
+ timedOut = true;
148
+ abortController.abort();
149
+ }, timeoutMs);
150
+ try {
151
+ const res = await fetch(url, {
152
+ headers: { ...headers, Accept: "text/event-stream" },
153
+ signal: abortController.signal
154
+ });
155
+ if (!res.ok) {
156
+ throw traceStreamPreflightError(`Trace stream pre-flight failed: ${res.status} ${res.statusText}`);
157
+ }
158
+ if (!res.body) {
159
+ throw traceStreamPreflightError("Trace stream pre-flight failed: response had no body.");
160
+ }
161
+ reader = res.body.getReader();
162
+ const decoder = new TextDecoder();
163
+ let buffer = "";
164
+ while (true) {
165
+ const { done, value } = await reader.read();
166
+ if (done) {
167
+ throw traceStreamPreflightError("Trace stream pre-flight failed: stream closed before snapshot.");
168
+ }
169
+ buffer += decoder.decode(value, { stream: true });
170
+ const parts = buffer.split("\n\n");
171
+ buffer = parts.pop() ?? "";
172
+ for (const part of parts) {
173
+ if (parseSSEEventName(part) === "snapshot") {
174
+ return;
175
+ }
176
+ }
177
+ }
178
+ } catch (err) {
179
+ if (err instanceof EvalRunnerError) {
180
+ throw err;
181
+ }
182
+ if (timedOut) {
183
+ throw traceStreamPreflightError(
184
+ `Trace stream pre-flight timed out after ${timeoutMs}ms before receiving a snapshot.`
185
+ );
186
+ }
187
+ throw traceStreamPreflightError(
188
+ `Trace stream pre-flight failed: ${err instanceof Error ? err.message : String(err)}`
189
+ );
190
+ } finally {
191
+ clearTimeout(timer);
192
+ reader?.cancel().catch(() => {
193
+ });
194
+ }
195
+ }
196
+ function parseSSEEventName(block) {
197
+ for (const line of block.split("\n")) {
198
+ if (line.startsWith("event:")) {
199
+ return line.slice("event:".length).trim();
200
+ }
201
+ }
202
+ return null;
203
+ }
204
+ function traceStreamPreflightError(message) {
205
+ return new EvalRunnerError({
206
+ code: "SSE_CONNECT_FAILED",
207
+ message,
208
+ expected: true,
209
+ suggestion: "Make sure the dev server is running (`adk dev`) and trace streaming is available."
210
+ });
211
+ }
212
+ async function assertChatChannelBound(devServerUrl, headers = {}, logger = defaultLogger) {
87
213
  let conversations = [];
88
214
  try {
89
215
  const res = await fetch(`${devServerUrl}/api/agent`, { headers });
90
216
  if (!res.ok) return;
91
217
  const agent = await res.json();
92
218
  conversations = agent.conversations ?? [];
93
- } catch {
219
+ } catch (err) {
220
+ logger?.warn(
221
+ `Skipping chat.channel binding pre-flight \u2014 could not query ${devServerUrl}/api/agent: ${err instanceof Error ? err.message : String(err)}`
222
+ );
94
223
  return;
95
224
  }
96
225
  if (conversations.length === 0) return;
@@ -101,8 +230,9 @@ async function assertChatChannelBound(devServerUrl, headers = {}) {
101
230
  };
102
231
  const bound = conversations.some((c) => matchesChatChannel(c.channel));
103
232
  if (bound) return;
104
- throw new Error(
105
- [
233
+ throw new EvalRunnerError({
234
+ code: "CHAT_CHANNEL_UNBOUND",
235
+ message: [
106
236
  "No Conversation is bound to `chat.channel` \u2014 messages via `@botpress/chat` will time out silently.",
107
237
  "",
108
238
  "To fix, bind a Conversation in `src/conversations/` to `chat.channel` (or `*`):",
@@ -111,12 +241,14 @@ async function assertChatChannelBound(devServerUrl, headers = {}) {
111
241
  " channel: ['chat.channel', ...], // array form",
112
242
  "",
113
243
  "The `chat` integration is how evals and `adk chat --single` send messages programmatically."
114
- ].join("\n")
115
- );
244
+ ].join("\n"),
245
+ expected: true
246
+ });
116
247
  }
117
248
  export {
118
249
  ChatSession,
119
250
  assertChatChannelBound,
251
+ assertTraceStreamReadable,
120
252
  discoverWebhookId,
121
253
  setDefaultChatClient
122
254
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/client.ts"],
4
- "sourcesContent": ["/**\n * Chat client for eval conversations.\n * Drives conversations against a running ADK bot via @botpress/chat.\n *\n * This is a send-only client. Observation (bot responses, tool calls, state)\n * is handled by the SSECollector via trace spans \u2014 not via WebSocket listeners.\n */\n\nimport { Client as BpClient } from '@botpress/client'\nimport { createRequire } from 'module'\nimport type { ChatClient, ChatClientConnection } from './types'\n\nlet defaultChatClient: ChatClient | null = null\n\nexport function setDefaultChatClient(chatClient: ChatClient | null): void {\n defaultChatClient = chatClient\n}\n\nfunction getDefaultChatClient(): ChatClient {\n if (defaultChatClient) return defaultChatClient\n\n const require = createRequire(import.meta.url)\n const mod = require('@botpress/chat') as { Client?: ChatClient; default?: { Client?: ChatClient } }\n const ChatClient = mod.default?.Client ?? mod.Client\n if (!ChatClient) {\n throw new Error('@botpress/chat did not expose a Client export. Pass EvalRunnerConfig.chatClient explicitly.')\n }\n return ChatClient\n}\n\n/**\n * A send-only chat session that maintains a single client connection across turns.\n * Messages and events are fire-and-forget; the SSECollector observes results via traces.\n */\nexport class ChatSession {\n private client: ChatClientConnection | null = null\n private conversationId: string | null = null\n\n constructor(\n private webhookId: string,\n private chatClient?: ChatClient\n ) {}\n\n async connect() {\n const ChatClient = this.chatClient ?? getDefaultChatClient()\n this.client = await ChatClient.connect({ webhookId: this.webhookId })\n }\n\n get userId(): string {\n if (!this.client) {\n throw new Error('ChatSession not connected. Call connect() first.')\n }\n return this.client.user.id\n }\n\n async ensureConversation(): Promise<string> {\n if (!this.client) {\n throw new Error('ChatSession not connected. Call connect() first.')\n }\n\n if (!this.conversationId) {\n const conv = await this.client.createConversation({})\n this.conversationId = conv.conversation.id\n return conv.conversation.id\n }\n\n return this.conversationId\n }\n\n /**\n * Send a user message. Fire-and-forget \u2014 observation is via SSECollector.\n */\n async sendMessage(message: string): Promise<void> {\n if (!this.client) {\n throw new Error('ChatSession not connected. Call connect() first.')\n }\n\n const conversationId = await this.ensureConversation()\n await this.client.createMessage({ conversationId, payload: { type: 'text', text: message } })\n }\n\n /**\n * Send an event. Fire-and-forget \u2014 observation is via SSECollector.\n */\n async sendEvent(type: string, payload: Record<string, unknown>): Promise<void> {\n if (!this.client) {\n throw new Error('ChatSession not connected. Call connect() first.')\n }\n\n const conversationId = await this.ensureConversation()\n await this.client.createEvent({ type, payload, conversationId })\n }\n}\n\n/**\n * Discover the chat integration's webhookId from a bot.\n *\n * Evals talk to the bot through the `chat` integration (via @botpress/chat).\n * If the bot doesn't have that integration installed, evals can't run.\n */\nexport async function discoverWebhookId(botId: string, token: string, apiUrl: string): Promise<string> {\n const client = new BpClient({ token, botId, apiUrl })\n const { bot } = await client.getBot({ id: botId })\n const integrations = bot.integrations || {}\n\n const chat = Object.values(integrations).find((int) => (int as Record<string, unknown>).name === 'chat')\n const webhookId = (chat as Record<string, unknown> | undefined)?.webhookId\n\n if (!webhookId || typeof webhookId !== 'string') {\n throw new Error(\n [\n 'The `chat` integration is not installed on this bot \u2014 evals require it.',\n '',\n 'To fix:',\n ' 1. Install it: adk add chat',\n ' 2. Redeploy: adk dev (or: adk deploy)',\n ].join('\\n')\n )\n }\n\n return webhookId\n}\n\n/**\n * Verify the bot has at least one Conversation bound to `chat.channel`.\n *\n * The `chat` integration can be installed without any handler listening on its\n * channel \u2014 in that case messages sent via @botpress/chat produce no response\n * and the caller times out silently. Fail fast with actionable guidance instead.\n *\n * Uses the dev server's `/api/agent` endpoint, so this is a no-op against\n * production bots (no devServerUrl).\n */\nexport async function assertChatChannelBound(\n devServerUrl: string,\n headers: Record<string, string> = {}\n): Promise<void> {\n type ConversationSummary = { channel?: string | string[] }\n let conversations: ConversationSummary[] = []\n try {\n const res = await fetch(`${devServerUrl}/api/agent`, { headers })\n if (!res.ok) return // Dev server unreachable \u2014 skip, let downstream errors surface\n const agent = (await res.json()) as { conversations?: ConversationSummary[] }\n conversations = agent.conversations ?? []\n } catch {\n return // Network error \u2014 skip check\n }\n\n if (conversations.length === 0) return // Bot has no conversations at all \u2014 different problem\n\n // Mirrors the runtime's matchesChannel semantics: a handler matches `chat.channel`\n // if it is the wildcard '*', the literal string 'chat.channel', or an array\n // containing either.\n const matchesChatChannel = (ch: string | string[] | undefined): boolean => {\n if (!ch) return false\n if (ch === '*' || ch === 'chat.channel') return true\n return Array.isArray(ch) && (ch.includes('chat.channel') || ch.includes('*'))\n }\n\n const bound = conversations.some((c) => matchesChatChannel(c.channel))\n if (bound) return\n\n throw new Error(\n [\n 'No Conversation is bound to `chat.channel` \u2014 messages via `@botpress/chat` will time out silently.',\n '',\n 'To fix, bind a Conversation in `src/conversations/` to `chat.channel` (or `*`):',\n \" channel: 'chat.channel', // explicit\",\n \" channel: '*', // wildcard \u2014 matches every channel\",\n \" channel: ['chat.channel', ...], // array form\",\n '',\n 'The `chat` integration is how evals and `adk chat --single` send messages programmatically.',\n ].join('\\n')\n )\n}\n"],
5
- "mappings": "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",
3
+ "sources": ["../src/client.ts", "../src/types.ts", "../../analytics/src/errors.ts", "../src/errors.ts"],
4
+ "sourcesContent": ["/**\n * Chat client for eval conversations.\n * Drives conversations against a running ADK bot via @botpress/chat.\n *\n * This is a send-only client. Observation (bot responses, tool calls, state)\n * is handled by the SSECollector via trace spans \u2014 not via WebSocket listeners.\n */\n\nimport { Client as BpClient } from '@botpress/client'\nimport { createRequire } from 'module'\nimport type { ChatClient, ChatClientConnection, EvalLogger } from './types'\nimport { defaultLogger } from './types'\nimport { EvalRunnerError } from './errors'\n\nconst TRACE_STREAM_PREFLIGHT_TIMEOUT_MS = 10_000\n\nlet defaultChatClient: ChatClient | null = null\n\nexport function setDefaultChatClient(chatClient: ChatClient | null): void {\n defaultChatClient = chatClient\n}\n\nfunction getDefaultChatClient(): ChatClient {\n if (defaultChatClient) return defaultChatClient\n\n const require = createRequire(import.meta.url)\n const mod = require('@botpress/chat') as { Client?: ChatClient; default?: { Client?: ChatClient } }\n const ChatClient = mod.default?.Client ?? mod.Client\n if (!ChatClient) {\n throw new EvalRunnerError({\n code: 'CHAT_CLIENT_MISSING',\n message: '@botpress/chat did not expose a Client export. Pass EvalRunnerConfig.chatClient explicitly.',\n })\n }\n return ChatClient\n}\n\n/**\n * A send-only chat session that maintains a single client connection across turns.\n * Messages and events are fire-and-forget; the SSECollector observes results via traces.\n */\nexport class ChatSession {\n private client: ChatClientConnection | null = null\n private conversationId: string | null = null\n\n constructor(\n private webhookId: string,\n private chatClient?: ChatClient\n ) {}\n\n async connect() {\n const ChatClient = this.chatClient ?? getDefaultChatClient()\n this.client = await ChatClient.connect({ webhookId: this.webhookId })\n }\n\n /** Invariant guard \u2014 calling any session method before connect() is a runner bug. */\n private assertConnected(): ChatClientConnection {\n if (!this.client) {\n throw new EvalRunnerError({\n code: 'CHAT_NOT_CONNECTED',\n message: 'ChatSession not connected. Call connect() first.',\n })\n }\n return this.client\n }\n\n get userId(): string {\n return this.assertConnected().user.id\n }\n\n async ensureConversation(): Promise<string> {\n const client = this.assertConnected()\n\n if (!this.conversationId) {\n const conv = await client.createConversation({})\n this.conversationId = conv.conversation.id\n return conv.conversation.id\n }\n\n return this.conversationId\n }\n\n /**\n * Send a user message. Fire-and-forget \u2014 observation is via SSECollector.\n */\n async sendMessage(message: string): Promise<void> {\n const client = this.assertConnected()\n const conversationId = await this.ensureConversation()\n await client.createMessage({ conversationId, payload: { type: 'text', text: message } })\n }\n\n /**\n * Send an event. Fire-and-forget \u2014 observation is via SSECollector.\n */\n async sendEvent(type: string, payload: Record<string, unknown>): Promise<void> {\n const client = this.assertConnected()\n const conversationId = await this.ensureConversation()\n await client.createEvent({ type, payload, conversationId })\n }\n}\n\n/**\n * Discover the chat integration's webhookId from a bot.\n *\n * Evals talk to the bot through the `chat` integration (via @botpress/chat).\n * If the bot doesn't have that integration installed, evals can't run.\n */\nexport async function discoverWebhookId(botId: string, token: string, apiUrl: string): Promise<string> {\n const client = new BpClient({ token, botId, apiUrl })\n const { bot } = await client.getBot({ id: botId })\n const integrations = bot.integrations || {}\n\n const chat = Object.values(integrations).find((int) => (int as Record<string, unknown>).name === 'chat')\n const webhookId = (chat as Record<string, unknown> | undefined)?.webhookId\n\n if (!webhookId || typeof webhookId !== 'string') {\n throw new EvalRunnerError({\n code: 'CHAT_INTEGRATION_MISSING',\n message: [\n 'The `chat` integration is not installed on this bot \u2014 evals require it.',\n '',\n 'To fix:',\n ' 1. Install it: adk add chat',\n ' 2. Redeploy: adk dev (or: adk deploy)',\n ].join('\\n'),\n expected: true,\n })\n }\n\n return webhookId\n}\n\n/**\n * Verify the dev server's trace stream can be read before eval turns start.\n *\n * This only checks that `/api/traces/stream` is reachable and emits its initial\n * snapshot. It does not prove the private runtime span-ingest path is healthy.\n */\nexport async function assertTraceStreamReadable(\n devServerUrl: string,\n headers: Record<string, string> = {},\n timeoutMs = TRACE_STREAM_PREFLIGHT_TIMEOUT_MS\n): Promise<void> {\n const url = `${devServerUrl.replace(/\\/$/, '')}/api/traces/stream?count=1`\n const abortController = new AbortController()\n let timedOut = false\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null\n\n const timer = setTimeout(() => {\n timedOut = true\n abortController.abort()\n }, timeoutMs)\n\n try {\n const res = await fetch(url, {\n headers: { ...headers, Accept: 'text/event-stream' },\n signal: abortController.signal,\n })\n\n if (!res.ok) {\n throw traceStreamPreflightError(`Trace stream pre-flight failed: ${res.status} ${res.statusText}`)\n }\n\n if (!res.body) {\n throw traceStreamPreflightError('Trace stream pre-flight failed: response had no body.')\n }\n\n reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) {\n throw traceStreamPreflightError('Trace stream pre-flight failed: stream closed before snapshot.')\n }\n\n buffer += decoder.decode(value, { stream: true })\n const parts = buffer.split('\\n\\n')\n buffer = parts.pop() ?? ''\n\n for (const part of parts) {\n if (parseSSEEventName(part) === 'snapshot') {\n return\n }\n }\n }\n } catch (err) {\n if (err instanceof EvalRunnerError) {\n throw err\n }\n\n if (timedOut) {\n throw traceStreamPreflightError(\n `Trace stream pre-flight timed out after ${timeoutMs}ms before receiving a snapshot.`\n )\n }\n\n throw traceStreamPreflightError(\n `Trace stream pre-flight failed: ${err instanceof Error ? err.message : String(err)}`\n )\n } finally {\n clearTimeout(timer)\n reader?.cancel().catch(() => {})\n }\n}\n\nfunction parseSSEEventName(block: string): string | null {\n for (const line of block.split('\\n')) {\n if (line.startsWith('event:')) {\n return line.slice('event:'.length).trim()\n }\n }\n return null\n}\n\nfunction traceStreamPreflightError(message: string): EvalRunnerError {\n return new EvalRunnerError({\n code: 'SSE_CONNECT_FAILED',\n message,\n expected: true,\n suggestion: 'Make sure the dev server is running (`adk dev`) and trace streaming is available.',\n })\n}\n\n/**\n * Verify the bot has at least one Conversation bound to `chat.channel`.\n *\n * The `chat` integration can be installed without any handler listening on its\n * channel \u2014 in that case messages sent via @botpress/chat produce no response\n * and the caller times out silently. Fail fast with actionable guidance instead.\n *\n * Uses the dev server's `/api/agent` endpoint, so this is a no-op against\n * production bots (no devServerUrl).\n */\nexport async function assertChatChannelBound(\n devServerUrl: string,\n headers: Record<string, string> = {},\n logger: EvalLogger = defaultLogger\n): Promise<void> {\n type ConversationSummary = { channel?: string | string[] }\n let conversations: ConversationSummary[] = []\n try {\n const res = await fetch(`${devServerUrl}/api/agent`, { headers })\n if (!res.ok) return // Dev server unreachable \u2014 skip, let downstream errors surface\n const agent = (await res.json()) as { conversations?: ConversationSummary[] }\n conversations = agent.conversations ?? []\n } catch (err) {\n // Soft pre-flight only \u2014 a missing dev server is normal (e.g. prod bots),\n // but log the skip so a *broken* dev server (malformed /api/agent JSON)\n // isn't invisible while every eval afterwards times out mysteriously.\n logger?.warn(\n `Skipping chat.channel binding pre-flight \u2014 could not query ${devServerUrl}/api/agent: ${err instanceof Error ? err.message : String(err)}`\n )\n return\n }\n\n if (conversations.length === 0) return // Bot has no conversations at all \u2014 different problem\n\n // Mirrors the runtime's matchesChannel semantics: a handler matches `chat.channel`\n // if it is the wildcard '*', the literal string 'chat.channel', or an array\n // containing either.\n const matchesChatChannel = (ch: string | string[] | undefined): boolean => {\n if (!ch) return false\n if (ch === '*' || ch === 'chat.channel') return true\n return Array.isArray(ch) && (ch.includes('chat.channel') || ch.includes('*'))\n }\n\n const bound = conversations.some((c) => matchesChatChannel(c.channel))\n if (bound) return\n\n throw new EvalRunnerError({\n code: 'CHAT_CHANNEL_UNBOUND',\n message: [\n 'No Conversation is bound to `chat.channel` \u2014 messages via `@botpress/chat` will time out silently.',\n '',\n 'To fix, bind a Conversation in `src/conversations/` to `chat.channel` (or `*`):',\n \" channel: 'chat.channel', // explicit\",\n \" channel: '*', // wildcard \u2014 matches every channel\",\n \" channel: ['chat.channel', ...], // array form\",\n '',\n 'The `chat` integration is how evals and `adk chat --single` send messages programmatically.',\n ].join('\\n'),\n expected: true,\n })\n}\n", "/**\n * Eval type definitions.\n *\n * User-facing types live in @botpress/evals so eval authors can update eval\n * tooling without tying the authoring API to the agent runtime package.\n */\n\nexport {\n Eval,\n type EvalDefinition,\n type EvalSetup,\n type ConversationTurn,\n type TurnAssertions,\n type OutcomeAssertions,\n type ResponseAssertion,\n type ToolAssertion,\n type StateAssertion,\n type WorkflowAssertion,\n type TimingAssertion,\n type MatchOperator,\n type NumericOperator,\n} from './definition'\n\nexport type { Span, SpanStatus } from './trace'\n\nexport interface ChatClientConnection {\n user: { id: string }\n createConversation: (props: Record<string, unknown>) => Promise<{ conversation: { id: string } }>\n // Broad parameter types keep this compatible with @botpress/chat's narrower\n // generated payload unions while avoiding a dependency on those definitions.\n createMessage: (props: any) => Promise<unknown>\n createEvent: (props: any) => Promise<unknown>\n}\n\nexport interface ChatClient {\n connect: (props: { webhookId: string }) => Promise<ChatClientConnection>\n}\n\n// --- Grader result (universal currency for all graders) ---\n\nexport interface GraderResult {\n assertion: string\n pass: boolean\n expected: string\n actual: string\n}\n\n// --- Tool call extracted from traces ---\n\nexport interface ToolCall {\n name: string\n input: Record<string, unknown>\n output: string\n status: string\n}\n\n// --- Turn report ---\n\nexport interface TurnReport {\n turnNumber: number\n userMessage: string\n botResponse: string\n assertions: GraderResult[]\n pass: boolean\n /** Time spent waiting for the bot to respond (ms) */\n botDuration: number\n /** Time spent fetching traces + running graders (ms) */\n evalDuration: number\n}\n\n// --- Single eval report ---\n\nexport interface EvalReport {\n name: string\n description?: string\n type?: 'capability' | 'regression'\n tags?: string[]\n turns: TurnReport[]\n outcomeAssertions: GraderResult[]\n pass: boolean\n duration: number\n error?: string\n /** Stable EvalErrorCode when `error` came from a typed EvalRunnerError \u2014 lets consumers distinguish setup/config failures from bot failures. */\n errorCode?: string\n}\n\n// --- Progress event for real-time UI updates ---\n\nexport type EvalProgressEvent =\n | { type: 'suite_start'; totalEvals: number }\n | { type: 'eval_start'; evalName: string; index: number; totalTurns: number }\n | {\n type: 'turn_start'\n evalName: string\n evalIndex: number\n turnNumber: number\n totalTurns: number\n userMessage: string\n }\n | {\n type: 'turn_complete'\n evalName: string\n evalIndex: number\n turnNumber: number\n totalTurns: number\n turnReport: TurnReport\n }\n | { type: 'eval_complete'; evalName: string; index: number; report: EvalReport }\n | { type: 'suite_complete'; report: EvalRunReport; error?: string }\n\n// --- Eval run report (full suite) ---\n\nexport interface EvalRunReport {\n id: string\n timestamp: string\n evals: EvalReport[]\n passed: number\n failed: number\n total: number\n duration: number\n filter?: {\n names?: string[]\n tags?: string[]\n type?: 'capability' | 'regression'\n }\n /**\n * True when the suite was aborted mid-run (e.g. user clicked Stop). The\n * persisted report is partial \u2014 `evals` reflects only the completed evals,\n * and `total` matches that count, not the original suite size.\n */\n aborted?: boolean\n}\n\n// --- Bot connection info ---\n\nexport interface BotConnection {\n webhookId: string\n botId: string\n token: string\n apiUrl: string\n}\n\n// --- Logger interface ---\n\nexport interface EvalLogger {\n info: (message: string) => void\n warn: (message: string) => void\n error: (message: string) => void\n}\n\n/**\n * Fallback when no logger is injected (programmatic use outside the CLI).\n * console rather than a no-op so warnings stay visible \u2014 a silent default\n * would hide judge failures and skipped pre-flights, the exact swallow\n * anti-pattern docs/ERROR-HANDLING.md bans. The CLI always injects its own\n * structured logger via EvalRunnerConfig.logger.\n */\nexport const defaultLogger: EvalLogger = console\n\n// --- Eval runner configuration ---\n\nexport interface EvalRunnerConfig {\n credentials: {\n token: string\n apiUrl: string\n botId: string\n workspaceId?: string\n webhookId?: string\n }\n agentPath: string\n /** URL of the dev server providing traces and config. Defaults to the discovered DevConsole URL. */\n devServerUrl?: string\n /** Headers for dev-server requests, e.g. X-Agent-Path when routed through the singleton proxy. */\n devServerHeaders?: Record<string, string>\n /**\n * Optional host-provided chat client. The CLI passes its bundled chat client\n * here so the eval engine does not depend on @botpress/adk.\n */\n chatClient?: ChatClient\n onProgress?: (event: EvalProgressEvent) => void\n /** Logger for warnings and errors. Defaults to console. */\n logger?: EvalLogger\n /**\n * Called for unexpected (internal-bug) eval failures that runEval folds\n * into report.error \u2014 they never propagate to the CLI's command boundary,\n * so without this hook they'd be invisible to error tracking. The CLI binds\n * this to telemetry.captureException; evals itself stays telemetry-free.\n */\n onException?: (error: unknown, properties?: Record<string, unknown>) => void\n /**\n * Optional abort signal \u2014 when fired, the suite stops starting new evals.\n * The currently running eval continues to completion (LLM calls in flight\n * cannot always be cancelled), but no further evals are started.\n */\n signal?: AbortSignal\n /** Bot-level eval options from agent.config.ts. Cascades: eval > agent config > default. */\n evalOptions?: {\n idleTimeout?: number\n /** LLM judge pass threshold (integer, 1-5). Values outside this range are clamped. Cascades: eval > agent config > default (3). */\n judgePassThreshold?: number\n /** Model to use for llm_judge assertions (e.g. 'openai:gpt-4o'). Defaults to 'fast'. */\n judgeModel?: string\n }\n}\n\n// --- Eval suite filter ---\n\nexport interface EvalFilter {\n names?: string[]\n tags?: string[]\n type?: 'capability' | 'regression'\n}\n\n// --- Transformer types (trace-driven eval engine) ---\n\nexport interface StateMutation {\n type: string // 'bot', 'user', 'conversation'\n changedKeys: string[]\n previous: unknown\n current: unknown\n swappedToFile: boolean // data['swapped_to_file'] === true\n}\n\nexport interface WorkflowSpan {\n name: string\n status: 'running' | 'ok' | 'error'\n statusFinal?: string // 'completed', 'continue', 'failed'\n}\n\nexport interface TurnData {\n messages: string[]\n toolCalls: ToolCall[]\n stateMutations: StateMutation[]\n workflowSpans: WorkflowSpan[]\n handlerDuration: number\n handlerStatus: 'ok' | 'error'\n}\n", "/**\n * Shared base error for ADK packages. Lives in `@botpress/analytics` because\n * it's the dependency-light package every surface already shares (cli, adk,\n * ui) \u2014 so all typed errors can genuinely inherit one base instead of\n * mirroring its shape.\n *\n * The policy lives in docs/ERROR-HANDLING.md. The short version: every error\n * we *intend* to throw should carry a stable `code` (so PostHog error\n * tracking can slice issues by code) and an `expected` flag separating\n * user/environment conditions (bad input, not logged in, network down \u2014 render\n * a friendly message, low alert priority) from internal bugs (invariant\n * violations \u2014 full stack, page someone).\n *\n * The runtime package is out of scope and keeps its own `Errors.AbstractError`\n * hierarchy. The adk typed families (`DependencyError`, `Agent0*Error`) extend\n * this base; new code should start here.\n */\nexport class AdkError<Code extends string = string> extends Error {\n /**\n * Marker for cross-module-instance checks \u2014 `instanceof` breaks when a\n * package is duplicated in the dependency graph.\n * @internal\n */\n public static readonly __IS_ADK_BASE_ERROR = true\n\n /**\n * Stable SCREAMING_SNAKE_CASE identifier, e.g. 'BOT_NOT_FOUND'. Subclasses\n * pin `Code` to a literal union (`class EvalRunnerError extends\n * AdkError<EvalErrorCode>`) so typos are compile errors and `code` narrows\n * in switches.\n */\n readonly code: Code\n /**\n * True for user/environment conditions the developer can fix themselves\n * (validation, auth, connectivity). False for internal bugs. Defaults to\n * false so unclassified errors surface as bugs until someone triages them.\n */\n readonly expected: boolean\n /** Structured context. Sanitized before any telemetry leaves the machine. */\n readonly details?: Record<string, unknown>\n /** Actionable next step shown to the user, e.g. \"Run 'adk login' first.\" */\n readonly suggestion?: string\n\n constructor(opts: {\n code: Code\n message: string\n expected?: boolean\n details?: Record<string, unknown>\n suggestion?: string\n cause?: unknown\n }) {\n super(opts.message, opts.cause !== undefined ? { cause: opts.cause } : undefined)\n this.name = this.constructor.name\n this.code = opts.code\n this.expected = opts.expected ?? false\n // Conditional assignment keeps the optional fields absent-not-undefined,\n // which downstream tsconfigs with exactOptionalPropertyTypes require.\n if (opts.details !== undefined) {\n this.details = opts.details\n }\n if (opts.suggestion !== undefined) {\n this.suggestion = opts.suggestion\n }\n }\n}\n\n/**\n * Duck-typed guard that survives duplicated module instances in the\n * dependency graph, where `instanceof AdkError` returns false for an error\n * constructed from another copy of this module. No manual prototype walking\n * needed: static properties are inherited through the constructor chain, so\n * a subclass's constructor resolves the marker in one lookup.\n */\nexport function isAdkError(error: unknown): error is AdkError {\n if (error instanceof AdkError) return true\n if (!(error instanceof Error)) return false\n return (error.constructor as unknown as Record<string, unknown> | undefined)?.__IS_ADK_BASE_ERROR === true\n}\n", "import { AdkError } from '@botpress/analytics'\n\n/**\n * Stable error codes for the eval engine. Typed as a literal union so a typo'd\n * code is a compile error and PostHog issue-slicing stays consistent.\n * Conventions per docs/ERROR-HANDLING.md.\n */\nexport const EVAL_ERROR_CODES = [\n 'EVAL_LOAD_FAILED',\n 'EVAL_FILE_EMPTY',\n 'EVAL_DUPLICATE_NAME',\n 'EVAL_SEED_NO_CONVERSATION',\n 'EVAL_NO_CONVERSATION_ID',\n 'EVAL_TURN_CONFIG_INVALID',\n 'SSE_CONNECT_FAILED',\n 'SSE_NO_BODY',\n 'CHAT_CLIENT_MISSING',\n 'CHAT_NOT_CONNECTED',\n 'CHAT_INTEGRATION_MISSING',\n 'CHAT_CHANNEL_UNBOUND',\n] as const\n\nexport type EvalErrorCode = (typeof EVAL_ERROR_CODES)[number]\n\n/**\n * Typed error for the eval engine. Named EvalRunnerError (not EvalError) to\n * avoid shadowing the JS built-in `EvalError`.\n */\nexport class EvalRunnerError extends AdkError<EvalErrorCode> {}\n"],
5
+ "mappings": "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",
6
6
  "names": ["require"]
7
7
  }
@@ -0,0 +1,15 @@
1
+ import { AdkError } from '@botpress/analytics';
2
+ /**
3
+ * Stable error codes for the eval engine. Typed as a literal union so a typo'd
4
+ * code is a compile error and PostHog issue-slicing stays consistent.
5
+ * Conventions per docs/ERROR-HANDLING.md.
6
+ */
7
+ export declare const EVAL_ERROR_CODES: readonly ["EVAL_LOAD_FAILED", "EVAL_FILE_EMPTY", "EVAL_DUPLICATE_NAME", "EVAL_SEED_NO_CONVERSATION", "EVAL_NO_CONVERSATION_ID", "EVAL_TURN_CONFIG_INVALID", "SSE_CONNECT_FAILED", "SSE_NO_BODY", "CHAT_CLIENT_MISSING", "CHAT_NOT_CONNECTED", "CHAT_INTEGRATION_MISSING", "CHAT_CHANNEL_UNBOUND"];
8
+ export type EvalErrorCode = (typeof EVAL_ERROR_CODES)[number];
9
+ /**
10
+ * Typed error for the eval engine. Named EvalRunnerError (not EvalError) to
11
+ * avoid shadowing the JS built-in `EvalError`.
12
+ */
13
+ export declare class EvalRunnerError extends AdkError<EvalErrorCode> {
14
+ }
15
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAE9C;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,mSAanB,CAAA;AAEV,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7D;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,QAAQ,CAAC,aAAa,CAAC;CAAG"}
@@ -45,6 +45,11 @@ function operatorToString(operator) {
45
45
  // src/graders/llm.ts
46
46
  import { Client } from "@botpress/client";
47
47
  import { Cognitive } from "@botpress/cognitive";
48
+
49
+ // src/types.ts
50
+ var defaultLogger = console;
51
+
52
+ // src/graders/llm.ts
48
53
  var DEFAULT_PASS_THRESHOLD = 3;
49
54
  function buildJudgeSystemPrompt(passThreshold) {
50
55
  return `You are an evaluation judge for a chatbot. You will be given:
@@ -74,13 +79,15 @@ var MODEL_ALIASES = ["fast", "best"];
74
79
  var _cognitive = null;
75
80
  var _availableModels = [];
76
81
  var _judgeModel = "fast";
82
+ var _log = defaultLogger;
77
83
  function _isModelValid(model) {
78
84
  if (MODEL_ALIASES.includes(model)) return true;
79
85
  if (_availableModels.length === 0) return false;
80
86
  return _availableModels.includes(model);
81
87
  }
82
88
  async function initLLMJudge(credentials, options) {
83
- const log = options?.logger ?? console;
89
+ const log = options?.logger ?? defaultLogger;
90
+ _log = log;
84
91
  const client = new Client({
85
92
  token: credentials.token,
86
93
  apiUrl: credentials.apiUrl,
@@ -105,11 +112,12 @@ async function gradeLLMJudge(botResponse, criteria, context) {
105
112
  const threshold = Math.max(1, Math.min(5, context.passThreshold ?? DEFAULT_PASS_THRESHOLD));
106
113
  try {
107
114
  if (!_cognitive) {
115
+ _log.warn(`LLM judge unavailable for "${criteria}": no credentials configured (initLLMJudge not called)`);
108
116
  return {
109
117
  assertion: `llm_judge: "${criteria}"`,
110
- pass: true,
118
+ pass: false,
111
119
  expected: criteria,
112
- actual: "SKIPPED \u2014 LLM judge unavailable: no credentials configured"
120
+ actual: "FAILED \u2014 LLM judge unavailable: no credentials configured"
113
121
  };
114
122
  }
115
123
  if (!_isModelValid(_judgeModel)) {
@@ -139,11 +147,12 @@ Criteria: ${criteria}`
139
147
  const rawContent = output?.choices?.[0]?.content;
140
148
  const content = typeof rawContent === "string" ? rawContent : null;
141
149
  if (!content) {
150
+ _log.warn(`LLM judge returned an empty response for "${criteria}"`);
142
151
  return {
143
152
  assertion: `llm_judge: "${criteria}"`,
144
- pass: true,
153
+ pass: false,
145
154
  expected: criteria,
146
- actual: "SKIPPED \u2014 LLM judge returned empty response"
155
+ actual: "FAILED \u2014 LLM judge returned empty response"
147
156
  };
148
157
  }
149
158
  const verdict = JSON.parse(content);
@@ -154,11 +163,12 @@ Criteria: ${criteria}`
154
163
  actual: `Score ${verdict.score}/5 (threshold: ${threshold}) \u2014 ${verdict.reason}`
155
164
  };
156
165
  } catch (err) {
166
+ _log.warn(`LLM judge failed for "${criteria}": ${err.message}`);
157
167
  return {
158
168
  assertion: `llm_judge: "${criteria}"`,
159
- pass: true,
169
+ pass: false,
160
170
  expected: criteria,
161
- actual: `SKIPPED \u2014 LLM judge unavailable: ${err.message}`
171
+ actual: `FAILED \u2014 LLM judge unavailable: ${err.message}`
162
172
  };
163
173
  }
164
174
  }