@arizeai/phoenix-client 7.6.0 → 7.7.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 (143) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +27 -3
  3. package/dist/esm/__generated__/api/v1.d.ts +1 -1
  4. package/dist/esm/__generated__/api/v1.d.ts.map +1 -1
  5. package/dist/esm/constants/serverRequirements.d.ts +1 -0
  6. package/dist/esm/constants/serverRequirements.d.ts.map +1 -1
  7. package/dist/esm/constants/serverRequirements.js +7 -0
  8. package/dist/esm/constants/serverRequirements.js.map +1 -1
  9. package/dist/esm/experiments/resumeEvaluation.d.ts +0 -60
  10. package/dist/esm/experiments/resumeEvaluation.d.ts.map +1 -1
  11. package/dist/esm/experiments/resumeEvaluation.js +44 -37
  12. package/dist/esm/experiments/resumeEvaluation.js.map +1 -1
  13. package/dist/esm/experiments/resumeExperiment.d.ts +0 -52
  14. package/dist/esm/experiments/resumeExperiment.d.ts.map +1 -1
  15. package/dist/esm/experiments/resumeExperiment.js +42 -35
  16. package/dist/esm/experiments/resumeExperiment.js.map +1 -1
  17. package/dist/esm/experiments/runExperiment.d.ts.map +1 -1
  18. package/dist/esm/experiments/runExperiment.js +152 -123
  19. package/dist/esm/experiments/runExperiment.js.map +1 -1
  20. package/dist/esm/projects/index.d.ts +1 -0
  21. package/dist/esm/projects/index.d.ts.map +1 -1
  22. package/dist/esm/projects/index.js +1 -0
  23. package/dist/esm/projects/index.js.map +1 -1
  24. package/dist/esm/projects/setProjectRetentionPolicy.d.ts +49 -0
  25. package/dist/esm/projects/setProjectRetentionPolicy.d.ts.map +1 -0
  26. package/dist/esm/projects/setProjectRetentionPolicy.js +52 -0
  27. package/dist/esm/projects/setProjectRetentionPolicy.js.map +1 -0
  28. package/dist/esm/prompts/sdks/toOpenAI.d.ts.map +1 -1
  29. package/dist/esm/prompts/sdks/toOpenAI.js +49 -58
  30. package/dist/esm/prompts/sdks/toOpenAI.js.map +1 -1
  31. package/dist/esm/sessions/sessionUtils.d.ts.map +1 -1
  32. package/dist/esm/sessions/sessionUtils.js +3 -0
  33. package/dist/esm/sessions/sessionUtils.js.map +1 -1
  34. package/dist/esm/spans/getSpans.d.ts +0 -70
  35. package/dist/esm/spans/getSpans.d.ts.map +1 -1
  36. package/dist/esm/spans/getSpans.js +42 -93
  37. package/dist/esm/spans/getSpans.js.map +1 -1
  38. package/dist/esm/testing/phoenix-test-tracking.d.ts.map +1 -1
  39. package/dist/esm/testing/phoenix-test-tracking.js +109 -78
  40. package/dist/esm/testing/phoenix-test-tracking.js.map +1 -1
  41. package/dist/esm/traces/getTraces.d.ts +1 -1
  42. package/dist/esm/traces/getTraces.d.ts.map +1 -1
  43. package/dist/esm/traces/index.d.ts +1 -0
  44. package/dist/esm/traces/index.d.ts.map +1 -1
  45. package/dist/esm/traces/index.js +1 -0
  46. package/dist/esm/traces/index.js.map +1 -1
  47. package/dist/esm/traces/transferTraces.d.ts +56 -0
  48. package/dist/esm/traces/transferTraces.d.ts.map +1 -0
  49. package/dist/esm/traces/transferTraces.js +56 -0
  50. package/dist/esm/traces/transferTraces.js.map +1 -0
  51. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  52. package/dist/esm/types/sessions.d.ts +6 -0
  53. package/dist/esm/types/sessions.d.ts.map +1 -1
  54. package/dist/esm/users/getCurrentUser.d.ts +25 -0
  55. package/dist/esm/users/getCurrentUser.d.ts.map +1 -0
  56. package/dist/esm/users/getCurrentUser.js +30 -0
  57. package/dist/esm/users/getCurrentUser.js.map +1 -0
  58. package/dist/esm/users/index.d.ts +2 -0
  59. package/dist/esm/users/index.d.ts.map +1 -0
  60. package/dist/esm/users/index.js +2 -0
  61. package/dist/esm/users/index.js.map +1 -0
  62. package/dist/src/__generated__/api/v1.d.ts +1 -1
  63. package/dist/src/__generated__/api/v1.d.ts.map +1 -1
  64. package/dist/src/constants/serverRequirements.d.ts +1 -0
  65. package/dist/src/constants/serverRequirements.d.ts.map +1 -1
  66. package/dist/src/constants/serverRequirements.js +8 -1
  67. package/dist/src/constants/serverRequirements.js.map +1 -1
  68. package/dist/src/experiments/resumeEvaluation.d.ts +0 -60
  69. package/dist/src/experiments/resumeEvaluation.d.ts.map +1 -1
  70. package/dist/src/experiments/resumeEvaluation.js +44 -37
  71. package/dist/src/experiments/resumeEvaluation.js.map +1 -1
  72. package/dist/src/experiments/resumeExperiment.d.ts +0 -52
  73. package/dist/src/experiments/resumeExperiment.d.ts.map +1 -1
  74. package/dist/src/experiments/resumeExperiment.js +42 -35
  75. package/dist/src/experiments/resumeExperiment.js.map +1 -1
  76. package/dist/src/experiments/runExperiment.d.ts.map +1 -1
  77. package/dist/src/experiments/runExperiment.js +148 -116
  78. package/dist/src/experiments/runExperiment.js.map +1 -1
  79. package/dist/src/projects/index.d.ts +1 -0
  80. package/dist/src/projects/index.d.ts.map +1 -1
  81. package/dist/src/projects/index.js +1 -0
  82. package/dist/src/projects/index.js.map +1 -1
  83. package/dist/src/projects/setProjectRetentionPolicy.d.ts +49 -0
  84. package/dist/src/projects/setProjectRetentionPolicy.d.ts.map +1 -0
  85. package/dist/src/projects/setProjectRetentionPolicy.js +59 -0
  86. package/dist/src/projects/setProjectRetentionPolicy.js.map +1 -0
  87. package/dist/src/prompts/sdks/toOpenAI.d.ts.map +1 -1
  88. package/dist/src/prompts/sdks/toOpenAI.js +56 -63
  89. package/dist/src/prompts/sdks/toOpenAI.js.map +1 -1
  90. package/dist/src/sessions/sessionUtils.d.ts.map +1 -1
  91. package/dist/src/sessions/sessionUtils.js +3 -0
  92. package/dist/src/sessions/sessionUtils.js.map +1 -1
  93. package/dist/src/spans/getSpans.d.ts +0 -70
  94. package/dist/src/spans/getSpans.d.ts.map +1 -1
  95. package/dist/src/spans/getSpans.js +43 -94
  96. package/dist/src/spans/getSpans.js.map +1 -1
  97. package/dist/src/testing/phoenix-test-tracking.d.ts.map +1 -1
  98. package/dist/src/testing/phoenix-test-tracking.js +117 -84
  99. package/dist/src/testing/phoenix-test-tracking.js.map +1 -1
  100. package/dist/src/traces/getTraces.d.ts +1 -1
  101. package/dist/src/traces/getTraces.d.ts.map +1 -1
  102. package/dist/src/traces/index.d.ts +1 -0
  103. package/dist/src/traces/index.d.ts.map +1 -1
  104. package/dist/src/traces/index.js +1 -0
  105. package/dist/src/traces/index.js.map +1 -1
  106. package/dist/src/traces/transferTraces.d.ts +56 -0
  107. package/dist/src/traces/transferTraces.d.ts.map +1 -0
  108. package/dist/src/traces/transferTraces.js +59 -0
  109. package/dist/src/traces/transferTraces.js.map +1 -0
  110. package/dist/src/types/sessions.d.ts +6 -0
  111. package/dist/src/types/sessions.d.ts.map +1 -1
  112. package/dist/src/users/getCurrentUser.d.ts +25 -0
  113. package/dist/src/users/getCurrentUser.d.ts.map +1 -0
  114. package/dist/src/users/getCurrentUser.js +36 -0
  115. package/dist/src/users/getCurrentUser.js.map +1 -0
  116. package/dist/src/users/index.d.ts +2 -0
  117. package/dist/src/users/index.d.ts.map +1 -0
  118. package/dist/src/users/index.js +18 -0
  119. package/dist/src/users/index.js.map +1 -0
  120. package/dist/tsconfig.tsbuildinfo +1 -1
  121. package/docs/overview.mdx +8 -4
  122. package/docs/projects.mdx +71 -0
  123. package/docs/sessions.mdx +10 -1
  124. package/docs/traces.mdx +28 -2
  125. package/docs/users.mdx +44 -0
  126. package/package.json +6 -2
  127. package/src/__generated__/api/v1.ts +1 -1
  128. package/src/constants/serverRequirements.ts +8 -0
  129. package/src/experiments/resumeEvaluation.ts +78 -48
  130. package/src/experiments/resumeExperiment.ts +73 -46
  131. package/src/experiments/runExperiment.ts +235 -129
  132. package/src/projects/index.ts +1 -0
  133. package/src/projects/setProjectRetentionPolicy.ts +80 -0
  134. package/src/prompts/sdks/toOpenAI.ts +58 -61
  135. package/src/sessions/sessionUtils.ts +3 -0
  136. package/src/spans/getSpans.ts +84 -48
  137. package/src/testing/phoenix-test-tracking.ts +154 -90
  138. package/src/traces/getTraces.ts +1 -1
  139. package/src/traces/index.ts +1 -0
  140. package/src/traces/transferTraces.ts +89 -0
  141. package/src/types/sessions.ts +6 -0
  142. package/src/users/getCurrentUser.ts +39 -0
  143. package/src/users/index.ts +1 -0
