@struktur/telemetry 2.1.2 → 2.3.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/dist/index.js ADDED
@@ -0,0 +1,601 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/adapters/phoenix/PhoenixAdapter.ts
12
+ function createPhoenixAdapter(config) {
13
+ return new PhoenixAdapter(config);
14
+ }
15
+ var PhoenixAdapter;
16
+ var init_PhoenixAdapter = __esm({
17
+ "src/adapters/phoenix/PhoenixAdapter.ts"() {
18
+ "use strict";
19
+ PhoenixAdapter = class {
20
+ name = "phoenix";
21
+ version = "1.0.0";
22
+ config;
23
+ tracerProvider = null;
24
+ activeSpans = /* @__PURE__ */ new Map();
25
+ otelApi = null;
26
+ phoenixOtel = null;
27
+ constructor(config) {
28
+ this.config = {
29
+ url: "http://localhost:6006",
30
+ batch: true,
31
+ ...config
32
+ };
33
+ }
34
+ async initialize() {
35
+ const [{ register }, otelApi] = await Promise.all([
36
+ import("@arizeai/phoenix-otel"),
37
+ import("@opentelemetry/api")
38
+ ]);
39
+ this.otelApi = otelApi;
40
+ this.phoenixOtel = { register };
41
+ this.tracerProvider = register({
42
+ projectName: this.config.projectName,
43
+ url: this.config.url,
44
+ apiKey: this.config.apiKey,
45
+ batch: this.config.batch,
46
+ headers: this.config.headers
47
+ });
48
+ }
49
+ async shutdown() {
50
+ if (this.tracerProvider?.forceFlush) {
51
+ await this.tracerProvider.forceFlush();
52
+ }
53
+ }
54
+ startSpan(context) {
55
+ if (!this.otelApi) {
56
+ throw new Error("PhoenixAdapter not initialized");
57
+ }
58
+ const tracer = this.otelApi.trace.getTracer("struktur");
59
+ const spanKind = context.kind;
60
+ const otelSpan = tracer.startSpan(context.name, {
61
+ attributes: {
62
+ "openinference.span.kind": spanKind,
63
+ ...context.attributes
64
+ }
65
+ });
66
+ const spanContext = otelSpan.spanContext();
67
+ const span = {
68
+ id: spanContext.spanId,
69
+ traceId: spanContext.traceId,
70
+ name: context.name,
71
+ kind: context.kind,
72
+ startTime: context.startTime ?? Date.now(),
73
+ parentId: context.parentSpan?.id
74
+ };
75
+ this.activeSpans.set(span.id, otelSpan);
76
+ return span;
77
+ }
78
+ endSpan(span, result) {
79
+ const otelSpan = this.activeSpans.get(span.id);
80
+ if (!otelSpan) return;
81
+ if (result) {
82
+ otelSpan.setStatus({
83
+ code: result.status === "ok" ? 1 : 2,
84
+ message: result.error?.message
85
+ });
86
+ if (result.output !== void 0) {
87
+ try {
88
+ const outputStr = typeof result.output === "string" ? result.output : JSON.stringify(result.output);
89
+ otelSpan.setAttribute("output.value", outputStr);
90
+ } catch {
91
+ otelSpan.setAttribute("output.value", "[object]");
92
+ }
93
+ }
94
+ if (result.latencyMs !== void 0) {
95
+ otelSpan.setAttribute("latency_ms", result.latencyMs);
96
+ }
97
+ }
98
+ otelSpan.end();
99
+ this.activeSpans.delete(span.id);
100
+ }
101
+ recordEvent(span, event) {
102
+ const otelSpan = this.activeSpans.get(span.id);
103
+ if (!otelSpan) return;
104
+ switch (event.type) {
105
+ case "llm_call":
106
+ this.recordLLMCall(otelSpan, event);
107
+ break;
108
+ case "validation":
109
+ this.recordValidation(otelSpan, event);
110
+ break;
111
+ case "chunk":
112
+ this.recordChunk(otelSpan, event);
113
+ break;
114
+ case "tool_call":
115
+ this.recordToolCall(otelSpan, event);
116
+ break;
117
+ case "merge":
118
+ this.recordMerge(otelSpan, event);
119
+ break;
120
+ case "parse":
121
+ this.recordParse(otelSpan, event);
122
+ break;
123
+ }
124
+ }
125
+ setAttributes(span, attributes) {
126
+ const otelSpan = this.activeSpans.get(span.id);
127
+ if (!otelSpan) return;
128
+ const stringAttrs = {};
129
+ for (const [key, value] of Object.entries(attributes)) {
130
+ if (value !== void 0 && value !== null) {
131
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
132
+ stringAttrs[key] = value;
133
+ } else {
134
+ try {
135
+ stringAttrs[key] = JSON.stringify(value);
136
+ } catch {
137
+ stringAttrs[key] = String(value);
138
+ }
139
+ }
140
+ }
141
+ }
142
+ otelSpan.setAttributes(stringAttrs);
143
+ }
144
+ setContext(_context) {
145
+ }
146
+ recordLLMCall(span, event) {
147
+ const attrs = {
148
+ "llm.model_name": event.model,
149
+ "llm.provider": event.provider,
150
+ "llm.temperature": event.input.temperature ?? "",
151
+ "llm.max_tokens": event.input.maxTokens ?? ""
152
+ };
153
+ if (event.input.messages.length > 0) {
154
+ attrs["llm.input_messages"] = JSON.stringify(event.input.messages);
155
+ }
156
+ if (event.input.schema) {
157
+ try {
158
+ attrs["llm.schema"] = JSON.stringify(event.input.schema);
159
+ } catch {
160
+ }
161
+ }
162
+ if (event.output) {
163
+ attrs["output.value"] = event.output.content;
164
+ attrs["llm.structured_output"] = event.output.structured ?? false;
165
+ if (event.output.usage) {
166
+ this.setTokenUsageAttrs(attrs, event.output.usage);
167
+ }
168
+ }
169
+ attrs["latency_ms"] = event.latencyMs;
170
+ if (event.error) {
171
+ span.recordException(event.error);
172
+ }
173
+ span.setAttributes(attrs);
174
+ }
175
+ setTokenUsageAttrs(attrs, usage) {
176
+ attrs["llm.token_count.prompt"] = usage.input;
177
+ attrs["llm.token_count.completion"] = usage.output;
178
+ attrs["llm.token_count.total"] = usage.total;
179
+ }
180
+ recordValidation(span, event) {
181
+ const attrs = {
182
+ "validation.attempt": event.attempt,
183
+ "validation.max_attempts": event.maxAttempts,
184
+ "validation.success": event.success
185
+ };
186
+ if (event.errors && event.errors.length > 0) {
187
+ attrs["validation.errors"] = JSON.stringify(event.errors);
188
+ }
189
+ if (event.latencyMs !== void 0) {
190
+ attrs["latency_ms"] = event.latencyMs;
191
+ }
192
+ span.setAttributes(attrs);
193
+ }
194
+ recordChunk(span, event) {
195
+ span.setAttributes({
196
+ "chunk.index": event.chunkIndex,
197
+ "chunk.total": event.totalChunks,
198
+ "chunk.tokens": event.tokens,
199
+ "chunk.images": event.images
200
+ });
201
+ if (event.content) {
202
+ span.setAttribute("chunk.content_preview", event.content.slice(0, 1e3));
203
+ }
204
+ }
205
+ recordToolCall(span, event) {
206
+ const attrs = {
207
+ "tool.name": event.toolName,
208
+ "tool.args": JSON.stringify(event.args)
209
+ };
210
+ if (event.result !== void 0) {
211
+ try {
212
+ attrs["tool.result"] = JSON.stringify(event.result);
213
+ } catch {
214
+ attrs["tool.result"] = "[object]";
215
+ }
216
+ }
217
+ if (event.error) {
218
+ attrs["tool.error"] = event.error.message;
219
+ }
220
+ if (event.latencyMs !== void 0) {
221
+ attrs["latency_ms"] = event.latencyMs;
222
+ }
223
+ span.setAttributes(attrs);
224
+ if (event.error) {
225
+ span.recordException(event.error);
226
+ }
227
+ }
228
+ recordMerge(span, event) {
229
+ const attrs = {
230
+ "merge.strategy": event.strategy,
231
+ "merge.input_count": event.inputCount,
232
+ "merge.output_count": event.outputCount
233
+ };
234
+ if (event.deduped !== void 0) {
235
+ attrs["merge.deduped"] = event.deduped;
236
+ }
237
+ span.setAttributes(attrs);
238
+ }
239
+ recordParse(span, event) {
240
+ span.setAttributes({
241
+ "parse.mime_type": event.mimeType,
242
+ "parse.parser": event.parser,
243
+ "parse.input_size": event.inputSize,
244
+ "parse.output_tokens": event.outputTokens,
245
+ "parse.output_images": event.outputImages,
246
+ "latency_ms": event.latencyMs
247
+ });
248
+ }
249
+ };
250
+ }
251
+ });
252
+
253
+ // src/adapters/phoenix/index.ts
254
+ var phoenix_exports = {};
255
+ __export(phoenix_exports, {
256
+ PhoenixAdapter: () => PhoenixAdapter,
257
+ createPhoenixAdapter: () => createPhoenixAdapter
258
+ });
259
+ var init_phoenix = __esm({
260
+ "src/adapters/phoenix/index.ts"() {
261
+ "use strict";
262
+ init_PhoenixAdapter();
263
+ }
264
+ });
265
+
266
+ // src/adapters/langfuse/LangfuseAdapter.ts
267
+ function createLangfuseAdapter(config) {
268
+ return new LangfuseAdapter(config);
269
+ }
270
+ var LangfuseAdapter;
271
+ var init_LangfuseAdapter = __esm({
272
+ "src/adapters/langfuse/LangfuseAdapter.ts"() {
273
+ "use strict";
274
+ LangfuseAdapter = class {
275
+ name = "langfuse";
276
+ version = "1.0.0";
277
+ config;
278
+ sdk = null;
279
+ activeSpans = /* @__PURE__ */ new Map();
280
+ otelApi = null;
281
+ constructor(config) {
282
+ this.config = {
283
+ baseUrl: "https://cloud.langfuse.com",
284
+ ...config
285
+ };
286
+ }
287
+ async initialize() {
288
+ const [{ LangfuseSpanProcessor }, { NodeSDK }, otelApi] = await Promise.all([
289
+ import("@langfuse/otel"),
290
+ import("@opentelemetry/sdk-node"),
291
+ import("@opentelemetry/api")
292
+ ]);
293
+ this.otelApi = otelApi;
294
+ const processor = new LangfuseSpanProcessor({
295
+ publicKey: this.config.publicKey,
296
+ secretKey: this.config.secretKey,
297
+ baseUrl: this.config.baseUrl
298
+ });
299
+ const sdk = new NodeSDK({
300
+ spanProcessors: [processor]
301
+ });
302
+ sdk.start();
303
+ this.sdk = sdk;
304
+ }
305
+ async shutdown() {
306
+ if (this.sdk) {
307
+ await this.sdk.shutdown();
308
+ }
309
+ }
310
+ startSpan(context) {
311
+ if (!this.otelApi) {
312
+ throw new Error("LangfuseAdapter not initialized");
313
+ }
314
+ const tracer = this.otelApi.trace.getTracer("struktur");
315
+ const otelSpan = tracer.startSpan(context.name, {
316
+ attributes: {
317
+ "observation.type": context.kind.toLowerCase(),
318
+ ...context.attributes
319
+ }
320
+ });
321
+ const spanContext = otelSpan.spanContext();
322
+ const span = {
323
+ id: spanContext.spanId,
324
+ traceId: spanContext.traceId,
325
+ name: context.name,
326
+ kind: context.kind,
327
+ startTime: context.startTime ?? Date.now(),
328
+ parentId: context.parentSpan?.id
329
+ };
330
+ this.activeSpans.set(span.id, otelSpan);
331
+ return span;
332
+ }
333
+ endSpan(span, result) {
334
+ const otelSpan = this.activeSpans.get(span.id);
335
+ if (!otelSpan) return;
336
+ if (result) {
337
+ otelSpan.setStatus({
338
+ code: result.status === "ok" ? 1 : 2,
339
+ message: result.error?.message
340
+ });
341
+ if (result.output !== void 0) {
342
+ try {
343
+ const outputStr = typeof result.output === "string" ? result.output : JSON.stringify(result.output);
344
+ otelSpan.setAttribute("output", outputStr);
345
+ } catch {
346
+ otelSpan.setAttribute("output", "[object]");
347
+ }
348
+ }
349
+ if (result.latencyMs !== void 0) {
350
+ otelSpan.setAttribute("latency_ms", result.latencyMs);
351
+ }
352
+ }
353
+ otelSpan.end();
354
+ this.activeSpans.delete(span.id);
355
+ }
356
+ recordEvent(span, event) {
357
+ const otelSpan = this.activeSpans.get(span.id);
358
+ if (!otelSpan) return;
359
+ switch (event.type) {
360
+ case "llm_call":
361
+ this.recordLLMCall(otelSpan, event);
362
+ break;
363
+ case "validation":
364
+ this.recordValidation(otelSpan, event);
365
+ break;
366
+ case "chunk":
367
+ this.recordChunk(otelSpan, event);
368
+ break;
369
+ case "tool_call":
370
+ this.recordToolCall(otelSpan, event);
371
+ break;
372
+ case "merge":
373
+ this.recordMerge(otelSpan, event);
374
+ break;
375
+ case "parse":
376
+ this.recordParse(otelSpan, event);
377
+ break;
378
+ }
379
+ }
380
+ setAttributes(span, attributes) {
381
+ const otelSpan = this.activeSpans.get(span.id);
382
+ if (!otelSpan) return;
383
+ const stringAttrs = {};
384
+ for (const [key, value] of Object.entries(attributes)) {
385
+ if (value !== void 0 && value !== null) {
386
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
387
+ stringAttrs[key] = value;
388
+ } else {
389
+ try {
390
+ stringAttrs[key] = JSON.stringify(value);
391
+ } catch {
392
+ stringAttrs[key] = String(value);
393
+ }
394
+ }
395
+ }
396
+ }
397
+ otelSpan.setAttributes(stringAttrs);
398
+ }
399
+ setContext(_context) {
400
+ }
401
+ recordLLMCall(span, event) {
402
+ const attrs = {
403
+ model: event.model,
404
+ provider: event.provider,
405
+ input: JSON.stringify(event.input.messages),
406
+ temperature: event.input.temperature ?? "",
407
+ max_tokens: event.input.maxTokens ?? ""
408
+ };
409
+ if (event.output) {
410
+ attrs.output = event.output.content;
411
+ if (event.output.usage) {
412
+ attrs["usage.input"] = event.output.usage.input;
413
+ attrs["usage.output"] = event.output.usage.output;
414
+ attrs["usage.total"] = event.output.usage.total;
415
+ }
416
+ }
417
+ attrs.latency_ms = event.latencyMs;
418
+ if (event.error) {
419
+ attrs.error = event.error.message;
420
+ span.recordException(event.error);
421
+ }
422
+ span.setAttributes(attrs);
423
+ }
424
+ recordValidation(span, event) {
425
+ const attrs = {
426
+ attempt: event.attempt,
427
+ max_attempts: event.maxAttempts,
428
+ success: event.success
429
+ };
430
+ if (event.errors && event.errors.length > 0) {
431
+ attrs.errors = JSON.stringify(event.errors);
432
+ }
433
+ if (event.latencyMs !== void 0) {
434
+ attrs.latency_ms = event.latencyMs;
435
+ }
436
+ span.setAttributes(attrs);
437
+ }
438
+ recordChunk(span, event) {
439
+ const attrs = {
440
+ chunk_index: event.chunkIndex,
441
+ chunk_total: event.totalChunks,
442
+ chunk_tokens: event.tokens,
443
+ chunk_images: event.images
444
+ };
445
+ if (event.content) {
446
+ attrs.chunk_content = event.content.slice(0, 1e3);
447
+ }
448
+ span.setAttributes(attrs);
449
+ }
450
+ recordToolCall(span, event) {
451
+ const attrs = {
452
+ tool_name: event.toolName,
453
+ tool_args: JSON.stringify(event.args)
454
+ };
455
+ if (event.result !== void 0) {
456
+ try {
457
+ attrs.tool_result = JSON.stringify(event.result);
458
+ } catch {
459
+ attrs.tool_result = "[object]";
460
+ }
461
+ }
462
+ if (event.error) {
463
+ attrs.tool_error = event.error.message;
464
+ }
465
+ if (event.latencyMs !== void 0) {
466
+ attrs.latency_ms = event.latencyMs;
467
+ }
468
+ span.setAttributes(attrs);
469
+ if (event.error) {
470
+ span.recordException(event.error);
471
+ }
472
+ }
473
+ recordMerge(span, event) {
474
+ const attrs = {
475
+ strategy: event.strategy,
476
+ input_count: event.inputCount,
477
+ output_count: event.outputCount
478
+ };
479
+ if (event.deduped !== void 0) {
480
+ attrs.deduped = event.deduped;
481
+ }
482
+ span.setAttributes(attrs);
483
+ }
484
+ recordParse(span, event) {
485
+ span.setAttributes({
486
+ mime_type: event.mimeType,
487
+ parser: event.parser,
488
+ input_size: event.inputSize,
489
+ output_tokens: event.outputTokens,
490
+ output_images: event.outputImages,
491
+ latency_ms: event.latencyMs
492
+ });
493
+ }
494
+ };
495
+ }
496
+ });
497
+
498
+ // src/adapters/langfuse/index.ts
499
+ var langfuse_exports = {};
500
+ __export(langfuse_exports, {
501
+ LangfuseAdapter: () => LangfuseAdapter,
502
+ createLangfuseAdapter: () => createLangfuseAdapter
503
+ });
504
+ var init_langfuse = __esm({
505
+ "src/adapters/langfuse/index.ts"() {
506
+ "use strict";
507
+ init_LangfuseAdapter();
508
+ }
509
+ });
510
+
511
+ // src/types.ts
512
+ var NoopTelemetryAdapter = class {
513
+ name = "noop";
514
+ version = "1.0.0";
515
+ currentId = 0;
516
+ mockSpans = /* @__PURE__ */ new Map();
517
+ async initialize() {
518
+ }
519
+ async shutdown() {
520
+ }
521
+ startSpan(context) {
522
+ const id = `noop-${++this.currentId}`;
523
+ const span = {
524
+ id,
525
+ traceId: `trace-${this.currentId}`,
526
+ name: context.name,
527
+ kind: context.kind,
528
+ startTime: Date.now(),
529
+ parentId: context.parentSpan?.id
530
+ };
531
+ this.mockSpans.set(id, span);
532
+ return span;
533
+ }
534
+ endSpan(span, _result) {
535
+ this.mockSpans.delete(span.id);
536
+ }
537
+ recordEvent(_span, _event) {
538
+ }
539
+ setAttributes(_span, _attributes) {
540
+ }
541
+ setContext(_context) {
542
+ }
543
+ };
544
+
545
+ // src/factory.ts
546
+ async function createTelemetry(options) {
547
+ if (options.enabled === false) {
548
+ return null;
549
+ }
550
+ const { provider, config } = options;
551
+ try {
552
+ switch (provider) {
553
+ case "phoenix": {
554
+ const { PhoenixAdapter: PhoenixAdapter2 } = await Promise.resolve().then(() => (init_phoenix(), phoenix_exports));
555
+ return new PhoenixAdapter2(config);
556
+ }
557
+ case "langfuse": {
558
+ const { LangfuseAdapter: LangfuseAdapter2 } = await Promise.resolve().then(() => (init_langfuse(), langfuse_exports));
559
+ return new LangfuseAdapter2(config);
560
+ }
561
+ default:
562
+ throw new Error(
563
+ `Unknown telemetry provider: ${provider}. Supported providers: phoenix, langfuse`
564
+ );
565
+ }
566
+ } catch (error) {
567
+ if (error instanceof Error && error.message.includes("Cannot find module")) {
568
+ throw new Error(
569
+ `Provider '${provider}' requires optional dependencies. Install them with: bun add @arizeai/phoenix-otel @arizeai/openinference-core or bun add @langfuse/otel`
570
+ );
571
+ }
572
+ throw error;
573
+ }
574
+ }
575
+ async function createPhoenixTelemetry(config) {
576
+ const { PhoenixAdapter: PhoenixAdapter2 } = await Promise.resolve().then(() => (init_phoenix(), phoenix_exports));
577
+ return new PhoenixAdapter2(config);
578
+ }
579
+ async function createLangfuseTelemetry(config) {
580
+ const { LangfuseAdapter: LangfuseAdapter2 } = await Promise.resolve().then(() => (init_langfuse(), langfuse_exports));
581
+ return new LangfuseAdapter2(config);
582
+ }
583
+ function createNoopTelemetry() {
584
+ return new NoopTelemetryAdapter();
585
+ }
586
+
587
+ // src/index.ts
588
+ init_phoenix();
589
+ init_langfuse();
590
+ export {
591
+ LangfuseAdapter,
592
+ NoopTelemetryAdapter,
593
+ PhoenixAdapter,
594
+ createLangfuseAdapter,
595
+ createLangfuseTelemetry,
596
+ createNoopTelemetry,
597
+ createPhoenixAdapter,
598
+ createPhoenixTelemetry,
599
+ createTelemetry
600
+ };
601
+ //# sourceMappingURL=index.js.map