@arizeai/phoenix-client 6.6.2 → 6.8.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 (55) hide show
  1. package/dist/esm/__generated__/api/v1.d.ts +97 -1
  2. package/dist/esm/__generated__/api/v1.d.ts.map +1 -1
  3. package/dist/esm/constants/serverRequirements.d.ts +2 -0
  4. package/dist/esm/constants/serverRequirements.d.ts.map +1 -1
  5. package/dist/esm/constants/serverRequirements.js +15 -0
  6. package/dist/esm/constants/serverRequirements.js.map +1 -1
  7. package/dist/esm/spans/addSpanNote.d.ts +1 -1
  8. package/dist/esm/spans/addSpanNote.js +1 -1
  9. package/dist/esm/spans/getSpans.d.ts +10 -1
  10. package/dist/esm/spans/getSpans.d.ts.map +1 -1
  11. package/dist/esm/spans/getSpans.js +40 -2
  12. package/dist/esm/spans/getSpans.js.map +1 -1
  13. package/dist/esm/traces/addTraceNote.d.ts +43 -0
  14. package/dist/esm/traces/addTraceNote.d.ts.map +1 -0
  15. package/dist/esm/traces/addTraceNote.js +42 -0
  16. package/dist/esm/traces/addTraceNote.js.map +1 -0
  17. package/dist/esm/traces/index.d.ts +1 -0
  18. package/dist/esm/traces/index.d.ts.map +1 -1
  19. package/dist/esm/traces/index.js +1 -0
  20. package/dist/esm/traces/index.js.map +1 -1
  21. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  22. package/dist/esm/utils/formatPromptMessages.d.ts.map +1 -1
  23. package/dist/esm/utils/getPromptBySelector.d.ts.map +1 -1
  24. package/dist/src/__generated__/api/v1.d.ts +97 -1
  25. package/dist/src/__generated__/api/v1.d.ts.map +1 -1
  26. package/dist/src/constants/serverRequirements.d.ts +2 -0
  27. package/dist/src/constants/serverRequirements.d.ts.map +1 -1
  28. package/dist/src/constants/serverRequirements.js +16 -1
  29. package/dist/src/constants/serverRequirements.js.map +1 -1
  30. package/dist/src/spans/addSpanNote.d.ts +1 -1
  31. package/dist/src/spans/addSpanNote.js +1 -1
  32. package/dist/src/spans/getSpans.d.ts +10 -1
  33. package/dist/src/spans/getSpans.d.ts.map +1 -1
  34. package/dist/src/spans/getSpans.js +39 -1
  35. package/dist/src/spans/getSpans.js.map +1 -1
  36. package/dist/src/traces/addTraceNote.d.ts +43 -0
  37. package/dist/src/traces/addTraceNote.d.ts.map +1 -0
  38. package/dist/src/traces/addTraceNote.js +45 -0
  39. package/dist/src/traces/addTraceNote.js.map +1 -0
  40. package/dist/src/traces/index.d.ts +1 -0
  41. package/dist/src/traces/index.d.ts.map +1 -1
  42. package/dist/src/traces/index.js +1 -0
  43. package/dist/src/traces/index.js.map +1 -1
  44. package/dist/src/utils/formatPromptMessages.d.ts.map +1 -1
  45. package/dist/src/utils/getPromptBySelector.d.ts.map +1 -1
  46. package/dist/tsconfig.tsbuildinfo +1 -1
  47. package/docs/span-annotations.mdx +1 -1
  48. package/docs/spans.mdx +34 -1
  49. package/package.json +3 -3
  50. package/src/__generated__/api/v1.ts +97 -1
  51. package/src/constants/serverRequirements.ts +17 -0
  52. package/src/spans/addSpanNote.ts +1 -1
  53. package/src/spans/getSpans.ts +57 -0
  54. package/src/traces/addTraceNote.ts +71 -0
  55. package/src/traces/index.ts +1 -0
