@arizeai/phoenix-client 6.10.0 → 6.11.0

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 (244) hide show
  1. package/README.md +62 -0
  2. package/dist/esm/__generated__/api/v1.d.ts +452 -28
  3. package/dist/esm/__generated__/api/v1.d.ts.map +1 -1
  4. package/dist/esm/experiments/helpers/getExampleGlobalId.d.ts +8 -0
  5. package/dist/esm/experiments/helpers/getExampleGlobalId.d.ts.map +1 -0
  6. package/dist/esm/experiments/helpers/getExampleGlobalId.js +9 -0
  7. package/dist/esm/experiments/helpers/getExampleGlobalId.js.map +1 -0
  8. package/dist/esm/experiments/resumeEvaluation.d.ts.map +1 -1
  9. package/dist/esm/experiments/resumeEvaluation.js +2 -1
  10. package/dist/esm/experiments/resumeEvaluation.js.map +1 -1
  11. package/dist/esm/experiments/resumeExperiment.d.ts.map +1 -1
  12. package/dist/esm/experiments/resumeExperiment.js +3 -2
  13. package/dist/esm/experiments/resumeExperiment.js.map +1 -1
  14. package/dist/esm/experiments/runExperiment.d.ts.map +1 -1
  15. package/dist/esm/experiments/runExperiment.js +6 -3
  16. package/dist/esm/experiments/runExperiment.js.map +1 -1
  17. package/dist/esm/jest/index.d.ts +5 -0
  18. package/dist/esm/jest/index.d.ts.map +1 -0
  19. package/dist/esm/jest/index.js +49 -0
  20. package/dist/esm/jest/index.js.map +1 -0
  21. package/dist/esm/jest/reporter.d.ts +13 -0
  22. package/dist/esm/jest/reporter.d.ts.map +1 -0
  23. package/dist/esm/jest/reporter.js +19 -0
  24. package/dist/esm/jest/reporter.js.map +1 -0
  25. package/dist/esm/prompts/sdks/toAI.d.ts +2 -2
  26. package/dist/esm/prompts/sdks/toAI.d.ts.map +1 -1
  27. package/dist/esm/prompts/sdks/toAI.js.map +1 -1
  28. package/dist/esm/prompts/sdks/toAnthropic.d.ts +2 -2
  29. package/dist/esm/prompts/sdks/toAnthropic.d.ts.map +1 -1
  30. package/dist/esm/prompts/sdks/toAnthropic.js.map +1 -1
  31. package/dist/esm/prompts/sdks/toOpenAI.d.ts +2 -2
  32. package/dist/esm/prompts/sdks/toOpenAI.d.ts.map +1 -1
  33. package/dist/esm/prompts/sdks/toOpenAI.js.map +1 -1
  34. package/dist/esm/prompts/sdks/toSDK.d.ts +8 -8
  35. package/dist/esm/prompts/sdks/toSDK.d.ts.map +1 -1
  36. package/dist/esm/prompts/sdks/toSDK.js.map +1 -1
  37. package/dist/esm/prompts/sdks/types.d.ts +2 -2
  38. package/dist/esm/prompts/sdks/types.d.ts.map +1 -1
  39. package/dist/esm/schemas/llm/anthropic/converters.d.ts +8 -8
  40. package/dist/esm/schemas/llm/anthropic/messagePartSchemas.d.ts +4 -4
  41. package/dist/esm/schemas/llm/anthropic/messageSchemas.d.ts +6 -6
  42. package/dist/esm/schemas/llm/constants.d.ts +3 -3
  43. package/dist/esm/schemas/llm/converters.d.ts +12 -12
  44. package/dist/esm/schemas/llm/openai/converters.d.ts +3 -3
  45. package/dist/esm/schemas/llm/schemas.d.ts +2 -2
  46. package/dist/esm/testing/acceptance.d.ts +20 -0
  47. package/dist/esm/testing/acceptance.d.ts.map +1 -0
  48. package/dist/esm/testing/acceptance.js +129 -0
  49. package/dist/esm/testing/acceptance.js.map +1 -0
  50. package/dist/esm/testing/define-api.d.ts +157 -0
  51. package/dist/esm/testing/define-api.d.ts.map +1 -0
  52. package/dist/esm/testing/define-api.js +78 -0
  53. package/dist/esm/testing/define-api.js.map +1 -0
  54. package/dist/esm/testing/helpers.d.ts +55 -0
  55. package/dist/esm/testing/helpers.d.ts.map +1 -0
  56. package/dist/esm/testing/helpers.js +179 -0
  57. package/dist/esm/testing/helpers.js.map +1 -0
  58. package/dist/esm/testing/phoenix-test-tracking.d.ts +68 -0
  59. package/dist/esm/testing/phoenix-test-tracking.d.ts.map +1 -0
  60. package/dist/esm/testing/phoenix-test-tracking.js +521 -0
  61. package/dist/esm/testing/phoenix-test-tracking.js.map +1 -0
  62. package/dist/esm/testing/report-artifacts.d.ts +45 -0
  63. package/dist/esm/testing/report-artifacts.d.ts.map +1 -0
  64. package/dist/esm/testing/report-artifacts.js +218 -0
  65. package/dist/esm/testing/report-artifacts.js.map +1 -0
  66. package/dist/esm/testing/report-run.d.ts +22 -0
  67. package/dist/esm/testing/report-run.d.ts.map +1 -0
  68. package/dist/esm/testing/report-run.js +41 -0
  69. package/dist/esm/testing/report-run.js.map +1 -0
  70. package/dist/esm/testing/reporter-format.d.ts +83 -0
  71. package/dist/esm/testing/reporter-format.d.ts.map +1 -0
  72. package/dist/esm/testing/reporter-format.js +852 -0
  73. package/dist/esm/testing/reporter-format.js.map +1 -0
  74. package/dist/esm/testing/runner.d.ts +31 -0
  75. package/dist/esm/testing/runner.d.ts.map +1 -0
  76. package/dist/esm/testing/runner.js +238 -0
  77. package/dist/esm/testing/runner.js.map +1 -0
  78. package/dist/esm/testing/state.d.ts +138 -0
  79. package/dist/esm/testing/state.d.ts.map +1 -0
  80. package/dist/esm/testing/state.js +31 -0
  81. package/dist/esm/testing/state.js.map +1 -0
  82. package/dist/esm/testing/types.d.ts +319 -0
  83. package/dist/esm/testing/types.d.ts.map +1 -0
  84. package/dist/esm/testing/types.js +9 -0
  85. package/dist/esm/testing/types.js.map +1 -0
  86. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  87. package/dist/esm/utils/channel.d.ts +7 -7
  88. package/dist/esm/utils/channel.d.ts.map +1 -1
  89. package/dist/esm/utils/channel.js +1 -1
  90. package/dist/esm/utils/channel.js.map +1 -1
  91. package/dist/esm/utils/formatPromptMessages.d.ts.map +1 -1
  92. package/dist/esm/utils/getPromptBySelector.d.ts.map +1 -1
  93. package/dist/esm/utils/promisifyResult.d.ts +1 -1
  94. package/dist/esm/utils/promisifyResult.d.ts.map +1 -1
  95. package/dist/esm/utils/promisifyResult.js.map +1 -1
  96. package/dist/esm/utils/schemaMatches.d.ts +5 -5
  97. package/dist/esm/utils/schemaMatches.d.ts.map +1 -1
  98. package/dist/esm/utils/schemaMatches.js.map +1 -1
  99. package/dist/esm/vitest/index.d.ts +5 -0
  100. package/dist/esm/vitest/index.d.ts.map +1 -0
  101. package/dist/esm/vitest/index.js +15 -0
  102. package/dist/esm/vitest/index.js.map +1 -0
  103. package/dist/esm/vitest/reporter.d.ts +19 -0
  104. package/dist/esm/vitest/reporter.d.ts.map +1 -0
  105. package/dist/esm/vitest/reporter.js +27 -0
  106. package/dist/esm/vitest/reporter.js.map +1 -0
  107. package/dist/src/__generated__/api/v1.d.ts +452 -28
  108. package/dist/src/__generated__/api/v1.d.ts.map +1 -1
  109. package/dist/src/experiments/helpers/getExampleGlobalId.d.ts +8 -0
  110. package/dist/src/experiments/helpers/getExampleGlobalId.d.ts.map +1 -0
  111. package/dist/src/experiments/helpers/getExampleGlobalId.js +13 -0
  112. package/dist/src/experiments/helpers/getExampleGlobalId.js.map +1 -0
  113. package/dist/src/experiments/resumeEvaluation.d.ts.map +1 -1
  114. package/dist/src/experiments/resumeEvaluation.js +2 -1
  115. package/dist/src/experiments/resumeEvaluation.js.map +1 -1
  116. package/dist/src/experiments/resumeExperiment.d.ts.map +1 -1
  117. package/dist/src/experiments/resumeExperiment.js +3 -2
  118. package/dist/src/experiments/resumeExperiment.js.map +1 -1
  119. package/dist/src/experiments/runExperiment.d.ts.map +1 -1
  120. package/dist/src/experiments/runExperiment.js +6 -3
  121. package/dist/src/experiments/runExperiment.js.map +1 -1
  122. package/dist/src/jest/index.d.ts +5 -0
  123. package/dist/src/jest/index.d.ts.map +1 -0
  124. package/dist/src/jest/index.js +58 -0
  125. package/dist/src/jest/index.js.map +1 -0
  126. package/dist/src/jest/reporter.d.ts +13 -0
  127. package/dist/src/jest/reporter.d.ts.map +1 -0
  128. package/dist/src/jest/reporter.js +23 -0
  129. package/dist/src/jest/reporter.js.map +1 -0
  130. package/dist/src/prompts/sdks/toAI.d.ts +2 -2
  131. package/dist/src/prompts/sdks/toAI.d.ts.map +1 -1
  132. package/dist/src/prompts/sdks/toAI.js.map +1 -1
  133. package/dist/src/prompts/sdks/toAnthropic.d.ts +2 -2
  134. package/dist/src/prompts/sdks/toAnthropic.d.ts.map +1 -1
  135. package/dist/src/prompts/sdks/toAnthropic.js.map +1 -1
  136. package/dist/src/prompts/sdks/toOpenAI.d.ts +2 -2
  137. package/dist/src/prompts/sdks/toOpenAI.d.ts.map +1 -1
  138. package/dist/src/prompts/sdks/toOpenAI.js.map +1 -1
  139. package/dist/src/prompts/sdks/toSDK.d.ts +8 -8
  140. package/dist/src/prompts/sdks/toSDK.d.ts.map +1 -1
  141. package/dist/src/prompts/sdks/toSDK.js.map +1 -1
  142. package/dist/src/prompts/sdks/types.d.ts +2 -2
  143. package/dist/src/prompts/sdks/types.d.ts.map +1 -1
  144. package/dist/src/schemas/llm/anthropic/converters.d.ts +8 -8
  145. package/dist/src/schemas/llm/anthropic/messagePartSchemas.d.ts +4 -4
  146. package/dist/src/schemas/llm/anthropic/messageSchemas.d.ts +6 -6
  147. package/dist/src/schemas/llm/constants.d.ts +3 -3
  148. package/dist/src/schemas/llm/converters.d.ts +12 -12
  149. package/dist/src/schemas/llm/openai/converters.d.ts +3 -3
  150. package/dist/src/schemas/llm/schemas.d.ts +2 -2
  151. package/dist/src/testing/acceptance.d.ts +20 -0
  152. package/dist/src/testing/acceptance.d.ts.map +1 -0
  153. package/dist/src/testing/acceptance.js +114 -0
  154. package/dist/src/testing/acceptance.js.map +1 -0
  155. package/dist/src/testing/define-api.d.ts +157 -0
  156. package/dist/src/testing/define-api.d.ts.map +1 -0
  157. package/dist/src/testing/define-api.js +81 -0
  158. package/dist/src/testing/define-api.js.map +1 -0
  159. package/dist/src/testing/helpers.d.ts +55 -0
  160. package/dist/src/testing/helpers.d.ts.map +1 -0
  161. package/dist/src/testing/helpers.js +182 -0
  162. package/dist/src/testing/helpers.js.map +1 -0
  163. package/dist/src/testing/phoenix-test-tracking.d.ts +68 -0
  164. package/dist/src/testing/phoenix-test-tracking.d.ts.map +1 -0
  165. package/dist/src/testing/phoenix-test-tracking.js +530 -0
  166. package/dist/src/testing/phoenix-test-tracking.js.map +1 -0
  167. package/dist/src/testing/report-artifacts.d.ts +45 -0
  168. package/dist/src/testing/report-artifacts.d.ts.map +1 -0
  169. package/dist/src/testing/report-artifacts.js +225 -0
  170. package/dist/src/testing/report-artifacts.js.map +1 -0
  171. package/dist/src/testing/report-run.d.ts +22 -0
  172. package/dist/src/testing/report-run.d.ts.map +1 -0
  173. package/dist/src/testing/report-run.js +47 -0
  174. package/dist/src/testing/report-run.js.map +1 -0
  175. package/dist/src/testing/reporter-format.d.ts +83 -0
  176. package/dist/src/testing/reporter-format.d.ts.map +1 -0
  177. package/dist/src/testing/reporter-format.js +870 -0
  178. package/dist/src/testing/reporter-format.js.map +1 -0
  179. package/dist/src/testing/runner.d.ts +31 -0
  180. package/dist/src/testing/runner.d.ts.map +1 -0
  181. package/dist/src/testing/runner.js +258 -0
  182. package/dist/src/testing/runner.js.map +1 -0
  183. package/dist/src/testing/state.d.ts +138 -0
  184. package/dist/src/testing/state.d.ts.map +1 -0
  185. package/dist/src/testing/state.js +38 -0
  186. package/dist/src/testing/state.js.map +1 -0
  187. package/dist/src/testing/types.d.ts +319 -0
  188. package/dist/src/testing/types.d.ts.map +1 -0
  189. package/dist/src/testing/types.js +13 -0
  190. package/dist/src/testing/types.js.map +1 -0
  191. package/dist/src/utils/channel.d.ts +7 -7
  192. package/dist/src/utils/channel.d.ts.map +1 -1
  193. package/dist/src/utils/channel.js +1 -1
  194. package/dist/src/utils/channel.js.map +1 -1
  195. package/dist/src/utils/formatPromptMessages.d.ts.map +1 -1
  196. package/dist/src/utils/getPromptBySelector.d.ts.map +1 -1
  197. package/dist/src/utils/promisifyResult.d.ts +1 -1
  198. package/dist/src/utils/promisifyResult.d.ts.map +1 -1
  199. package/dist/src/utils/promisifyResult.js.map +1 -1
  200. package/dist/src/utils/schemaMatches.d.ts +5 -5
  201. package/dist/src/utils/schemaMatches.d.ts.map +1 -1
  202. package/dist/src/utils/schemaMatches.js.map +1 -1
  203. package/dist/src/vitest/index.d.ts +5 -0
  204. package/dist/src/vitest/index.d.ts.map +1 -0
  205. package/dist/src/vitest/index.js +23 -0
  206. package/dist/src/vitest/index.js.map +1 -0
  207. package/dist/src/vitest/reporter.d.ts +19 -0
  208. package/dist/src/vitest/reporter.d.ts.map +1 -0
  209. package/dist/src/vitest/reporter.js +34 -0
  210. package/dist/src/vitest/reporter.js.map +1 -0
  211. package/dist/tsconfig.tsbuildinfo +1 -1
  212. package/docs/ci-evals-annotations.mdx +190 -0
  213. package/docs/ci-evals-jest.mdx +78 -0
  214. package/docs/ci-evals-vitest.mdx +240 -0
  215. package/docs/ci-evals.mdx +263 -0
  216. package/docs/overview.mdx +9 -1
  217. package/package.json +49 -17
  218. package/src/__generated__/api/v1.ts +452 -28
  219. package/src/experiments/helpers/getExampleGlobalId.ts +12 -0
  220. package/src/experiments/resumeEvaluation.ts +2 -1
  221. package/src/experiments/resumeExperiment.ts +3 -2
  222. package/src/experiments/runExperiment.ts +6 -3
  223. package/src/jest/index.ts +124 -0
  224. package/src/jest/reporter.ts +22 -0
  225. package/src/prompts/sdks/toAI.ts +4 -3
  226. package/src/prompts/sdks/toAnthropic.ts +4 -3
  227. package/src/prompts/sdks/toOpenAI.ts +4 -3
  228. package/src/prompts/sdks/toSDK.ts +16 -11
  229. package/src/prompts/sdks/types.ts +2 -2
  230. package/src/testing/acceptance.ts +190 -0
  231. package/src/testing/define-api.ts +279 -0
  232. package/src/testing/helpers.ts +251 -0
  233. package/src/testing/phoenix-test-tracking.ts +637 -0
  234. package/src/testing/report-artifacts.ts +272 -0
  235. package/src/testing/report-run.ts +44 -0
  236. package/src/testing/reporter-format.ts +1072 -0
  237. package/src/testing/runner.ts +350 -0
  238. package/src/testing/state.ts +165 -0
  239. package/src/testing/types.ts +366 -0
  240. package/src/utils/channel.ts +17 -15
  241. package/src/utils/promisifyResult.ts +6 -4
  242. package/src/utils/schemaMatches.ts +12 -10
  243. package/src/vitest/index.ts +57 -0
  244. package/src/vitest/reporter.ts +32 -0