@@ -155,28 +155,23 @@ export type GetSpansResult = {
155
155
  * } while (cursor);
156
156
  * ```
157
157
  */
158
- export async function getSpans({
159
- client: _client,
160
- project,
161
- cursor,
162
- limit = 100,
163
- startTime,
164
- endTime,
158
+ type SpansQuery = NonNullable<operations["getSpans"]["parameters"]["query"]>;
159
+
160
+ async function ensureSpanFilterCapabilities({
161
+ client,
165
162
  traceIds,
166
163
  spanIds,
167
- parentId,
168
164
  name,
169
165
  spanKind,
170
166
  statusCode,
171
- attributes,
172
- }: GetSpansParams): Promise<GetSpansResult> {
173
- const client = _client ?? createClient();
174
- const serializedAttributes =
175
- attributes != null ? serializeAttributes(attributes) : undefined;
176
- const attributeFilters =
177
- serializedAttributes != null && serializedAttributes.length > 0
178
- ? serializedAttributes
179
- : undefined;
167
+ attributeFilters,
168
+ }: Pick<
169
+ GetSpansParams,
170
+ "traceIds" | "spanIds" | "name" | "spanKind" | "statusCode"
171
+ > & {
172
+ client: NonNullable<GetSpansParams["client"]>;
173
+ attributeFilters: string[] | undefined;
174
+ }): Promise<void> {
180
175
  if (traceIds) {
181
176
  await ensureServerCapability({ client, requirement: GET_SPANS_TRACE_IDS });
182
177
  }
@@ -186,58 +181,99 @@ export async function getSpans({
186
181
  if (name != null || spanKind != null || statusCode != null) {
187
182
  await ensureServerCapability({ client, requirement: GET_SPANS_FILTERS });
188
183
  }
189
- if (attributeFilters != null) {
184
+ if (attributeFilters) {
190
185
  await ensureServerCapability({
191
186
  client,
192
187
  requirement: GET_SPANS_BY_ATTRIBUTE,
193
188
  });
194
189
  }
195
- const projectIdentifier = resolveProjectIdentifier(project);
196
-
197
- const params: NonNullable<operations["getSpans"]["parameters"]["query"]> = {
198
- limit,
199
- };
200
-
201
- if (cursor) {
202
- params.cursor = cursor;
203
- }
190
+ }
204
191
 
192
+ function buildSpansQuery({
193
+ cursor,
194
+ limit,
195
+ startTime,
196
+ endTime,
197
+ traceIds,
198
+ spanIds,
199
+ parentId,
200
+ name,
201
+ spanKind,
202
+ statusCode,
203
+ attributeFilters,
204
+ }: Omit<GetSpansParams, "client" | "project" | "attributes"> & {
205
+ limit: number;
206
+ attributeFilters: string[] | undefined;
207
+ }): SpansQuery {
208
+ const params: SpansQuery = { limit };
209
+ if (cursor) params.cursor = cursor;
205
210
  if (startTime) {
206
211
  params.start_time =
207
212
  startTime instanceof Date ? startTime.toISOString() : startTime;
208
213
  }
209
-
210
214
  if (endTime) {
211
215
  params.end_time = endTime instanceof Date ? endTime.toISOString() : endTime;
212
216
  }
213
-
214
- if (traceIds) {
215
- params.trace_id = traceIds;
216
- }
217
-
218
- if (spanIds) {
219
- params.span_id = spanIds;
220
- }
221
-
217
+ if (traceIds) params.trace_id = traceIds;
218
+ if (spanIds) params.span_id = spanIds;
222
219
  if (parentId !== undefined) {
223
220
  params.parent_id = parentId === null ? "null" : parentId;
224
221
  }
225
-
226
- if (name) {
227
- params.name = Array.isArray(name) ? name : [name];
228
- }
229
-
230
- if (spanKind) {
222
+ if (name) params.name = Array.isArray(name) ? name : [name];
223
+ if (spanKind)
231
224
  params.span_kind = Array.isArray(spanKind) ? spanKind : [spanKind];
232
- }
233
-
234
225
  if (statusCode) {
235
226
  params.status_code = Array.isArray(statusCode) ? statusCode : [statusCode];
236
227
  }
228
+ if (attributeFilters) params.attribute = attributeFilters;
229
+ return params;
230
+ }
237
231
 
238
- if (attributeFilters != null) {
239
- params.attribute = attributeFilters;
240
- }
232
+ export async function getSpans({
233
+ client: _client,
234
+ project,
235
+ cursor,
236
+ limit = 100,
237
+ startTime,
238
+ endTime,
239
+ traceIds,
240
+ spanIds,
241
+ parentId,
242
+ name,
243
+ spanKind,
244
+ statusCode,
245
+ attributes,
246
+ }: GetSpansParams): Promise<GetSpansResult> {
247
+ const client = _client ?? createClient();
248
+ const serializedAttributes =
249
+ attributes != null ? serializeAttributes(attributes) : undefined;
250
+ const attributeFilters =
251
+ serializedAttributes != null && serializedAttributes.length > 0
252
+ ? serializedAttributes
253
+ : undefined;
254
+ await ensureSpanFilterCapabilities({
255
+ client,
256
+ traceIds,
257
+ spanIds,
258
+ name,
259
+ spanKind,
260
+ statusCode,
261
+ attributeFilters,
262
+ });
263
+ const projectIdentifier = resolveProjectIdentifier(project);
264
+ const params = buildSpansQuery({
265
+ cursor,
266
+ limit,
267
+ startTime,
268
+ endTime,
269
+ traceIds,
270
+ spanIds,
271
+ parentId,
272
+ name,
273
+ spanKind,
274
+ statusCode,
275
+ attributeFilters,
276
+ });
241
277
 
242
278
  const { data, error } = await client.GET(
243
279
  "/v1/projects/{project_identifier}/spans",
@@ -220,31 +220,33 @@ function buildLinks(
220
220
  ];
221
221
  }
222
222
 
223
- /**
224
- * Initialize the suite: upload the dataset, create the experiment, and
225
- * register the OpenInference tracer.
226
- *
227
- * If tracing is disabled (no Phoenix env vars, or PHOENIX_TEST_TRACKING=false),
228
- * this populates a no-op tracer and exits without making any network calls.
229
- */
230
- export async function initializeSuite(suite: SuiteState): Promise<void> {
231
- const tracking = isTrackingEnabled(suite);
232
- if (!tracking.enabled) {
233
- suite.trackingDisabled = true;
234
- suite.trackingDisabledReason = tracking.reason;
235
- suite.tracer = createNoOpProvider().getTracer("no-op");
236
- suite.evaluatorTracer = suite.tracer;
237
- return;
223
+ function disableSuiteTracking({
224
+ suite,
225
+ error,
226
+ }: {
227
+ suite: SuiteState;
228
+ error?: unknown;
229
+ }): void {
230
+ suite.trackingDisabled = true;
231
+ if (error !== undefined) {
232
+ suite.setupError =
233
+ error instanceof Error ? error : new Error(String(error));
238
234
  }
235
+ suite.tracer = createNoOpProvider().getTracer("no-op");
236
+ suite.evaluatorTracer = suite.tracer;
237
+ }
239
238
 
240
- const client = suite.config.client ?? createClient();
241
- suite.client = client;
242
-
243
- const datasetName = suite.config.datasetName ?? suite.name;
244
- const description =
245
- suite.config.description ??
246
- `Phoenix test dataset auto-generated from ${suite.name}`;
247
-
239
+ async function createSuiteDataset({
240
+ suite,
241
+ client,
242
+ datasetName,
243
+ description,
244
+ }: {
245
+ suite: SuiteState;
246
+ client: PhoenixClient;
247
+ datasetName: string;
248
+ description: string;
249
+ }): Promise<string | null> {
248
250
  const examples = Array.from(suite.registeredExamples.values()).map(
249
251
  (registered) => ({
250
252
  id: registered.params.id ?? null,
@@ -254,8 +256,6 @@ export async function initializeSuite(suite: SuiteState): Promise<void> {
254
256
  splits: registered.params.splits,
255
257
  })
256
258
  );
257
-
258
- let datasetId: string;
259
259
  try {
260
260
  const created = await createDataset({
261
261
  client,
@@ -263,67 +263,81 @@ export async function initializeSuite(suite: SuiteState): Promise<void> {
263
263
  description,
264
264
  examples,
265
265
  });
266
- datasetId = created.datasetId;
267
- } catch (err) {
268
- suite.trackingDisabled = true;
269
- suite.setupError = err instanceof Error ? err : new Error(String(err));
270
- suite.tracer = createNoOpProvider().getTracer("no-op");
271
- suite.evaluatorTracer = suite.tracer;
272
- return;
266
+ return created.datasetId;
267
+ } catch (error) {
268
+ disableSuiteTracking({ suite, error });
269
+ return null;
273
270
  }
274
- suite.datasetId = datasetId;
271
+ }
275
272
 
276
- // Map test names to server-side example ids by re-fetching the dataset.
277
- // The server doesn't promise that the GET response order matches the
278
- // upload order, so we match by user-supplied `id` first, then by
279
- // `JSON.stringify(input)` deep-equality with FIFO-on-collision.
273
+ async function resolveSuiteExampleIds({
274
+ suite,
275
+ client,
276
+ datasetId,
277
+ }: {
278
+ suite: SuiteState;
279
+ client: PhoenixClient;
280
+ datasetId: string;
281
+ }): Promise<void> {
280
282
  try {
281
283
  const { data: response } = await client.GET("/v1/datasets/{id}/examples", {
282
284
  params: { path: { id: datasetId } },
283
285
  });
284
286
  const fetched = response?.data?.examples ?? [];
285
-
286
287
  const idToTestName = new Map<string, string>();
287
288
  const inputKeyToTestNames = new Map<string, string[]>();
288
289
  for (const [testName, registered] of suite.registeredExamples.entries()) {
289
290
  if (registered.params.id) {
290
291
  idToTestName.set(registered.params.id, testName);
291
- continue;
292
+ } else {
293
+ const key = stableKey(registered.params.input);
294
+ inputKeyToTestNames.set(key, [
295
+ ...(inputKeyToTestNames.get(key) ?? []),
296
+ testName,
297
+ ]);
292
298
  }
293
- const key = stableKey(registered.params.input);
294
- const arr = inputKeyToTestNames.get(key) ?? [];
295
- arr.push(testName);
296
- inputKeyToTestNames.set(key, arr);
297
299
  }
298
-
299
- for (const ex of fetched) {
300
- const byId = idToTestName.get(ex.id);
301
- if (byId) {
302
- suite.exampleIdsByTest.set(byId, {
303
- exampleId: ex.id,
304
- nodeId: ex.node_id,
300
+ for (const example of fetched) {
301
+ const testName = idToTestName.get(example.id);
302
+ if (testName) {
303
+ suite.exampleIdsByTest.set(testName, {
304
+ exampleId: example.id,
305
+ nodeId: example.node_id,
305
306
  });
306
- idToTestName.delete(ex.id);
307
+ idToTestName.delete(example.id);
307
308
  continue;
308
309
  }
309
- const queue = inputKeyToTestNames.get(stableKey(ex.input));
310
- if (queue && queue.length) {
311
- const testName = queue.shift() as string;
312
- suite.exampleIdsByTest.set(testName, {
313
- exampleId: ex.id,
314
- nodeId: ex.node_id,
310
+ const queue = inputKeyToTestNames.get(stableKey(example.input));
311
+ const matchedTestName = queue?.shift();
312
+ if (matchedTestName) {
313
+ suite.exampleIdsByTest.set(matchedTestName, {
314
+ exampleId: example.id,
315
+ nodeId: example.node_id,
315
316
  });
316
317
  }
317
318
  }
318
319
  } catch {
319
- // If we cannot resolve example ids, runs will be logged without one.
320
+ // Runs can still be logged without resolved example IDs.
320
321
  }
322
+ }
321
323
 
324
+ async function createSuiteExperiment({
325
+ suite,
326
+ client,
327
+ datasetId,
328
+ datasetName,
329
+ description,
330
+ }: {
331
+ suite: SuiteState;
332
+ client: PhoenixClient;
333
+ datasetId: string;
334
+ datasetName: string;
335
+ description: string;
336
+ }): Promise<boolean> {
322
337
  const projectName = `${datasetName}-${new Date().toISOString()}`;
323
338
  suite.projectName = projectName;
324
-
325
339
  try {
326
- const experimentResponse = await client
340
+ const response = await client
327
341
  .POST("/v1/datasets/{dataset_id}/experiments", {
328
342
  params: { path: { dataset_id: datasetId } },
329
343
  body: {
@@ -334,37 +348,46 @@ export async function initializeSuite(suite: SuiteState): Promise<void> {
334
348
  repetitions: Math.max(1, suite.maxRepetitions ?? 1),
335
349
  },
336
350
  })
337
- .then((res) => res.data?.data);
338
- if (!experimentResponse) {
339
- throw new Error("Failed to create experiment");
340
- }
341
- suite.experimentId = experimentResponse.id;
342
- suite.projectName = experimentResponse.project_name ?? projectName;
343
- } catch (err) {
344
- suite.trackingDisabled = true;
345
- suite.setupError = err instanceof Error ? err : new Error(String(err));
346
- suite.tracer = createNoOpProvider().getTracer("no-op");
347
- suite.evaluatorTracer = suite.tracer;
348
- return;
351
+ .then((result) => result.data?.data);
352
+ if (!response) throw new Error("Failed to create experiment");
353
+ suite.experimentId = response.id;
354
+ suite.projectName = response.project_name ?? projectName;
355
+ return true;
356
+ } catch (error) {
357
+ disableSuiteTracking({ suite, error });
358
+ return false;
349
359
  }
360
+ }
350
361
 
362
+ function setupSuiteTracer({
363
+ suite,
364
+ client,
365
+ }: {
366
+ suite: SuiteState;
367
+ client: PhoenixClient;
368
+ }): boolean {
351
369
  const baseUrl = client.config.baseUrl;
352
370
  if (!baseUrl) {
353
- suite.trackingDisabled = true;
354
- suite.setupError = new Error(
355
- "Phoenix base URL not found. Set PHOENIX_ENDPOINT (or PHOENIX_COLLECTOR_ENDPOINT) or pass baseUrl on the client."
356
- );
357
- suite.tracer = createNoOpProvider().getTracer("no-op");
358
- suite.evaluatorTracer = suite.tracer;
359
- return;
371
+ disableSuiteTracking({
372
+ suite,
373
+ error: new Error(
374
+ "Phoenix base URL not found. Set PHOENIX_ENDPOINT (or PHOENIX_COLLECTOR_ENDPOINT) or pass baseUrl on the client."
375
+ ),
376
+ });
377
+ return false;
360
378
  }
361
-
362
379
  maybeWarnHttpScheme(baseUrl, client.config.headers);
363
-
364
- let provider: NodeTracerProvider;
380
+ const projectName = suite.projectName;
381
+ if (!projectName) {
382
+ disableSuiteTracking({
383
+ suite,
384
+ error: new Error("Experiment project name is missing."),
385
+ });
386
+ return false;
387
+ }
365
388
  try {
366
- provider = register({
367
- projectName: suite.projectName,
389
+ const provider = register({
390
+ projectName,
368
391
  url: getTraceExportUrl(client.config),
369
392
  headers: client.config.headers
370
393
  ? toObjectHeaders(client.config.headers)
@@ -374,18 +397,59 @@ export async function initializeSuite(suite: SuiteState): Promise<void> {
374
397
  });
375
398
  suite.tracerProvider = provider;
376
399
  suite.globalRegistration = attachGlobalTracerProvider(provider);
377
- } catch (err) {
400
+ suite.tracer = provider.getTracer(projectName);
401
+ suite.evaluatorTracer = provider.getTracer(`${projectName}-evaluators`);
402
+ return true;
403
+ } catch (error) {
404
+ disableSuiteTracking({ suite, error });
405
+ return false;
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Initialize the suite: upload the dataset, create the experiment, and
411
+ * register the OpenInference tracer.
412
+ *
413
+ * If tracing is disabled (no Phoenix env vars, or PHOENIX_TEST_TRACKING=false),
414
+ * this populates a no-op tracer and exits without making any network calls.
415
+ */
416
+ export async function initializeSuite(suite: SuiteState): Promise<void> {
417
+ const tracking = isTrackingEnabled(suite);
418
+ if (!tracking.enabled) {
378
419
  suite.trackingDisabled = true;
379
- suite.setupError = err instanceof Error ? err : new Error(String(err));
420
+ suite.trackingDisabledReason = tracking.reason;
380
421
  suite.tracer = createNoOpProvider().getTracer("no-op");
381
422
  suite.evaluatorTracer = suite.tracer;
382
423
  return;
383
424
  }
384
- suite.tracer = provider.getTracer(suite.projectName);
385
- suite.evaluatorTracer = provider.getTracer(`${suite.projectName}-evaluators`);
386
425
 
387
- if (suite.datasetId && suite.experimentId) {
388
- suite.links = buildLinks(client, suite.datasetId, suite.experimentId);
426
+ const client = suite.config.client ?? createClient();
427
+ suite.client = client;
428
+
429
+ const datasetName = suite.config.datasetName ?? suite.name;
430
+ const description =
431
+ suite.config.description ??
432
+ `Phoenix test dataset auto-generated from ${suite.name}`;
433
+
434
+ const datasetId = await createSuiteDataset({
435
+ suite,
436
+ client,
437
+ datasetName,
438
+ description,
439
+ });
440
+ if (!datasetId) return;
441
+ suite.datasetId = datasetId;
442
+ await resolveSuiteExampleIds({ suite, client, datasetId });
443
+ const hasExperiment = await createSuiteExperiment({
444
+ suite,
445
+ client,
446
+ datasetId,
447
+ datasetName,
448
+ description,
449
+ });
450
+ if (!hasExperiment || !setupSuiteTracer({ suite, client })) return;
451
+ if (suite.experimentId) {
452
+ suite.links = buildLinks(client, datasetId, suite.experimentId);
389
453
  }
390
454
  }
391
455
 
@@ -22,7 +22,7 @@ export interface GetTracesParams extends ClientFn {
22
22
  order?: "asc" | "desc";
23
23
  /** Maximum number of traces to return */
24
24
  limit?: number;
25
- /** Pagination cursor (Trace GlobalID) */
25
+ /** Pagination cursor */
26
26
  cursor?: string | null;
27
27
  /** If true, include full span details for each trace */
28
28
  includeSpans?: boolean;
@@ -2,4 +2,5 @@ export * from "./addTraceAnnotation";
2
2
  export * from "./addTraceNote";
3
3
  export * from "./getTraces";
4
4
  export * from "./logTraceAnnotations";
5
+ export * from "./transferTraces";
5
6
  export type { TraceAnnotation } from "./types";
@@ -0,0 +1,89 @@
1
+ import { createClient } from "../client";
2
+ import { TRANSFER_TRACES } from "../constants/serverRequirements";
3
+ import type { ClientFn } from "../types/core";
4
+ import { ensureServerCapability } from "../utils/serverVersionUtils";
5
+
6
+ /**
7
+ * Parameters for moving traces to another project.
8
+ */
9
+ export interface TransferTracesParams extends ClientFn {
10
+ /**
11
+ * Trace GlobalIDs or OpenTelemetry trace IDs to move. All traces must
12
+ * currently belong to the same source project.
13
+ */
14
+ traceIdentifiers: string[];
15
+ /**
16
+ * The destination project name or GlobalID.
17
+ */
18
+ destinationProjectIdentifier: string;
19
+ }
20
+
21
+ /**
22
+ * The result of moving traces to another project.
23
+ */
24
+ export interface TransferTracesResult {
25
+ /** The number of distinct traces moved. */
26
+ transferredTraceCount: number;
27
+ /** The destination project's GlobalID. */
28
+ destinationProjectId: string;
29
+ }
30
+
31
+ /**
32
+ * Move traces from one project to another.
33
+ *
34
+ * This operation re-parents the traces; it does not copy them. After the move,
35
+ * the traces no longer appear in their original project. Every trace must
36
+ * currently belong to the same source project.
37
+ *
38
+ * @param params - The parameters for moving traces.
39
+ * @param params.traceIdentifiers - Trace GlobalIDs or OpenTelemetry trace IDs to move.
40
+ * @param params.destinationProjectIdentifier - The destination project name or GlobalID.
41
+ * @returns The number of traces moved and the destination project's GlobalID.
42
+ * @throws {RangeError} If no trace identifiers are provided.
43
+ * @throws {HttpError} If a trace or destination project is missing, the traces
44
+ * belong to multiple source projects, or the transfer otherwise fails.
45
+ *
46
+ * @requires Phoenix server >= 20.4.0
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * import { transferTraces } from "@arizeai/phoenix-client/traces";
51
+ *
52
+ * const result = await transferTraces({
53
+ * traceIdentifiers: ["8f3a...", "VHJhY2U6Mg=="],
54
+ * destinationProjectIdentifier: "production",
55
+ * });
56
+ *
57
+ * console.log(result.transferredTraceCount);
58
+ * console.log(result.destinationProjectId);
59
+ * ```
60
+ */
61
+ export async function transferTraces({
62
+ client: _client,
63
+ traceIdentifiers,
64
+ destinationProjectIdentifier,
65
+ }: TransferTracesParams): Promise<TransferTracesResult> {
66
+ if (traceIdentifiers.length === 0) {
67
+ throw new RangeError("At least one trace identifier is required");
68
+ }
69
+
70
+ const client = _client ?? createClient();
71
+ await ensureServerCapability({ client, requirement: TRANSFER_TRACES });
72
+
73
+ const { data, error } = await client.POST("/v1/traces/transfer", {
74
+ body: {
75
+ trace_identifiers: traceIdentifiers,
76
+ destination_project_identifier: destinationProjectIdentifier,
77
+ },
78
+ });
79
+
80
+ if (error) throw error;
81
+ if (!data?.data) {
82
+ throw new Error("Failed to transfer traces: no data returned");
83
+ }
84
+
85
+ return {
86
+ transferredTraceCount: data.data.transferred_trace_count,
87
+ destinationProjectId: data.data.destination_project_id,
88
+ };
89
+ }
@@ -24,6 +24,12 @@ export interface Session extends Node {
24
24
  startTime: string;
25
25
  /** ISO 8601 timestamp of when the last trace in the session ended */
26
26
  endTime: string;
27
+ /** Cumulative prompt token count across all spans in the session */
28
+ tokenCountPrompt?: number;
29
+ /** Cumulative completion token count across all spans in the session */
30
+ tokenCountCompletion?: number;
31
+ /** Cumulative total token count across all spans in the session */
32
+ tokenCountTotal?: number;
27
33
  /** The traces that belong to this session */
28
34
  traces: SessionTrace[];
29
35
  }