@@ -226,7 +226,7 @@ do {
226
226
 
227
227
  ## Span Notes
228
228
 
229
- `addSpanNote` attaches a free-text comment to a span. Unlike structured annotations (which are keyed by `name` + `identifier`), notes are append-only — each call creates a new note with a unique timestamp-based identifier, so multiple notes naturally accumulate on the same span.
229
+ `addSpanNote` attaches a free-text comment to a span. Unlike structured annotations (which are keyed by `name` + `identifier`), notes are append-only — each call creates a new note with a unique UUIDv4 identifier, so multiple notes naturally accumulate on the same span.
230
230
 
231
231
  This makes notes a good mechanism for **open coding**: reviewers can leave qualitative observations on spans during exploratory analysis without needing to define annotation names or scoring rubrics up front. The accumulated notes can later inform what structured annotations to create.
232
232
 
package/docs/spans.mdx CHANGED
@@ -32,6 +32,29 @@ for (const span of result.spans) {
32
32
  }
33
33
  ```
34
34
 
35
+ ### Filtering by Attributes
36
+
37
+ Use the `attributes` parameter to filter spans by OpenInference attribute key-value pairs. Multiple entries are ANDed together. The JS type of the value determines how the stored attribute is matched: a `number` matches a stored integer or float, a `boolean` matches a stored boolean, and a `string` matches a stored string.
38
+
39
+ ```ts
40
+ // Spans from a specific model
41
+ const result = await getSpans({
42
+ project: { projectName: "support-bot" },
43
+ attributes: { "llm.model_name": "gpt-4o" },
44
+ });
45
+
46
+ // Spans for a session and a specific user (AND semantics)
47
+ const result = await getSpans({
48
+ project: { projectName: "support-bot" },
49
+ attributes: {
50
+ "session.id": "sess-abc123",
51
+ "user.id": "user-42",
52
+ },
53
+ });
54
+ ```
55
+
56
+ Non-finite number values (`Infinity`, `NaN`) throw a `RangeError`. Requires Phoenix server ≥ 14.9.0.
57
+
35
58
  ## Root Span Queries
36
59
 
37
60
  Use `parentId: null` to limit results to root spans only.
@@ -45,8 +68,12 @@ const rootSpans = await getSpans({
45
68
 
46
69
  ## Span Maintenance
47
70
 
71
+ ### Adding Notes
72
+
73
+ Use `addSpanNote` to attach a free-text note to a span. Multiple notes can be added to the same span — they are independent entries, each with its own timestamp.
74
+
48
75
  ```ts
49
- import { addSpanNote, deleteSpan } from "@arizeai/phoenix-client/spans";
76
+ import { addSpanNote } from "@arizeai/phoenix-client/spans";
50
77
 
51
78
  await addSpanNote({
52
79
  spanNote: {
@@ -54,6 +81,12 @@ await addSpanNote({
54
81
  note: "Escalated due to failed retrieval",
55
82
  },
56
83
  });
84
+ ```
85
+
86
+ ### Deleting Spans
87
+
88
+ ```ts
89
+ import { deleteSpan } from "@arizeai/phoenix-client/spans";
57
90
 
58
91
  await deleteSpan({
59
92
  spanIdentifier: "abc123def456",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arizeai/phoenix-client",
3
- "version": "6.6.2",
3
+ "version": "6.8.0",
4
4
  "description": "A client for the Phoenix API",
5
5
  "keywords": [
6
6
  "arize",
@@ -79,8 +79,8 @@
79
79
  "openapi-fetch": "^0.12.5",
80
80
  "tiny-invariant": "^1.3.3",
81
81
  "zod": "^4.0.14",
82
- "@arizeai/phoenix-config": "0.1.3",
83
- "@arizeai/phoenix-otel": "1.0.0"
82
+ "@arizeai/phoenix-otel": "1.0.0",
83
+ "@arizeai/phoenix-config": "0.1.3"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@ai-sdk/openai": "^3.0.29",
@@ -458,6 +458,26 @@ export interface paths {
458
458
  patch?: never;
459
459
  trace?: never;
460
460
  };
461
+ "/v1/trace_notes": {
462
+ parameters: {
463
+ query?: never;
464
+ header?: never;
465
+ path?: never;
466
+ cookie?: never;
467
+ };
468
+ get?: never;
469
+ put?: never;
470
+ /**
471
+ * Create a trace note
472
+ * @description Add a note annotation to a trace. Notes are special annotations that allow multiple entries per trace (unlike regular annotations which are unique by name and identifier). Each note gets a unique UUIDv4 identifier.
473
+ */
474
+ post: operations["createTraceNote"];
475
+ delete?: never;
476
+ options?: never;
477
+ head?: never;
478
+ patch?: never;
479
+ trace?: never;
480
+ };
461
481
  "/v1/traces/{trace_identifier}": {
462
482
  parameters: {
463
483
  query?: never;
@@ -554,7 +574,7 @@ export interface paths {
554
574
  put?: never;
555
575
  /**
556
576
  * Create a span note
557
- * @description Add a note annotation to a span. Notes are special annotations that allow multiple entries per span (unlike regular annotations which are unique by name and identifier). Each note gets a unique timestamp-based identifier.
577
+ * @description Add a note annotation to a span. Notes are special annotations that allow multiple entries per span (unlike regular annotations which are unique by name and identifier). Each note gets a unique UUIDv4 identifier.
558
578
  */
559
579
  post: operations["createSpanNote"];
560
580
  delete?: never;
@@ -1291,6 +1311,14 @@ export interface components {
1291
1311
  */
1292
1312
  total_queued: number;
1293
1313
  };
1314
+ /** CreateTraceNoteRequestBody */
1315
+ CreateTraceNoteRequestBody: {
1316
+ data: components["schemas"]["TraceNoteData"];
1317
+ };
1318
+ /** CreateTraceNoteResponseBody */
1319
+ CreateTraceNoteResponseBody: {
1320
+ data: components["schemas"]["InsertedTraceAnnotation"];
1321
+ };
1294
1322
  /** CreateUserRequestBody */
1295
1323
  CreateUserRequestBody: {
1296
1324
  /** User */
@@ -3382,6 +3410,19 @@ export interface components {
3382
3410
  /** Spans */
3383
3411
  spans?: components["schemas"]["TraceSpanData"][] | null;
3384
3412
  };
3413
+ /** TraceNoteData */
3414
+ TraceNoteData: {
3415
+ /**
3416
+ * Trace Id
3417
+ * @description OpenTelemetry Trace ID (hex format w/o 0x prefix)
3418
+ */
3419
+ trace_id: string;
3420
+ /**
3421
+ * Note
3422
+ * @description The note text to add to the trace
3423
+ */
3424
+ note: string;
3425
+ };
3385
3426
  /** TraceSpanData */
3386
3427
  TraceSpanData: {
3387
3428
  /** Id */
@@ -5082,6 +5123,57 @@ export interface operations {
5082
5123
  };
5083
5124
  };
5084
5125
  };
5126
+ createTraceNote: {
5127
+ parameters: {
5128
+ query?: never;
5129
+ header?: never;
5130
+ path?: never;
5131
+ cookie?: never;
5132
+ };
5133
+ requestBody: {
5134
+ content: {
5135
+ "application/json": components["schemas"]["CreateTraceNoteRequestBody"];
5136
+ };
5137
+ };
5138
+ responses: {
5139
+ /** @description Trace note created successfully */
5140
+ 200: {
5141
+ headers: {
5142
+ [name: string]: unknown;
5143
+ };
5144
+ content: {
5145
+ "application/json": components["schemas"]["CreateTraceNoteResponseBody"];
5146
+ };
5147
+ };
5148
+ /** @description Forbidden */
5149
+ 403: {
5150
+ headers: {
5151
+ [name: string]: unknown;
5152
+ };
5153
+ content: {
5154
+ "text/plain": string;
5155
+ };
5156
+ };
5157
+ /** @description Trace not found */
5158
+ 404: {
5159
+ headers: {
5160
+ [name: string]: unknown;
5161
+ };
5162
+ content: {
5163
+ "text/plain": string;
5164
+ };
5165
+ };
5166
+ /** @description Validation Error */
5167
+ 422: {
5168
+ headers: {
5169
+ [name: string]: unknown;
5170
+ };
5171
+ content: {
5172
+ "application/json": components["schemas"]["HTTPValidationError"];
5173
+ };
5174
+ };
5175
+ };
5176
+ };
5085
5177
  deleteTrace: {
5086
5178
  parameters: {
5087
5179
  query?: never;
@@ -5149,6 +5241,8 @@ export interface operations {
5149
5241
  name?: string[] | null;
5150
5242
  /** @description Filter by status code(s). Values: OK, ERROR, UNSET */
5151
5243
  status_code?: string[] | null;
5244
+ /** @description Filter spans by `key:value`. Key is a dot-path (e.g. `user.id`, `metadata.tier`). Value is JSON-parsed: `k:12345` is int, `k:true` is bool, otherwise string (`k:user-42`). To match a numeric- or boolean-looking STRING, JSON-quote it: `user.id:"12345"` (URL-encoded `%2212345%22`). Split is on the first `:` only, so values may contain colons (`session.id:sess:abc:123`, ISO timestamps). Repeat the param to AND filters. List-valued attributes (e.g. `tag.tags`) cannot be matched here. Returns 422 on malformed input (missing colon, empty key/value, or list/dict/null value). */
5245
+ attribute?: string[] | null;
5152
5246
  };
5153
5247
  header?: never;
5154
5248
  path: {
@@ -5218,6 +5312,8 @@ export interface operations {
5218
5312
  span_kind?: string[] | null;
5219
5313
  /** @description Filter by status code(s). Values: OK, ERROR, UNSET */
5220
5314
  status_code?: string[] | null;
5315
+ /** @description Filter spans by `key:value`. Key is a dot-path (e.g. `user.id`, `metadata.tier`). Value is JSON-parsed: `k:12345` is int, `k:true` is bool, otherwise string (`k:user-42`). To match a numeric- or boolean-looking STRING, JSON-quote it: `user.id:"12345"` (URL-encoded `%2212345%22`). Split is on the first `:` only, so values may contain colons (`session.id:sess:abc:123`, ISO timestamps). Repeat the param to AND filters. List-valued attributes (e.g. `tag.tags`) cannot be matched here. Returns 422 on malformed input (missing colon, empty key/value, or list/dict/null value). */
5316
+ attribute?: string[] | null;
5221
5317
  };
5222
5318
  header?: never;
5223
5319
  path: {
@@ -53,6 +53,13 @@ export const ANNOTATE_SESSIONS: RouteRequirement = {
53
53
  minServerVersion: [12, 0, 0],
54
54
  };
55
55
 
56
+ export const ADD_TRACE_NOTE: RouteRequirement = {
57
+ kind: "route",
58
+ method: "POST",
59
+ path: "/v1/trace_notes",
60
+ minServerVersion: [14, 13, 0],
61
+ };
62
+
56
63
  export const GET_SPANS_TRACE_IDS: ParameterRequirement = {
57
64
  kind: "parameter",
58
65
  parameterName: "trace_id",
@@ -76,6 +83,14 @@ export const LIST_PROJECT_TRACES: RouteRequirement = {
76
83
  minServerVersion: [13, 15, 0],
77
84
  };
78
85
 
86
+ export const GET_SPANS_BY_ATTRIBUTE: ParameterRequirement = {
87
+ kind: "parameter",
88
+ parameterName: "attribute",
89
+ parameterLocation: "query",
90
+ route: "GET /v1/projects/{id}/spans",
91
+ minServerVersion: [14, 9, 0],
92
+ };
93
+
79
94
  /**
80
95
  * Aggregate list of every known capability requirement.
81
96
  *
@@ -88,7 +103,9 @@ export const ALL_REQUIREMENTS: readonly CapabilityRequirement[] = [
88
103
  DELETE_SESSIONS,
89
104
  LIST_PROJECT_SESSIONS,
90
105
  ANNOTATE_SESSIONS,
106
+ ADD_TRACE_NOTE,
91
107
  GET_SPANS_TRACE_IDS,
92
108
  GET_SPANS_FILTERS,
109
+ GET_SPANS_BY_ATTRIBUTE,
93
110
  LIST_PROJECT_TRACES,
94
111
  ] as const;
@@ -27,7 +27,7 @@ export interface AddSpanNoteParams extends ClientFn {
27
27
  *
28
28
  * Notes are a special type of annotation that allow multiple entries per span
29
29
  * (unlike regular annotations which are unique by name and identifier).
30
- * Each note gets a unique timestamp-based identifier.
30
+ * Each note gets a unique UUIDv4 identifier.
31
31
  *
32
32
  * @param params - The parameters to add a span note
33
33
  * @returns The ID of the created note annotation
@@ -1,6 +1,7 @@
1
1
  import type { operations } from "../__generated__/api/v1";
2
2
  import { createClient } from "../client";
3
3
  import {
4
+ GET_SPANS_BY_ATTRIBUTE,
4
5
  GET_SPANS_FILTERS,
5
6
  GET_SPANS_TRACE_IDS,
6
7
  } from "../constants/serverRequirements";
@@ -10,6 +11,38 @@ import { resolveProjectIdentifier } from "../types/projects";
10
11
  import type { SpanKindFilter, SpanStatusCode } from "../types/spans";
11
12
  import { ensureServerCapability } from "../utils/serverVersionUtils";
12
13
 
14
+ export type SpanAttributeValue = string | number | boolean;
15
+ export type SpanAttributes = Record<string, SpanAttributeValue>;
16
+
17
+ function serializeAttributeValue(value: SpanAttributeValue): string {
18
+ if (typeof value === "boolean") {
19
+ return JSON.stringify(value);
20
+ }
21
+ if (typeof value === "number") {
22
+ if (!Number.isFinite(value)) {
23
+ throw new RangeError(
24
+ `Non-finite attribute filter values are not supported: ${value}`
25
+ );
26
+ }
27
+ return String(value);
28
+ }
29
+ if (value === "") {
30
+ return JSON.stringify(value);
31
+ }
32
+ try {
33
+ const parsed = JSON.parse(value);
34
+ return typeof parsed === "string" ? value : JSON.stringify(value);
35
+ } catch {
36
+ return value;
37
+ }
38
+ }
39
+
40
+ function serializeAttributes(attributes: SpanAttributes): string[] {
41
+ return Object.entries(attributes).map(
42
+ ([key, value]) => `${key}:${serializeAttributeValue(value)}`
43
+ );
44
+ }
45
+
13
46
  /**
14
47
  * Parameters to get spans from a project using auto-generated types
15
48
  */
@@ -34,6 +67,12 @@ export interface GetSpansParams extends ClientFn {
34
67
  spanKind?: SpanKindFilter | SpanKindFilter[] | null;
35
68
  /** Filter by status code(s) (OK, ERROR, UNSET) */
36
69
  statusCode?: SpanStatusCode | SpanStatusCode[] | null;
70
+ /**
71
+ * Filter by attribute key/value pairs with AND semantics. The value's JS type
72
+ * selects how the stored attribute is matched: `{ "user.id": 12345 }` matches
73
+ * a stored integer, while `{ "user.id": "12345" }` matches a stored string.
74
+ */
75
+ attributes?: SpanAttributes | null;
37
76
  }
38
77
 
39
78
  export type GetSpansResponse = operations["getSpans"]["responses"]["200"];
@@ -57,6 +96,7 @@ export type GetSpansResult = {
57
96
  * @returns A paginated response containing spans and optional next cursor
58
97
  *
59
98
  * @requires Phoenix server >= 13.9.0 when filtering by `traceIds`
99
+ * @requires Phoenix server >= 14.9.0 when filtering by `attributes`
60
100
  *
61
101
  * @example
62
102
  * ```ts
@@ -116,14 +156,27 @@ export async function getSpans({
116
156
  name,
117
157
  spanKind,
118
158
  statusCode,
159
+ attributes,
119
160
  }: GetSpansParams): Promise<GetSpansResult> {
120
161
  const client = _client ?? createClient();
162
+ const serializedAttributes =
163
+ attributes != null ? serializeAttributes(attributes) : undefined;
164
+ const attributeFilters =
165
+ serializedAttributes != null && serializedAttributes.length > 0
166
+ ? serializedAttributes
167
+ : undefined;
121
168
  if (traceIds) {
122
169
  await ensureServerCapability({ client, requirement: GET_SPANS_TRACE_IDS });
123
170
  }
124
171
  if (name != null || spanKind != null || statusCode != null) {
125
172
  await ensureServerCapability({ client, requirement: GET_SPANS_FILTERS });
126
173
  }
174
+ if (attributeFilters != null) {
175
+ await ensureServerCapability({
176
+ client,
177
+ requirement: GET_SPANS_BY_ATTRIBUTE,
178
+ });
179
+ }
127
180
  const projectIdentifier = resolveProjectIdentifier(project);
128
181
 
129
182
  const params: NonNullable<operations["getSpans"]["parameters"]["query"]> = {
@@ -163,6 +216,10 @@ export async function getSpans({
163
216
  params.status_code = Array.isArray(statusCode) ? statusCode : [statusCode];
164
217
  }
165
218
 
219
+ if (attributeFilters != null) {
220
+ params.attribute = attributeFilters;
221
+ }
222
+
166
223
  const { data, error } = await client.GET(
167
224
  "/v1/projects/{project_identifier}/spans",
168
225
  {
@@ -0,0 +1,71 @@
1
+ import { createClient } from "../client";
2
+ import { ADD_TRACE_NOTE } from "../constants/serverRequirements";
3
+ import type { ClientFn } from "../types/core";
4
+ import { ensureServerCapability } from "../utils/serverVersionUtils";
5
+
6
+ /**
7
+ * Parameters for a single trace note.
8
+ */
9
+ export interface TraceNote {
10
+ /**
11
+ * The OpenTelemetry trace ID (hex format without 0x prefix).
12
+ */
13
+ traceId: string;
14
+ /**
15
+ * The note text to add to the trace.
16
+ */
17
+ note: string;
18
+ }
19
+
20
+ /**
21
+ * Parameters to add a trace note.
22
+ */
23
+ export interface AddTraceNoteParams extends ClientFn {
24
+ traceNote: TraceNote;
25
+ }
26
+
27
+ /**
28
+ * Add a note to a trace.
29
+ *
30
+ * Notes are a special type of annotation that allow multiple entries per trace.
31
+ * Each note gets a unique timestamp-based identifier.
32
+ *
33
+ * @param params - The parameters to add a trace note.
34
+ * @returns The ID of the created note annotation.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const result = await addTraceNote({
39
+ * traceNote: {
40
+ * traceId: "abc123",
41
+ * note: "Needs review"
42
+ * }
43
+ * });
44
+ * ```
45
+ */
46
+ export async function addTraceNote({
47
+ client: _client,
48
+ traceNote,
49
+ }: AddTraceNoteParams): Promise<{ id: string }> {
50
+ const client = _client ?? createClient();
51
+ await ensureServerCapability({ client, requirement: ADD_TRACE_NOTE });
52
+
53
+ const { data, error } = await client.POST("/v1/trace_notes", {
54
+ body: {
55
+ data: {
56
+ trace_id: traceNote.traceId.trim(),
57
+ note: traceNote.note,
58
+ },
59
+ },
60
+ });
61
+
62
+ if (error) {
63
+ throw new Error(`Failed to add trace note: ${error}`);
64
+ }
65
+
66
+ if (!data?.data) {
67
+ throw new Error("Failed to add trace note: no data returned");
68
+ }
69
+
70
+ return data.data;
71
+ }
@@ -1 +1,2 @@
1
+ export * from "./addTraceNote";
1
2
  export * from "./getTraces";