@@ -0,0 +1,12 @@
1
+ import type { ExampleWithId } from "../../types/datasets";
2
+
3
+ /**
4
+ * The example's node GlobalID, which experiment runs record as their
5
+ * `dataset_example_id`. Servers that predate the `nodeId` field deliver the
6
+ * GlobalID in the `id` field instead.
7
+ */
8
+ export function getExampleGlobalId(
9
+ example: Pick<ExampleWithId, "id"> & Partial<Pick<ExampleWithId, "nodeId">>
10
+ ): string {
11
+ return example.nodeId ?? example.id;
12
+ }
@@ -29,6 +29,7 @@ import { ensureString } from "../utils/ensureString";
29
29
  import { toObjectHeaders } from "../utils/toObjectHeaders";
30
30
  import { getExperimentInfo } from "./getExperimentInfo.js";
31
31
  import { getExperimentEvaluators } from "./helpers";
32
+ import { getExampleGlobalId } from "./helpers/getExampleGlobalId";
32
33
  import { logEvalResumeSummary, PROGRESS_PREFIX } from "./logging";
33
34
  import { cleanupOwnedTracerProvider } from "./tracing";
34
35
 
@@ -729,7 +730,7 @@ async function runSingleEvaluation({
729
730
  ...objectAsAttributes({
730
731
  experiment_id: experimentId,
731
732
  experiment_run_id: experimentRun.id,
732
- dataset_example_id: datasetExample.nodeId,
733
+ dataset_example_id: getExampleGlobalId(datasetExample),
733
734
  }),
734
735
  });
