@struktur/telemetry 2.1.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.
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@struktur/telemetry",
3
+ "version": "2.1.1",
4
+ "license": "FSL-1.1-MIT",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./types": "./src/types.ts",
11
+ "./factory": "./src/factory.ts",
12
+ "./adapters/phoenix": "./src/adapters/phoenix/index.ts",
13
+ "./adapters/langfuse": "./src/adapters/langfuse/index.ts"
14
+ },
15
+ "scripts": {
16
+ "test": "bun test",
17
+ "typecheck": "tsc --noEmit"
18
+ },
19
+ "dependencies": {
20
+ "@struktur/sdk": "2.1.0"
21
+ },
22
+ "optionalDependencies": {
23
+ "@arizeai/phoenix-otel": "^0.4.2",
24
+ "@arizeai/openinference-core": "^1.0.0",
25
+ "@arizeai/openinference-semantic-conventions": "^1.0.0",
26
+ "@langfuse/otel": "^2.0.0",
27
+ "@opentelemetry/api": "^1.9.0",
28
+ "@opentelemetry/sdk-node": "^0.200.0"
29
+ },
30
+ "peerDependencies": {
31
+ "typescript": "^5"
32
+ },
33
+ "devDependencies": {
34
+ "@types/bun": "latest"
35
+ }
36
+ }
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Langfuse telemetry adapter
3
+ *
4
+ * Implements TelemetryAdapter for Langfuse using their OpenTelemetry SDK.
5
+ */
6
+
7
+ import type {
8
+ TelemetryAdapter,
9
+ SpanContext,
10
+ Span,
11
+ SpanResult,
12
+ TelemetryEvent,
13
+ TelemetryContext,
14
+ LangfuseConfig,
15
+ LLMCallEvent,
16
+ ValidationEvent,
17
+ ChunkEvent,
18
+ ToolCallEvent,
19
+ MergeEvent,
20
+ ParseEvent,
21
+ } from "../../types.js";
22
+
23
+ type OtelSpan = {
24
+ spanContext: () => { spanId: string; traceId: string };
25
+ setStatus: (status: { code: number; message?: string }) => void;
26
+ setAttribute: (key: string, value: string | number | boolean | undefined) => void;
27
+ setAttributes: (attrs: Record<string, string | number | boolean>) => void;
28
+ recordException: (error: Error) => void;
29
+ end: () => void;
30
+ };
31
+
32
+ /**
33
+ * Langfuse telemetry adapter using OpenTelemetry
34
+ */
35
+ export class LangfuseAdapter implements TelemetryAdapter {
36
+ readonly name = "langfuse";
37
+ readonly version = "1.0.0";
38
+
39
+ private config: LangfuseConfig;
40
+ private sdk: { shutdown: () => Promise<void> } | null = null;
41
+ private activeSpans = new Map<string, OtelSpan>();
42
+ private otelApi: typeof import("@opentelemetry/api") | null = null;
43
+
44
+ constructor(config: LangfuseConfig) {
45
+ this.config = {
46
+ baseUrl: "https://cloud.langfuse.com",
47
+ ...config,
48
+ };
49
+ }
50
+
51
+ async initialize(): Promise<void> {
52
+ // Dynamically import Langfuse OTel SDK
53
+ const [{ LangfuseSpanProcessor }, { NodeSDK }, otelApi] = await Promise.all([
54
+ import("@langfuse/otel"),
55
+ import("@opentelemetry/sdk-node"),
56
+ import("@opentelemetry/api"),
57
+ ]);
58
+
59
+ this.otelApi = otelApi;
60
+
61
+ const processor = new LangfuseSpanProcessor({
62
+ publicKey: this.config.publicKey,
63
+ secretKey: this.config.secretKey,
64
+ baseUrl: this.config.baseUrl,
65
+ });
66
+
67
+ const sdk = new NodeSDK({
68
+ spanProcessors: [processor],
69
+ });
70
+
71
+ sdk.start();
72
+ this.sdk = sdk;
73
+ }
74
+
75
+ async shutdown(): Promise<void> {
76
+ if (this.sdk) {
77
+ await this.sdk.shutdown();
78
+ }
79
+ }
80
+
81
+ startSpan(context: SpanContext): Span {
82
+ if (!this.otelApi) {
83
+ throw new Error("LangfuseAdapter not initialized");
84
+ }
85
+
86
+ const tracer = this.otelApi.trace.getTracer("struktur");
87
+
88
+ const otelSpan = tracer.startSpan(context.name, {
89
+ attributes: {
90
+ "observation.type": context.kind.toLowerCase(),
91
+ ...context.attributes,
92
+ },
93
+ }) as OtelSpan;
94
+
95
+ const spanContext = otelSpan.spanContext();
96
+ const span: Span = {
97
+ id: spanContext.spanId,
98
+ traceId: spanContext.traceId,
99
+ name: context.name,
100
+ kind: context.kind,
101
+ startTime: context.startTime ?? Date.now(),
102
+ parentId: context.parentSpan?.id,
103
+ };
104
+
105
+ this.activeSpans.set(span.id, otelSpan);
106
+ return span;
107
+ }
108
+
109
+ endSpan(span: Span, result?: SpanResult): void {
110
+ const otelSpan = this.activeSpans.get(span.id);
111
+ if (!otelSpan) return;
112
+
113
+ if (result) {
114
+ otelSpan.setStatus({
115
+ code: result.status === "ok" ? 1 : 2,
116
+ message: result.error?.message,
117
+ });
118
+
119
+ if (result.output !== undefined) {
120
+ try {
121
+ const outputStr = typeof result.output === "string"
122
+ ? result.output
123
+ : JSON.stringify(result.output);
124
+ otelSpan.setAttribute("output", outputStr);
125
+ } catch {
126
+ otelSpan.setAttribute("output", "[object]");
127
+ }
128
+ }
129
+
130
+ if (result.latencyMs !== undefined) {
131
+ otelSpan.setAttribute("latency_ms", result.latencyMs);
132
+ }
133
+ }
134
+
135
+ otelSpan.end();
136
+ this.activeSpans.delete(span.id);
137
+ }
138
+
139
+ recordEvent(span: Span, event: TelemetryEvent): void {
140
+ const otelSpan = this.activeSpans.get(span.id);
141
+ if (!otelSpan) return;
142
+
143
+ switch (event.type) {
144
+ case "llm_call":
145
+ this.recordLLMCall(otelSpan, event);
146
+ break;
147
+ case "validation":
148
+ this.recordValidation(otelSpan, event);
149
+ break;
150
+ case "chunk":
151
+ this.recordChunk(otelSpan, event);
152
+ break;
153
+ case "tool_call":
154
+ this.recordToolCall(otelSpan, event);
155
+ break;
156
+ case "merge":
157
+ this.recordMerge(otelSpan, event);
158
+ break;
159
+ case "parse":
160
+ this.recordParse(otelSpan, event);
161
+ break;
162
+ }
163
+ }
164
+
165
+ setAttributes(span: Span, attributes: Record<string, unknown>): void {
166
+ const otelSpan = this.activeSpans.get(span.id);
167
+ if (!otelSpan) return;
168
+
169
+ const stringAttrs: Record<string, string | number | boolean> = {};
170
+ for (const [key, value] of Object.entries(attributes)) {
171
+ if (value !== undefined && value !== null) {
172
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
173
+ stringAttrs[key] = value;
174
+ } else {
175
+ try {
176
+ stringAttrs[key] = JSON.stringify(value);
177
+ } catch {
178
+ stringAttrs[key] = String(value);
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ otelSpan.setAttributes(stringAttrs);
185
+ }
186
+
187
+ setContext(_context: TelemetryContext): void {
188
+ // Langfuse supports session_id, user_id, metadata, tags via span attributes
189
+ // These would be set on individual spans
190
+ }
191
+
192
+ private recordLLMCall(span: OtelSpan, event: LLMCallEvent): void {
193
+ const attrs: Record<string, string | number | boolean> = {
194
+ model: event.model,
195
+ provider: event.provider,
196
+ input: JSON.stringify(event.input.messages),
197
+ temperature: event.input.temperature ?? "",
198
+ max_tokens: event.input.maxTokens ?? "",
199
+ };
200
+
201
+ if (event.output) {
202
+ attrs.output = event.output.content;
203
+
204
+ if (event.output.usage) {
205
+ attrs["usage.input"] = event.output.usage.input;
206
+ attrs["usage.output"] = event.output.usage.output;
207
+ attrs["usage.total"] = event.output.usage.total;
208
+ }
209
+ }
210
+
211
+ attrs.latency_ms = event.latencyMs;
212
+
213
+ if (event.error) {
214
+ attrs.error = event.error.message;
215
+ span.recordException(event.error);
216
+ }
217
+
218
+ span.setAttributes(attrs);
219
+ }
220
+
221
+ private recordValidation(span: OtelSpan, event: ValidationEvent): void {
222
+ const attrs: Record<string, string | number | boolean> = {
223
+ attempt: event.attempt,
224
+ max_attempts: event.maxAttempts,
225
+ success: event.success,
226
+ };
227
+
228
+ if (event.errors && event.errors.length > 0) {
229
+ attrs.errors = JSON.stringify(event.errors);
230
+ }
231
+
232
+ if (event.latencyMs !== undefined) {
233
+ attrs.latency_ms = event.latencyMs;
234
+ }
235
+
236
+ span.setAttributes(attrs);
237
+ }
238
+
239
+ private recordChunk(span: OtelSpan, event: ChunkEvent): void {
240
+ const attrs: Record<string, string | number | boolean> = {
241
+ chunk_index: event.chunkIndex,
242
+ chunk_total: event.totalChunks,
243
+ chunk_tokens: event.tokens,
244
+ chunk_images: event.images,
245
+ };
246
+
247
+ if (event.content) {
248
+ attrs.chunk_content = event.content.slice(0, 1000);
249
+ }
250
+
251
+ span.setAttributes(attrs);
252
+ }
253
+
254
+ private recordToolCall(span: OtelSpan, event: ToolCallEvent): void {
255
+ const attrs: Record<string, string | number | boolean> = {
256
+ tool_name: event.toolName,
257
+ tool_args: JSON.stringify(event.args),
258
+ };
259
+
260
+ if (event.result !== undefined) {
261
+ try {
262
+ attrs.tool_result = JSON.stringify(event.result);
263
+ } catch {
264
+ attrs.tool_result = "[object]";
265
+ }
266
+ }
267
+
268
+ if (event.error) {
269
+ attrs.tool_error = event.error.message;
270
+ }
271
+
272
+ if (event.latencyMs !== undefined) {
273
+ attrs.latency_ms = event.latencyMs;
274
+ }
275
+
276
+ span.setAttributes(attrs);
277
+
278
+ if (event.error) {
279
+ span.recordException(event.error);
280
+ }
281
+ }
282
+
283
+ private recordMerge(span: OtelSpan, event: MergeEvent): void {
284
+ const attrs: Record<string, string | number | boolean> = {
285
+ strategy: event.strategy,
286
+ input_count: event.inputCount,
287
+ output_count: event.outputCount,
288
+ };
289
+
290
+ if (event.deduped !== undefined) {
291
+ attrs.deduped = event.deduped;
292
+ }
293
+
294
+ span.setAttributes(attrs);
295
+ }
296
+
297
+ private recordParse(span: OtelSpan, event: ParseEvent): void {
298
+ span.setAttributes({
299
+ mime_type: event.mimeType,
300
+ parser: event.parser,
301
+ input_size: event.inputSize,
302
+ output_tokens: event.outputTokens,
303
+ output_images: event.outputImages,
304
+ latency_ms: event.latencyMs,
305
+ });
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Create a Langfuse telemetry adapter
311
+ *
312
+ * @param config - Langfuse configuration
313
+ * @returns Langfuse telemetry adapter
314
+ */
315
+ export function createLangfuseAdapter(config: LangfuseConfig): LangfuseAdapter {
316
+ return new LangfuseAdapter(config);
317
+ }
318
+
319
+ export type { LangfuseConfig };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Langfuse telemetry adapter exports
3
+ */
4
+
5
+ export { LangfuseAdapter, createLangfuseAdapter } from "./LangfuseAdapter.js";
6
+ export type { LangfuseConfig } from "../../types.js";
@@ -0,0 +1,338 @@
1
+ /**
2
+ * Phoenix (Arize) telemetry adapter
3
+ *
4
+ * Implements TelemetryAdapter for Phoenix using OpenTelemetry and
5
+ * OpenInference semantic conventions.
6
+ */
7
+
8
+ import type {
9
+ TelemetryAdapter,
10
+ SpanContext,
11
+ Span,
12
+ SpanResult,
13
+ TelemetryEvent,
14
+ TelemetryContext,
15
+ PhoenixConfig,
16
+ LLMCallEvent,
17
+ ValidationEvent,
18
+ ChunkEvent,
19
+ ToolCallEvent,
20
+ MergeEvent,
21
+ ParseEvent,
22
+ TokenUsage,
23
+ } from "../../types.js";
24
+
25
+ type OtelSpan = {
26
+ spanContext: () => { spanId: string; traceId: string };
27
+ setStatus: (status: { code: number; message?: string }) => void;
28
+ setAttribute: (key: string, value: string | number | boolean | undefined) => void;
29
+ setAttributes: (attrs: Record<string, string | number | boolean>) => void;
30
+ recordException: (error: Error) => void;
31
+ end: () => void;
32
+ };
33
+
34
+ /**
35
+ * Phoenix telemetry adapter using OpenTelemetry
36
+ */
37
+ export class PhoenixAdapter implements TelemetryAdapter {
38
+ readonly name = "phoenix";
39
+ readonly version = "1.0.0";
40
+
41
+ private config: PhoenixConfig;
42
+ private tracerProvider: { forceFlush?: () => Promise<void> } | null = null;
43
+ private activeSpans = new Map<string, OtelSpan>();
44
+ private otelApi: typeof import("@opentelemetry/api") | null = null;
45
+ private phoenixOtel: typeof import("@arizeai/phoenix-otel") | null = null;
46
+
47
+ constructor(config: PhoenixConfig) {
48
+ this.config = {
49
+ url: "http://localhost:6006",
50
+ batch: true,
51
+ ...config,
52
+ };
53
+ }
54
+
55
+ async initialize(): Promise<void> {
56
+ // Dynamically import OTel dependencies
57
+ const [{ register }, otelApi] = await Promise.all([
58
+ import("@arizeai/phoenix-otel"),
59
+ import("@opentelemetry/api"),
60
+ ]);
61
+
62
+ this.otelApi = otelApi;
63
+ this.phoenixOtel = { register } as typeof import("@arizeai/phoenix-otel");
64
+
65
+ this.tracerProvider = register({
66
+ projectName: this.config.projectName,
67
+ url: this.config.url,
68
+ apiKey: this.config.apiKey,
69
+ batch: this.config.batch,
70
+ headers: this.config.headers,
71
+ });
72
+ }
73
+
74
+ async shutdown(): Promise<void> {
75
+ if (this.tracerProvider?.forceFlush) {
76
+ await this.tracerProvider.forceFlush();
77
+ }
78
+ }
79
+
80
+ startSpan(context: SpanContext): Span {
81
+ if (!this.otelApi) {
82
+ throw new Error("PhoenixAdapter not initialized");
83
+ }
84
+
85
+ const tracer = this.otelApi.trace.getTracer("struktur");
86
+
87
+ const spanKind = context.kind;
88
+ const otelSpan = tracer.startSpan(context.name, {
89
+ attributes: {
90
+ "openinference.span.kind": spanKind,
91
+ ...context.attributes,
92
+ },
93
+ }) as OtelSpan;
94
+
95
+ const spanContext = otelSpan.spanContext();
96
+ const span: Span = {
97
+ id: spanContext.spanId,
98
+ traceId: spanContext.traceId,
99
+ name: context.name,
100
+ kind: context.kind,
101
+ startTime: context.startTime ?? Date.now(),
102
+ parentId: context.parentSpan?.id,
103
+ };
104
+
105
+ this.activeSpans.set(span.id, otelSpan);
106
+ return span;
107
+ }
108
+
109
+ endSpan(span: Span, result?: SpanResult): void {
110
+ const otelSpan = this.activeSpans.get(span.id);
111
+ if (!otelSpan) return;
112
+
113
+ if (result) {
114
+ // OTel status codes: 1 = OK, 2 = ERROR
115
+ otelSpan.setStatus({
116
+ code: result.status === "ok" ? 1 : 2,
117
+ message: result.error?.message,
118
+ });
119
+
120
+ if (result.output !== undefined) {
121
+ try {
122
+ const outputStr = typeof result.output === "string"
123
+ ? result.output
124
+ : JSON.stringify(result.output);
125
+ otelSpan.setAttribute("output.value", outputStr);
126
+ } catch {
127
+ otelSpan.setAttribute("output.value", "[object]");
128
+ }
129
+ }
130
+
131
+ if (result.latencyMs !== undefined) {
132
+ otelSpan.setAttribute("latency_ms", result.latencyMs);
133
+ }
134
+ }
135
+
136
+ otelSpan.end();
137
+ this.activeSpans.delete(span.id);
138
+ }
139
+
140
+ recordEvent(span: Span, event: TelemetryEvent): void {
141
+ const otelSpan = this.activeSpans.get(span.id);
142
+ if (!otelSpan) return;
143
+
144
+ switch (event.type) {
145
+ case "llm_call":
146
+ this.recordLLMCall(otelSpan, event);
147
+ break;
148
+ case "validation":
149
+ this.recordValidation(otelSpan, event);
150
+ break;
151
+ case "chunk":
152
+ this.recordChunk(otelSpan, event);
153
+ break;
154
+ case "tool_call":
155
+ this.recordToolCall(otelSpan, event);
156
+ break;
157
+ case "merge":
158
+ this.recordMerge(otelSpan, event);
159
+ break;
160
+ case "parse":
161
+ this.recordParse(otelSpan, event);
162
+ break;
163
+ }
164
+ }
165
+
166
+ setAttributes(span: Span, attributes: Record<string, unknown>): void {
167
+ const otelSpan = this.activeSpans.get(span.id);
168
+ if (!otelSpan) return;
169
+
170
+ const stringAttrs: Record<string, string | number | boolean> = {};
171
+ for (const [key, value] of Object.entries(attributes)) {
172
+ if (value !== undefined && value !== null) {
173
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
174
+ stringAttrs[key] = value;
175
+ } else {
176
+ try {
177
+ stringAttrs[key] = JSON.stringify(value);
178
+ } catch {
179
+ stringAttrs[key] = String(value);
180
+ }
181
+ }
182
+ }
183
+ }
184
+
185
+ otelSpan.setAttributes(stringAttrs);
186
+ }
187
+
188
+ setContext(_context: TelemetryContext): void {
189
+ // Phoenix/OpenInference supports context via OTel context propagation
190
+ // This would require setting up context managers
191
+ // For now, attributes can be set on spans directly
192
+ }
193
+
194
+ private recordLLMCall(span: OtelSpan, event: LLMCallEvent): void {
195
+ const attrs: Record<string, string | number | boolean> = {
196
+ "llm.model_name": event.model,
197
+ "llm.provider": event.provider,
198
+ "llm.temperature": event.input.temperature ?? "",
199
+ "llm.max_tokens": event.input.maxTokens ?? "",
200
+ };
201
+
202
+ // Record input messages
203
+ if (event.input.messages.length > 0) {
204
+ attrs["llm.input_messages"] = JSON.stringify(event.input.messages);
205
+ }
206
+
207
+ // Record schema if present
208
+ if (event.input.schema) {
209
+ try {
210
+ attrs["llm.schema"] = JSON.stringify(event.input.schema);
211
+ } catch {
212
+ // Ignore schema serialization errors
213
+ }
214
+ }
215
+
216
+ // Record output
217
+ if (event.output) {
218
+ attrs["output.value"] = event.output.content;
219
+ attrs["llm.structured_output"] = event.output.structured ?? false;
220
+
221
+ // Record token usage
222
+ if (event.output.usage) {
223
+ this.setTokenUsageAttrs(attrs, event.output.usage);
224
+ }
225
+ }
226
+
227
+ attrs["latency_ms"] = event.latencyMs;
228
+
229
+ if (event.error) {
230
+ span.recordException(event.error);
231
+ }
232
+
233
+ span.setAttributes(attrs);
234
+ }
235
+
236
+ private setTokenUsageAttrs(attrs: Record<string, string | number | boolean>, usage: TokenUsage): void {
237
+ attrs["llm.token_count.prompt"] = usage.input;
238
+ attrs["llm.token_count.completion"] = usage.output;
239
+ attrs["llm.token_count.total"] = usage.total;
240
+ }
241
+
242
+ private recordValidation(span: OtelSpan, event: ValidationEvent): void {
243
+ const attrs: Record<string, string | number | boolean> = {
244
+ "validation.attempt": event.attempt,
245
+ "validation.max_attempts": event.maxAttempts,
246
+ "validation.success": event.success,
247
+ };
248
+
249
+ if (event.errors && event.errors.length > 0) {
250
+ attrs["validation.errors"] = JSON.stringify(event.errors);
251
+ }
252
+
253
+ if (event.latencyMs !== undefined) {
254
+ attrs["latency_ms"] = event.latencyMs;
255
+ }
256
+
257
+ span.setAttributes(attrs);
258
+ }
259
+
260
+ private recordChunk(span: OtelSpan, event: ChunkEvent): void {
261
+ span.setAttributes({
262
+ "chunk.index": event.chunkIndex,
263
+ "chunk.total": event.totalChunks,
264
+ "chunk.tokens": event.tokens,
265
+ "chunk.images": event.images,
266
+ });
267
+
268
+ if (event.content) {
269
+ span.setAttribute("chunk.content_preview", event.content.slice(0, 1000));
270
+ }
271
+ }
272
+
273
+ private recordToolCall(span: OtelSpan, event: ToolCallEvent): void {
274
+ const attrs: Record<string, string | number | boolean> = {
275
+ "tool.name": event.toolName,
276
+ "tool.args": JSON.stringify(event.args),
277
+ };
278
+
279
+ if (event.result !== undefined) {
280
+ try {
281
+ attrs["tool.result"] = JSON.stringify(event.result);
282
+ } catch {
283
+ attrs["tool.result"] = "[object]";
284
+ }
285
+ }
286
+
287
+ if (event.error) {
288
+ attrs["tool.error"] = event.error.message;
289
+ }
290
+
291
+ if (event.latencyMs !== undefined) {
292
+ attrs["latency_ms"] = event.latencyMs;
293
+ }
294
+
295
+ span.setAttributes(attrs);
296
+
297
+ if (event.error) {
298
+ span.recordException(event.error);
299
+ }
300
+ }
301
+
302
+ private recordMerge(span: OtelSpan, event: MergeEvent): void {
303
+ const attrs: Record<string, string | number | boolean> = {
304
+ "merge.strategy": event.strategy,
305
+ "merge.input_count": event.inputCount,
306
+ "merge.output_count": event.outputCount,
307
+ };
308
+
309
+ if (event.deduped !== undefined) {
310
+ attrs["merge.deduped"] = event.deduped;
311
+ }
312
+
313
+ span.setAttributes(attrs);
314
+ }
315
+
316
+ private recordParse(span: OtelSpan, event: ParseEvent): void {
317
+ span.setAttributes({
318
+ "parse.mime_type": event.mimeType,
319
+ "parse.parser": event.parser,
320
+ "parse.input_size": event.inputSize,
321
+ "parse.output_tokens": event.outputTokens,
322
+ "parse.output_images": event.outputImages,
323
+ "latency_ms": event.latencyMs,
324
+ });
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Create a Phoenix telemetry adapter
330
+ *
331
+ * @param config - Phoenix configuration
332
+ * @returns Phoenix telemetry adapter
333
+ */
334
+ export function createPhoenixAdapter(config: PhoenixConfig): PhoenixAdapter {
335
+ return new PhoenixAdapter(config);
336
+ }
337
+
338
+ export type { PhoenixConfig };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Phoenix (Arize) telemetry adapter exports
3
+ */
4
+
5
+ export { PhoenixAdapter, createPhoenixAdapter } from "./PhoenixAdapter.js";
6
+ export type { PhoenixConfig } from "../../types.js";