@arizeai/phoenix-client 6.8.0 → 6.9.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 (170) hide show
  1. package/README.md +77 -0
  2. package/dist/esm/__generated__/api/v1.d.ts +313 -19
  3. package/dist/esm/__generated__/api/v1.d.ts.map +1 -1
  4. package/dist/esm/constants/serverRequirements.d.ts +2 -0
  5. package/dist/esm/constants/serverRequirements.d.ts.map +1 -1
  6. package/dist/esm/constants/serverRequirements.js +15 -0
  7. package/dist/esm/constants/serverRequirements.js.map +1 -1
  8. package/dist/esm/datasets/appendDatasetExamples.d.ts +9 -3
  9. package/dist/esm/datasets/appendDatasetExamples.d.ts.map +1 -1
  10. package/dist/esm/datasets/appendDatasetExamples.js +23 -3
  11. package/dist/esm/datasets/appendDatasetExamples.js.map +1 -1
  12. package/dist/esm/datasets/createDataset.d.ts +4 -1
  13. package/dist/esm/datasets/createDataset.d.ts.map +1 -1
  14. package/dist/esm/datasets/createDataset.js +38 -3
  15. package/dist/esm/datasets/createDataset.js.map +1 -1
  16. package/dist/esm/datasets/getDatasetExamples.d.ts.map +1 -1
  17. package/dist/esm/datasets/getDatasetExamples.js +3 -2
  18. package/dist/esm/datasets/getDatasetExamples.js.map +1 -1
  19. package/dist/esm/datasets/index.d.ts +0 -1
  20. package/dist/esm/datasets/index.d.ts.map +1 -1
  21. package/dist/esm/datasets/index.js +0 -1
  22. package/dist/esm/datasets/index.js.map +1 -1
  23. package/dist/esm/experiments/resumeEvaluation.d.ts.map +1 -1
  24. package/dist/esm/experiments/resumeEvaluation.js +2 -1
  25. package/dist/esm/experiments/resumeEvaluation.js.map +1 -1
  26. package/dist/esm/experiments/resumeExperiment.d.ts.map +1 -1
  27. package/dist/esm/experiments/resumeExperiment.js +3 -2
  28. package/dist/esm/experiments/resumeExperiment.js.map +1 -1
  29. package/dist/esm/experiments/runExperiment.js +1 -1
  30. package/dist/esm/experiments/runExperiment.js.map +1 -1
  31. package/dist/esm/sessions/addSessionNote.d.ts +45 -0
  32. package/dist/esm/sessions/addSessionNote.d.ts.map +1 -0
  33. package/dist/esm/sessions/addSessionNote.js +45 -0
  34. package/dist/esm/sessions/addSessionNote.js.map +1 -0
  35. package/dist/esm/sessions/index.d.ts +1 -0
  36. package/dist/esm/sessions/index.d.ts.map +1 -1
  37. package/dist/esm/sessions/index.js +1 -0
  38. package/dist/esm/sessions/index.js.map +1 -1
  39. package/dist/esm/spans/addSpanNote.d.ts +5 -3
  40. package/dist/esm/spans/addSpanNote.d.ts.map +1 -1
  41. package/dist/esm/spans/addSpanNote.js +7 -4
  42. package/dist/esm/spans/addSpanNote.js.map +1 -1
  43. package/dist/esm/traces/addTraceAnnotation.d.ts +43 -0
  44. package/dist/esm/traces/addTraceAnnotation.d.ts.map +1 -0
  45. package/dist/esm/traces/addTraceAnnotation.js +43 -0
  46. package/dist/esm/traces/addTraceAnnotation.js.map +1 -0
  47. package/dist/esm/traces/addTraceNote.d.ts +5 -2
  48. package/dist/esm/traces/addTraceNote.d.ts.map +1 -1
  49. package/dist/esm/traces/addTraceNote.js +7 -3
  50. package/dist/esm/traces/addTraceNote.js.map +1 -1
  51. package/dist/esm/traces/index.d.ts +3 -0
  52. package/dist/esm/traces/index.d.ts.map +1 -1
  53. package/dist/esm/traces/index.js +2 -0
  54. package/dist/esm/traces/index.js.map +1 -1
  55. package/dist/esm/traces/logTraceAnnotations.d.ts +53 -0
  56. package/dist/esm/traces/logTraceAnnotations.d.ts.map +1 -0
  57. package/dist/esm/traces/logTraceAnnotations.js +50 -0
  58. package/dist/esm/traces/logTraceAnnotations.js.map +1 -0
  59. package/dist/esm/traces/types.d.ts +24 -0
  60. package/dist/esm/traces/types.d.ts.map +1 -0
  61. package/dist/esm/traces/types.js +38 -0
  62. package/dist/esm/traces/types.js.map +1 -0
  63. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  64. package/dist/esm/types/datasets.d.ts +10 -1
  65. package/dist/esm/types/datasets.d.ts.map +1 -1
  66. package/dist/esm/utils/apiErrorUtils.d.ts +2 -0
  67. package/dist/esm/utils/apiErrorUtils.d.ts.map +1 -0
  68. package/dist/esm/utils/apiErrorUtils.js +19 -0
  69. package/dist/esm/utils/apiErrorUtils.js.map +1 -0
  70. package/dist/esm/utils/formatPromptMessages.d.ts.map +1 -1
  71. package/dist/esm/utils/getPromptBySelector.d.ts.map +1 -1
  72. package/dist/src/__generated__/api/v1.d.ts +313 -19
  73. package/dist/src/__generated__/api/v1.d.ts.map +1 -1
  74. package/dist/src/constants/serverRequirements.d.ts +2 -0
  75. package/dist/src/constants/serverRequirements.d.ts.map +1 -1
  76. package/dist/src/constants/serverRequirements.js +16 -1
  77. package/dist/src/constants/serverRequirements.js.map +1 -1
  78. package/dist/src/datasets/appendDatasetExamples.d.ts +9 -3
  79. package/dist/src/datasets/appendDatasetExamples.d.ts.map +1 -1
  80. package/dist/src/datasets/appendDatasetExamples.js +24 -5
  81. package/dist/src/datasets/appendDatasetExamples.js.map +1 -1
  82. package/dist/src/datasets/createDataset.d.ts +4 -1
  83. package/dist/src/datasets/createDataset.d.ts.map +1 -1
  84. package/dist/src/datasets/createDataset.js +42 -5
  85. package/dist/src/datasets/createDataset.js.map +1 -1
  86. package/dist/src/datasets/getDatasetExamples.d.ts.map +1 -1
  87. package/dist/src/datasets/getDatasetExamples.js +15 -1
  88. package/dist/src/datasets/getDatasetExamples.js.map +1 -1
  89. package/dist/src/datasets/index.d.ts +0 -1
  90. package/dist/src/datasets/index.d.ts.map +1 -1
  91. package/dist/src/datasets/index.js +0 -1
  92. package/dist/src/datasets/index.js.map +1 -1
  93. package/dist/src/experiments/resumeEvaluation.d.ts.map +1 -1
  94. package/dist/src/experiments/resumeEvaluation.js +2 -1
  95. package/dist/src/experiments/resumeEvaluation.js.map +1 -1
  96. package/dist/src/experiments/resumeExperiment.d.ts.map +1 -1
  97. package/dist/src/experiments/resumeExperiment.js +3 -2
  98. package/dist/src/experiments/resumeExperiment.js.map +1 -1
  99. package/dist/src/experiments/runExperiment.js +1 -1
  100. package/dist/src/experiments/runExperiment.js.map +1 -1
  101. package/dist/src/sessions/addSessionNote.d.ts +45 -0
  102. package/dist/src/sessions/addSessionNote.d.ts.map +1 -0
  103. package/dist/src/sessions/addSessionNote.js +48 -0
  104. package/dist/src/sessions/addSessionNote.js.map +1 -0
  105. package/dist/src/sessions/index.d.ts +1 -0
  106. package/dist/src/sessions/index.d.ts.map +1 -1
  107. package/dist/src/sessions/index.js +1 -0
  108. package/dist/src/sessions/index.js.map +1 -1
  109. package/dist/src/spans/addSpanNote.d.ts +5 -3
  110. package/dist/src/spans/addSpanNote.d.ts.map +1 -1
  111. package/dist/src/spans/addSpanNote.js +7 -4
  112. package/dist/src/spans/addSpanNote.js.map +1 -1
  113. package/dist/src/traces/addTraceAnnotation.d.ts +43 -0
  114. package/dist/src/traces/addTraceAnnotation.d.ts.map +1 -0
  115. package/dist/src/traces/addTraceAnnotation.js +47 -0
  116. package/dist/src/traces/addTraceAnnotation.js.map +1 -0
  117. package/dist/src/traces/addTraceNote.d.ts +5 -2
  118. package/dist/src/traces/addTraceNote.d.ts.map +1 -1
  119. package/dist/src/traces/addTraceNote.js +7 -3
  120. package/dist/src/traces/addTraceNote.js.map +1 -1
  121. package/dist/src/traces/index.d.ts +3 -0
  122. package/dist/src/traces/index.d.ts.map +1 -1
  123. package/dist/src/traces/index.js +2 -0
  124. package/dist/src/traces/index.js.map +1 -1
  125. package/dist/src/traces/logTraceAnnotations.d.ts +53 -0
  126. package/dist/src/traces/logTraceAnnotations.d.ts.map +1 -0
  127. package/dist/src/traces/logTraceAnnotations.js +53 -0
  128. package/dist/src/traces/logTraceAnnotations.js.map +1 -0
  129. package/dist/src/traces/types.d.ts +24 -0
  130. package/dist/src/traces/types.d.ts.map +1 -0
  131. package/dist/src/traces/types.js +42 -0
  132. package/dist/src/traces/types.js.map +1 -0
  133. package/dist/src/types/datasets.d.ts +10 -1
  134. package/dist/src/types/datasets.d.ts.map +1 -1
  135. package/dist/src/utils/apiErrorUtils.d.ts +2 -0
  136. package/dist/src/utils/apiErrorUtils.d.ts.map +1 -0
  137. package/dist/src/utils/apiErrorUtils.js +22 -0
  138. package/dist/src/utils/apiErrorUtils.js.map +1 -0
  139. package/dist/src/utils/formatPromptMessages.d.ts.map +1 -1
  140. package/dist/src/utils/getPromptBySelector.d.ts.map +1 -1
  141. package/dist/tsconfig.tsbuildinfo +1 -1
  142. package/package.json +3 -3
  143. package/src/__generated__/api/v1.ts +313 -19
  144. package/src/constants/serverRequirements.ts +17 -0
  145. package/src/datasets/appendDatasetExamples.ts +26 -5
  146. package/src/datasets/createDataset.ts +66 -18
  147. package/src/datasets/getDatasetExamples.ts +7 -4
  148. package/src/datasets/index.ts +0 -1
  149. package/src/experiments/resumeEvaluation.ts +2 -1
  150. package/src/experiments/resumeExperiment.ts +3 -2
  151. package/src/experiments/runExperiment.ts +1 -1
  152. package/src/sessions/addSessionNote.ts +74 -0
  153. package/src/sessions/index.ts +1 -0
  154. package/src/spans/addSpanNote.ts +7 -4
  155. package/src/traces/addTraceAnnotation.ts +65 -0
  156. package/src/traces/addTraceNote.ts +7 -3
  157. package/src/traces/index.ts +3 -0
  158. package/src/traces/logTraceAnnotations.ts +75 -0
  159. package/src/traces/types.ts +72 -0
  160. package/src/types/datasets.ts +10 -1
  161. package/src/utils/apiErrorUtils.ts +17 -0
  162. package/dist/esm/datasets/createOrGetDataset.d.ts +0 -18
  163. package/dist/esm/datasets/createOrGetDataset.d.ts.map +0 -1
  164. package/dist/esm/datasets/createOrGetDataset.js +0 -29
  165. package/dist/esm/datasets/createOrGetDataset.js.map +0 -1
  166. package/dist/src/datasets/createOrGetDataset.d.ts +0 -18
  167. package/dist/src/datasets/createOrGetDataset.d.ts.map +0 -1
  168. package/dist/src/datasets/createOrGetDataset.js +0 -32
  169. package/dist/src/datasets/createOrGetDataset.js.map +0 -1
  170. package/src/datasets/createOrGetDataset.ts +0 -40
