@launchdarkly/server-sdk-ai 0.13.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +1 -1
  3. package/__tests__/Judge.test.ts +521 -0
  4. package/__tests__/LDAIClientImpl.test.ts +535 -323
  5. package/__tests__/LDAIConfigTrackerImpl.test.ts +50 -290
  6. package/__tests__/TrackedChat.test.ts +5 -5
  7. package/dist/package.json +53 -0
  8. package/dist/src/LDAIClientImpl.d.ts +39 -0
  9. package/dist/src/LDAIClientImpl.d.ts.map +1 -0
  10. package/dist/src/LDAIClientImpl.js +164 -0
  11. package/dist/src/LDAIClientImpl.js.map +1 -0
  12. package/dist/{LDAIConfigTrackerImpl.d.ts → src/LDAIConfigTrackerImpl.d.ts} +12 -11
  13. package/dist/src/LDAIConfigTrackerImpl.d.ts.map +1 -0
  14. package/dist/{LDAIConfigTrackerImpl.js → src/LDAIConfigTrackerImpl.js} +21 -44
  15. package/dist/src/LDAIConfigTrackerImpl.js.map +1 -0
  16. package/dist/src/LDClientMin.d.ts.map +1 -0
  17. package/dist/{LDClientMin.js.map → src/LDClientMin.js.map} +1 -1
  18. package/dist/src/api/LDAIClient.d.ts +258 -0
  19. package/dist/src/api/LDAIClient.d.ts.map +1 -0
  20. package/dist/{api → src/api}/LDAIClient.js.map +1 -1
  21. package/dist/{api → src/api}/chat/TrackedChat.d.ts +22 -4
  22. package/dist/src/api/chat/TrackedChat.d.ts.map +1 -0
  23. package/dist/{api → src/api}/chat/TrackedChat.js +43 -2
  24. package/dist/src/api/chat/TrackedChat.js.map +1 -0
  25. package/dist/src/api/chat/index.d.ts.map +1 -0
  26. package/dist/src/api/chat/index.js.map +1 -0
  27. package/dist/src/api/chat/types.d.ts +22 -0
  28. package/dist/src/api/chat/types.d.ts.map +1 -0
  29. package/dist/{api → src/api}/chat/types.js.map +1 -1
  30. package/dist/{api → src/api}/config/LDAIConfigTracker.d.ts +25 -25
  31. package/dist/src/api/config/LDAIConfigTracker.d.ts.map +1 -0
  32. package/dist/src/api/config/LDAIConfigTracker.js.map +1 -0
  33. package/dist/src/api/config/LDAIConfigUtils.d.ts +2 -0
  34. package/dist/src/api/config/LDAIConfigUtils.d.ts.map +1 -0
  35. package/dist/src/api/config/LDAIConfigUtils.js +145 -0
  36. package/dist/src/api/config/LDAIConfigUtils.js.map +1 -0
  37. package/dist/src/api/config/index.d.ts +3 -0
  38. package/dist/src/api/config/index.d.ts.map +1 -0
  39. package/dist/{api/agents → src/api/config}/index.js +1 -1
  40. package/dist/src/api/config/index.js.map +1 -0
  41. package/dist/src/api/config/types.d.ts +206 -0
  42. package/dist/src/api/config/types.d.ts.map +1 -0
  43. package/dist/{api/agents/LDAIAgent.js → src/api/config/types.js} +1 -1
  44. package/dist/src/api/config/types.js.map +1 -0
  45. package/dist/{api → src/api}/index.d.ts +1 -1
  46. package/dist/src/api/index.d.ts.map +1 -0
  47. package/dist/{api → src/api}/index.js +1 -1
  48. package/dist/src/api/index.js.map +1 -0
  49. package/dist/src/api/judge/EvaluationSchemaBuilder.d.ts +11 -0
  50. package/dist/src/api/judge/EvaluationSchemaBuilder.d.ts.map +1 -0
  51. package/dist/src/api/judge/EvaluationSchemaBuilder.js +52 -0
  52. package/dist/src/api/judge/EvaluationSchemaBuilder.js.map +1 -0
  53. package/dist/src/api/judge/Judge.d.ts +63 -0
  54. package/dist/src/api/judge/Judge.d.ts.map +1 -0
  55. package/dist/src/api/judge/Judge.js +151 -0
  56. package/dist/src/api/judge/Judge.js.map +1 -0
  57. package/dist/src/api/judge/index.d.ts +3 -0
  58. package/dist/src/api/judge/index.d.ts.map +1 -0
  59. package/dist/src/api/judge/index.js +6 -0
  60. package/dist/src/api/judge/index.js.map +1 -0
  61. package/dist/src/api/judge/types.d.ts +37 -0
  62. package/dist/src/api/judge/types.d.ts.map +1 -0
  63. package/dist/{api/config/LDAIConfig.js → src/api/judge/types.js} +1 -1
  64. package/dist/src/api/judge/types.js.map +1 -0
  65. package/dist/src/api/metrics/BedrockTokenUsage.d.ts.map +1 -0
  66. package/dist/src/api/metrics/BedrockTokenUsage.js.map +1 -0
  67. package/dist/src/api/metrics/LDAIMetrics.d.ts.map +1 -0
  68. package/dist/src/api/metrics/LDAIMetrics.js.map +1 -0
  69. package/dist/src/api/metrics/LDFeedbackKind.d.ts.map +1 -0
  70. package/dist/src/api/metrics/LDFeedbackKind.js.map +1 -0
  71. package/dist/src/api/metrics/LDTokenUsage.d.ts.map +1 -0
  72. package/dist/src/api/metrics/LDTokenUsage.js.map +1 -0
  73. package/dist/src/api/metrics/OpenAiUsage.d.ts.map +1 -0
  74. package/dist/src/api/metrics/OpenAiUsage.js.map +1 -0
  75. package/dist/src/api/metrics/VercelAISDKTokenUsage.d.ts.map +1 -0
  76. package/dist/src/api/metrics/VercelAISDKTokenUsage.js.map +1 -0
  77. package/dist/src/api/metrics/index.d.ts.map +1 -0
  78. package/dist/src/api/metrics/index.js.map +1 -0
  79. package/dist/{api → src/api}/providers/AIProvider.d.ts +20 -3
  80. package/dist/src/api/providers/AIProvider.d.ts.map +1 -0
  81. package/dist/src/api/providers/AIProvider.js +88 -0
  82. package/dist/src/api/providers/AIProvider.js.map +1 -0
  83. package/dist/{api → src/api}/providers/AIProviderFactory.d.ts +2 -2
  84. package/dist/src/api/providers/AIProviderFactory.d.ts.map +1 -0
  85. package/dist/src/api/providers/AIProviderFactory.js.map +1 -0
  86. package/dist/src/api/providers/index.d.ts.map +1 -0
  87. package/dist/src/api/providers/index.js.map +1 -0
  88. package/dist/src/index.d.ts.map +1 -0
  89. package/dist/src/index.js.map +1 -0
  90. package/docs/assets/search.js +1 -1
  91. package/docs/classes/AIProvider.html +55 -20
  92. package/docs/classes/AIProviderFactory.html +27 -17
  93. package/docs/classes/Judge.html +322 -0
  94. package/docs/classes/TrackedChat.html +97 -29
  95. package/docs/enums/LDFeedbackKind.html +22 -12
  96. package/docs/functions/createBedrockTokenUsage.html +20 -10
  97. package/docs/functions/createOpenAiUsage.html +20 -10
  98. package/docs/functions/createVercelAISDKTokenUsage.html +20 -10
  99. package/docs/functions/initAi.html +20 -10
  100. package/docs/index.html +36 -16
  101. package/docs/interfaces/ChatResponse.html +35 -14
  102. package/docs/interfaces/EvalScore.html +119 -0
  103. package/docs/interfaces/JudgeResponse.html +139 -0
  104. package/docs/interfaces/LDAIAgentConfig.html +90 -31
  105. package/docs/interfaces/{LDAIAgent.html → LDAIAgentConfigDefault.html} +51 -41
  106. package/docs/interfaces/LDAIAgentRequestConfig.html +129 -0
  107. package/docs/interfaces/LDAIClient.html +234 -40
  108. package/docs/interfaces/{VercelAISDKConfig.html → LDAICompletionConfig.html} +96 -90
  109. package/docs/interfaces/LDAICompletionConfigDefault.html +155 -0
  110. package/docs/interfaces/LDAIConfig.html +52 -75
  111. package/docs/interfaces/LDAIConfigDefault.html +133 -0
  112. package/docs/interfaces/LDAIConfigTracker.html +102 -63
  113. package/docs/interfaces/LDAIJudgeConfig.html +178 -0
  114. package/docs/interfaces/LDAIJudgeConfigDefault.html +155 -0
  115. package/docs/interfaces/LDAIMetrics.html +22 -12
  116. package/docs/interfaces/LDJudge.html +119 -0
  117. package/docs/interfaces/{VercelAISDKMapOptions.html → LDJudgeConfiguration.html} +35 -23
  118. package/docs/interfaces/LDLogger.html +19 -9
  119. package/docs/interfaces/LDMessage.html +22 -12
  120. package/docs/interfaces/LDModelConfig.html +23 -13
  121. package/docs/interfaces/LDProviderConfig.html +21 -11
  122. package/docs/interfaces/LDTokenUsage.html +23 -13
  123. package/docs/interfaces/StructuredResponse.html +129 -0
  124. package/docs/types/{VercelAISDKProvider.html → LDAIConfigDefaultKind.html} +26 -35
  125. package/docs/types/{LDAIAgentDefaults.html → LDAIConfigKind.html} +24 -14
  126. package/docs/types/{LDAIDefaults.html → LDAIConfigMode.html} +24 -24
  127. package/docs/types/SupportedAIProvider.html +20 -10
  128. package/docs/variables/SUPPORTED_AI_PROVIDERS.html +20 -10
  129. package/package.json +3 -3
  130. package/src/LDAIClientImpl.ts +222 -176
  131. package/src/LDAIConfigTrackerImpl.ts +31 -54
  132. package/src/api/LDAIClient.ts +166 -33
  133. package/src/api/chat/TrackedChat.ts +68 -5
  134. package/src/api/chat/types.ts +8 -1
  135. package/src/api/config/LDAIConfigTracker.ts +27 -30
  136. package/src/api/config/LDAIConfigUtils.ts +212 -0
  137. package/src/api/config/index.ts +2 -2
  138. package/src/api/config/types.ts +260 -0
  139. package/src/api/index.ts +1 -1
  140. package/src/api/judge/EvaluationSchemaBuilder.ts +54 -0
  141. package/src/api/judge/Judge.ts +218 -0
  142. package/src/api/judge/index.ts +2 -0
  143. package/src/api/judge/types.ts +41 -0
  144. package/src/api/providers/AIProvider.ts +54 -3
  145. package/src/api/providers/AIProviderFactory.ts +4 -4
  146. package/tsconfig.json +3 -3
  147. package/tsconfig.ref.json +1 -1
  148. package/__tests__/LDAIConfigMapper.test.ts +0 -159
  149. package/dist/LDAIClientImpl.d.ts +0 -23
  150. package/dist/LDAIClientImpl.d.ts.map +0 -1
  151. package/dist/LDAIClientImpl.js +0 -128
  152. package/dist/LDAIClientImpl.js.map +0 -1
  153. package/dist/LDAIConfigMapper.d.ts +0 -14
  154. package/dist/LDAIConfigMapper.d.ts.map +0 -1
  155. package/dist/LDAIConfigMapper.js +0 -59
  156. package/dist/LDAIConfigMapper.js.map +0 -1
  157. package/dist/LDAIConfigTrackerImpl.d.ts.map +0 -1
  158. package/dist/LDAIConfigTrackerImpl.js.map +0 -1
  159. package/dist/LDClientMin.d.ts.map +0 -1
  160. package/dist/api/LDAIClient.d.ts +0 -169
  161. package/dist/api/LDAIClient.d.ts.map +0 -1
  162. package/dist/api/agents/LDAIAgent.d.ts +0 -32
  163. package/dist/api/agents/LDAIAgent.d.ts.map +0 -1
  164. package/dist/api/agents/LDAIAgent.js.map +0 -1
  165. package/dist/api/agents/index.d.ts +0 -2
  166. package/dist/api/agents/index.d.ts.map +0 -1
  167. package/dist/api/agents/index.js.map +0 -1
  168. package/dist/api/chat/TrackedChat.d.ts.map +0 -1
  169. package/dist/api/chat/TrackedChat.js.map +0 -1
  170. package/dist/api/chat/index.d.ts.map +0 -1
  171. package/dist/api/chat/index.js.map +0 -1
  172. package/dist/api/chat/types.d.ts +0 -16
  173. package/dist/api/chat/types.d.ts.map +0 -1
  174. package/dist/api/config/LDAIConfig.d.ts +0 -95
  175. package/dist/api/config/LDAIConfig.d.ts.map +0 -1
  176. package/dist/api/config/LDAIConfig.js.map +0 -1
  177. package/dist/api/config/LDAIConfigTracker.d.ts.map +0 -1
  178. package/dist/api/config/LDAIConfigTracker.js.map +0 -1
  179. package/dist/api/config/VercelAISDK.d.ts +0 -31
  180. package/dist/api/config/VercelAISDK.d.ts.map +0 -1
  181. package/dist/api/config/VercelAISDK.js +0 -3
  182. package/dist/api/config/VercelAISDK.js.map +0 -1
  183. package/dist/api/config/index.d.ts +0 -4
  184. package/dist/api/config/index.d.ts.map +0 -1
  185. package/dist/api/config/index.js +0 -19
  186. package/dist/api/config/index.js.map +0 -1
  187. package/dist/api/index.d.ts.map +0 -1
  188. package/dist/api/index.js.map +0 -1
  189. package/dist/api/metrics/BedrockTokenUsage.d.ts.map +0 -1
  190. package/dist/api/metrics/BedrockTokenUsage.js.map +0 -1
  191. package/dist/api/metrics/LDAIMetrics.d.ts.map +0 -1
  192. package/dist/api/metrics/LDAIMetrics.js.map +0 -1
  193. package/dist/api/metrics/LDFeedbackKind.d.ts.map +0 -1
  194. package/dist/api/metrics/LDFeedbackKind.js.map +0 -1
  195. package/dist/api/metrics/LDTokenUsage.d.ts.map +0 -1
  196. package/dist/api/metrics/LDTokenUsage.js.map +0 -1
  197. package/dist/api/metrics/OpenAiUsage.d.ts.map +0 -1
  198. package/dist/api/metrics/OpenAiUsage.js.map +0 -1
  199. package/dist/api/metrics/VercelAISDKTokenUsage.d.ts.map +0 -1
  200. package/dist/api/metrics/VercelAISDKTokenUsage.js.map +0 -1
  201. package/dist/api/metrics/index.d.ts.map +0 -1
  202. package/dist/api/metrics/index.js.map +0 -1
  203. package/dist/api/providers/AIProvider.d.ts.map +0 -1
  204. package/dist/api/providers/AIProvider.js +0 -31
  205. package/dist/api/providers/AIProvider.js.map +0 -1
  206. package/dist/api/providers/AIProviderFactory.d.ts.map +0 -1
  207. package/dist/api/providers/AIProviderFactory.js.map +0 -1
  208. package/dist/api/providers/index.d.ts.map +0 -1
  209. package/dist/api/providers/index.js.map +0 -1
  210. package/dist/index.d.ts.map +0 -1
  211. package/dist/index.js.map +0 -1
  212. package/src/LDAIConfigMapper.ts +0 -69
  213. package/src/api/agents/LDAIAgent.ts +0 -36
  214. package/src/api/agents/index.ts +0 -1
  215. package/src/api/config/LDAIConfig.ts +0 -104
  216. package/src/api/config/VercelAISDK.ts +0 -33
  217. /package/dist/{LDClientMin.d.ts → src/LDClientMin.d.ts} +0 -0
  218. /package/dist/{LDClientMin.js → src/LDClientMin.js} +0 -0
  219. /package/dist/{api → src/api}/LDAIClient.js +0 -0
  220. /package/dist/{api → src/api}/chat/index.d.ts +0 -0
  221. /package/dist/{api → src/api}/chat/index.js +0 -0
  222. /package/dist/{api → src/api}/chat/types.js +0 -0
  223. /package/dist/{api → src/api}/config/LDAIConfigTracker.js +0 -0
  224. /package/dist/{api → src/api}/metrics/BedrockTokenUsage.d.ts +0 -0
  225. /package/dist/{api → src/api}/metrics/BedrockTokenUsage.js +0 -0
  226. /package/dist/{api → src/api}/metrics/LDAIMetrics.d.ts +0 -0
  227. /package/dist/{api → src/api}/metrics/LDAIMetrics.js +0 -0
  228. /package/dist/{api → src/api}/metrics/LDFeedbackKind.d.ts +0 -0
  229. /package/dist/{api → src/api}/metrics/LDFeedbackKind.js +0 -0
  230. /package/dist/{api → src/api}/metrics/LDTokenUsage.d.ts +0 -0
  231. /package/dist/{api → src/api}/metrics/LDTokenUsage.js +0 -0
  232. /package/dist/{api → src/api}/metrics/OpenAiUsage.d.ts +0 -0
  233. /package/dist/{api → src/api}/metrics/OpenAiUsage.js +0 -0
  234. /package/dist/{api → src/api}/metrics/VercelAISDKTokenUsage.d.ts +0 -0
  235. /package/dist/{api → src/api}/metrics/VercelAISDKTokenUsage.js +0 -0
  236. /package/dist/{api → src/api}/metrics/index.d.ts +0 -0
  237. /package/dist/{api → src/api}/metrics/index.js +0 -0
  238. /package/dist/{api → src/api}/providers/AIProviderFactory.js +0 -0
  239. /package/dist/{api → src/api}/providers/index.d.ts +0 -0
  240. /package/dist/{api → src/api}/providers/index.js +0 -0
  241. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  242. /package/dist/{index.js → src/index.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.14.1](https://github.com/launchdarkly/js-core/compare/server-sdk-ai-v0.14.0...server-sdk-ai-v0.14.1) (2025-11-13)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * Include the AI Judge Config key with tracked metrics ([#986](https://github.com/launchdarkly/js-core/issues/986)) ([213fc79](https://github.com/launchdarkly/js-core/commit/213fc793c752af6517ba7c117219205fb62b9c65))
9
+
10
+ ## [0.14.0](https://github.com/launchdarkly/js-core/compare/server-sdk-ai-v0.13.0...server-sdk-ai-v0.14.0) (2025-11-06)
11
+
12
+
13
+ ### ⚠ BREAKING CHANGES
14
+
15
+ * Removed deprecated Vercel methods ([#983](https://github.com/launchdarkly/js-core/issues/983))
16
+ * Add support for real time judge evals ([#969](https://github.com/launchdarkly/js-core/issues/969))
17
+ * AI Config defaults require the "enabled" attribute
18
+ * Renamed LDAIAgentConfig to LDAIAgentConfigRequest for clarity
19
+ * Renamed LDAIAgent to LDAIAgentConfig *note the previous use of this name
20
+ * Renamed LDAIAgentDefault to LDAIAgentConfigDefault for clarity
21
+ * Renamed LDAIDefaults to LDAICompletionConfigDefault for clarity
22
+
23
+ ### Features
24
+
25
+ * Add support for real time judge evals ([#969](https://github.com/launchdarkly/js-core/issues/969)) ([6ecd9ab](https://github.com/launchdarkly/js-core/commit/6ecd9ab4d97f6445adfd377709f14d7f3b420363))
26
+ * Added createJudge method ([6ecd9ab](https://github.com/launchdarkly/js-core/commit/6ecd9ab4d97f6445adfd377709f14d7f3b420363))
27
+ * Added judgeConfig method to AI SDK to retrieve an AI Judge Config ([6ecd9ab](https://github.com/launchdarkly/js-core/commit/6ecd9ab4d97f6445adfd377709f14d7f3b420363))
28
+ * Added trackEvalScores method to config tracker ([6ecd9ab](https://github.com/launchdarkly/js-core/commit/6ecd9ab4d97f6445adfd377709f14d7f3b420363))
29
+ * Chat will evaluate responses with configured judges ([6ecd9ab](https://github.com/launchdarkly/js-core/commit/6ecd9ab4d97f6445adfd377709f14d7f3b420363))
30
+ * Include AI SDK version in tracking information ([#985](https://github.com/launchdarkly/js-core/issues/985)) ([ef90564](https://github.com/launchdarkly/js-core/commit/ef90564ee1ed9411e77b836d2b5b8037ff671b07))
31
+ * Removed deprecated Vercel methods ([#983](https://github.com/launchdarkly/js-core/issues/983)) ([960a499](https://github.com/launchdarkly/js-core/commit/960a49927e795890e5093b0156ec6d721c3066fd))
32
+
33
+
34
+ ### Bug Fixes
35
+
36
+ * AI Config defaults require the "enabled" attribute ([6ecd9ab](https://github.com/launchdarkly/js-core/commit/6ecd9ab4d97f6445adfd377709f14d7f3b420363))
37
+ * Renamed LDAIAgent to LDAIAgentConfig *note the previous use of this name ([6ecd9ab](https://github.com/launchdarkly/js-core/commit/6ecd9ab4d97f6445adfd377709f14d7f3b420363))
38
+ * Renamed LDAIAgentConfig to LDAIAgentConfigRequest for clarity ([6ecd9ab](https://github.com/launchdarkly/js-core/commit/6ecd9ab4d97f6445adfd377709f14d7f3b420363))
39
+ * Renamed LDAIAgentDefault to LDAIAgentConfigDefault for clarity ([6ecd9ab](https://github.com/launchdarkly/js-core/commit/6ecd9ab4d97f6445adfd377709f14d7f3b420363))
40
+ * Renamed LDAIDefaults to LDAICompletionConfigDefault for clarity ([6ecd9ab](https://github.com/launchdarkly/js-core/commit/6ecd9ab4d97f6445adfd377709f14d7f3b420363))
41
+
3
42
  ## [0.13.0](https://github.com/launchdarkly/js-core/compare/server-sdk-ai-v0.12.3...server-sdk-ai-v0.13.0) (2025-11-04)
4
43
 
5
44
 
package/README.md CHANGED
@@ -97,7 +97,7 @@ if (aiConfig.enabled) {
97
97
 
98
98
  ```typescript
99
99
  // Use the same defaultConfig from the retrieval section above
100
- const chat = await aiClient.initChat(
100
+ const chat = await aiClient.createChat(
101
101
  'customer-support-chat',
102
102
  context,
103
103
  defaultConfig,
@@ -0,0 +1,521 @@
1
+ import { LDLogger } from '@launchdarkly/js-server-sdk-common';
2
+
3
+ import { LDAIConfigTracker } from '../src/api/config/LDAIConfigTracker';
4
+ import { LDAIJudgeConfig, LDMessage } from '../src/api/config/types';
5
+ import { Judge } from '../src/api/judge/Judge';
6
+ import { StructuredResponse } from '../src/api/judge/types';
7
+ import { AIProvider } from '../src/api/providers/AIProvider';
8
+
9
+ describe('Judge', () => {
10
+ let mockProvider: jest.Mocked<AIProvider>;
11
+ let mockTracker: jest.Mocked<LDAIConfigTracker>;
12
+ let mockLogger: jest.Mocked<LDLogger>;
13
+ let judgeConfig: LDAIJudgeConfig;
14
+
15
+ const mockTrackData = {
16
+ variationKey: 'test-variation',
17
+ configKey: 'test-config',
18
+ version: 1,
19
+ };
20
+
21
+ beforeEach(() => {
22
+ // Mock the AIProvider - only mock what's actually used
23
+ mockProvider = {
24
+ invokeStructuredModel: jest.fn(),
25
+ } as any;
26
+
27
+ // Mock the LDAIConfigTracker - only mock what's actually used
28
+ mockTracker = {
29
+ trackMetricsOf: jest.fn(),
30
+ getTrackData: jest.fn().mockReturnValue(mockTrackData),
31
+ } as any;
32
+
33
+ // Mock the logger - only mock what's actually used
34
+ mockLogger = {
35
+ debug: jest.fn(),
36
+ warn: jest.fn(),
37
+ error: jest.fn(),
38
+ } as any;
39
+
40
+ // Create a basic judge config
41
+ judgeConfig = {
42
+ key: 'test-judge',
43
+ enabled: true,
44
+ messages: [
45
+ { role: 'system', content: 'You are a helpful judge that evaluates AI responses.' },
46
+ {
47
+ role: 'user',
48
+ content:
49
+ 'Evaluate and report scores for important metrics: Input: {{message_history}}, Output: {{response_to_evaluate}}',
50
+ },
51
+ ],
52
+ model: { name: 'gpt-4' },
53
+ provider: { name: 'openai' },
54
+ tracker: mockTracker,
55
+ evaluationMetricKeys: ['relevance', 'accuracy', 'helpfulness'],
56
+ };
57
+ });
58
+
59
+ describe('constructor', () => {
60
+ it('initializes with proper configuration', () => {
61
+ const judge = new Judge(judgeConfig, mockTracker, mockProvider, mockLogger);
62
+
63
+ expect(judge).toBeDefined();
64
+ });
65
+ });
66
+
67
+ describe('evaluate', () => {
68
+ let judge: Judge;
69
+
70
+ beforeEach(() => {
71
+ judge = new Judge(judgeConfig, mockTracker, mockProvider, mockLogger);
72
+ });
73
+
74
+ it('evaluates AI response successfully', async () => {
75
+ const mockStructuredResponse: StructuredResponse = {
76
+ data: {
77
+ evaluations: {
78
+ relevance: { score: 0.8, reasoning: 'The response is relevant to the question' },
79
+ accuracy: { score: 0.9, reasoning: 'The response is factually accurate' },
80
+ helpfulness: { score: 0.7, reasoning: 'The response provides helpful information' },
81
+ },
82
+ },
83
+ rawResponse: JSON.stringify({
84
+ evaluations: {
85
+ relevance: { score: 0.8, reasoning: 'The response is relevant to the question' },
86
+ accuracy: { score: 0.9, reasoning: 'The response is factually accurate' },
87
+ helpfulness: { score: 0.7, reasoning: 'The response provides helpful information' },
88
+ },
89
+ }),
90
+ metrics: {
91
+ success: true,
92
+ usage: {
93
+ total: 100,
94
+ input: 50,
95
+ output: 50,
96
+ },
97
+ },
98
+ };
99
+
100
+ mockTracker.trackMetricsOf.mockImplementation(async (extractor, func) => func());
101
+ mockProvider.invokeStructuredModel.mockResolvedValue(mockStructuredResponse);
102
+
103
+ const result = await judge.evaluate(
104
+ 'What is the capital of France?',
105
+ 'Paris is the capital of France.',
106
+ );
107
+
108
+ expect(result).toEqual({
109
+ evals: {
110
+ relevance: {
111
+ score: 0.8,
112
+ reasoning: 'The response is relevant to the question',
113
+ },
114
+ accuracy: {
115
+ score: 0.9,
116
+ reasoning: 'The response is factually accurate',
117
+ },
118
+ helpfulness: {
119
+ score: 0.7,
120
+ reasoning: 'The response provides helpful information',
121
+ },
122
+ },
123
+ success: true,
124
+ judgeConfigKey: 'test-judge',
125
+ });
126
+
127
+ expect(mockProvider.invokeStructuredModel).toHaveBeenCalledWith(
128
+ expect.arrayContaining([
129
+ expect.objectContaining({
130
+ role: 'system',
131
+ content: 'You are a helpful judge that evaluates AI responses.',
132
+ }),
133
+ expect.objectContaining({
134
+ role: 'user',
135
+ content:
136
+ 'Evaluate and report scores for important metrics: Input: What is the capital of France?, Output: Paris is the capital of France.',
137
+ }),
138
+ ]),
139
+ expect.any(Object), // evaluation response structure
140
+ );
141
+ });
142
+
143
+ it('handles sampling rate correctly', async () => {
144
+ // Mock Math.random to return 0.3 (should be sampled with rate 0.5 since 0.3 <= 0.5)
145
+ const originalRandom = Math.random;
146
+ Math.random = jest.fn().mockReturnValue(0.3);
147
+
148
+ // Mock the structured response
149
+ const mockStructuredResponse: StructuredResponse = {
150
+ data: {
151
+ evaluations: {
152
+ relevance: { score: 0.8, reasoning: 'Good' },
153
+ accuracy: { score: 0.9, reasoning: 'Accurate' },
154
+ helpfulness: { score: 0.7, reasoning: 'Helpful' },
155
+ },
156
+ },
157
+ rawResponse: JSON.stringify({
158
+ evaluations: {
159
+ relevance: { score: 0.8, reasoning: 'Good' },
160
+ accuracy: { score: 0.9, reasoning: 'Accurate' },
161
+ helpfulness: { score: 0.7, reasoning: 'Helpful' },
162
+ },
163
+ }),
164
+ metrics: {
165
+ success: true,
166
+ usage: { total: 100, input: 50, output: 50 },
167
+ },
168
+ };
169
+
170
+ mockTracker.trackMetricsOf.mockImplementation(async (extractor, func) => func());
171
+ mockProvider.invokeStructuredModel.mockResolvedValue(mockStructuredResponse);
172
+
173
+ const result = await judge.evaluate('test input', 'test output', 0.5);
174
+
175
+ expect(result).toBeDefined();
176
+ expect(mockProvider.invokeStructuredModel).toHaveBeenCalled();
177
+
178
+ Math.random = originalRandom;
179
+ });
180
+
181
+ it('returns undefined when not sampled', async () => {
182
+ // Mock Math.random to return 0.8 (should not be sampled with rate 0.5 since 0.8 > 0.5)
183
+ const originalRandom = Math.random;
184
+ Math.random = jest.fn().mockReturnValue(0.8);
185
+
186
+ const result = await judge.evaluate('test input', 'test output', 0.5);
187
+
188
+ expect(result).toBeUndefined();
189
+ expect(mockProvider.invokeStructuredModel).not.toHaveBeenCalled();
190
+ expect(mockLogger.debug).toHaveBeenCalledWith(
191
+ 'Judge evaluation skipped due to sampling rate: 0.5',
192
+ );
193
+
194
+ Math.random = originalRandom;
195
+ });
196
+
197
+ it('returns undefined when evaluationMetricKeys is empty', async () => {
198
+ const configWithoutMetrics: LDAIJudgeConfig = {
199
+ ...judgeConfig,
200
+ evaluationMetricKeys: [],
201
+ };
202
+ const judgeWithoutMetrics = new Judge(
203
+ configWithoutMetrics,
204
+ mockTracker,
205
+ mockProvider,
206
+ mockLogger,
207
+ );
208
+
209
+ const result = await judgeWithoutMetrics.evaluate('test input', 'test output');
210
+
211
+ expect(result).toBeUndefined();
212
+ expect(mockLogger.warn).toHaveBeenCalledWith(
213
+ 'Judge configuration is missing required evaluationMetricKeys',
214
+ mockTrackData,
215
+ );
216
+ });
217
+
218
+ it('returns undefined when messages are missing', async () => {
219
+ const configWithoutMessages: LDAIJudgeConfig = {
220
+ ...judgeConfig,
221
+ messages: undefined,
222
+ };
223
+ const judgeWithoutMessages = new Judge(
224
+ configWithoutMessages,
225
+ mockTracker,
226
+ mockProvider,
227
+ mockLogger,
228
+ );
229
+
230
+ const result = await judgeWithoutMessages.evaluate('test input', 'test output');
231
+
232
+ expect(result).toBeUndefined();
233
+ expect(mockLogger.warn).toHaveBeenCalledWith(
234
+ 'Judge configuration must include messages',
235
+ mockTrackData,
236
+ );
237
+ });
238
+
239
+ it('returns partial evaluations when some metrics are missing', async () => {
240
+ const mockStructuredResponse: StructuredResponse = {
241
+ data: {
242
+ evaluations: {
243
+ relevance: { score: 0.8, reasoning: 'Good' },
244
+ // accuracy is missing
245
+ helpfulness: { score: 0.7, reasoning: 'Helpful' },
246
+ },
247
+ },
248
+ rawResponse: JSON.stringify({
249
+ evaluations: {
250
+ relevance: { score: 0.8, reasoning: 'Good' },
251
+ helpfulness: { score: 0.7, reasoning: 'Helpful' },
252
+ },
253
+ }),
254
+ metrics: {
255
+ success: true,
256
+ usage: { total: 100, input: 50, output: 50 },
257
+ },
258
+ };
259
+
260
+ mockTracker.trackMetricsOf.mockImplementation(async (extractor, func) => func());
261
+ mockProvider.invokeStructuredModel.mockResolvedValue(mockStructuredResponse);
262
+
263
+ const result = await judge.evaluate('test input', 'test output');
264
+
265
+ // When one metric is missing, it returns the partial evals it has with success: false
266
+ expect(result).toEqual({
267
+ evals: {
268
+ relevance: { score: 0.8, reasoning: 'Good' },
269
+ helpfulness: { score: 0.7, reasoning: 'Helpful' },
270
+ },
271
+ success: false,
272
+ judgeConfigKey: 'test-judge',
273
+ });
274
+ });
275
+
276
+ it('returns empty evaluations when response structure is malformed', async () => {
277
+ const mockStructuredResponse: StructuredResponse = {
278
+ data: {
279
+ // Missing 'evaluations' wrapper - malformed structure
280
+ relevance: { score: 0.8, reasoning: 'Good' },
281
+ accuracy: { score: 0.9, reasoning: 'Accurate' },
282
+ helpfulness: { score: 0.7, reasoning: 'Helpful' },
283
+ },
284
+ rawResponse: JSON.stringify({
285
+ relevance: { score: 0.8, reasoning: 'Good' },
286
+ accuracy: { score: 0.9, reasoning: 'Accurate' },
287
+ helpfulness: { score: 0.7, reasoning: 'Helpful' },
288
+ }),
289
+ metrics: {
290
+ success: true,
291
+ usage: { total: 100, input: 50, output: 50 },
292
+ },
293
+ };
294
+
295
+ mockTracker.trackMetricsOf.mockImplementation(async (extractor, func) => func());
296
+ mockProvider.invokeStructuredModel.mockResolvedValue(mockStructuredResponse);
297
+
298
+ const result = await judge.evaluate('test input', 'test output');
299
+
300
+ // When the structure is completely wrong, returns empty evals with success: false
301
+ expect(result).toEqual({
302
+ evals: {},
303
+ success: false,
304
+ judgeConfigKey: 'test-judge',
305
+ });
306
+ });
307
+
308
+ it('handles provider errors gracefully', async () => {
309
+ const error = new Error('Provider error');
310
+ mockTracker.trackMetricsOf.mockRejectedValue(error);
311
+
312
+ const result = await judge.evaluate('test input', 'test output');
313
+
314
+ expect(result).toEqual({
315
+ evals: {},
316
+ success: false,
317
+ error: 'Provider error',
318
+ judgeConfigKey: 'test-judge',
319
+ });
320
+ expect(mockLogger.error).toHaveBeenCalledWith('Judge evaluation failed:', error);
321
+ });
322
+
323
+ it('handles non-Error exceptions', async () => {
324
+ mockTracker.trackMetricsOf.mockRejectedValue('String error');
325
+
326
+ const result = await judge.evaluate('test input', 'test output');
327
+
328
+ expect(result).toEqual({
329
+ evals: {},
330
+ success: false,
331
+ error: 'Unknown error',
332
+ judgeConfigKey: 'test-judge',
333
+ });
334
+ });
335
+ });
336
+
337
+ describe('evaluateMessages', () => {
338
+ let judge: Judge;
339
+
340
+ beforeEach(() => {
341
+ judge = new Judge(judgeConfig, mockTracker, mockProvider, mockLogger);
342
+ });
343
+
344
+ it('evaluates messages and response successfully', async () => {
345
+ const messages: LDMessage[] = [
346
+ { role: 'user', content: 'What is the capital of France?' },
347
+ { role: 'assistant', content: 'Paris is the capital of France.' },
348
+ ];
349
+ const response = {
350
+ message: { role: 'assistant' as const, content: 'Paris is the capital of France.' },
351
+ metrics: { success: true },
352
+ };
353
+
354
+ const mockStructuredResponse: StructuredResponse = {
355
+ data: {
356
+ evaluations: {
357
+ relevance: { score: 0.8, reasoning: 'The response is relevant to the question' },
358
+ accuracy: { score: 0.9, reasoning: 'The response is factually accurate' },
359
+ helpfulness: { score: 0.7, reasoning: 'The response provides helpful information' },
360
+ },
361
+ },
362
+ rawResponse: JSON.stringify({
363
+ evaluations: {
364
+ relevance: { score: 0.8, reasoning: 'The response is relevant to the question' },
365
+ accuracy: { score: 0.9, reasoning: 'The response is factually accurate' },
366
+ helpfulness: { score: 0.7, reasoning: 'The response provides helpful information' },
367
+ },
368
+ }),
369
+ metrics: {
370
+ success: true,
371
+ usage: { total: 100, input: 50, output: 50 },
372
+ },
373
+ };
374
+
375
+ mockTracker.trackMetricsOf.mockImplementation(async (extractor, func) => func());
376
+ mockProvider.invokeStructuredModel.mockResolvedValue(mockStructuredResponse);
377
+
378
+ const result = await judge.evaluateMessages(messages, response);
379
+
380
+ expect(result).toEqual({
381
+ evals: {
382
+ relevance: {
383
+ score: 0.8,
384
+ reasoning: 'The response is relevant to the question',
385
+ },
386
+ accuracy: {
387
+ score: 0.9,
388
+ reasoning: 'The response is factually accurate',
389
+ },
390
+ helpfulness: {
391
+ score: 0.7,
392
+ reasoning: 'The response provides helpful information',
393
+ },
394
+ },
395
+ success: true,
396
+ judgeConfigKey: 'test-judge',
397
+ });
398
+
399
+ expect(mockProvider.invokeStructuredModel).toHaveBeenCalledWith(
400
+ expect.arrayContaining([
401
+ expect.objectContaining({
402
+ role: 'system',
403
+ content: 'You are a helpful judge that evaluates AI responses.',
404
+ }),
405
+ expect.objectContaining({
406
+ role: 'user',
407
+ content:
408
+ 'Evaluate and report scores for important metrics: Input: What is the capital of France?\r\nParis is the capital of France., Output: Paris is the capital of France.',
409
+ }),
410
+ ]),
411
+ expect.any(Object), // evaluation response structure
412
+ );
413
+ });
414
+
415
+ it('handles sampling rate correctly', async () => {
416
+ const messages: LDMessage[] = [{ role: 'user', content: 'test' }];
417
+ const response = {
418
+ message: { role: 'assistant' as const, content: 'test response' },
419
+ metrics: { success: true },
420
+ };
421
+
422
+ // Mock Math.random to return 0.8 (should not be sampled with rate 0.5 since 0.8 > 0.5)
423
+ const originalRandom = Math.random;
424
+ Math.random = jest.fn().mockReturnValue(0.8);
425
+
426
+ const result = await judge.evaluateMessages(messages, response, 0.5);
427
+
428
+ expect(result).toBeUndefined();
429
+ expect(mockProvider.invokeStructuredModel).not.toHaveBeenCalled();
430
+
431
+ Math.random = originalRandom;
432
+ });
433
+ });
434
+
435
+ describe('_constructEvaluationMessages', () => {
436
+ let judge: Judge;
437
+
438
+ beforeEach(() => {
439
+ judge = new Judge(judgeConfig, mockTracker, mockProvider, mockLogger);
440
+ });
441
+
442
+ it('constructs evaluation messages correctly', () => {
443
+ // Access private method for testing
444
+ // eslint-disable-next-line no-underscore-dangle
445
+ const constructMessages = (judge as any)._constructEvaluationMessages.bind(judge);
446
+ const messages = constructMessages('test input', 'test output');
447
+
448
+ expect(messages).toHaveLength(2);
449
+ expect(messages[0]).toEqual({
450
+ role: 'system',
451
+ content: 'You are a helpful judge that evaluates AI responses.',
452
+ });
453
+ expect(messages[1]).toEqual({
454
+ role: 'user',
455
+ content:
456
+ 'Evaluate and report scores for important metrics: Input: test input, Output: test output',
457
+ });
458
+ });
459
+ });
460
+
461
+ describe('_parseEvaluationResponse', () => {
462
+ let judge: Judge;
463
+
464
+ beforeEach(() => {
465
+ judge = new Judge(judgeConfig, mockTracker, mockProvider, mockLogger);
466
+ });
467
+
468
+ it('parses valid evaluation response correctly', () => {
469
+ // eslint-disable-next-line no-underscore-dangle
470
+ const parseResponse = (judge as any)._parseEvaluationResponse.bind(judge);
471
+ const responseData = {
472
+ evaluations: {
473
+ relevance: { score: 0.8, reasoning: 'Good' },
474
+ accuracy: { score: 0.9, reasoning: 'Accurate' },
475
+ helpfulness: { score: 0.7, reasoning: 'Helpful' },
476
+ },
477
+ };
478
+
479
+ const result = parseResponse(responseData);
480
+
481
+ expect(result).toEqual({
482
+ relevance: { score: 0.8, reasoning: 'Good' },
483
+ accuracy: { score: 0.9, reasoning: 'Accurate' },
484
+ helpfulness: { score: 0.7, reasoning: 'Helpful' },
485
+ });
486
+ });
487
+
488
+ it('returns empty object for invalid response data', () => {
489
+ // eslint-disable-next-line no-underscore-dangle
490
+ const parseResponse = (judge as any)._parseEvaluationResponse.bind(judge);
491
+ const responseData = {
492
+ relevance: { score: 0.8, reasoning: 'Good' },
493
+ // Missing evaluations wrapper - invalid structure
494
+ };
495
+
496
+ const result = parseResponse(responseData);
497
+
498
+ // Returns empty object when evaluations structure is missing
499
+ expect(result).toEqual({});
500
+ });
501
+
502
+ it('handles missing score or reasoning fields', () => {
503
+ // eslint-disable-next-line no-underscore-dangle
504
+ const parseResponse = (judge as any)._parseEvaluationResponse.bind(judge);
505
+ const responseData = {
506
+ evaluations: {
507
+ relevance: { score: 0.8 }, // Missing reasoning
508
+ accuracy: { reasoning: 'Accurate' }, // Missing score
509
+ helpfulness: { score: 0.7, reasoning: 'Helpful' },
510
+ },
511
+ };
512
+
513
+ const result = parseResponse(responseData);
514
+
515
+ // Only helpfulness passes validation, relevance and accuracy are skipped
516
+ expect(result).toEqual({
517
+ helpfulness: { score: 0.7, reasoning: 'Helpful' },
518
+ });
519
+ });
520
+ });
521
+ });