@@ -0,0 +1,39 @@
1
+ import invariant from "tiny-invariant";
2
+
3
+ import type { components } from "../__generated__/api/v1";
4
+ import { createClient } from "../client";
5
+ import type { ClientFn } from "../types/core";
6
+
7
+ /** The user profile returned for the current Phoenix client credentials. */
8
+ export type CurrentUser =
9
+ components["schemas"]["GetViewerResponseBody"]["data"];
10
+
11
+ /**
12
+ * Get the currently authenticated user.
13
+ *
14
+ * When authentication is disabled, Phoenix returns an anonymous user with
15
+ * `auth_method: "ANONYMOUS"`.
16
+ *
17
+ * @param params - The parameters for fetching the current user.
18
+ * @param params.client - An optional Phoenix client instance.
19
+ * @returns The current user's generated API profile shape.
20
+ * @throws {HttpError} If the request is not authenticated or is forbidden.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { getCurrentUser } from "@arizeai/phoenix-client/users";
25
+ *
26
+ * const user = await getCurrentUser();
27
+ * console.log(user.auth_method);
28
+ * ```
29
+ */
30
+ export async function getCurrentUser({
31
+ client: _client,
32
+ }: ClientFn = {}): Promise<CurrentUser> {
33
+ const client = _client ?? createClient();
34
+ const { data, error } = await client.GET("/v1/user");
35
+
36
+ if (error) throw error;
37
+ invariant(data?.data, "Failed to get current user");
38
+ return data.data;
39
+ }
@@ -0,0 +1 @@
1
+ export * from "./getCurrentUser";