@@ -1,8 +1,10 @@
1
1
  import invariant from "tiny-invariant";
2
2
 
3
3
  import { createClient } from "../client";
4
+ import { DATASET_UPLOAD_EXAMPLE_IDS } from "../constants/serverRequirements";
4
5
  import type { ClientFn } from "../types/core";
5
6
  import type { DatasetSelector, Example } from "../types/datasets";
7
+ import { ensureServerCapability } from "../utils/serverVersionUtils";
6
8
  import { getDatasetInfo } from "./getDatasetInfo";
7
9
 
8
10
  export type AppendDatasetExamplesParams = ClientFn & {
@@ -18,8 +20,7 @@ export type AppendDatasetExamplesParams = ClientFn & {
18
20
 
19
21
  export type AppendDatasetExamplesResponse = {
20
22
  datasetId: string;
21
- // TODO: respond with the versionId
22
- // versionId: string;
23
+ versionId: string;
23
24
  };
24
25
 
25
26
  /**
@@ -36,19 +37,24 @@ export type AppendDatasetExamplesResponse = {
36
37
  * - `metadata`: Optional metadata for the example
37
38
  * - `splits`: Optional split assignment (string, array of strings, or null)
38
39
  * - `spanId`: Optional OpenTelemetry span ID to link the example back to its source span
40
+ * - `id`: Optional stable ID for the example, used to reference or update it later
39
41
  *
40
- * @returns A promise that resolves to the dataset ID
42
+ * @returns A promise that resolves to the dataset ID and version ID
41
43
  *
42
44
  * @example
43
45
  * ```ts
44
- * // Append examples with span links to an existing dataset
45
- * const { datasetId } = await appendDatasetExamples({
46
+ * const { datasetId, versionId } = await appendDatasetExamples({
46
47
  * dataset: { datasetName: "qa-dataset" },
47
48
  * examples: [
48
49
  * {
49
50
  * input: { question: "What is deep learning?" },
50
51
  * output: { answer: "Deep learning is..." },
51
52
  * spanId: "span123abc" // Links to the source span
53
+ * },
54
+ * {
55
+ * id: "my-stable-id", // Stable ID for referencing this example later
56
+ * input: { question: "What is a transformer?" },
57
+ * output: { answer: "A transformer is..." },
52
58
  * }
53
59
  * ]
54
60
  * });
@@ -73,6 +79,12 @@ export async function appendDatasetExamples({
73
79
  // Only include span_ids in the request if at least one example has a span ID
74
80
  const hasSpanIds = spanIds.some((id) => id !== null);
75
81
 
82
+ // Extract example IDs from examples, preserving null/undefined as null
83
+ const exampleIds = examples.map((example) => example?.id ?? null);
84
+
85
+ // Only include example_ids in the request if at least one example has an ID
86
+ const hasExampleIds = exampleIds.some((id) => id !== null);
87
+
76
88
  let datasetName: string;
77
89
  if ("datasetName" in dataset) {
78
90
  datasetName = dataset.datasetName;
@@ -83,6 +95,12 @@ export async function appendDatasetExamples({
83
95
  });
84
96
  datasetName = datasetInfo.name;
85
97
  }
98
+ if (hasExampleIds) {
99
+ await ensureServerCapability({
100
+ client,
101
+ requirement: DATASET_UPLOAD_EXAMPLE_IDS,
102
+ });
103
+ }
86
104
  const appendResponse = await client.POST("/v1/datasets/upload", {
87
105
  params: {
88
106
  query: {
@@ -97,11 +115,14 @@ export async function appendDatasetExamples({
97
115
  metadata,
98
116
  splits,
99
117
  ...(hasSpanIds ? { span_ids: spanIds } : {}),
118
+ ...(hasExampleIds ? { example_ids: exampleIds } : {}),
100
119
  },
101
120
  });
102
121
  invariant(appendResponse.data?.data, "Failed to append dataset examples");
103
122
  const datasetId = appendResponse.data.data.dataset_id;
123
+ const versionId = appendResponse.data.data.version_id;
104
124
  return {
105
125
  datasetId,
126
+ versionId,
106
127
  };
107
128
  }
@@ -1,8 +1,10 @@
1
1
  import invariant from "tiny-invariant";
2
2
 
3
3
  import { createClient } from "../client";
4
+ import { DATASET_UPLOAD_EXAMPLE_IDS } from "../constants/serverRequirements";
4
5
  import type { ClientFn } from "../types/core";
5
6
  import type { Example } from "../types/datasets";
7
+ import { ensureServerCapability } from "../utils/serverVersionUtils";
6
8
 
7
9
  export type CreateDatasetParams = ClientFn & {
8
10
  /**
@@ -24,7 +26,10 @@ export type CreateDatasetResponse = {
24
26
  };
25
27
 
26
28
  /**
27
- * Create a new dataset with examples.
29
+ * Create a dataset with the given examples.
30
+ *
31
+ * If a dataset with the same name already exists, it is updated to match the
32
+ * provided examples. Re-running with the same inputs is a no-op.
28
33
  *
29
34
  * @experimental this interface may change in the future
30
35
  *
@@ -82,27 +87,70 @@ export async function createDataset({
82
87
  // Only include span_ids in the request if at least one example has a span ID
83
88
  const hasSpanIds = spanIds.some((id) => id !== null);
84
89
 
85
- const createDatasetResponse = await client.POST("/v1/datasets/upload", {
86
- params: {
87
- query: {
88
- // TODO: parameterize this
89
- sync: true,
90
+ // Extract example IDs from examples, preserving null/undefined as null
91
+ const exampleIds = examples.map((example) => example?.id ?? null);
92
+
93
+ // Only include example_ids in the request if at least one example has an ID
94
+ const hasExampleIds = exampleIds.some((id) => id !== null);
95
+
96
+ const post = (action: "update" | "create") =>
97
+ client.POST("/v1/datasets/upload", {
98
+ params: {
99
+ query: {
100
+ // TODO: parameterize this
101
+ sync: true,
102
+ },
90
103
  },
91
- },
92
- body: {
93
- name,
94
- description,
95
- action: "create",
96
- inputs,
97
- outputs,
98
- metadata,
99
- splits,
100
- ...(hasSpanIds ? { span_ids: spanIds } : {}),
101
- },
102
- });
104
+ body: {
105
+ name,
106
+ description,
107
+ action,
108
+ inputs,
109
+ outputs,
110
+ metadata,
111
+ splits,
112
+ ...(hasSpanIds ? { span_ids: spanIds } : {}),
113
+ ...(hasExampleIds ? { example_ids: exampleIds } : {}),
114
+ },
115
+ });
116
+
117
+ if (hasExampleIds) {
118
+ await ensureServerCapability({
119
+ client,
120
+ requirement: DATASET_UPLOAD_EXAMPLE_IDS,
121
+ });
122
+ }
123
+ let createDatasetResponse = await post("update");
124
+ if (isUnsupportedUpdateActionResponse(createDatasetResponse)) {
125
+ warnUpdateFallback();
126
+ createDatasetResponse = await post("create");
127
+ }
103
128
  invariant(createDatasetResponse.data?.data, "Failed to create dataset");
104
129
  const datasetId = createDatasetResponse.data.data.dataset_id;
105
130
  return {
106
131
  datasetId,
107
132
  };
108
133
  }
134
+
135
+ function isUnsupportedUpdateActionResponse(result: {
136
+ response?: Response;
137
+ error?: unknown;
138
+ }): boolean {
139
+ if (result.response?.status !== 422) return false;
140
+ const body =
141
+ typeof result.error === "string"
142
+ ? result.error
143
+ : JSON.stringify(result.error ?? "");
144
+ return (
145
+ body.includes("Invalid dateset action") ||
146
+ body.includes("Invalid dataset action")
147
+ );
148
+ }
149
+
150
+ function warnUpdateFallback(): void {
151
+ // eslint-disable-next-line no-console
152
+ console.warn(
153
+ "Phoenix server does not support declarative update semantics. " +
154
+ "Upgrade to Phoenix v15 or later."
155
+ );
156
+ }
@@ -51,9 +51,12 @@ export async function getDatasetExamples({
51
51
  const examplesData = response.data.data;
52
52
  return {
53
53
  versionId: examplesData.version_id,
54
- examples: examplesData.examples.map((example) => ({
55
- ...example,
56
- updatedAt: new Date(example.updated_at),
57
- })),
54
+ examples: examplesData.examples.map(
55
+ ({ node_id, updated_at, ...example }) => ({
56
+ ...example,
57
+ nodeId: node_id,
58
+ updatedAt: new Date(updated_at),
59
+ })
60
+ ),
58
61
  };
59
62
  }
@@ -3,4 +3,3 @@ export * from "./getDataset";
3
3
  export * from "./getDatasetExamples";
4
4
  export * from "./appendDatasetExamples";
5
5
  export * from "./getDatasetInfo";
6
- export * from "./createOrGetDataset";
@@ -136,6 +136,7 @@ function buildIncompleteEvaluation(
136
136
  },
137
137
  datasetExample: {
138
138
  id: apiResponse.dataset_example.id,
139
+ nodeId: apiResponse.dataset_example.node_id,
139
140
  input: apiResponse.dataset_example.input,
140
141
  output: apiResponse.dataset_example.output ?? null,
141
142
  metadata: apiResponse.dataset_example.metadata || {},
@@ -728,7 +729,7 @@ async function runSingleEvaluation({
728
729
  ...objectAsAttributes({
729
730
  experiment_id: experimentId,
730
731
  experiment_run_id: experimentRun.id,
731
- dataset_example_id: datasetExample.id,
732
+ dataset_example_id: datasetExample.nodeId,
732
733
  }),
733
734
  });
734
735
 
@@ -132,6 +132,7 @@ function buildExampleFromApiResponse(
132
132
  ): ExampleWithId {
133
133
  return {
134
134
  id: apiExample.id,
135
+ nodeId: apiExample.node_id,
135
136
  input: apiExample.input,
136
137
  output: apiExample.output || null,
137
138
  metadata: apiExample.metadata || {},
@@ -618,7 +619,7 @@ async function recordTaskResult({
618
619
  },
619
620
  },
620
621
  body: {
621
- dataset_example_id: example.id,
622
+ dataset_example_id: example.nodeId,
622
623
  repetition_number: repetitionNumber,
623
624
  output: output as Record<string, unknown>,
624
625
  start_time: startTime.toISOString(),
@@ -694,7 +695,7 @@ async function runSingleTask({
694
695
  [SemanticConventions.INPUT_MIME_TYPE]: MimeType.JSON,
695
696
  ...objectAsAttributes({
696
697
  experiment_id: experimentId,
697
- dataset_example_id: example.id,
698
+ dataset_example_id: example.nodeId,
698
699
  repetition_number: repetitionNumber,
699
700
  }),
700
701
  });
@@ -509,7 +509,7 @@ function runTaskWithExamples({
509
509
  },
510
510
  },
511
511
  body: {
512
- dataset_example_id: example.id,
512
+ dataset_example_id: example.nodeId,
513
513
  output: thisRun.output,
514
514
  repetition_number: repetitionNumber,
515
515
  start_time: thisRun.startTime.toISOString(),
@@ -0,0 +1,74 @@
1
+ import { createClient } from "../client";
2
+ import { ADD_SESSION_NOTE } from "../constants/serverRequirements";
3
+ import type { ClientFn } from "../types/core";
4
+ import { formatApiError } from "../utils/apiErrorUtils";
5
+ import { ensureServerCapability } from "../utils/serverVersionUtils";
6
+
7
+ /**
8
+ * Parameters for a single session note.
9
+ */
10
+ export interface SessionNote {
11
+ /**
12
+ * The session ID used to track a conversation, thread, or session.
13
+ */
14
+ sessionId: string;
15
+ /**
16
+ * The note text to add to the session.
17
+ */
18
+ note: string;
19
+ }
20
+
21
+ /**
22
+ * Parameters to add a session note.
23
+ */
24
+ export interface AddSessionNoteParams extends ClientFn {
25
+ sessionNote: SessionNote;
26
+ }
27
+
28
+ /**
29
+ * Add a note to a session.
30
+ *
31
+ * Notes are a special type of annotation that allow multiple entries per session.
32
+ * Each note gets a unique UUIDv4 identifier.
33
+ *
34
+ * @param params - The parameters to add a session note.
35
+ * @returns The ID of the created note annotation.
36
+ *
37
+ * @requires Phoenix server >= 14.17.0
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const result = await addSessionNote({
42
+ * sessionNote: {
43
+ * sessionId: "my-session",
44
+ * note: "Needs review"
45
+ * }
46
+ * });
47
+ * ```
48
+ */
49
+ export async function addSessionNote({
50
+ client: _client,
51
+ sessionNote,
52
+ }: AddSessionNoteParams): Promise<{ id: string }> {
53
+ const client = _client ?? createClient();
54
+ await ensureServerCapability({ client, requirement: ADD_SESSION_NOTE });
55
+
56
+ const { data, error } = await client.POST("/v1/session_notes", {
57
+ body: {
58
+ data: {
59
+ session_id: sessionNote.sessionId.trim(),
60
+ note: sessionNote.note,
61
+ },
62
+ },
63
+ });
64
+
65
+ if (error) {
66
+ throw new Error(`Failed to add session note: ${formatApiError(error)}`);
67
+ }
68
+
69
+ if (!data?.data) {
70
+ throw new Error("Failed to add session note: no data returned");
71
+ }
72
+
73
+ return data.data;
74
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./addSessionAnnotation";
2
+ export * from "./addSessionNote";
2
3
  export * from "./deleteSession";
3
4
  export * from "./deleteSessions";
4
5
  export * from "./getSession";
@@ -1,5 +1,6 @@
1
1
  import { createClient } from "../client";
2
2
  import type { ClientFn } from "../types/core";
3
+ import { formatApiError } from "../utils/apiErrorUtils";
3
4
 
4
5
  /**
5
6
  * Parameters for a single span note
@@ -25,9 +26,11 @@ export interface AddSpanNoteParams extends ClientFn {
25
26
  /**
26
27
  * Add a note to a span.
27
28
  *
28
- * Notes are a special type of annotation that allow multiple entries per span
29
- * (unlike regular annotations which are unique by name and identifier).
30
- * Each note gets a unique UUIDv4 identifier.
29
+ * Notes are append-only: each call creates a new note with an auto-generated
30
+ * UUIDv4 identifier, so multiple notes accumulate on the same span. Structured
31
+ * annotations, by contrast, are keyed by `(name, spanId, identifier)` — to keep
32
+ * multiple structured annotations with the same name on a span, supply distinct
33
+ * identifiers; otherwise re-writing the same name overwrites the existing one.
31
34
  *
32
35
  * @param params - The parameters to add a span note
33
36
  * @returns The ID of the created note annotation
@@ -58,7 +61,7 @@ export async function addSpanNote({
58
61
  });
59
62
 
60
63
  if (error) {
61
- throw new Error(`Failed to add span note: ${error}`);
64
+ throw new Error(`Failed to add span note: ${formatApiError(error)}`);
62
65
  }
63
66
 
64
67
  if (!data?.data) {
@@ -0,0 +1,65 @@
1
+ import { createClient } from "../client";
2
+ import type { ClientFn } from "../types/core";
3
+ import type { TraceAnnotation } from "./types";
4
+ import { toTraceAnnotationData } from "./types";
5
+
6
+ /**
7
+ * Parameters to add a trace annotation
8
+ */
9
+ export interface AddTraceAnnotationParams extends ClientFn {
10
+ traceAnnotation: TraceAnnotation;
11
+ /**
12
+ * If true, the request will be fulfilled synchronously and return the annotation ID.
13
+ * If false, the request will be processed asynchronously and return null.
14
+ * @default false
15
+ */
16
+ sync?: boolean;
17
+ }
18
+
19
+ /**
20
+ * Add an annotation to a trace.
21
+ *
22
+ * The annotation can be of type "LLM", "CODE", or "HUMAN" and can include a label, score, and metadata.
23
+ * If an identifier is provided and an annotation with that identifier already exists, it will be updated.
24
+ *
25
+ * @param params - The parameters to add a trace annotation
26
+ * @returns The ID of the created or updated annotation
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const result = await addTraceAnnotation({
31
+ * traceAnnotation: {
32
+ * traceId: "abc123",
33
+ * name: "correctness",
34
+ * label: "correct",
35
+ * score: 1.0,
36
+ * annotatorKind: "HUMAN",
37
+ * identifier: "custom_id_123",
38
+ * metadata: { reviewer: "alice" }
39
+ * },
40
+ * sync: true,
41
+ * });
42
+ * ```
43
+ */
44
+ export async function addTraceAnnotation({
45
+ client: _client,
46
+ traceAnnotation,
47
+ sync = false,
48
+ }: AddTraceAnnotationParams): Promise<{ id: string } | null> {
49
+ const client = _client ?? createClient();
50
+
51
+ const { data, error } = await client.POST("/v1/trace_annotations", {
52
+ params: {
53
+ query: { sync },
54
+ },
55
+ body: {
56
+ data: [toTraceAnnotationData(traceAnnotation)],
57
+ },
58
+ });
59
+
60
+ if (error) {
61
+ throw new Error(`Failed to add trace annotation: ${error}`);
62
+ }
63
+
64
+ return data?.data?.[0] || null;
65
+ }
@@ -1,6 +1,7 @@
1
1
  import { createClient } from "../client";
2
2
  import { ADD_TRACE_NOTE } from "../constants/serverRequirements";
3
3
  import type { ClientFn } from "../types/core";
4
+ import { formatApiError } from "../utils/apiErrorUtils";
4
5
  import { ensureServerCapability } from "../utils/serverVersionUtils";
5
6
 
6
7
  /**
@@ -27,8 +28,11 @@ export interface AddTraceNoteParams extends ClientFn {
27
28
  /**
28
29
  * Add a note to a trace.
29
30
  *
30
- * Notes are a special type of annotation that allow multiple entries per trace.
31
- * Each note gets a unique timestamp-based identifier.
31
+ * Notes are append-only: each call creates a new note with an auto-generated
32
+ * UUIDv4 identifier, so multiple notes accumulate on the same trace. Structured
33
+ * annotations, by contrast, are keyed by `(name, traceId, identifier)` — to keep
34
+ * multiple structured annotations with the same name on a trace, supply distinct
35
+ * identifiers; otherwise re-writing the same name overwrites the existing one.
32
36
  *
33
37
  * @param params - The parameters to add a trace note.
34
38
  * @returns The ID of the created note annotation.
@@ -60,7 +64,7 @@ export async function addTraceNote({
60
64
  });
61
65
 
62
66
  if (error) {
63
- throw new Error(`Failed to add trace note: ${error}`);
67
+ throw new Error(`Failed to add trace note: ${formatApiError(error)}`);
64
68
  }
65
69
 
66
70
  if (!data?.data) {
@@ -1,2 +1,5 @@
1
+ export * from "./addTraceAnnotation";
1
2
  export * from "./addTraceNote";
2
3
  export * from "./getTraces";
4
+ export * from "./logTraceAnnotations";
5
+ export type { TraceAnnotation } from "./types";
@@ -0,0 +1,75 @@
1
+ import { createClient } from "../client";
2
+ import type { ClientFn } from "../types/core";
3
+ import type { TraceAnnotation } from "./types";
4
+ import { toTraceAnnotationData } from "./types";
5
+
6
+ /**
7
+ * Parameters to log multiple trace annotations
8
+ */
9
+ export interface LogTraceAnnotationsParams extends ClientFn {
10
+ /**
11
+ * The trace annotations to log
12
+ */
13
+ traceAnnotations: TraceAnnotation[];
14
+ /**
15
+ * If true, the request will be fulfilled synchronously and return the annotation IDs.
16
+ * If false, the request will be processed asynchronously and return null.
17
+ * @default false
18
+ */
19
+ sync?: boolean;
20
+ }
21
+
22
+ /**
23
+ * Log multiple trace annotations in a single request.
24
+ *
25
+ * Each annotation can be of type "LLM", "CODE", or "HUMAN" and can include a label, score, and metadata.
26
+ * If an identifier is provided and an annotation with that identifier already exists, it will be updated.
27
+ *
28
+ * @param params - The parameters to log trace annotations
29
+ * @returns The IDs of the created or updated annotations
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const results = await logTraceAnnotations({
34
+ * traceAnnotations: [
35
+ * {
36
+ * traceId: "abc123",
37
+ * name: "correctness",
38
+ * label: "correct",
39
+ * score: 1.0,
40
+ * annotatorKind: "HUMAN",
41
+ * },
42
+ * {
43
+ * traceId: "def456",
44
+ * name: "faithfulness",
45
+ * label: "faithful",
46
+ * score: 0.9,
47
+ * annotatorKind: "LLM",
48
+ * },
49
+ * ],
50
+ * sync: true,
51
+ * });
52
+ * ```
53
+ */
54
+ export async function logTraceAnnotations({
55
+ client: _client,
56
+ traceAnnotations,
57
+ sync = false,
58
+ }: LogTraceAnnotationsParams): Promise<{ id: string }[]> {
59
+ const client = _client ?? createClient();
60
+
61
+ const { data, error } = await client.POST("/v1/trace_annotations", {
62
+ params: {
63
+ query: { sync },
64
+ },
65
+ body: {
66
+ data: traceAnnotations.map(toTraceAnnotationData),
67
+ },
68
+ });
69
+
70
+ if (error) {
71
+ throw new Error(`Failed to log trace annotations: ${error}`);
72
+ }
73
+
74
+ return data?.data || [];
75
+ }
@@ -0,0 +1,72 @@
1
+ import type { paths } from "../__generated__/api/v1";
2
+ import type { Annotation, AnnotationResult } from "../types/annotations";
3
+
4
+ type TraceAnnotationData =
5
+ paths["/v1/trace_annotations"]["post"]["requestBody"]["content"]["application/json"]["data"][0];
6
+
7
+ /**
8
+ * Parameters for a single trace annotation
9
+ */
10
+ export interface TraceAnnotation extends Annotation {
11
+ /**
12
+ * The OpenTelemetry Trace ID (hex format without 0x prefix)
13
+ */
14
+ traceId: string;
15
+ /**
16
+ * The kind of annotator used for the annotation
17
+ * Can be "HUMAN", "LLM", or "CODE"
18
+ * @default "HUMAN"
19
+ */
20
+ annotatorKind?: TraceAnnotationData["annotator_kind"];
21
+ }
22
+
23
+ /**
24
+ * Build and validate annotation result fields
25
+ */
26
+ function buildTraceAnnotationResult(
27
+ annotation: Pick<TraceAnnotation, "label" | "score" | "explanation">
28
+ ): AnnotationResult {
29
+ const result: AnnotationResult = {};
30
+
31
+ if (annotation.label !== undefined) {
32
+ result.label = annotation.label.trim() || null;
33
+ }
34
+ if (annotation.score !== undefined) {
35
+ result.score = annotation.score;
36
+ }
37
+ if (annotation.explanation !== undefined) {
38
+ result.explanation = annotation.explanation.trim() || null;
39
+ }
40
+
41
+ const hasValidResult =
42
+ result.label || result.score !== undefined || result.explanation;
43
+ if (!hasValidResult) {
44
+ throw new Error(
45
+ "At least one of label, score, or explanation must be provided for trace annotation"
46
+ );
47
+ }
48
+ return result;
49
+ }
50
+
51
+ /**
52
+ * Convert a TraceAnnotation to the API format
53
+ */
54
+ export function toTraceAnnotationData(
55
+ annotation: TraceAnnotation
56
+ ): TraceAnnotationData {
57
+ if (annotation.name.trim() === "note") {
58
+ throw new Error(
59
+ 'The name "note" is reserved for trace and span notes. Use addTraceNote instead.'
60
+ );
61
+ }
62
+ const result = buildTraceAnnotationResult(annotation);
63
+
64
+ return {
65
+ trace_id: annotation.traceId.trim(),
66
+ name: annotation.name.trim(),
67
+ annotator_kind: annotation.annotatorKind ?? "HUMAN",
68
+ result,
69
+ metadata: annotation.metadata ?? null,
70
+ identifier: annotation.identifier?.trim() ?? "",
71
+ };
72
+ }
@@ -58,12 +58,21 @@ export interface Example {
58
58
  * in the Phoenix UI, enabling traceability from datasets back to traces.
59
59
  */
60
60
  spanId?: string | null;
61
+ /**
62
+ * Optional user-provided ID for this example.
63
+ * If not provided, the server will generate an ID.
64
+ */
65
+ id?: string | null;
61
66
  }
62
67
 
63
68
  /**
64
69
  * An example that has been synced to the server
65
70
  */
66
- export interface ExampleWithId extends Example, Node {
71
+ export interface ExampleWithId extends Example {
72
+ /** The user-provided or server-generated ID for this example. */
73
+ id: string;
74
+ /** Server-generated node ID for this example. */
75
+ nodeId: string;
67
76
  updatedAt: Date;
68
77
  }
69
78
 
@@ -0,0 +1,17 @@
1
+ export function formatApiError(error: unknown): string {
2
+ if (typeof error === "string") {
3
+ return error;
4
+ }
5
+ if (error && typeof error === "object") {
6
+ const errorWithDetail = error as { detail?: unknown };
7
+ if (typeof errorWithDetail.detail === "string") {
8
+ return errorWithDetail.detail;
9
+ }
10
+ try {
11
+ return JSON.stringify(error);
12
+ } catch {
13
+ return String(error);
14
+ }
15
+ }
16
+ return String(error);
17
+ }