@arizeai/phoenix-client 6.3.0 → 6.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arizeai/phoenix-client",
3
- "version": "6.3.0",
3
+ "version": "6.4.0",
4
4
  "description": "A client for the Phoenix API",
5
5
  "keywords": [
6
6
  "arize",
@@ -0,0 +1,219 @@
1
+ import { SemanticConventions } from "@arizeai/openinference-semantic-conventions";
2
+
3
+ import type { components } from "../__generated__/api/v1";
4
+ import { createClient } from "../client";
5
+ import { getSpans } from "../spans/getSpans";
6
+ import type { ClientFn } from "../types/core";
7
+ import type { Session, SessionTrace } from "../types/sessions";
8
+ import { getSession } from "./getSession";
9
+
10
+ type Span = components["schemas"]["Span"];
11
+
12
+ const MAX_TRACE_IDS_PER_BATCH = 50;
13
+
14
+ /**
15
+ * Input or output extracted from a root span's attributes.
16
+ *
17
+ * @experimental this interface is experimental and may change in the future
18
+ */
19
+ export interface SessionTurnIO {
20
+ /** The string value of the input or output */
21
+ value: string;
22
+ /** Optional MIME type (e.g. "text/plain", "application/json") */
23
+ mimeType?: string;
24
+ }
25
+
26
+ /**
27
+ * A single turn in a session, representing one trace's root span input/output.
28
+ *
29
+ * **Note:** A "turn" is derived from a trace's root span. For input/output to appear,
30
+ * the root span must have `input.value` and `output.value` attributes set
31
+ * (per OpenInference semantic conventions). This typically requires instrumentation
32
+ * that records these attributes on the top-level span.
33
+ *
34
+ * @experimental this interface is experimental and may change in the future
35
+ */
36
+ export interface SessionTurn {
37
+ /** The trace ID for this turn */
38
+ traceId: string;
39
+ /** ISO 8601 timestamp of when the trace started */
40
+ startTime: string;
41
+ /** ISO 8601 timestamp of when the trace ended */
42
+ endTime: string;
43
+ /** Input extracted from the root span's attributes */
44
+ input?: SessionTurnIO;
45
+ /** Output extracted from the root span's attributes */
46
+ output?: SessionTurnIO;
47
+ /** The full root span, if found */
48
+ rootSpan?: Span;
49
+ }
50
+
51
+ /**
52
+ * @experimental this interface is experimental and may change in the future
53
+ */
54
+ export interface GetSessionTurnsParams extends ClientFn {
55
+ /** The session identifier: either a GlobalID or user-provided session_id string. */
56
+ sessionId: string;
57
+ }
58
+
59
+ /**
60
+ * Get the turns (root span I/O) for a session.
61
+ *
62
+ * Returns input/output extracted from root spans for each trace, along with
63
+ * the full root span. Turns are ordered by trace start_time.
64
+ *
65
+ * **Note:** A "turn" is derived from a trace's root span. For input/output to appear,
66
+ * the root span must have `input.value` and `output.value` attributes set
67
+ * (per OpenInference semantic conventions). This typically requires instrumentation
68
+ * that records these attributes on the top-level span.
69
+ *
70
+ * @experimental this function is experimental and may change in the future
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * import { getSessionTurns } from "@arizeai/phoenix-client/sessions";
75
+ *
76
+ * const turns = await getSessionTurns({ sessionId: "my-session" });
77
+ * for (const turn of turns) {
78
+ * console.log(`[${turn.startTime}] Input: ${turn.input?.value}`);
79
+ * console.log(`[${turn.startTime}] Output: ${turn.output?.value}`);
80
+ * }
81
+ * ```
82
+ */
83
+ export async function getSessionTurns({
84
+ client: _client,
85
+ sessionId,
86
+ }: GetSessionTurnsParams): Promise<SessionTurn[]> {
87
+ const client = _client ?? createClient();
88
+
89
+ const session: Session = await getSession({ client, sessionId });
90
+ const traces = session.traces;
91
+ if (traces.length === 0) {
92
+ return [];
93
+ }
94
+
95
+ const projectId = session.projectId;
96
+ const traceInfo = new Map<string, SessionTrace>(
97
+ traces.map((t) => [t.traceId, t])
98
+ );
99
+ const allTraceIds = [...traceInfo.keys()];
100
+
101
+ // Fetch root spans in batches
102
+ const rootSpansByTrace = new Map<string, Span>();
103
+ for (let i = 0; i < allTraceIds.length; i += MAX_TRACE_IDS_PER_BATCH) {
104
+ const traceIdBatch = allTraceIds.slice(i, i + MAX_TRACE_IDS_PER_BATCH);
105
+ const spans = await getAllRootSpansForBatch({
106
+ client,
107
+ projectId,
108
+ traceIdBatch,
109
+ });
110
+ for (const span of spans) {
111
+ const traceId = span.context.trace_id;
112
+ if (!rootSpansByTrace.has(traceId)) {
113
+ rootSpansByTrace.set(traceId, span);
114
+ }
115
+ }
116
+ }
117
+
118
+ return buildSessionTurns({ allTraceIds, traceInfo, rootSpansByTrace });
119
+ }
120
+
121
+ /**
122
+ * Fetch all root spans for a batch of trace IDs, handling pagination.
123
+ */
124
+ async function getAllRootSpansForBatch({
125
+ client,
126
+ projectId,
127
+ traceIdBatch,
128
+ }: {
129
+ client: ReturnType<typeof createClient>;
130
+ projectId: string;
131
+ traceIdBatch: string[];
132
+ }): Promise<Span[]> {
133
+ const allSpans: Span[] = [];
134
+ let cursor: string | null = null;
135
+
136
+ do {
137
+ const result = await getSpans({
138
+ client,
139
+ project: { projectId },
140
+ traceIds: traceIdBatch,
141
+ parentId: null,
142
+ limit: traceIdBatch.length,
143
+ ...(cursor ? { cursor } : {}),
144
+ });
145
+ allSpans.push(...result.spans);
146
+ cursor = result.nextCursor;
147
+ } while (cursor != null);
148
+
149
+ return allSpans;
150
+ }
151
+
152
+ /**
153
+ * Extract a SessionTurnIO from span attributes for a given prefix.
154
+ */
155
+ function extractIO({
156
+ attrs,
157
+ valueKey,
158
+ mimeTypeKey,
159
+ }: {
160
+ attrs: Record<string, unknown>;
161
+ valueKey: string;
162
+ mimeTypeKey: string;
163
+ }): SessionTurnIO | undefined {
164
+ const value = attrs[valueKey];
165
+ if (value == null) return undefined;
166
+ const io: SessionTurnIO = { value: String(value) };
167
+ const mimeType = attrs[mimeTypeKey];
168
+ if (mimeType != null) {
169
+ io.mimeType = String(mimeType);
170
+ }
171
+ return io;
172
+ }
173
+
174
+ /**
175
+ * Build session turns from trace info and root spans, ordered by start_time.
176
+ */
177
+ function buildSessionTurns({
178
+ allTraceIds,
179
+ traceInfo,
180
+ rootSpansByTrace,
181
+ }: {
182
+ allTraceIds: string[];
183
+ traceInfo: Map<string, SessionTrace>;
184
+ rootSpansByTrace: Map<string, Span>;
185
+ }): SessionTurn[] {
186
+ const turns: SessionTurn[] = [];
187
+
188
+ for (const traceId of allTraceIds) {
189
+ const info = traceInfo.get(traceId);
190
+ if (!info) continue;
191
+
192
+ const turn: SessionTurn = {
193
+ traceId,
194
+ startTime: info.startTime,
195
+ endTime: info.endTime,
196
+ };
197
+
198
+ const rootSpan = rootSpansByTrace.get(traceId);
199
+ if (rootSpan) {
200
+ turn.rootSpan = rootSpan;
201
+ const attrs = rootSpan.attributes ?? {};
202
+ turn.input = extractIO({
203
+ attrs,
204
+ valueKey: SemanticConventions.INPUT_VALUE,
205
+ mimeTypeKey: SemanticConventions.INPUT_MIME_TYPE,
206
+ });
207
+ turn.output = extractIO({
208
+ attrs,
209
+ valueKey: SemanticConventions.OUTPUT_VALUE,
210
+ mimeTypeKey: SemanticConventions.OUTPUT_MIME_TYPE,
211
+ });
212
+ }
213
+
214
+ turns.push(turn);
215
+ }
216
+
217
+ turns.sort((a, b) => a.startTime.localeCompare(b.startTime));
218
+ return turns;
219
+ }
@@ -2,5 +2,6 @@ export * from "./addSessionAnnotation";
2
2
  export * from "./deleteSession";
3
3
  export * from "./deleteSessions";
4
4
  export * from "./getSession";
5
+ export * from "./getSessionTurns";
5
6
  export * from "./listSessions";
6
7
  export * from "./logSessionAnnotations";