735
736
 
@@ -28,6 +28,7 @@ import { isHttpErrorWithStatus } from "../utils/isHttpError";
28
28
  import { toObjectHeaders } from "../utils/toObjectHeaders";
29
29
  import { getDatasetExperimentsUrl, getExperimentUrl } from "../utils/urlUtils";
30
30
  import { getExperimentInfo } from "./getExperimentInfo.js";
31
+ import { getExampleGlobalId } from "./helpers/getExampleGlobalId";
31
32
  import {
32
33
  logExperimentResumeSummary,
33
34
  logLinks,
@@ -619,7 +620,7 @@ async function recordTaskResult({
619
620
  },
620
621
  },
621
622
  body: {
622
- dataset_example_id: example.nodeId,
623
+ dataset_example_id: getExampleGlobalId(example),
623
624
  repetition_number: repetitionNumber,
624
625
  output: output as Record<string, unknown>,
625
626
  start_time: startTime.toISOString(),
@@ -695,7 +696,7 @@ async function runSingleTask({
695
696
  [SemanticConventions.INPUT_MIME_TYPE]: MimeType.JSON,
696
697
  ...objectAsAttributes({
697
698
  experiment_id: experimentId,
698
- dataset_example_id: example.nodeId,
699
+ dataset_example_id: getExampleGlobalId(example),
699
700
  repetition_number: repetitionNumber,
700
701
  }),
701
702
  });
@@ -47,6 +47,7 @@ import {
47
47
  } from "../utils/urlUtils";
48
48
  import { getExperimentInfo } from "./getExperimentInfo";
49
49
  import { getExperimentEvaluators } from "./helpers";
50
+ import { getExampleGlobalId } from "./helpers/getExampleGlobalId";
50
51
  import {
51
52
  logEvalSummary,
52
53
  logLinks,
@@ -485,7 +486,7 @@ function runTaskWithExamples({
485
486
  id: localId(), // initialized with local id, will be replaced with server-assigned id when dry run is false
486
487
  traceId,
487
488
  experimentId,
488
- datasetExampleId: example.id,
489
+ datasetExampleId: getExampleGlobalId(example),
489
490
  startTime: new Date(),
490
491
  endTime: new Date(), // will get replaced with actual end time
491
492
  output: null,
@@ -509,7 +510,7 @@ function runTaskWithExamples({
509
510
  },
510
511
  },
511
512
  body: {
512
- dataset_example_id: example.nodeId,
513
+ dataset_example_id: getExampleGlobalId(example),
513
514
  output: thisRun.output,
514
515
  repetition_number: repetitionNumber,
515
516
  start_time: thisRun.startTime.toISOString(),
@@ -681,9 +682,11 @@ export async function evaluateExperiment({
681
682
  type EvaluationId = string;
682
683
  const evaluationRuns: Record<EvaluationId, ExperimentEvaluationRun> = {};
683
684
 
685
+ // Index examples by node GlobalID, matching how runs record
686
+ // datasetExampleId.
684
687
  const examplesById: Record<string, Example> = {};
685
688
  for (const example of dataset.examples) {
686
- examplesById[example.id] = example;
689
+ examplesById[getExampleGlobalId(example)] = example;
687
690
  }
688
691
 
689
692
  const onEvaluationComplete = (run: ExperimentEvaluationRun) => {
@@ -0,0 +1,124 @@
1
+ import { createTestApi } from "../testing/define-api";
2
+ import type { RunnerHooks } from "../testing/runner";
3
+
4
+ export type {
5
+ PhoenixDescribe,
6
+ PhoenixTest,
7
+ PhoenixTestApi,
8
+ PhoenixTestEach,
9
+ } from "../testing/define-api";
10
+
11
+ export type {
12
+ AcceptanceCriterion,
13
+ AcceptanceMetric,
14
+ AcceptanceResult,
15
+ Annotation,
16
+ AnnotatorKind,
17
+ EvaluationParams,
18
+ EvaluationResult,
19
+ Evaluator,
20
+ EvaluatorResult,
21
+ KVMap,
22
+ ReferenceOutput,
23
+ SuiteConfig,
24
+ TestArgs,
25
+ TestConfig,
26
+ TestEachRow,
27
+ TestFn,
28
+ TestParams,
29
+ TestParamsBase,
30
+ } from "../testing/types";
31
+
32
+ export {
33
+ evaluate,
34
+ logAnnotation,
35
+ logOutput,
36
+ traceEvaluator,
37
+ } from "../testing/helpers";
38
+
39
+ /**
40
+ * Resolve jest globals lazily. Jest injects `describe`, `test`, `it`,
41
+ * `beforeAll`, `afterAll` onto the global object during test runs; we read
42
+ * them off `globalThis` so importing this module outside of a jest run
43
+ * doesn't error.
44
+ */
45
+ function getJestGlobals(): {
46
+ describe: JestDescribe;
47
+ test: JestTest;
48
+ beforeAll: (fn: () => unknown | Promise<unknown>) => void;
49
+ afterAll: (fn: () => unknown | Promise<unknown>) => void;
50
+ } {
51
+ const g = globalThis as unknown as {
52
+ describe?: JestDescribe;
53
+ test?: JestTest;
54
+ it?: JestTest;
55
+ beforeAll?: (fn: () => unknown | Promise<unknown>) => void;
56
+ afterAll?: (fn: () => unknown | Promise<unknown>) => void;
57
+ };
58
+ if (!g.describe || !g.beforeAll || !g.afterAll) {
59
+ throw new Error(
60
+ "@arizeai/phoenix-client/jest could not find Jest globals. Make sure this module " +
61
+ "is imported from inside a Jest test file."
62
+ );
63
+ }
64
+ const test = g.test ?? g.it;
65
+ if (!test) {
66
+ throw new Error(
67
+ "@arizeai/phoenix-client/jest could not find Jest's test/it."
68
+ );
69
+ }
70
+ return {
71
+ describe: g.describe,
72
+ test,
73
+ beforeAll: g.beforeAll,
74
+ afterAll: g.afterAll,
75
+ };
76
+ }
77
+
78
+ interface JestDescribe {
79
+ (name: string, fn: () => void): void;
80
+ only: (name: string, fn: () => void) => void;
81
+ skip: (name: string, fn: () => void) => void;
82
+ }
83
+
84
+ interface JestTest {
85
+ (
86
+ name: string,
87
+ fn: (...args: unknown[]) => unknown | Promise<unknown>,
88
+ timeout?: number
89
+ ): void;
90
+ only: (
91
+ name: string,
92
+ fn: (...args: unknown[]) => unknown | Promise<unknown>,
93
+ timeout?: number
94
+ ) => void;
95
+ skip: (
96
+ name: string,
97
+ fn: (...args: unknown[]) => unknown | Promise<unknown>,
98
+ timeout?: number
99
+ ) => void;
100
+ }
101
+
102
+ /**
103
+ * Build the runner hooks from the (lazily-resolved) jest globals. Memoized so
104
+ * repeated declarations don't re-read `globalThis` on every call; jest injects
105
+ * its globals once per worker before any test file is evaluated.
106
+ */
107
+ let cachedHooks: RunnerHooks | undefined;
108
+ function getHooks(): RunnerHooks {
109
+ if (cachedHooks) return cachedHooks;
110
+ const g = getJestGlobals();
111
+ cachedHooks = {
112
+ describe: (name, fn) => g.describe(name, fn),
113
+ describeOnly: (name, fn) => g.describe.only(name, fn),
114
+ describeSkip: (name, fn) => g.describe.skip(name, fn),
115
+ test: (name, fn, timeout) => g.test(name, fn, timeout),
116
+ testOnly: (name, fn, timeout) => g.test.only(name, fn, timeout),
117
+ testSkip: (name, fn, timeout) => g.test.skip(name, fn, timeout),
118
+ beforeAll: (fn) => g.beforeAll(fn),
119
+ afterAll: (fn) => g.afterAll(fn),
120
+ };
121
+ return cachedHooks;
122
+ }
123
+
124
+ export const { describe, test, it } = createTestApi(getHooks);
@@ -0,0 +1,22 @@
1
+ import { SuiteSummaryReportRun } from "../testing/report-run";
2
+
3
+ /**
4
+ * Jest reporter for `@arizeai/phoenix-client/jest`.
5
+ *
6
+ * Like the Vitest reporter, this does not replace Jest's default output. It
7
+ * appends a Phoenix summary block at the end of the run.
8
+ */
9
+ export default class PhoenixJestReporter {
10
+ private readonly report = new SuiteSummaryReportRun();
11
+
12
+ // jest passes globalConfig + reporterOptions; we ignore both
13
+ constructor(_globalConfig?: unknown, _reporterOptions?: unknown) {}
14
+
15
+ onRunStart(): void {
16
+ this.report.begin();
17
+ }
18
+
19
+ onRunComplete(): void {
20
+ this.report.finish();
21
+ }
22
+ }
@@ -40,7 +40,8 @@ The tool choice strategy. Default: 'auto'.
40
40
  toolChoice?: ToolChoice<ToolSet>;
41
41
  };
42
42
 
43
- export type ToAIParams<V extends Variables> = toSDKParamsBase<V>;
43
+ export type ToAIParams<PromptVariables extends Variables> =
44
+ toSDKParamsBase<PromptVariables>;
44
45
 
45
46
  /**
46
47
  * Converts a Phoenix prompt to Vercel AI sdk params.
@@ -48,10 +49,10 @@ export type ToAIParams<V extends Variables> = toSDKParamsBase<V>;
48
49
  * - note: To use response format, you must pass `prompt.response_format.json_schema.schema` to generateObject or streamObject
49
50
  * via `jsonSchema()`, through the `schema` argument.
50
51
  */
51
- export const toAI = <V extends Variables>({
52
+ export const toAI = <PromptVariables extends Variables>({
52
53
  prompt,
53
54
  variables,
54
- }: ToAIParams<V>): PartialAIParams | null => {
55
+ }: ToAIParams<PromptVariables>): PartialAIParams | null => {
55
56
  // eslint-disable-next-line no-console
56
57
  console.warn(
57
58
  "Prompt invocation parameters not currently supported in AI SDK, falling back to default invocation parameters"
@@ -14,15 +14,16 @@ import type { toSDKParamsBase, Variables } from "./types";
14
14
  // We must re-export these types so that they are included in the phoenix-client distribution
15
15
  export type { MessageCreateParams };
16
16
 
17
- export type ToAnthropicParams<V extends Variables> = toSDKParamsBase<V>;
17
+ export type ToAnthropicParams<PromptVariables extends Variables> =
18
+ toSDKParamsBase<PromptVariables>;
18
19
 
19
20
  /**
20
21
  * Convert a Phoenix prompt to Anthropic client sdk's message create parameters
21
22
  */
22
- export const toAnthropic = <V extends Variables = Variables>({
23
+ export const toAnthropic = <PromptVariables extends Variables = Variables>({
23
24
  prompt,
24
25
  variables,
25
- }: ToAnthropicParams<V>): MessageCreateParams | null => {
26
+ }: ToAnthropicParams<PromptVariables>): MessageCreateParams | null => {
26
27
  try {
27
28
  let invocationParameters: { max_tokens: number } | undefined;
28
29
  if (prompt.invocation_parameters.type === "anthropic") {
@@ -23,17 +23,18 @@ export type {
23
23
  ResponseFormatJSONSchema,
24
24
  };
25
25
 
26
- export type ToOpenAIParams<V extends Variables> = toSDKParamsBase<V>;
26
+ export type ToOpenAIParams<PromptVariables extends Variables> =
27
+ toSDKParamsBase<PromptVariables>;
27
28
 
28
29
  /**
29
30
  * Convert a Phoenix prompt to OpenAI client sdk's chat completion parameters
30
31
  *
31
32
  * @returns The converted chat completion parameters
32
33
  */
33
- export const toOpenAI = <V extends Variables = Variables>({
34
+ export const toOpenAI = <PromptVariables extends Variables = Variables>({
34
35
  prompt,
35
36
  variables,
36
- }: ToOpenAIParams<V>): ChatCompletionCreateParams | null => {
37
+ }: ToOpenAIParams<PromptVariables>): ChatCompletionCreateParams | null => {
37
38
  try {
38
39
  let invocationParameters: Partial<ChatCompletionCreateParams>;
39
40
  switch (prompt.invocation_parameters.type) {
@@ -15,10 +15,10 @@ import type { SupportedSDK, toSDKParamsBase, Variables } from "./types";
15
15
  * toOpenAI(params)
16
16
  * ```
17
17
  */
18
- export type SDKParams<T extends SupportedSDK> = Parameters<
19
- (typeof PROVIDER_TO_SDK)[T]
18
+ export type SDKParams<SDK extends SupportedSDK> = Parameters<
19
+ (typeof PROVIDER_TO_SDK)[SDK]
20
20
  >[number] extends toSDKParamsBase
21
- ? Parameters<(typeof PROVIDER_TO_SDK)[T]>[number]
21
+ ? Parameters<(typeof PROVIDER_TO_SDK)[SDK]>[number]
22
22
  : never;
23
23
 
24
24
  /**
@@ -33,7 +33,7 @@ export const PROVIDER_TO_SDK = {
33
33
  /**
34
34
  * Get the conversion function for a specific SDK name
35
35
  */
36
- const getTargetSDK = <T extends SupportedSDK>(sdk: T) => {
36
+ const getTargetSDK = <SDK extends SupportedSDK>(sdk: SDK) => {
37
37
  switch (sdk) {
38
38
  case "openai":
39
39
  return PROVIDER_TO_SDK.openai;
@@ -50,19 +50,19 @@ const getTargetSDK = <T extends SupportedSDK>(sdk: T) => {
50
50
  * Parameters specific to the toSDK function
51
51
  */
52
52
  export type ToSDKParams<
53
- T extends SupportedSDK,
54
- V extends Variables = Variables,
53
+ SDK extends SupportedSDK,
54
+ PromptVariables extends Variables = Variables,
55
55
  > = {
56
56
  /**
57
57
  * String representing the SDK to convert to
58
58
  */
59
- sdk: T;
59
+ sdk: SDK;
60
60
  /**
61
61
  * Optional variables to format the prompt with
62
62
  * Keys are the variable names, values are the variable values
63
63
  * The variable format is determined via prompt.template_format
64
64
  */
65
- variables?: V;
65
+ variables?: PromptVariables;
66
66
  };
67
67
 
68
68
  /**
@@ -86,11 +86,16 @@ export type ToSDKParams<
86
86
  * @param params - The parameters to convert a prompt to an SDK's parameters
87
87
  * @returns The SDK's parameters
88
88
  */
89
- export const toSDK = <T extends SupportedSDK, V extends Variables = Variables>({
89
+ export const toSDK = <
90
+ SDK extends SupportedSDK,
91
+ PromptVariables extends Variables = Variables,
92
+ >({
90
93
  sdk: _sdk,
91
94
  ...rest
92
- }: ToSDKParams<T, V> & SDKParams<T>) => {
95
+ }: ToSDKParams<SDK, PromptVariables> & SDKParams<SDK>) => {
93
96
  const sdk = getTargetSDK(_sdk);
94
97
  invariant(sdk, `No SDK found for provider ${_sdk}`);
95
- return sdk<V>(rest) as ReturnType<(typeof PROVIDER_TO_SDK)[T]>;
98
+ return sdk<PromptVariables>(rest) as ReturnType<
99
+ (typeof PROVIDER_TO_SDK)[SDK]
100
+ >;
96
101
  };
@@ -11,7 +11,7 @@ export type Variables = Record<string, string | { toString: () => string }>;
11
11
  /**
12
12
  * Base parameters for an SDK conversion function
13
13
  */
14
- export type toSDKParamsBase<V extends Variables = Variables> = {
14
+ export type toSDKParamsBase<PromptVariables extends Variables = Variables> = {
15
15
  /**
16
16
  * The Phoenix prompt to convert
17
17
  */
@@ -19,7 +19,7 @@ export type toSDKParamsBase<V extends Variables = Variables> = {
19
19
  /**
20
20
  * The variables to use in the prompt
21
21
  */
22
- variables?: V;
22
+ variables?: PromptVariables;
23
23
  };
24
24
 
25
25
  /**
@@ -0,0 +1,190 @@
1
+ import type { TestResult } from "./state";
2
+ import type {
3
+ AcceptanceCriterion,
4
+ AcceptanceResult,
5
+ Annotation,
6
+ OptimizationDirection,
7
+ } from "./types";
8
+
9
+ /**
10
+ * Evaluate all configured aggregate acceptance rules against completed runs.
11
+ * @param params - Evaluation parameters.
12
+ * @param params.criteria - Aggregate rules configured on the suite.
13
+ * @param params.results - Completed test results in the suite.
14
+ */
15
+ export function evaluateAcceptanceCriteria({
16
+ criteria,
17
+ results,
18
+ }: {
19
+ criteria: readonly AcceptanceCriterion[] | undefined;
20
+ results: readonly TestResult[];
21
+ }): AcceptanceResult[] {
22
+ if (!criteria || criteria.length === 0) {
23
+ return [];
24
+ }
25
+ return criteria.map((criterion) =>
26
+ evaluateAcceptanceCriterion({ criterion, results })
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Build one error containing every failed aggregate criterion.
32
+ * @param results - Computed acceptance results.
33
+ */
34
+ export function createAcceptanceFailureError(
35
+ results: readonly AcceptanceResult[]
36
+ ): Error | undefined {
37
+ const failedResults = results.filter((result) => !result.passed);
38
+ if (failedResults.length === 0) {
39
+ return undefined;
40
+ }
41
+ return new Error(
42
+ [
43
+ "Acceptance criteria failed:",
44
+ ...failedResults.map((result) => ` ${formatAcceptanceResult(result)}`),
45
+ ].join("\n")
46
+ );
47
+ }
48
+
49
+ /** Format an acceptance result for reporters and thrown errors. */
50
+ export function formatAcceptanceResult(result: AcceptanceResult): string {
51
+ const status = result.passed ? "PASS" : "FAIL";
52
+ const value = result.value === null ? "n/a" : result.value.toFixed(3);
53
+ const sampleLabel = result.sampleCount === 1 ? "sample" : "samples";
54
+ let requirement: string;
55
+ if (result.metric === "average") {
56
+ const cmp = (result.direction ?? "maximize") === "minimize" ? "<=" : ">=";
57
+ requirement = `mean ${cmp} ${result.threshold.toFixed(3)}`;
58
+ } else {
59
+ requirement = `pass rate >= ${result.minPassRate.toFixed(3)}`;
60
+ }
61
+ const reason = result.failureReason ? ` - ${result.failureReason}` : "";
62
+ return `${status} ${result.annotationName} ${result.metric} ${value} (need ${requirement}; ${result.sampleCount} ${sampleLabel})${reason}`;
63
+ }
64
+
65
+ function evaluateAcceptanceCriterion({
66
+ criterion,
67
+ results,
68
+ }: {
69
+ criterion: AcceptanceCriterion;
70
+ results: readonly TestResult[];
71
+ }): AcceptanceResult {
72
+ const annotations = collectAnnotations({ criterion, results });
73
+
74
+ if (criterion.metric === "average") {
75
+ // Only numeric / boolean scores can be averaged.
76
+ const scores = annotations
77
+ .map((annotation) => annotation.score)
78
+ .filter(isValidScore);
79
+ if (scores.length === 0) {
80
+ return {
81
+ ...criterion,
82
+ value: null,
83
+ sampleCount: 0,
84
+ passed: false,
85
+ failureReason: "no numeric or boolean scores found",
86
+ };
87
+ }
88
+ const direction = criterion.direction ?? "maximize";
89
+ const value = calculateAverage(scores);
90
+ return {
91
+ ...criterion,
92
+ value,
93
+ sampleCount: scores.length,
94
+ passed: meetsBar(value, criterion.threshold, direction),
95
+ };
96
+ }
97
+
98
+ // passRate: each run passes when `passFn` returns true for its annotation;
99
+ // the suite passes when the fraction of passing runs is at least
100
+ // `minPassRate`. The reported value is that fraction.
101
+ if (annotations.length === 0) {
102
+ return {
103
+ ...criterion,
104
+ value: null,
105
+ sampleCount: 0,
106
+ passed: false,
107
+ failureReason: "no matching annotations found",
108
+ };
109
+ }
110
+ const passed = annotations.filter((annotation) =>
111
+ criterion.passFn(annotation)
112
+ ).length;
113
+ const value = passed / annotations.length;
114
+ return {
115
+ ...criterion,
116
+ value,
117
+ sampleCount: annotations.length,
118
+ passed: value >= criterion.minPassRate,
119
+ };
120
+ }
121
+
122
+ /** Whether `value` clears `bar` in the given optimization direction. */
123
+ function meetsBar(
124
+ value: number,
125
+ bar: number,
126
+ direction: OptimizationDirection
127
+ ): boolean {
128
+ return direction === "minimize" ? value <= bar : value >= bar;
129
+ }
130
+
131
+ /**
132
+ * The last annotation matching `annotationName` from each non-skipped run that
133
+ * logged it. One entry per run; runs that never logged the annotation are
134
+ * omitted.
135
+ */
136
+ function collectAnnotations({
137
+ criterion,
138
+ results,
139
+ }: {
140
+ criterion: AcceptanceCriterion;
141
+ results: readonly TestResult[];
142
+ }): Annotation[] {
143
+ return results
144
+ .filter((result) => result.status !== "skipped")
145
+ .map((result) =>
146
+ findLastAnnotation({
147
+ annotations: result.annotations,
148
+ annotationName: criterion.annotationName,
149
+ })
150
+ )
151
+ .filter((annotation): annotation is Annotation => annotation !== undefined);
152
+ }
153
+
154
+ function findLastAnnotation({
155
+ annotations,
156
+ annotationName,
157
+ }: {
158
+ annotations: readonly Annotation[];
159
+ annotationName: string;
160
+ }): Annotation | undefined {
161
+ for (
162
+ let annotationIndex = annotations.length - 1;
163
+ annotationIndex >= 0;
164
+ annotationIndex--
165
+ ) {
166
+ const annotation = annotations[annotationIndex];
167
+ if (annotation?.name === annotationName) {
168
+ return annotation;
169
+ }
170
+ }
171
+ return undefined;
172
+ }
173
+
174
+ function isValidScore(score: Annotation["score"]): score is number | boolean {
175
+ return (
176
+ typeof score === "boolean" ||
177
+ (typeof score === "number" && Number.isFinite(score))
178
+ );
179
+ }
180
+
181
+ function calculateAverage(scores: readonly (number | boolean)[]): number {
182
+ const total = scores
183
+ .map(scoreToNumber)
184
+ .reduce((sum, score) => sum + score, 0);
185
+ return total / scores.length;
186
+ }
187
+
188
+ function scoreToNumber(score: number | boolean): number {
189
+ return typeof score === "boolean" ? (score ? 1 : 0) : score;
190
+ }