@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.
- package/README.md +62 -0
- package/dist/esm/__generated__/api/v1.d.ts +452 -28
- package/dist/esm/__generated__/api/v1.d.ts.map +1 -1
- package/dist/esm/experiments/helpers/getExampleGlobalId.d.ts +8 -0
- package/dist/esm/experiments/helpers/getExampleGlobalId.d.ts.map +1 -0
- package/dist/esm/experiments/helpers/getExampleGlobalId.js +9 -0
- package/dist/esm/experiments/helpers/getExampleGlobalId.js.map +1 -0
- package/dist/esm/experiments/resumeEvaluation.d.ts.map +1 -1
- package/dist/esm/experiments/resumeEvaluation.js +2 -1
- package/dist/esm/experiments/resumeEvaluation.js.map +1 -1
- package/dist/esm/experiments/resumeExperiment.d.ts.map +1 -1
- package/dist/esm/experiments/resumeExperiment.js +3 -2
- package/dist/esm/experiments/resumeExperiment.js.map +1 -1
- package/dist/esm/experiments/runExperiment.d.ts.map +1 -1
- package/dist/esm/experiments/runExperiment.js +6 -3
- package/dist/esm/experiments/runExperiment.js.map +1 -1
- package/dist/esm/jest/index.d.ts +5 -0
- package/dist/esm/jest/index.d.ts.map +1 -0
- package/dist/esm/jest/index.js +49 -0
- package/dist/esm/jest/index.js.map +1 -0
- package/dist/esm/jest/reporter.d.ts +13 -0
- package/dist/esm/jest/reporter.d.ts.map +1 -0
- package/dist/esm/jest/reporter.js +19 -0
- package/dist/esm/jest/reporter.js.map +1 -0
- package/dist/esm/prompts/sdks/toAI.d.ts +2 -2
- package/dist/esm/prompts/sdks/toAI.d.ts.map +1 -1
- package/dist/esm/prompts/sdks/toAI.js.map +1 -1
- package/dist/esm/prompts/sdks/toAnthropic.d.ts +2 -2
- package/dist/esm/prompts/sdks/toAnthropic.d.ts.map +1 -1
- package/dist/esm/prompts/sdks/toAnthropic.js.map +1 -1
- package/dist/esm/prompts/sdks/toOpenAI.d.ts +2 -2
- package/dist/esm/prompts/sdks/toOpenAI.d.ts.map +1 -1
- package/dist/esm/prompts/sdks/toOpenAI.js.map +1 -1
- package/dist/esm/prompts/sdks/toSDK.d.ts +8 -8
- package/dist/esm/prompts/sdks/toSDK.d.ts.map +1 -1
- package/dist/esm/prompts/sdks/toSDK.js.map +1 -1
- package/dist/esm/prompts/sdks/types.d.ts +2 -2
- package/dist/esm/prompts/sdks/types.d.ts.map +1 -1
- package/dist/esm/schemas/llm/anthropic/converters.d.ts +8 -8
- package/dist/esm/schemas/llm/anthropic/messagePartSchemas.d.ts +4 -4
- package/dist/esm/schemas/llm/anthropic/messageSchemas.d.ts +6 -6
- package/dist/esm/schemas/llm/constants.d.ts +3 -3
- package/dist/esm/schemas/llm/converters.d.ts +12 -12
- package/dist/esm/schemas/llm/openai/converters.d.ts +3 -3
- package/dist/esm/schemas/llm/schemas.d.ts +2 -2
- package/dist/esm/testing/acceptance.d.ts +20 -0
- package/dist/esm/testing/acceptance.d.ts.map +1 -0
- package/dist/esm/testing/acceptance.js +129 -0
- package/dist/esm/testing/acceptance.js.map +1 -0
- package/dist/esm/testing/define-api.d.ts +157 -0
- package/dist/esm/testing/define-api.d.ts.map +1 -0
- package/dist/esm/testing/define-api.js +78 -0
- package/dist/esm/testing/define-api.js.map +1 -0
- package/dist/esm/testing/helpers.d.ts +55 -0
- package/dist/esm/testing/helpers.d.ts.map +1 -0
- package/dist/esm/testing/helpers.js +179 -0
- package/dist/esm/testing/helpers.js.map +1 -0
- package/dist/esm/testing/phoenix-test-tracking.d.ts +68 -0
- package/dist/esm/testing/phoenix-test-tracking.d.ts.map +1 -0
- package/dist/esm/testing/phoenix-test-tracking.js +521 -0
- package/dist/esm/testing/phoenix-test-tracking.js.map +1 -0
- package/dist/esm/testing/report-artifacts.d.ts +45 -0
- package/dist/esm/testing/report-artifacts.d.ts.map +1 -0
- package/dist/esm/testing/report-artifacts.js +218 -0
- package/dist/esm/testing/report-artifacts.js.map +1 -0
- package/dist/esm/testing/report-run.d.ts +22 -0
- package/dist/esm/testing/report-run.d.ts.map +1 -0
- package/dist/esm/testing/report-run.js +41 -0
- package/dist/esm/testing/report-run.js.map +1 -0
- package/dist/esm/testing/reporter-format.d.ts +83 -0
- package/dist/esm/testing/reporter-format.d.ts.map +1 -0
- package/dist/esm/testing/reporter-format.js +852 -0
- package/dist/esm/testing/reporter-format.js.map +1 -0
- package/dist/esm/testing/runner.d.ts +31 -0
- package/dist/esm/testing/runner.d.ts.map +1 -0
- package/dist/esm/testing/runner.js +238 -0
- package/dist/esm/testing/runner.js.map +1 -0
- package/dist/esm/testing/state.d.ts +138 -0
- package/dist/esm/testing/state.d.ts.map +1 -0
- package/dist/esm/testing/state.js +31 -0
- package/dist/esm/testing/state.js.map +1 -0
- package/dist/esm/testing/types.d.ts +319 -0
- package/dist/esm/testing/types.d.ts.map +1 -0
- package/dist/esm/testing/types.js +9 -0
- package/dist/esm/testing/types.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/utils/channel.d.ts +7 -7
- package/dist/esm/utils/channel.d.ts.map +1 -1
- package/dist/esm/utils/channel.js +1 -1
- package/dist/esm/utils/channel.js.map +1 -1
- package/dist/esm/utils/formatPromptMessages.d.ts.map +1 -1
- package/dist/esm/utils/getPromptBySelector.d.ts.map +1 -1
- package/dist/esm/utils/promisifyResult.d.ts +1 -1
- package/dist/esm/utils/promisifyResult.d.ts.map +1 -1
- package/dist/esm/utils/promisifyResult.js.map +1 -1
- package/dist/esm/utils/schemaMatches.d.ts +5 -5
- package/dist/esm/utils/schemaMatches.d.ts.map +1 -1
- package/dist/esm/utils/schemaMatches.js.map +1 -1
- package/dist/esm/vitest/index.d.ts +5 -0
- package/dist/esm/vitest/index.d.ts.map +1 -0
- package/dist/esm/vitest/index.js +15 -0
- package/dist/esm/vitest/index.js.map +1 -0
- package/dist/esm/vitest/reporter.d.ts +19 -0
- package/dist/esm/vitest/reporter.d.ts.map +1 -0
- package/dist/esm/vitest/reporter.js +27 -0
- package/dist/esm/vitest/reporter.js.map +1 -0
- package/dist/src/__generated__/api/v1.d.ts +452 -28
- package/dist/src/__generated__/api/v1.d.ts.map +1 -1
- package/dist/src/experiments/helpers/getExampleGlobalId.d.ts +8 -0
- package/dist/src/experiments/helpers/getExampleGlobalId.d.ts.map +1 -0
- package/dist/src/experiments/helpers/getExampleGlobalId.js +13 -0
- package/dist/src/experiments/helpers/getExampleGlobalId.js.map +1 -0
- package/dist/src/experiments/resumeEvaluation.d.ts.map +1 -1
- package/dist/src/experiments/resumeEvaluation.js +2 -1
- package/dist/src/experiments/resumeEvaluation.js.map +1 -1
- package/dist/src/experiments/resumeExperiment.d.ts.map +1 -1
- package/dist/src/experiments/resumeExperiment.js +3 -2
- package/dist/src/experiments/resumeExperiment.js.map +1 -1
- package/dist/src/experiments/runExperiment.d.ts.map +1 -1
- package/dist/src/experiments/runExperiment.js +6 -3
- package/dist/src/experiments/runExperiment.js.map +1 -1
- package/dist/src/jest/index.d.ts +5 -0
- package/dist/src/jest/index.d.ts.map +1 -0
- package/dist/src/jest/index.js +58 -0
- package/dist/src/jest/index.js.map +1 -0
- package/dist/src/jest/reporter.d.ts +13 -0
- package/dist/src/jest/reporter.d.ts.map +1 -0
- package/dist/src/jest/reporter.js +23 -0
- package/dist/src/jest/reporter.js.map +1 -0
- package/dist/src/prompts/sdks/toAI.d.ts +2 -2
- package/dist/src/prompts/sdks/toAI.d.ts.map +1 -1
- package/dist/src/prompts/sdks/toAI.js.map +1 -1
- package/dist/src/prompts/sdks/toAnthropic.d.ts +2 -2
- package/dist/src/prompts/sdks/toAnthropic.d.ts.map +1 -1
- package/dist/src/prompts/sdks/toAnthropic.js.map +1 -1
- package/dist/src/prompts/sdks/toOpenAI.d.ts +2 -2
- package/dist/src/prompts/sdks/toOpenAI.d.ts.map +1 -1
- package/dist/src/prompts/sdks/toOpenAI.js.map +1 -1
- package/dist/src/prompts/sdks/toSDK.d.ts +8 -8
- package/dist/src/prompts/sdks/toSDK.d.ts.map +1 -1
- package/dist/src/prompts/sdks/toSDK.js.map +1 -1
- package/dist/src/prompts/sdks/types.d.ts +2 -2
- package/dist/src/prompts/sdks/types.d.ts.map +1 -1
- package/dist/src/schemas/llm/anthropic/converters.d.ts +8 -8
- package/dist/src/schemas/llm/anthropic/messagePartSchemas.d.ts +4 -4
- package/dist/src/schemas/llm/anthropic/messageSchemas.d.ts +6 -6
- package/dist/src/schemas/llm/constants.d.ts +3 -3
- package/dist/src/schemas/llm/converters.d.ts +12 -12
- package/dist/src/schemas/llm/openai/converters.d.ts +3 -3
- package/dist/src/schemas/llm/schemas.d.ts +2 -2
- package/dist/src/testing/acceptance.d.ts +20 -0
- package/dist/src/testing/acceptance.d.ts.map +1 -0
- package/dist/src/testing/acceptance.js +114 -0
- package/dist/src/testing/acceptance.js.map +1 -0
- package/dist/src/testing/define-api.d.ts +157 -0
- package/dist/src/testing/define-api.d.ts.map +1 -0
- package/dist/src/testing/define-api.js +81 -0
- package/dist/src/testing/define-api.js.map +1 -0
- package/dist/src/testing/helpers.d.ts +55 -0
- package/dist/src/testing/helpers.d.ts.map +1 -0
- package/dist/src/testing/helpers.js +182 -0
- package/dist/src/testing/helpers.js.map +1 -0
- package/dist/src/testing/phoenix-test-tracking.d.ts +68 -0
- package/dist/src/testing/phoenix-test-tracking.d.ts.map +1 -0
- package/dist/src/testing/phoenix-test-tracking.js +530 -0
- package/dist/src/testing/phoenix-test-tracking.js.map +1 -0
- package/dist/src/testing/report-artifacts.d.ts +45 -0
- package/dist/src/testing/report-artifacts.d.ts.map +1 -0
- package/dist/src/testing/report-artifacts.js +225 -0
- package/dist/src/testing/report-artifacts.js.map +1 -0
- package/dist/src/testing/report-run.d.ts +22 -0
- package/dist/src/testing/report-run.d.ts.map +1 -0
- package/dist/src/testing/report-run.js +47 -0
- package/dist/src/testing/report-run.js.map +1 -0
- package/dist/src/testing/reporter-format.d.ts +83 -0
- package/dist/src/testing/reporter-format.d.ts.map +1 -0
- package/dist/src/testing/reporter-format.js +870 -0
- package/dist/src/testing/reporter-format.js.map +1 -0
- package/dist/src/testing/runner.d.ts +31 -0
- package/dist/src/testing/runner.d.ts.map +1 -0
- package/dist/src/testing/runner.js +258 -0
- package/dist/src/testing/runner.js.map +1 -0
- package/dist/src/testing/state.d.ts +138 -0
- package/dist/src/testing/state.d.ts.map +1 -0
- package/dist/src/testing/state.js +38 -0
- package/dist/src/testing/state.js.map +1 -0
- package/dist/src/testing/types.d.ts +319 -0
- package/dist/src/testing/types.d.ts.map +1 -0
- package/dist/src/testing/types.js +13 -0
- package/dist/src/testing/types.js.map +1 -0
- package/dist/src/utils/channel.d.ts +7 -7
- package/dist/src/utils/channel.d.ts.map +1 -1
- package/dist/src/utils/channel.js +1 -1
- package/dist/src/utils/channel.js.map +1 -1
- package/dist/src/utils/formatPromptMessages.d.ts.map +1 -1
- package/dist/src/utils/getPromptBySelector.d.ts.map +1 -1
- package/dist/src/utils/promisifyResult.d.ts +1 -1
- package/dist/src/utils/promisifyResult.d.ts.map +1 -1
- package/dist/src/utils/promisifyResult.js.map +1 -1
- package/dist/src/utils/schemaMatches.d.ts +5 -5
- package/dist/src/utils/schemaMatches.d.ts.map +1 -1
- package/dist/src/utils/schemaMatches.js.map +1 -1
- package/dist/src/vitest/index.d.ts +5 -0
- package/dist/src/vitest/index.d.ts.map +1 -0
- package/dist/src/vitest/index.js +23 -0
- package/dist/src/vitest/index.js.map +1 -0
- package/dist/src/vitest/reporter.d.ts +19 -0
- package/dist/src/vitest/reporter.d.ts.map +1 -0
- package/dist/src/vitest/reporter.js +34 -0
- package/dist/src/vitest/reporter.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/ci-evals-annotations.mdx +190 -0
- package/docs/ci-evals-jest.mdx +78 -0
- package/docs/ci-evals-vitest.mdx +240 -0
- package/docs/ci-evals.mdx +263 -0
- package/docs/overview.mdx +9 -1
- package/package.json +49 -17
- package/src/__generated__/api/v1.ts +452 -28
- package/src/experiments/helpers/getExampleGlobalId.ts +12 -0
- package/src/experiments/resumeEvaluation.ts +2 -1
- package/src/experiments/resumeExperiment.ts +3 -2
- package/src/experiments/runExperiment.ts +6 -3
- package/src/jest/index.ts +124 -0
- package/src/jest/reporter.ts +22 -0
- package/src/prompts/sdks/toAI.ts +4 -3
- package/src/prompts/sdks/toAnthropic.ts +4 -3
- package/src/prompts/sdks/toOpenAI.ts +4 -3
- package/src/prompts/sdks/toSDK.ts +16 -11
- package/src/prompts/sdks/types.ts +2 -2
- package/src/testing/acceptance.ts +190 -0
- package/src/testing/define-api.ts +279 -0
- package/src/testing/helpers.ts +251 -0
- package/src/testing/phoenix-test-tracking.ts +637 -0
- package/src/testing/report-artifacts.ts +272 -0
- package/src/testing/report-run.ts +44 -0
- package/src/testing/reporter-format.ts +1072 -0
- package/src/testing/runner.ts +350 -0
- package/src/testing/state.ts +165 -0
- package/src/testing/types.ts +366 -0
- package/src/utils/channel.ts +17 -15
- package/src/utils/promisifyResult.ts +6 -4
- package/src/utils/schemaMatches.ts +12 -10
- package/src/vitest/index.ts +57 -0
- package/src/vitest/reporter.ts +32 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAcceptanceFailureError,
|
|
3
|
+
evaluateAcceptanceCriteria,
|
|
4
|
+
} from "./acceptance";
|
|
5
|
+
import { flushAnnotations } from "./helpers";
|
|
6
|
+
import {
|
|
7
|
+
initializeSuite,
|
|
8
|
+
postExperimentRun,
|
|
9
|
+
resolveRepetitions,
|
|
10
|
+
runTaskWithTracing,
|
|
11
|
+
teardownSuite,
|
|
12
|
+
} from "./phoenix-test-tracking";
|
|
13
|
+
import { writeSuiteSummaryArtifact } from "./report-artifacts";
|
|
14
|
+
import {
|
|
15
|
+
currentSuite,
|
|
16
|
+
popSuite,
|
|
17
|
+
pushSuite,
|
|
18
|
+
runStorage,
|
|
19
|
+
type RunState,
|
|
20
|
+
type SuiteState,
|
|
21
|
+
type TestResult,
|
|
22
|
+
} from "./state";
|
|
23
|
+
import {
|
|
24
|
+
type KVMap,
|
|
25
|
+
resolveReference,
|
|
26
|
+
type SuiteConfig,
|
|
27
|
+
type TestArgs,
|
|
28
|
+
type TestFn,
|
|
29
|
+
type TestParams,
|
|
30
|
+
} from "./types";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The minimum slice of a test runner (vitest or jest) the runner needs in
|
|
34
|
+
* order to declare suites, tests, and lifecycle hooks.
|
|
35
|
+
*/
|
|
36
|
+
export interface RunnerHooks {
|
|
37
|
+
describe: (name: string, fn: () => void) => void;
|
|
38
|
+
describeOnly?: (name: string, fn: () => void) => void;
|
|
39
|
+
describeSkip?: (name: string, fn: () => void) => void;
|
|
40
|
+
test: (
|
|
41
|
+
name: string,
|
|
42
|
+
fn: (...args: unknown[]) => unknown | Promise<unknown>,
|
|
43
|
+
timeout?: number
|
|
44
|
+
) => void;
|
|
45
|
+
testOnly?: (
|
|
46
|
+
name: string,
|
|
47
|
+
fn: (...args: unknown[]) => unknown | Promise<unknown>,
|
|
48
|
+
timeout?: number
|
|
49
|
+
) => void;
|
|
50
|
+
testSkip?: (
|
|
51
|
+
name: string,
|
|
52
|
+
fn: (...args: unknown[]) => unknown | Promise<unknown>,
|
|
53
|
+
timeout?: number
|
|
54
|
+
) => void;
|
|
55
|
+
beforeAll: (fn: () => unknown | Promise<unknown>) => void;
|
|
56
|
+
afterAll: (fn: () => unknown | Promise<unknown>) => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Variants exposed by `test` (and `.skip`/`.only`). */
|
|
60
|
+
type TestVariant = "default" | "only" | "skip";
|
|
61
|
+
|
|
62
|
+
/** Globally accessible registry of all suites we created in this process. */
|
|
63
|
+
const allSuites: SuiteState[] = [];
|
|
64
|
+
|
|
65
|
+
/** Drive a `describe(name, fn, config?)` invocation. */
|
|
66
|
+
export function declareDescribe(
|
|
67
|
+
hooks: RunnerHooks,
|
|
68
|
+
name: string,
|
|
69
|
+
fn: () => void,
|
|
70
|
+
config: SuiteConfig = {},
|
|
71
|
+
variant: TestVariant = "default"
|
|
72
|
+
): void {
|
|
73
|
+
const describeFn =
|
|
74
|
+
variant === "only"
|
|
75
|
+
? (hooks.describeOnly ?? hooks.describe)
|
|
76
|
+
: variant === "skip"
|
|
77
|
+
? (hooks.describeSkip ?? hooks.describe)
|
|
78
|
+
: hooks.describe;
|
|
79
|
+
|
|
80
|
+
describeFn(name, () => {
|
|
81
|
+
const suite: SuiteState = {
|
|
82
|
+
name,
|
|
83
|
+
config,
|
|
84
|
+
registeredExamples: new Map(),
|
|
85
|
+
exampleIdsByTest: new Map(),
|
|
86
|
+
trackingDisabled: false,
|
|
87
|
+
results: [],
|
|
88
|
+
links: [],
|
|
89
|
+
};
|
|
90
|
+
pushSuite(suite);
|
|
91
|
+
try {
|
|
92
|
+
fn();
|
|
93
|
+
} finally {
|
|
94
|
+
popSuite();
|
|
95
|
+
}
|
|
96
|
+
allSuites.push(suite);
|
|
97
|
+
|
|
98
|
+
hooks.beforeAll(async () => {
|
|
99
|
+
await initializeSuite(suite);
|
|
100
|
+
});
|
|
101
|
+
hooks.afterAll(async () => {
|
|
102
|
+
let teardownError: unknown;
|
|
103
|
+
let acceptanceError: Error | undefined;
|
|
104
|
+
try {
|
|
105
|
+
await teardownSuite(suite);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
teardownError = err;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
suite.acceptanceResults = evaluateAcceptanceCriteria({
|
|
111
|
+
criteria: suite.config.acceptanceCriteria,
|
|
112
|
+
results: suite.results,
|
|
113
|
+
});
|
|
114
|
+
acceptanceError = createAcceptanceFailureError(suite.acceptanceResults);
|
|
115
|
+
} finally {
|
|
116
|
+
writeSuiteSummaryArtifact(suite);
|
|
117
|
+
}
|
|
118
|
+
if (teardownError) {
|
|
119
|
+
throw teardownError;
|
|
120
|
+
}
|
|
121
|
+
if (acceptanceError) {
|
|
122
|
+
throw acceptanceError;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Drive a `test(name, params, fn)` invocation. */
|
|
129
|
+
export function declareTest<Input extends KVMap, Expected extends KVMap>(
|
|
130
|
+
hooks: RunnerHooks,
|
|
131
|
+
name: string,
|
|
132
|
+
params: TestParams<Input, Expected>,
|
|
133
|
+
fn: TestFn<Input, Expected>,
|
|
134
|
+
variant: TestVariant = "default",
|
|
135
|
+
timeout?: number
|
|
136
|
+
): void {
|
|
137
|
+
const suite = currentSuite();
|
|
138
|
+
if (!suite) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
"Phoenix eval test() must be declared inside a describe() block"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Collapse the `expected` / `reference` / `output` aliases into the single
|
|
145
|
+
// canonical `expected` slot up front, so every downstream consumer (dataset
|
|
146
|
+
// upload, evaluator params, test args) reads one field.
|
|
147
|
+
params = normalizeReferenceOutput(params);
|
|
148
|
+
|
|
149
|
+
// A per-test `dryRun` opts the case out of Phoenix entirely: no dataset
|
|
150
|
+
// example, no experiment run, no annotations — it runs as an ordinary
|
|
151
|
+
// local test. Suite-level dryRun (or PHOENIX_TEST_TRACING=false) is
|
|
152
|
+
// handled in `initializeSuite`, which never uploads anything anyway.
|
|
153
|
+
const isDryRun = !!params.dryRun;
|
|
154
|
+
|
|
155
|
+
// Avoid silently overwriting an example registration when the same test name
|
|
156
|
+
// is declared twice in the same suite.
|
|
157
|
+
const uniqueName = ensureUniqueName(suite, name);
|
|
158
|
+
// Skipped cases never run, and registering them would still upload their
|
|
159
|
+
// example to the tracked dataset via `initializeSuite()` — contradicting the
|
|
160
|
+
// skip contract and letting unfinished/flaky cases mutate the dataset. Dry-run
|
|
161
|
+
// cases opt out of Phoenix entirely. Neither should register an example.
|
|
162
|
+
if (!isDryRun && variant !== "skip") {
|
|
163
|
+
suite.registeredExamples.set(uniqueName, {
|
|
164
|
+
testName: uniqueName,
|
|
165
|
+
params: params as TestParams,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const repetitions = isDryRun
|
|
170
|
+
? 1
|
|
171
|
+
: resolveRepetitions(params.repetitions, suite);
|
|
172
|
+
suite.maxRepetitions = Math.max(suite.maxRepetitions ?? 1, repetitions);
|
|
173
|
+
|
|
174
|
+
const testFn =
|
|
175
|
+
variant === "only"
|
|
176
|
+
? (hooks.testOnly ?? hooks.test)
|
|
177
|
+
: variant === "skip"
|
|
178
|
+
? (hooks.testSkip ?? hooks.test)
|
|
179
|
+
: hooks.test;
|
|
180
|
+
|
|
181
|
+
for (let rep = 1; rep <= repetitions; rep++) {
|
|
182
|
+
const runnerName =
|
|
183
|
+
repetitions > 1
|
|
184
|
+
? `${uniqueName} [rep ${rep}/${repetitions}]`
|
|
185
|
+
: uniqueName;
|
|
186
|
+
testFn(
|
|
187
|
+
runnerName,
|
|
188
|
+
async () => {
|
|
189
|
+
await executeRun({
|
|
190
|
+
suite,
|
|
191
|
+
runnerName,
|
|
192
|
+
logicalName: uniqueName,
|
|
193
|
+
repetitionNumber: rep,
|
|
194
|
+
repetitions,
|
|
195
|
+
dryRun: isDryRun,
|
|
196
|
+
params: params as TestParams,
|
|
197
|
+
fn: fn as TestFn,
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
timeout
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Execute a single (possibly-repeated) run of a declared test. */
|
|
206
|
+
async function executeRun(opts: {
|
|
207
|
+
suite: SuiteState;
|
|
208
|
+
runnerName: string;
|
|
209
|
+
logicalName: string;
|
|
210
|
+
repetitionNumber: number;
|
|
211
|
+
repetitions: number;
|
|
212
|
+
dryRun: boolean;
|
|
213
|
+
params: TestParams;
|
|
214
|
+
fn: TestFn;
|
|
215
|
+
}): Promise<void> {
|
|
216
|
+
const { suite, runnerName, logicalName, repetitionNumber, repetitions } =
|
|
217
|
+
opts;
|
|
218
|
+
const run: RunState = {
|
|
219
|
+
suite,
|
|
220
|
+
testName: runnerName,
|
|
221
|
+
logicalName,
|
|
222
|
+
repetitionNumber,
|
|
223
|
+
dryRun: opts.dryRun,
|
|
224
|
+
params: opts.params,
|
|
225
|
+
output: undefined,
|
|
226
|
+
outputSet: false,
|
|
227
|
+
annotations: [],
|
|
228
|
+
startTime: new Date(),
|
|
229
|
+
runMetadata: {},
|
|
230
|
+
};
|
|
231
|
+
const start = Date.now();
|
|
232
|
+
let status: "passed" | "failed" = "passed";
|
|
233
|
+
let thrown: unknown;
|
|
234
|
+
let testError: string | undefined;
|
|
235
|
+
await runStorage.run(run, async () => {
|
|
236
|
+
const taskOutcome = await runTaskWithTracing(
|
|
237
|
+
suite,
|
|
238
|
+
runnerName,
|
|
239
|
+
async () => {
|
|
240
|
+
const args: TestArgs = {
|
|
241
|
+
input: opts.params.input,
|
|
242
|
+
expected: opts.params.expected,
|
|
243
|
+
metadata: opts.params.metadata,
|
|
244
|
+
};
|
|
245
|
+
const result = await opts.fn(args);
|
|
246
|
+
// If the user returned a value and didn't call logOutput(),
|
|
247
|
+
// adopt the return value as the run's output.
|
|
248
|
+
if (
|
|
249
|
+
result !== undefined &&
|
|
250
|
+
!run.outputSet &&
|
|
251
|
+
isPlainObjectOrPrimitive(result)
|
|
252
|
+
) {
|
|
253
|
+
run.output = result;
|
|
254
|
+
run.outputSet = true;
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
run.traceId = taskOutcome.traceId;
|
|
260
|
+
if ("error" in taskOutcome && taskOutcome.error) {
|
|
261
|
+
status = "failed";
|
|
262
|
+
testError = taskOutcome.error.message;
|
|
263
|
+
if (taskOutcome.isTaskError) {
|
|
264
|
+
run.error = taskOutcome.error.message;
|
|
265
|
+
}
|
|
266
|
+
thrown = taskOutcome.error;
|
|
267
|
+
}
|
|
268
|
+
run.endTime = new Date();
|
|
269
|
+
|
|
270
|
+
// The pass/fail annotation is recorded regardless of tracking mode
|
|
271
|
+
// so it shows up consistently in the reporter summary.
|
|
272
|
+
run.annotations.unshift({
|
|
273
|
+
name: "pass",
|
|
274
|
+
score: status === "passed",
|
|
275
|
+
annotatorKind: "CODE",
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const runId = await postExperimentRun(suite, run);
|
|
279
|
+
run.runId = runId;
|
|
280
|
+
await flushAnnotations(runId, run.annotations, suite);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const result: TestResult = {
|
|
284
|
+
suiteName: suite.name,
|
|
285
|
+
testName: runnerName,
|
|
286
|
+
status,
|
|
287
|
+
output: run.outputSet ? run.output : undefined,
|
|
288
|
+
annotations: run.annotations,
|
|
289
|
+
error: testError,
|
|
290
|
+
durationMs: Date.now() - start,
|
|
291
|
+
repetitionNumber: repetitions > 1 ? repetitionNumber : undefined,
|
|
292
|
+
repetitions: repetitions > 1 ? repetitions : undefined,
|
|
293
|
+
dryRun: opts.dryRun || undefined,
|
|
294
|
+
traceId: run.traceId,
|
|
295
|
+
runId: run.runId,
|
|
296
|
+
exampleId: suite.exampleIdsByTest.get(logicalName)?.exampleId,
|
|
297
|
+
};
|
|
298
|
+
suite.results.push(result);
|
|
299
|
+
|
|
300
|
+
if (thrown) {
|
|
301
|
+
throw thrown;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Return a copy of `params` with the reference output collapsed onto the
|
|
307
|
+
* canonical `expected` key and the `reference` / `output` aliases dropped.
|
|
308
|
+
*/
|
|
309
|
+
function normalizeReferenceOutput<Input extends KVMap, Expected extends KVMap>(
|
|
310
|
+
params: TestParams<Input, Expected>
|
|
311
|
+
): TestParams<Input, Expected> {
|
|
312
|
+
const expected = resolveReference(params);
|
|
313
|
+
const { reference: _reference, output: _output, ...rest } = params;
|
|
314
|
+
return { ...rest, expected };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function isPlainObjectOrPrimitive(value: unknown): boolean {
|
|
318
|
+
if (value === null) return true;
|
|
319
|
+
const type = typeof value;
|
|
320
|
+
return (
|
|
321
|
+
type === "string" ||
|
|
322
|
+
type === "number" ||
|
|
323
|
+
type === "boolean" ||
|
|
324
|
+
type === "object"
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function ensureUniqueName(suite: SuiteState, name: string): string {
|
|
329
|
+
if (!suite.registeredExamples.has(name)) {
|
|
330
|
+
return name;
|
|
331
|
+
}
|
|
332
|
+
let i = 2;
|
|
333
|
+
while (suite.registeredExamples.has(`${name} (${i})`)) {
|
|
334
|
+
i++;
|
|
335
|
+
}
|
|
336
|
+
return `${name} (${i})`;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Suite registry exposed for reporters and the public summary helper. */
|
|
340
|
+
export function getAllSuites(): readonly SuiteState[] {
|
|
341
|
+
return allSuites;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Reset the suite registry. Reporters call this at the start of every test
|
|
346
|
+
* run so module-cached state from previous watch invocations is released.
|
|
347
|
+
*/
|
|
348
|
+
export function clearAllSuites(): void {
|
|
349
|
+
allSuites.length = 0;
|
|
350
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import type {
|
|
3
|
+
GlobalTracerProviderRegistration,
|
|
4
|
+
NodeTracerProvider,
|
|
5
|
+
Tracer,
|
|
6
|
+
} from "@arizeai/phoenix-otel";
|
|
7
|
+
|
|
8
|
+
import type { PhoenixClient } from "../index";
|
|
9
|
+
import type {
|
|
10
|
+
AcceptanceResult,
|
|
11
|
+
Annotation,
|
|
12
|
+
KVMap,
|
|
13
|
+
SuiteConfig,
|
|
14
|
+
TestParams,
|
|
15
|
+
} from "./types";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* State for a single `describe()` block. Created during test collection
|
|
19
|
+
* and shared across all tests in the suite.
|
|
20
|
+
*/
|
|
21
|
+
export interface SuiteState {
|
|
22
|
+
/** The user-facing suite (and dataset/experiment) name. */
|
|
23
|
+
name: string;
|
|
24
|
+
config: SuiteConfig;
|
|
25
|
+
/**
|
|
26
|
+
* Map of test name → registered example metadata. Filled as tests are
|
|
27
|
+
* declared inside the describe callback. Keys are test names.
|
|
28
|
+
*/
|
|
29
|
+
registeredExamples: Map<string, RegisteredExample>;
|
|
30
|
+
/**
|
|
31
|
+
* Map of test name → resolved server-side example id. Populated after the
|
|
32
|
+
* dataset is synced.
|
|
33
|
+
*/
|
|
34
|
+
exampleIdsByTest: Map<string, { exampleId: string; nodeId: string }>;
|
|
35
|
+
/** Phoenix dataset id (after upload). */
|
|
36
|
+
datasetId?: string;
|
|
37
|
+
/** Phoenix experiment id (created at suite start). */
|
|
38
|
+
experimentId?: string;
|
|
39
|
+
/** Phoenix project name used for the experiment. */
|
|
40
|
+
projectName?: string;
|
|
41
|
+
/** Phoenix client used for this suite (resolved lazily). */
|
|
42
|
+
client?: PhoenixClient;
|
|
43
|
+
/** OpenInference tracer for task spans. */
|
|
44
|
+
tracer?: Tracer;
|
|
45
|
+
/** OpenInference tracer for evaluator spans. */
|
|
46
|
+
evaluatorTracer?: Tracer;
|
|
47
|
+
/** Tracer provider owned by this suite (cleaned up at suite end). */
|
|
48
|
+
tracerProvider?: NodeTracerProvider;
|
|
49
|
+
/** Global tracer registration owned by this suite (if any). */
|
|
50
|
+
globalRegistration?: GlobalTracerProviderRegistration | null;
|
|
51
|
+
/** Whether sync to Phoenix is disabled for this run. */
|
|
52
|
+
trackingDisabled: boolean;
|
|
53
|
+
/** Recorded test results for the reporter / summary. */
|
|
54
|
+
results: TestResult[];
|
|
55
|
+
/** Computed aggregate acceptance results for this suite. */
|
|
56
|
+
acceptanceResults?: AcceptanceResult[];
|
|
57
|
+
/** Links printed at the end of the suite (Phoenix dataset / experiment URLs). */
|
|
58
|
+
links: Array<{ label: string; url: string }>;
|
|
59
|
+
/** Reason the suite became inert (no tracking), if applicable. */
|
|
60
|
+
trackingDisabledReason?: string;
|
|
61
|
+
/** Setup error captured during suite initialization. */
|
|
62
|
+
setupError?: Error;
|
|
63
|
+
/** Count of best-effort POSTs (runs + annotations) that failed during upload. */
|
|
64
|
+
uploadFailureCount?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Highest per-test repetition count seen in this suite. Used as the
|
|
67
|
+
* experiment's `repetitions` value (informational; runs are keyed by
|
|
68
|
+
* example + repetition number regardless).
|
|
69
|
+
*/
|
|
70
|
+
maxRepetitions?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Per-test registration captured when `test()` is declared. */
|
|
74
|
+
export interface RegisteredExample {
|
|
75
|
+
testName: string;
|
|
76
|
+
params: TestParams;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Run-time state attached via AsyncLocalStorage to each running test. */
|
|
80
|
+
export interface RunState {
|
|
81
|
+
suite: SuiteState;
|
|
82
|
+
/** Runner-facing test name (may include a `[rep i/N]` suffix). */
|
|
83
|
+
testName: string;
|
|
84
|
+
/** The logical test name; the key into `registeredExamples`. */
|
|
85
|
+
logicalName: string;
|
|
86
|
+
/** 1-based repetition index for this run. */
|
|
87
|
+
repetitionNumber: number;
|
|
88
|
+
/** When true, this run is local-only: no dataset example, no upload. */
|
|
89
|
+
dryRun: boolean;
|
|
90
|
+
params: TestParams;
|
|
91
|
+
output?: unknown;
|
|
92
|
+
outputSet: boolean;
|
|
93
|
+
annotations: Annotation[];
|
|
94
|
+
startTime: Date;
|
|
95
|
+
/** End time for the task span/run, which may be before the test body ends. */
|
|
96
|
+
taskEndTime?: Date;
|
|
97
|
+
endTime?: Date;
|
|
98
|
+
traceId?: string;
|
|
99
|
+
runId?: string;
|
|
100
|
+
error?: string;
|
|
101
|
+
/** Optional metadata accumulated during the test. */
|
|
102
|
+
runMetadata: KVMap;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** A single test outcome surfaced to the reporter / summary printer. */
|
|
106
|
+
export interface TestResult {
|
|
107
|
+
suiteName: string;
|
|
108
|
+
testName: string;
|
|
109
|
+
status: "passed" | "failed" | "skipped";
|
|
110
|
+
output?: unknown;
|
|
111
|
+
annotations: Annotation[];
|
|
112
|
+
error?: string;
|
|
113
|
+
durationMs: number;
|
|
114
|
+
/** 1-based repetition index, when the test ran more than once. */
|
|
115
|
+
repetitionNumber?: number;
|
|
116
|
+
/** Total repetitions configured for this test (≥ 1). */
|
|
117
|
+
repetitions?: number;
|
|
118
|
+
/** True when this test ran local-only (not uploaded to Phoenix). */
|
|
119
|
+
dryRun?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* OpenInference trace id for the task span, when the run was traced. Surfaced
|
|
122
|
+
* in the reporter's failure detail so an agent can pull the exact trace from
|
|
123
|
+
* Phoenix (e.g. `px`/GraphQL) to see what the task actually did.
|
|
124
|
+
*/
|
|
125
|
+
traceId?: string;
|
|
126
|
+
/** Phoenix experiment run id, when the run was uploaded. */
|
|
127
|
+
runId?: string;
|
|
128
|
+
/** Phoenix dataset example id this run was scored against, when synced. */
|
|
129
|
+
exampleId?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* AsyncLocalStorage that lets `logOutput` / `logAnnotation` / `evaluate`
|
|
134
|
+
* reach the running test's state without threading it through arguments.
|
|
135
|
+
*/
|
|
136
|
+
export const runStorage = new AsyncLocalStorage<RunState>();
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Stack of currently active suites. Tests declared inside a `describe`
|
|
140
|
+
* callback consult the top of this stack to register themselves.
|
|
141
|
+
*/
|
|
142
|
+
const suiteStack: SuiteState[] = [];
|
|
143
|
+
|
|
144
|
+
/** Push a suite onto the stack as we enter its `describe` callback. */
|
|
145
|
+
export function pushSuite(suite: SuiteState): void {
|
|
146
|
+
suiteStack.push(suite);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Pop the current suite as we leave its `describe` callback. */
|
|
150
|
+
export function popSuite(): SuiteState | undefined {
|
|
151
|
+
return suiteStack.pop();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The suite currently being declared, or `undefined` outside any `describe`. */
|
|
155
|
+
export function currentSuite(): SuiteState | undefined {
|
|
156
|
+
return suiteStack[suiteStack.length - 1];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The state for the test currently executing, or `undefined` when called
|
|
161
|
+
* outside a Phoenix test body. Backed by {@link runStorage}.
|
|
162
|
+
*/
|
|
163
|
+
export function currentRun(): RunState | undefined {
|
|
164
|
+
return runStorage.getStore();
|
|
165
|
+
}
|