@cloudbase/agent-observability 0.0.16 → 0.0.18
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/README.md +20 -5
- package/dist/chunk-5EXUNUGP.mjs +60 -0
- package/dist/chunk-5EXUNUGP.mjs.map +1 -0
- package/dist/{chunk-ZGEMAYS4.mjs → chunk-AHSI4KTT.mjs} +399 -92
- package/dist/chunk-AHSI4KTT.mjs.map +1 -0
- package/dist/index.d.mts +369 -33
- package/dist/index.d.ts +369 -33
- package/dist/index.js +399 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +16 -5
- package/dist/langchain.d.mts +7 -5
- package/dist/langchain.d.ts +7 -5
- package/dist/langchain.js +421 -60
- package/dist/langchain.js.map +1 -1
- package/dist/langchain.mjs +31 -11
- package/dist/langchain.mjs.map +1 -1
- package/dist/server.d.mts +72 -6
- package/dist/server.d.ts +72 -6
- package/dist/server.js +129 -13
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +96 -13
- package/dist/server.mjs.map +1 -1
- package/package.json +2 -2
- package/src/core/attributes.ts +256 -11
- package/src/core/constants.ts +14 -16
- package/src/core/spanWrapper.ts +34 -33
- package/src/core/trace-context.ts +469 -0
- package/src/core/tracerProvider.ts +1 -4
- package/src/index.ts +54 -40
- package/src/langchain/CallbackHandler.ts +48 -17
- package/src/langchain/index.ts +1 -1
- package/src/server/SingleLineConsoleSpanExporter.ts +141 -0
- package/src/server/config.ts +2 -4
- package/src/server/index.ts +9 -3
- package/src/server/setup.ts +30 -20
- package/src/types.ts +112 -10
- package/dist/chunk-ZGEMAYS4.mjs.map +0 -1
package/dist/server.mjs
CHANGED
|
@@ -1,10 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OBSERVABILITY_TRACER_NAME,
|
|
3
|
+
init_constants
|
|
4
|
+
} from "./chunk-5EXUNUGP.mjs";
|
|
1
5
|
import "./chunk-NFEGQTCC.mjs";
|
|
2
6
|
|
|
7
|
+
// src/server/SingleLineConsoleSpanExporter.ts
|
|
8
|
+
init_constants();
|
|
9
|
+
var SingleLineConsoleSpanExporter = class {
|
|
10
|
+
/**
|
|
11
|
+
* Export spans as single-line JSON.
|
|
12
|
+
*/
|
|
13
|
+
export(spans, resultCallback) {
|
|
14
|
+
try {
|
|
15
|
+
for (const span of spans) {
|
|
16
|
+
const spanDict = this.spanToDict(span);
|
|
17
|
+
const jsonLine = JSON.stringify(spanDict);
|
|
18
|
+
console.log(jsonLine);
|
|
19
|
+
}
|
|
20
|
+
resultCallback({ code: 0 /* SUCCESS */ });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
resultCallback({ code: 1 /* FAILED */, error });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Shutdown the exporter.
|
|
27
|
+
*/
|
|
28
|
+
shutdown() {
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Force flush the exporter.
|
|
33
|
+
*/
|
|
34
|
+
forceFlush() {
|
|
35
|
+
return Promise.resolve();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Convert a ReadableSpan to a dictionary for JSON serialization.
|
|
39
|
+
*/
|
|
40
|
+
spanToDict(span) {
|
|
41
|
+
try {
|
|
42
|
+
const context = span.spanContext();
|
|
43
|
+
const parentId = span.parentSpanId || span.parentSpanContext?.spanId;
|
|
44
|
+
const result = {
|
|
45
|
+
name: span.name,
|
|
46
|
+
context: {
|
|
47
|
+
trace_id: context.traceId,
|
|
48
|
+
span_id: context.spanId,
|
|
49
|
+
trace_flags: context.traceFlags
|
|
50
|
+
},
|
|
51
|
+
kind: span.kind,
|
|
52
|
+
parent_id: parentId,
|
|
53
|
+
start_time: span.startTime,
|
|
54
|
+
end_time: span.endTime,
|
|
55
|
+
status: {
|
|
56
|
+
status_code: span.status.code,
|
|
57
|
+
description: span.status.message
|
|
58
|
+
},
|
|
59
|
+
attributes: span.attributes,
|
|
60
|
+
events: span.events.map((event) => ({
|
|
61
|
+
name: event.name,
|
|
62
|
+
timestamp: event.time,
|
|
63
|
+
attributes: event.attributes
|
|
64
|
+
})),
|
|
65
|
+
links: span.links.map((link) => ({
|
|
66
|
+
context: {
|
|
67
|
+
trace_id: link.context.traceId,
|
|
68
|
+
span_id: link.context.spanId
|
|
69
|
+
},
|
|
70
|
+
attributes: link.attributes
|
|
71
|
+
})),
|
|
72
|
+
resource: {
|
|
73
|
+
attributes: span.resource.attributes
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
result["_log_from"] = OBSERVABILITY_TRACER_NAME;
|
|
77
|
+
return result;
|
|
78
|
+
} catch {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
function isSingleLineConsoleExporterEnabled() {
|
|
84
|
+
const value = process.env.CONSOLE_EXPORTER_SINGLE_LINE?.toLowerCase() || "true";
|
|
85
|
+
return ["true", "1", "yes", "on"].includes(value);
|
|
86
|
+
}
|
|
87
|
+
|
|
3
88
|
// src/server/setup.ts
|
|
4
89
|
var TRUTHY_ENV_VALUES = /* @__PURE__ */ new Set(["true", "1", "yes", "on"]);
|
|
5
90
|
var DEFAULT_BATCH_CONFIG = {
|
|
6
91
|
maxExportBatchSize: 100,
|
|
7
|
-
scheduledDelayMillis:
|
|
92
|
+
scheduledDelayMillis: 1e3,
|
|
8
93
|
maxQueueSize: 2048,
|
|
9
94
|
exportTimeoutMillis: 3e4
|
|
10
95
|
};
|
|
@@ -52,33 +137,29 @@ async function setupConsoleExporter(config) {
|
|
|
52
137
|
const { trace } = await import("@opentelemetry/api");
|
|
53
138
|
const { resourceFromAttributes } = await import("@opentelemetry/resources");
|
|
54
139
|
const { NodeTracerProvider } = await import("@opentelemetry/sdk-trace-node");
|
|
55
|
-
const { ConsoleSpanExporter,
|
|
56
|
-
const
|
|
140
|
+
const { ConsoleSpanExporter, SimpleSpanProcessor } = await import("@opentelemetry/sdk-trace-base");
|
|
141
|
+
const useSingleLine = isSingleLineConsoleExporterEnabled();
|
|
142
|
+
const exporter = useSingleLine ? new SingleLineConsoleSpanExporter() : new ConsoleSpanExporter();
|
|
143
|
+
const exporterType = useSingleLine ? "single-line" : "multi-line";
|
|
57
144
|
let provider = trace.getTracerProvider();
|
|
58
145
|
const isRealProvider = "addSpanProcessor" in provider;
|
|
146
|
+
const processor = new SimpleSpanProcessor(exporter);
|
|
59
147
|
if (isRealProvider) {
|
|
60
|
-
const exporter = new ConsoleSpanExporter();
|
|
61
|
-
const processor = new BatchSpanProcessor(exporter, batchConfig);
|
|
62
148
|
provider.addSpanProcessor(processor);
|
|
63
|
-
console.info(
|
|
64
|
-
`[Observability] Console exporter configured (batch=${batchConfig.maxExportBatchSize}, delay=${batchConfig.scheduledDelayMillis}ms)`
|
|
65
|
-
);
|
|
66
149
|
} else {
|
|
67
150
|
const resource = resourceFromAttributes({
|
|
68
151
|
"service.name": process.env.OTEL_SERVICE_NAME || "ag-ui-server",
|
|
69
152
|
"service.version": "1.0.0"
|
|
70
153
|
});
|
|
71
|
-
const exporter = new ConsoleSpanExporter();
|
|
72
|
-
const processor = new BatchSpanProcessor(exporter, batchConfig);
|
|
73
154
|
const tracerProvider = new NodeTracerProvider({
|
|
74
155
|
resource,
|
|
75
156
|
spanProcessors: [processor]
|
|
76
157
|
});
|
|
77
158
|
tracerProvider.register();
|
|
78
|
-
console.info(
|
|
79
|
-
`[Observability] Console exporter configured (batch=${batchConfig.maxExportBatchSize}, delay=${batchConfig.scheduledDelayMillis}ms)`
|
|
80
|
-
);
|
|
81
159
|
}
|
|
160
|
+
console.info(
|
|
161
|
+
`[Observability] Console exporter configured (${exporterType}, simple processor)`
|
|
162
|
+
);
|
|
82
163
|
}
|
|
83
164
|
async function setupOTLPExporter(config) {
|
|
84
165
|
const { trace } = await import("@opentelemetry/api");
|
|
@@ -172,6 +253,8 @@ var ExporterType = {
|
|
|
172
253
|
};
|
|
173
254
|
export {
|
|
174
255
|
ExporterType,
|
|
256
|
+
SingleLineConsoleSpanExporter,
|
|
257
|
+
isSingleLineConsoleExporterEnabled,
|
|
175
258
|
setupObservability
|
|
176
259
|
};
|
|
177
260
|
//# sourceMappingURL=server.mjs.map
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/setup.ts","../src/server/config.ts"],"sourcesContent":["/**\n * Observability setup implementation for AG-Kit Server.\n *\n * Merges configuration from environment variables and parameters,\n * then applies each exporter configuration.\n *\n * @packageDocumentation\n */\n\nimport type {\n BatchConfig,\n ObservabilityConfig,\n ConsoleTraceConfig,\n OTLPTraceConfig,\n CustomTraceConfig,\n} from './config';\n\nexport type {\n BatchConfig,\n ObservabilityConfig,\n ConsoleTraceConfig,\n OTLPTraceConfig,\n CustomTraceConfig,\n} from './config';\n\n/**\n * Environment variable truthy values.\n * Matches Python SDK implementation for consistency.\n */\nconst TRUTHY_ENV_VALUES = new Set(['true', '1', 'yes', 'on']);\n\n/**\n * Merged configuration result.\n * Console config allows merge (param overrides env),\n * while OTLP and custom configs are arrays (additive).\n */\ninterface MergedConfig {\n console?: ConsoleTraceConfig | null;\n otlp: OTLPTraceConfig[];\n custom: CustomTraceConfig[];\n}\n\n/**\n * Default batch configuration.\n */\nconst DEFAULT_BATCH_CONFIG: Required<BatchConfig> = {\n maxExportBatchSize: 100,\n scheduledDelayMillis: 5000,\n maxQueueSize: 2048,\n exportTimeoutMillis: 30000,\n};\n\n/**\n * Merge environment variable and parameter configurations.\n *\n * - AUTO_TRACES_STDOUT env adds a console config\n * - Parameter configs override/extend env configs\n */\nfunction mergeConfigs(paramConfigs: ObservabilityConfig[]): MergedConfig {\n const result: MergedConfig = {\n console: null,\n otlp: [],\n custom: [],\n };\n\n // 1. Check AUTO_TRACES_STDOUT env\n const autoTracesStdout = process.env.AUTO_TRACES_STDOUT?.toLowerCase() || '';\n if (TRUTHY_ENV_VALUES.has(autoTracesStdout)) {\n result.console = { type: 'console' };\n console.debug(\n `[Observability] AUTO_TRACES_STDOUT=${autoTracesStdout}, console exporter enabled`\n );\n }\n\n // 2. Process parameter configs\n for (const config of paramConfigs) {\n switch (config.type) {\n case 'console':\n // Parameter overrides env (merge batch config)\n result.console = { ...result.console, ...config };\n break;\n\n case 'otlp':\n result.otlp.push(config);\n break;\n\n case 'custom':\n result.custom.push(config);\n break;\n }\n }\n\n return result;\n}\n\n/**\n * Apply batch configuration with defaults.\n */\nfunction resolveBatchConfig(batch?: BatchConfig): Required<BatchConfig> {\n return { ...DEFAULT_BATCH_CONFIG, ...batch };\n}\n\n/**\n * Safe wrapper for exporter setup functions.\n * Ensures individual exporter failures don't crash the entire setup.\n */\nasync function safeSetup(\n name: string,\n setupFn: () => Promise<void>\n): Promise<void> {\n try {\n await setupFn();\n } catch (error) {\n console.warn(\n `[Observability] ${name} setup failed (non-fatal): ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n // Don't rethrow - allow other exporters to continue\n }\n}\n\n/**\n * Setup console exporter.\n */\nasync function setupConsoleExporter(config: ConsoleTraceConfig): Promise<void> {\n const { trace } = await import('@opentelemetry/api');\n const { resourceFromAttributes } = await import('@opentelemetry/resources');\n const { NodeTracerProvider } = await import('@opentelemetry/sdk-trace-node');\n const { ConsoleSpanExporter, BatchSpanProcessor } = await import(\n '@opentelemetry/sdk-trace-base'\n );\n\n const batchConfig = resolveBatchConfig(config.batch);\n\n // Check if a real TracerProvider already exists\n let provider = trace.getTracerProvider();\n const isRealProvider = 'addSpanProcessor' in provider;\n\n if (isRealProvider) {\n // Add processor to existing provider\n const exporter = new ConsoleSpanExporter();\n const processor = new BatchSpanProcessor(exporter, batchConfig);\n (provider as any).addSpanProcessor(processor);\n\n console.info(\n `[Observability] Console exporter configured (batch=${batchConfig.maxExportBatchSize}, ` +\n `delay=${batchConfig.scheduledDelayMillis}ms)`\n );\n } else {\n // Create new provider with console exporter\n const resource = resourceFromAttributes({\n 'service.name': process.env.OTEL_SERVICE_NAME || 'ag-ui-server',\n 'service.version': '1.0.0',\n });\n\n const exporter = new ConsoleSpanExporter();\n const processor = new BatchSpanProcessor(exporter, batchConfig);\n\n const tracerProvider = new NodeTracerProvider({\n resource,\n spanProcessors: [processor],\n });\n\n tracerProvider.register();\n\n console.info(\n `[Observability] Console exporter configured (batch=${batchConfig.maxExportBatchSize}, ` +\n `delay=${batchConfig.scheduledDelayMillis}ms)`\n );\n }\n}\n\n/**\n * Setup OTLP exporter.\n */\nasync function setupOTLPExporter(config: OTLPTraceConfig): Promise<void> {\n const { trace } = await import('@opentelemetry/api');\n const { resourceFromAttributes } = await import('@opentelemetry/resources');\n const { NodeTracerProvider } = await import('@opentelemetry/sdk-trace-node');\n const { OTLPTraceExporter } = await import('@opentelemetry/exporter-trace-otlp-http');\n const { BatchSpanProcessor } = await import('@opentelemetry/sdk-trace-base');\n\n const batchConfig = resolveBatchConfig(config.batch);\n\n // Check if a real TracerProvider already exists\n let provider = trace.getTracerProvider();\n const isRealProvider = 'addSpanProcessor' in provider;\n\n if (isRealProvider) {\n // Add processor to existing provider\n const exporter = new OTLPTraceExporter({\n url: config.url,\n headers: config.headers,\n timeoutMillis: config.timeout ?? 10000,\n });\n\n const processor = new BatchSpanProcessor(exporter, batchConfig);\n (provider as any).addSpanProcessor(processor);\n\n console.info(\n `[Observability] OTLP exporter configured (url=${config.url}, ` +\n `batch=${batchConfig.maxExportBatchSize}, delay=${batchConfig.scheduledDelayMillis}ms)`\n );\n } else {\n // Create new provider with OTLP exporter\n const resource = resourceFromAttributes({\n 'service.name': process.env.OTEL_SERVICE_NAME || 'ag-ui-server',\n 'service.version': '1.0.0',\n });\n\n const exporter = new OTLPTraceExporter({\n url: config.url,\n headers: config.headers,\n timeoutMillis: config.timeout ?? 10000,\n });\n\n const processor = new BatchSpanProcessor(exporter, batchConfig);\n\n const tracerProvider = new NodeTracerProvider({\n resource,\n spanProcessors: [processor],\n });\n\n tracerProvider.register();\n\n console.info(\n `[Observability] OTLP exporter configured (url=${config.url}, ` +\n `batch=${batchConfig.maxExportBatchSize}, delay=${batchConfig.scheduledDelayMillis}ms)`\n );\n }\n}\n\n/**\n * Setup custom exporter.\n */\nasync function setupCustomExporter(config: CustomTraceConfig): Promise<void> {\n await config.setup();\n console.info(`[Observability] Custom exporter setup completed`);\n}\n\n/**\n * Setup observability from merged configuration.\n *\n * @internal\n */\nasync function applyMergedConfigs(merged: MergedConfig): Promise<void> {\n const setupTasks: Promise<void>[] = [];\n\n // Apply console (non-blocking)\n if (merged.console) {\n setupTasks.push(safeSetup('Console exporter', () => setupConsoleExporter(merged.console!)));\n }\n\n // Apply otlp (non-blocking)\n for (const otlp of merged.otlp) {\n setupTasks.push(safeSetup(`OTLP exporter (${otlp.url})`, () => setupOTLPExporter(otlp)));\n }\n\n // Apply custom (non-blocking)\n for (const custom of merged.custom) {\n setupTasks.push(safeSetup('Custom exporter', () => setupCustomExporter(custom)));\n }\n\n // Wait for all exporters to complete (or fail gracefully)\n await Promise.all(setupTasks);\n\n if (merged.console || merged.otlp.length > 0 || merged.custom.length > 0) {\n console.info(`[Observability] Setup completed`);\n }\n}\n\n/**\n * Setup observability from configuration.\n *\n * Merges environment variable (AUTO_TRACES_STDOUT) with parameter configs,\n * then applies each exporter configuration.\n *\n * Environment variables act as presets, parameter configs override or extend.\n *\n * Returns a promise that resolves when setup is complete. This allows callers\n * to await initialization before proceeding, eliminating race conditions.\n *\n * The returned promise is cached - subsequent calls return the same promise\n * to avoid duplicate initialization.\n *\n * @param configs - Observability configuration(s)\n *\n * @example\n * ```typescript\n * // Console only (from env)\n * await setupObservability();\n *\n * // Console + OTLP\n * await setupObservability([\n * { type: 'console' },\n * { type: 'otlp', url: 'http://localhost:4318/v1/traces' }\n * ]);\n *\n * // OTLP only\n * await setupObservability({\n * type: 'otlp',\n * url: 'https://cloud.langfuse.com/api/public/otlp/v1/traces',\n * headers: { 'Authorization': 'Basic xxx' }\n * });\n * ```\n *\n * @public\n */\n\nlet setupPromise: Promise<void> | null = null;\n\nexport async function setupObservability(\n configs?: ObservabilityConfig | ObservabilityConfig[]\n): Promise<void> {\n // Return cached promise if setup is in progress or completed\n if (setupPromise) {\n return setupPromise;\n }\n\n // Create the setup promise\n setupPromise = (async () => {\n try {\n // Normalize to array\n const configsArray = configs\n ? Array.isArray(configs)\n ? configs\n : [configs]\n : [];\n\n // Merge env and parameter configs\n const merged = mergeConfigs(configsArray);\n\n // Apply merged configs\n await applyMergedConfigs(merged);\n } catch (error) {\n // Reset promise on error to allow retry\n setupPromise = null;\n // Silent failure - observability should never block main flow\n console.warn(\n `[Observability] Setup failed: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n })();\n\n return setupPromise;\n}\n","/**\n * Observability configuration types for AG-Kit Server.\n *\n * Provides a unified configuration interface for trace exporters:\n * - Console: Development/debugging output\n * - OTLP: Production export to Langfuse, Jaeger, etc.\n * - Custom: User-defined setup logic\n *\n * @packageDocumentation\n */\n\n/**\n * Trace exporter type constants.\n *\n * @example\n * ```typescript\n * import { ExporterType } from '@cloudbase/agent-observability/server';\n *\n * { type: ExporterType.Console }\n * { type: ExporterType.OTLP }\n * { type: ExporterType.Custom }\n * ```\n *\n * @public\n */\nexport const ExporterType = {\n /** Console exporter - outputs traces to stdout */\n Console: 'console',\n /** OTLP exporter - sends traces to OTLP-compatible backend */\n OTLP: 'otlp',\n /** Custom exporter - user-defined setup logic */\n Custom: 'custom',\n} as const;\n\n/**\n * Trace exporter type literal values.\n *\n * @public\n */\nexport type ExporterType = typeof ExporterType[keyof typeof ExporterType];\n\n/**\n * Batch processing configuration for span exporters.\n *\n * Used by BatchSpanProcessor to optimize performance:\n * - Collects spans in memory and exports them in batches\n * - Reduces I/O operations (console) or network requests (OTLP)\n * - Recommended for production environments\n *\n * @public\n */\nexport interface BatchConfig {\n /** Maximum number of spans per export batch (default: 100) */\n maxExportBatchSize?: number;\n /** Maximum delay in milliseconds before exporting (default: 5000) */\n scheduledDelayMillis?: number;\n /** Maximum queue size (default: 2048) */\n maxQueueSize?: number;\n /** Export timeout in milliseconds (default: 30000) */\n exportTimeoutMillis?: number;\n}\n\n/**\n * Console trace exporter configuration.\n *\n * Outputs traces to stdout in JSON format using ConsoleSpanExporter.\n * Useful for development and debugging.\n *\n * @example\n * ```typescript\n * import { ExporterType } from '@cloudbase/agent-observability/server';\n *\n * { type: ExporterType.Console }\n * { type: ExporterType.Console, batch: { maxExportBatchSize: 200 } }\n * ```\n *\n * @public\n */\nexport interface ConsoleTraceConfig {\n /** Discriminator for console exporter */\n type: typeof ExporterType.Console;\n /** Optional batch processing configuration */\n batch?: BatchConfig;\n}\n\n/**\n * OTLP trace exporter configuration.\n *\n * Exports traces via OTLP protocol to any compatible backend:\n * - Langfuse: https://cloud.langfuse.com/api/public/otlp/v1/traces\n * - Jaeger: http://localhost:4318/v1/traces\n * - OTel Collector: custom endpoint\n *\n * @example\n * ```typescript\n * import { ExporterType } from '@cloudbase/agent-observability/server';\n *\n * {\n * type: ExporterType.OTLP,\n * url: 'https://cloud.langfuse.com/api/public/otlp/v1/traces',\n * headers: {\n * 'Authorization': 'Basic ' + btoa('pk-lf-xxx:sk-lf-xxx')\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface OTLPTraceConfig {\n /** Discriminator for OTLP exporter */\n type: typeof ExporterType.OTLP;\n /** OTLP endpoint URL (http/https) */\n url: string;\n /** Optional HTTP headers for authentication */\n headers?: Record<string, string>;\n /** Request timeout in milliseconds (default: 10000) */\n timeout?: number;\n /** Optional batch processing configuration */\n batch?: BatchConfig;\n}\n\n/**\n * Custom trace exporter configuration.\n *\n * Allows users to provide custom trace setup logic.\n * Useful for integrations not covered by console/otlp.\n *\n * @example\n * ```typescript\n * import { ExporterType } from '@cloudbase/agent-observability/server';\n *\n * {\n * type: ExporterType.Custom,\n * setup: async () => {\n * const exporter = new MyCustomExporter();\n * const provider = new BasicTracerProvider();\n * provider.addSpanProcessor(new SimpleSpanProcessor(exporter));\n * provider.register();\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface CustomTraceConfig {\n /** Discriminator for custom setup */\n type: typeof ExporterType.Custom;\n /** User-defined setup function */\n setup: () => Promise<void> | void;\n}\n\n/**\n * Union type of all supported trace exporter configurations.\n *\n * @public\n */\nexport type ObservabilityConfig =\n | ConsoleTraceConfig\n | OTLPTraceConfig\n | CustomTraceConfig;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server/SingleLineConsoleSpanExporter.ts","../src/server/setup.ts","../src/server/config.ts"],"sourcesContent":["/**\n * Custom console exporter that outputs single-line JSON.\n *\n * This exporter outputs spans as single-line JSON for easier parsing\n * with line-based tools (grep, jq, etc.).\n *\n * To switch back to standard multi-line output, use ConsoleSpanExporter instead.\n *\n * @example\n * ```typescript\n * // Single-line (current default)\n * const exporter = new SingleLineConsoleSpanExporter();\n *\n * // Multi-line (standard OTel)\n * import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base';\n * const exporter = new ConsoleSpanExporter();\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { OBSERVABILITY_TRACER_NAME } from '../core/constants';\n\n/**\n * Export result codes.\n */\nexport enum ExportResultCode {\n SUCCESS = 0,\n FAILED = 1,\n}\n\n/**\n * Export result interface.\n */\nexport interface ExportResult {\n code: ExportResultCode;\n error?: Error;\n}\n\n/**\n * Custom console exporter that outputs single-line JSON.\n *\n * This exporter outputs spans as single-line JSON for easier parsing\n * with line-based tools (grep, jq, etc.).\n */\nexport class SingleLineConsoleSpanExporter implements SpanExporter {\n /**\n * Export spans as single-line JSON.\n */\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {\n try {\n for (const span of spans) {\n const spanDict = this.spanToDict(span);\n // Single-line JSON output\n const jsonLine = JSON.stringify(spanDict);\n console.log(jsonLine);\n }\n resultCallback({ code: ExportResultCode.SUCCESS });\n } catch (error) {\n resultCallback({ code: ExportResultCode.FAILED, error: error as Error });\n }\n }\n\n /**\n * Shutdown the exporter.\n */\n shutdown(): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Force flush the exporter.\n */\n forceFlush?(): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Convert a ReadableSpan to a dictionary for JSON serialization.\n */\n private spanToDict(span: ReadableSpan): Record<string, unknown> {\n try {\n const context = span.spanContext();\n\n // Get parent span ID from parentSpanContext if available\n // Some versions use parentSpanId directly, others use parentSpanContext.spanId\n const parentId = (span as any).parentSpanId || span.parentSpanContext?.spanId;\n\n const result: Record<string, unknown> = {\n name: span.name,\n context: {\n trace_id: context.traceId,\n span_id: context.spanId,\n trace_flags: context.traceFlags,\n },\n kind: span.kind,\n parent_id: parentId,\n start_time: span.startTime,\n end_time: span.endTime,\n status: {\n status_code: span.status.code,\n description: span.status.message,\n },\n attributes: span.attributes,\n events: span.events.map((event) => ({\n name: event.name,\n timestamp: event.time,\n attributes: event.attributes,\n })),\n links: span.links.map((link) => ({\n context: {\n trace_id: link.context.traceId,\n span_id: link.context.spanId,\n },\n attributes: link.attributes,\n })),\n resource: {\n attributes: span.resource.attributes,\n },\n };\n\n // Add log source identifier for console output\n result['_log_from'] = OBSERVABILITY_TRACER_NAME;\n\n return result;\n } catch {\n return {};\n }\n }\n}\n\n/**\n * Check if single-line console exporter should be used.\n *\n * Set CONSOLE_EXPORTER_SINGLE_LINE=false to use standard multi-line output.\n */\nexport function isSingleLineConsoleExporterEnabled(): boolean {\n const value = process.env.CONSOLE_EXPORTER_SINGLE_LINE?.toLowerCase() || 'true';\n return ['true', '1', 'yes', 'on'].includes(value);\n}\n","/**\n * Observability setup implementation for the server.\n *\n * Merges configuration from environment variables and parameters,\n * then applies each exporter configuration.\n *\n * @packageDocumentation\n */\n\nimport type {\n BatchConfig,\n ObservabilityConfig,\n ConsoleTraceConfig,\n OTLPTraceConfig,\n CustomTraceConfig,\n} from './config';\nimport {\n SingleLineConsoleSpanExporter,\n isSingleLineConsoleExporterEnabled,\n} from './SingleLineConsoleSpanExporter';\n\nexport type {\n BatchConfig,\n ObservabilityConfig,\n ConsoleTraceConfig,\n OTLPTraceConfig,\n CustomTraceConfig,\n} from './config';\nexport { SingleLineConsoleSpanExporter } from './SingleLineConsoleSpanExporter';\n\n/**\n * Environment variable truthy values.\n * Matches Python SDK implementation for consistency.\n */\nconst TRUTHY_ENV_VALUES = new Set(['true', '1', 'yes', 'on']);\n\n/**\n * Merged configuration result.\n * Console config allows merge (param overrides env),\n * while OTLP and custom configs are arrays (additive).\n */\ninterface MergedConfig {\n console?: ConsoleTraceConfig | null;\n otlp: OTLPTraceConfig[];\n custom: CustomTraceConfig[];\n}\n\n/**\n * Default batch configuration (used by OTLP exporter).\n */\nconst DEFAULT_BATCH_CONFIG: Required<BatchConfig> = {\n maxExportBatchSize: 100,\n scheduledDelayMillis: 1000,\n maxQueueSize: 2048,\n exportTimeoutMillis: 30000,\n};\n\n/**\n * Merge environment variable and parameter configurations.\n *\n * - AUTO_TRACES_STDOUT env adds a console config\n * - Parameter configs override/extend env configs\n */\nfunction mergeConfigs(paramConfigs: ObservabilityConfig[]): MergedConfig {\n const result: MergedConfig = {\n console: null,\n otlp: [],\n custom: [],\n };\n\n // 1. Check AUTO_TRACES_STDOUT env\n const autoTracesStdout = process.env.AUTO_TRACES_STDOUT?.toLowerCase() || '';\n if (TRUTHY_ENV_VALUES.has(autoTracesStdout)) {\n result.console = { type: 'console' };\n console.debug(\n `[Observability] AUTO_TRACES_STDOUT=${autoTracesStdout}, console exporter enabled`\n );\n }\n\n // 2. Process parameter configs\n for (const config of paramConfigs) {\n switch (config.type) {\n case 'console':\n // Parameter overrides env (merge batch config)\n result.console = { ...result.console, ...config };\n break;\n\n case 'otlp':\n result.otlp.push(config);\n break;\n\n case 'custom':\n result.custom.push(config);\n break;\n }\n }\n\n return result;\n}\n\n/**\n * Apply batch configuration with defaults.\n */\nfunction resolveBatchConfig(batch?: BatchConfig): Required<BatchConfig> {\n return { ...DEFAULT_BATCH_CONFIG, ...batch };\n}\n\n/**\n * Safe wrapper for exporter setup functions.\n * Ensures individual exporter failures don't crash the entire setup.\n */\nasync function safeSetup(\n name: string,\n setupFn: () => Promise<void>\n): Promise<void> {\n try {\n await setupFn();\n } catch (error) {\n console.warn(\n `[Observability] ${name} setup failed (non-fatal): ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n // Don't rethrow - allow other exporters to continue\n }\n}\n\n/**\n * Setup console exporter.\n *\n * Uses SimpleSpanProcessor for immediate export without batching.\n * This is optimal for console output (stdout.write is microsecond-level)\n * and ensures reliable data export in serverless environments (e.g., SCF)\n * where batch background threads may be frozen.\n *\n * Set CONSOLE_EXPORTER_SINGLE_LINE=false to use standard multi-line output.\n */\nasync function setupConsoleExporter(config: ConsoleTraceConfig): Promise<void> {\n const { trace } = await import('@opentelemetry/api');\n const { resourceFromAttributes } = await import('@opentelemetry/resources');\n const { NodeTracerProvider } = await import('@opentelemetry/sdk-trace-node');\n const { ConsoleSpanExporter, SimpleSpanProcessor } = await import(\n '@opentelemetry/sdk-trace-base'\n );\n\n // Choose exporter type based on CONSOLE_EXPORTER_SINGLE_LINE env var\n // Single-line: easier for parsing with line-based tools (grep, jq, etc.)\n // Multi-line: more human-readable for debugging\n const useSingleLine = isSingleLineConsoleExporterEnabled();\n const exporter = useSingleLine\n ? new SingleLineConsoleSpanExporter()\n : new ConsoleSpanExporter();\n const exporterType = useSingleLine ? 'single-line' : 'multi-line';\n\n // Check if a real TracerProvider already exists\n let provider = trace.getTracerProvider();\n const isRealProvider = 'addSpanProcessor' in provider;\n\n const processor = new SimpleSpanProcessor(exporter);\n\n if (isRealProvider) {\n // Add processor to existing provider\n (provider as any).addSpanProcessor(processor);\n } else {\n // Create new provider with console exporter\n const resource = resourceFromAttributes({\n 'service.name': process.env.OTEL_SERVICE_NAME || 'ag-ui-server',\n 'service.version': '1.0.0',\n });\n\n const tracerProvider = new NodeTracerProvider({\n resource,\n spanProcessors: [processor],\n });\n\n tracerProvider.register();\n }\n\n console.info(\n `[Observability] Console exporter configured (${exporterType}, simple processor)`\n );\n}\n\n/**\n * Setup OTLP exporter.\n */\nasync function setupOTLPExporter(config: OTLPTraceConfig): Promise<void> {\n const { trace } = await import('@opentelemetry/api');\n const { resourceFromAttributes } = await import('@opentelemetry/resources');\n const { NodeTracerProvider } = await import('@opentelemetry/sdk-trace-node');\n const { OTLPTraceExporter } = await import('@opentelemetry/exporter-trace-otlp-http');\n const { BatchSpanProcessor } = await import('@opentelemetry/sdk-trace-base');\n\n const batchConfig = resolveBatchConfig(config.batch);\n\n // Check if a real TracerProvider already exists\n let provider = trace.getTracerProvider();\n const isRealProvider = 'addSpanProcessor' in provider;\n\n if (isRealProvider) {\n // Add processor to existing provider\n const exporter = new OTLPTraceExporter({\n url: config.url,\n headers: config.headers,\n timeoutMillis: config.timeout ?? 10000,\n });\n\n const processor = new BatchSpanProcessor(exporter, batchConfig);\n (provider as any).addSpanProcessor(processor);\n\n console.info(\n `[Observability] OTLP exporter configured (url=${config.url}, ` +\n `batch=${batchConfig.maxExportBatchSize}, delay=${batchConfig.scheduledDelayMillis}ms)`\n );\n } else {\n // Create new provider with OTLP exporter\n const resource = resourceFromAttributes({\n 'service.name': process.env.OTEL_SERVICE_NAME || 'ag-ui-server',\n 'service.version': '1.0.0',\n });\n\n const exporter = new OTLPTraceExporter({\n url: config.url,\n headers: config.headers,\n timeoutMillis: config.timeout ?? 10000,\n });\n\n const processor = new BatchSpanProcessor(exporter, batchConfig);\n\n const tracerProvider = new NodeTracerProvider({\n resource,\n spanProcessors: [processor],\n });\n\n tracerProvider.register();\n\n console.info(\n `[Observability] OTLP exporter configured (url=${config.url}, ` +\n `batch=${batchConfig.maxExportBatchSize}, delay=${batchConfig.scheduledDelayMillis}ms)`\n );\n }\n}\n\n/**\n * Setup custom exporter.\n */\nasync function setupCustomExporter(config: CustomTraceConfig): Promise<void> {\n await config.setup();\n console.info(`[Observability] Custom exporter setup completed`);\n}\n\n/**\n * Setup observability from merged configuration.\n *\n * @internal\n */\nasync function applyMergedConfigs(merged: MergedConfig): Promise<void> {\n const setupTasks: Promise<void>[] = [];\n\n // Apply console (non-blocking)\n if (merged.console) {\n setupTasks.push(safeSetup('Console exporter', () => setupConsoleExporter(merged.console!)));\n }\n\n // Apply otlp (non-blocking)\n for (const otlp of merged.otlp) {\n setupTasks.push(safeSetup(`OTLP exporter (${otlp.url})`, () => setupOTLPExporter(otlp)));\n }\n\n // Apply custom (non-blocking)\n for (const custom of merged.custom) {\n setupTasks.push(safeSetup('Custom exporter', () => setupCustomExporter(custom)));\n }\n\n // Wait for all exporters to complete (or fail gracefully)\n await Promise.all(setupTasks);\n\n if (merged.console || merged.otlp.length > 0 || merged.custom.length > 0) {\n console.info(`[Observability] Setup completed`);\n }\n}\n\n/**\n * Setup observability from configuration.\n *\n * Merges environment variable (AUTO_TRACES_STDOUT) with parameter configs,\n * then applies each exporter configuration.\n *\n * Environment variables act as presets, parameter configs override or extend.\n *\n * Returns a promise that resolves when setup is complete. This allows callers\n * to await initialization before proceeding, eliminating race conditions.\n *\n * The returned promise is cached - subsequent calls return the same promise\n * to avoid duplicate initialization.\n *\n * @param configs - Observability configuration(s)\n *\n * @example\n * ```typescript\n * // Console only (from env)\n * await setupObservability();\n *\n * // Console + OTLP\n * await setupObservability([\n * { type: 'console' },\n * { type: 'otlp', url: 'http://localhost:4318/v1/traces' }\n * ]);\n *\n * // OTLP only\n * await setupObservability({\n * type: 'otlp',\n * url: 'https://cloud.langfuse.com/api/public/otlp/v1/traces',\n * headers: { 'Authorization': 'Basic xxx' }\n * });\n * ```\n *\n * @public\n */\n\nlet setupPromise: Promise<void> | null = null;\n\nexport async function setupObservability(\n configs?: ObservabilityConfig | ObservabilityConfig[]\n): Promise<void> {\n // Return cached promise if setup is in progress or completed\n if (setupPromise) {\n return setupPromise;\n }\n\n // Create the setup promise\n setupPromise = (async () => {\n try {\n // Normalize to array\n const configsArray = configs\n ? Array.isArray(configs)\n ? configs\n : [configs]\n : [];\n\n // Merge env and parameter configs\n const merged = mergeConfigs(configsArray);\n\n // Apply merged configs\n await applyMergedConfigs(merged);\n } catch (error) {\n // Reset promise on error to allow retry\n setupPromise = null;\n // Silent failure - observability should never block main flow\n console.warn(\n `[Observability] Setup failed: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n })();\n\n return setupPromise;\n}\n","/**\n * Observability configuration types for the server.\n *\n * Provides a unified configuration interface for trace exporters:\n * - Console: Development/debugging output\n * - OTLP: Production export to Langfuse, Jaeger, etc.\n * - Custom: User-defined setup logic\n *\n * @packageDocumentation\n */\n\n/**\n * Trace exporter type constants.\n *\n * @example\n * ```typescript\n * import { ExporterType } from '@cloudbase/agent-observability/server';\n *\n * { type: ExporterType.Console }\n * { type: ExporterType.OTLP }\n * { type: ExporterType.Custom }\n * ```\n *\n * @public\n */\nexport const ExporterType = {\n /** Console exporter - outputs traces to stdout */\n Console: 'console',\n /** OTLP exporter - sends traces to OTLP-compatible backend */\n OTLP: 'otlp',\n /** Custom exporter - user-defined setup logic */\n Custom: 'custom',\n} as const;\n\n/**\n * Trace exporter type literal values.\n *\n * @public\n */\nexport type ExporterType = typeof ExporterType[keyof typeof ExporterType];\n\n/**\n * Batch processing configuration for span exporters.\n *\n * Used by BatchSpanProcessor to optimize performance:\n * - Collects spans in memory and exports them in batches\n * - Reduces I/O operations (console) or network requests (OTLP)\n * - Recommended for production environments\n *\n * @public\n */\nexport interface BatchConfig {\n /** Maximum number of spans per export batch (default: 100) */\n maxExportBatchSize?: number;\n /** Maximum delay in milliseconds before exporting (default: 5000) */\n scheduledDelayMillis?: number;\n /** Maximum queue size (default: 2048) */\n maxQueueSize?: number;\n /** Export timeout in milliseconds (default: 30000) */\n exportTimeoutMillis?: number;\n}\n\n/**\n * Console trace exporter configuration.\n *\n * Outputs traces to stdout in JSON format using ConsoleSpanExporter.\n * Uses SimpleSpanProcessor for immediate export (no batching).\n * Useful for development and debugging.\n *\n * @example\n * ```typescript\n * import { ExporterType } from '@cloudbase/agent-observability/server';\n *\n * { type: ExporterType.Console }\n * ```\n *\n * @public\n */\nexport interface ConsoleTraceConfig {\n /** Discriminator for console exporter */\n type: typeof ExporterType.Console;\n}\n\n/**\n * OTLP trace exporter configuration.\n *\n * Exports traces via OTLP protocol to any compatible backend:\n * - Langfuse: https://cloud.langfuse.com/api/public/otlp/v1/traces\n * - Jaeger: http://localhost:4318/v1/traces\n * - OTel Collector: custom endpoint\n *\n * @example\n * ```typescript\n * import { ExporterType } from '@cloudbase/agent-observability/server';\n *\n * {\n * type: ExporterType.OTLP,\n * url: 'https://cloud.langfuse.com/api/public/otlp/v1/traces',\n * headers: {\n * 'Authorization': 'Basic ' + btoa('pk-lf-xxx:sk-lf-xxx')\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface OTLPTraceConfig {\n /** Discriminator for OTLP exporter */\n type: typeof ExporterType.OTLP;\n /** OTLP endpoint URL (http/https) */\n url: string;\n /** Optional HTTP headers for authentication */\n headers?: Record<string, string>;\n /** Request timeout in milliseconds (default: 10000) */\n timeout?: number;\n /** Optional batch processing configuration */\n batch?: BatchConfig;\n}\n\n/**\n * Custom trace exporter configuration.\n *\n * Allows users to provide custom trace setup logic.\n * Useful for integrations not covered by console/otlp.\n *\n * @example\n * ```typescript\n * import { ExporterType } from '@cloudbase/agent-observability/server';\n *\n * {\n * type: ExporterType.Custom,\n * setup: async () => {\n * const exporter = new MyCustomExporter();\n * const provider = new BasicTracerProvider();\n * provider.addSpanProcessor(new SimpleSpanProcessor(exporter));\n * provider.register();\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface CustomTraceConfig {\n /** Discriminator for custom setup */\n type: typeof ExporterType.Custom;\n /** User-defined setup function */\n setup: () => Promise<void> | void;\n}\n\n/**\n * Union type of all supported trace exporter configurations.\n *\n * @public\n */\nexport type ObservabilityConfig =\n | ConsoleTraceConfig\n | OTLPTraceConfig\n | CustomTraceConfig;\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudbase/agent-observability",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "OpenInference-compatible observability for AG-Kit",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"@arizeai/openinference-semantic-conventions": "^2.1.7",
|
|
66
66
|
"@opentelemetry/api": "^1.9.0",
|
|
67
67
|
"@opentelemetry/semantic-conventions": "^1.39.0",
|
|
68
|
-
"@cloudbase/agent-shared": "^0.0.
|
|
68
|
+
"@cloudbase/agent-shared": "^0.0.18"
|
|
69
69
|
},
|
|
70
70
|
"optionalDependencies": {
|
|
71
71
|
"@opentelemetry/exporter-trace-otlp-http": "^0.210.0",
|
package/src/core/attributes.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OtelSpanAttributes, OpenInferenceSpanKind } from "./constants";
|
|
2
2
|
import { SemanticConventions } from "@arizeai/openinference-semantic-conventions";
|
|
3
|
-
import { ObservationAttributes, TraceAttributes, ObservationType } from "../types
|
|
3
|
+
import { ObservationAttributes, TraceAttributes, ObservationType, LLMMessage, Document, ToolCall } from "../types";
|
|
4
4
|
import { type Attributes } from "@opentelemetry/api";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -64,7 +64,7 @@ export function createTraceAttributes({
|
|
|
64
64
|
* - Uses `openinference.span.kind` for span type
|
|
65
65
|
* - Uses `llm.*` for LLM-specific attributes
|
|
66
66
|
* - Uses `tool.*` for tool-specific attributes
|
|
67
|
-
* -
|
|
67
|
+
* - Uses `observation.*` for non-standard attributes
|
|
68
68
|
*
|
|
69
69
|
* @param type - Observation type (llm, tool, chain, etc.)
|
|
70
70
|
* @param attributes - Observation attributes to convert
|
|
@@ -89,20 +89,24 @@ export function createObservationAttributes(
|
|
|
89
89
|
usageDetails,
|
|
90
90
|
} = attributes;
|
|
91
91
|
|
|
92
|
+
// Map observation type to OpenInference span kind
|
|
93
|
+
// Falls back to CHAIN for unknown types (CHAIN is the generic catch-all in OpenInference)
|
|
94
|
+
const spanKind = (OpenInferenceSpanKind as Record<string, string>)[type.toUpperCase()] || "CHAIN";
|
|
95
|
+
|
|
92
96
|
// Base attributes for all observation types
|
|
93
97
|
const otelAttributes: Attributes = {
|
|
94
|
-
[SemanticConventions.OPENINFERENCE_SPAN_KIND]:
|
|
98
|
+
[SemanticConventions.OPENINFERENCE_SPAN_KIND]: spanKind,
|
|
95
99
|
[OtelSpanAttributes.OBSERVATION_TYPE]: type,
|
|
96
100
|
[OtelSpanAttributes.OBSERVATION_LEVEL]: level,
|
|
97
101
|
[OtelSpanAttributes.OBSERVATION_STATUS_MESSAGE]: statusMessage,
|
|
98
102
|
[OtelSpanAttributes.VERSION]: version,
|
|
99
103
|
// Use OpenInference input.value convention
|
|
100
104
|
[SemanticConventions.INPUT_VALUE]: _serialize(input),
|
|
101
|
-
// Also set
|
|
105
|
+
// Also set observation.input for compatibility
|
|
102
106
|
[OtelSpanAttributes.OBSERVATION_INPUT]: _serialize(input),
|
|
103
107
|
// Use OpenInference output.value convention
|
|
104
108
|
[SemanticConventions.OUTPUT_VALUE]: _serialize(output),
|
|
105
|
-
// Also set
|
|
109
|
+
// Also set observation.output for compatibility
|
|
106
110
|
[OtelSpanAttributes.OBSERVATION_OUTPUT]: _serialize(output),
|
|
107
111
|
};
|
|
108
112
|
|
|
@@ -111,10 +115,24 @@ export function createObservationAttributes(
|
|
|
111
115
|
if (model) {
|
|
112
116
|
otelAttributes[SemanticConventions.LLM_MODEL_NAME] = model;
|
|
113
117
|
}
|
|
118
|
+
// Extract system and provider safely
|
|
119
|
+
const system = (attributes as Record<string, unknown>)?.system;
|
|
120
|
+
const provider = (attributes as Record<string, unknown>)?.provider;
|
|
121
|
+
const inputMessages = (attributes as Record<string, unknown>)?.inputMessages;
|
|
122
|
+
const outputMessages = (attributes as Record<string, unknown>)?.outputMessages;
|
|
123
|
+
const inputMimeType = (attributes as Record<string, unknown>)?.inputMimeType;
|
|
124
|
+
const outputMimeType = (attributes as Record<string, unknown>)?.outputMimeType;
|
|
125
|
+
|
|
126
|
+
if (system !== undefined) {
|
|
127
|
+
otelAttributes[SemanticConventions.LLM_SYSTEM] = String(system);
|
|
128
|
+
}
|
|
129
|
+
if (provider !== undefined) {
|
|
130
|
+
otelAttributes[SemanticConventions.LLM_PROVIDER] = String(provider);
|
|
131
|
+
}
|
|
114
132
|
if (modelParameters) {
|
|
115
133
|
otelAttributes[SemanticConventions.LLM_INVOCATION_PARAMETERS] =
|
|
116
134
|
_serialize(modelParameters);
|
|
117
|
-
// Also set
|
|
135
|
+
// Also set llm.model_parameters for compatibility
|
|
118
136
|
otelAttributes[OtelSpanAttributes.LLM_MODEL_PARAMETERS] =
|
|
119
137
|
_serialize(modelParameters);
|
|
120
138
|
}
|
|
@@ -135,7 +153,7 @@ export function createObservationAttributes(
|
|
|
135
153
|
usage.totalTokens;
|
|
136
154
|
}
|
|
137
155
|
}
|
|
138
|
-
// Also set
|
|
156
|
+
// Also set llm.usage_details for compatibility
|
|
139
157
|
otelAttributes[OtelSpanAttributes.LLM_USAGE_DETAILS] =
|
|
140
158
|
_serialize(usageDetails);
|
|
141
159
|
}
|
|
@@ -143,6 +161,22 @@ export function createObservationAttributes(
|
|
|
143
161
|
otelAttributes[OtelSpanAttributes.LLM_COMPLETION_START_TIME] =
|
|
144
162
|
_serialize(completionStartTime);
|
|
145
163
|
}
|
|
164
|
+
// Flatten LLM messages
|
|
165
|
+
if (inputMessages !== undefined && Array.isArray(inputMessages)) {
|
|
166
|
+
const messageAttrs = _flattenLLMMessages(inputMessages as LLMMessage[], "llm.input_messages");
|
|
167
|
+
Object.assign(otelAttributes, messageAttrs);
|
|
168
|
+
}
|
|
169
|
+
if (outputMessages !== undefined && Array.isArray(outputMessages)) {
|
|
170
|
+
const messageAttrs = _flattenLLMMessages(outputMessages as LLMMessage[], "llm.output_messages");
|
|
171
|
+
Object.assign(otelAttributes, messageAttrs);
|
|
172
|
+
}
|
|
173
|
+
// Add MIME types
|
|
174
|
+
if (inputMimeType !== undefined) {
|
|
175
|
+
otelAttributes[SemanticConventions.INPUT_MIME_TYPE] = String(inputMimeType);
|
|
176
|
+
}
|
|
177
|
+
if (outputMimeType !== undefined) {
|
|
178
|
+
otelAttributes[SemanticConventions.OUTPUT_MIME_TYPE] = String(outputMimeType);
|
|
179
|
+
}
|
|
146
180
|
}
|
|
147
181
|
|
|
148
182
|
// Embedding-specific attributes
|
|
@@ -156,6 +190,51 @@ export function createObservationAttributes(
|
|
|
156
190
|
}
|
|
157
191
|
}
|
|
158
192
|
|
|
193
|
+
// Tool-specific attributes
|
|
194
|
+
if (type === "tool") {
|
|
195
|
+
const toolName = (attributes as Record<string, unknown>)?.toolName ?? (attributes as Record<string, unknown>)?.tool_name;
|
|
196
|
+
const toolDescription = (attributes as Record<string, unknown>)?.toolDescription;
|
|
197
|
+
const toolParameters = (attributes as Record<string, unknown>)?.toolParameters;
|
|
198
|
+
const toolCall = (attributes as Record<string, unknown>)?.toolCall;
|
|
199
|
+
|
|
200
|
+
if (toolName !== undefined) {
|
|
201
|
+
otelAttributes[SemanticConventions.TOOL_NAME] = String(toolName);
|
|
202
|
+
}
|
|
203
|
+
if (toolDescription !== undefined) {
|
|
204
|
+
otelAttributes[SemanticConventions.TOOL_DESCRIPTION] = String(toolDescription);
|
|
205
|
+
}
|
|
206
|
+
if (toolParameters !== undefined) {
|
|
207
|
+
otelAttributes[SemanticConventions.TOOL_PARAMETERS] = _serialize(toolParameters);
|
|
208
|
+
}
|
|
209
|
+
// Flatten tool call if present
|
|
210
|
+
if (toolCall !== undefined && typeof toolCall === "object") {
|
|
211
|
+
const toolCallAttrs = _flattenToolCall(toolCall as ToolCall);
|
|
212
|
+
Object.assign(otelAttributes, toolCallAttrs);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Agent-specific attributes
|
|
217
|
+
if (type === "agent") {
|
|
218
|
+
const agentName = (attributes as Record<string, unknown>)?.agentName;
|
|
219
|
+
if (agentName !== undefined) {
|
|
220
|
+
otelAttributes[SemanticConventions.AGENT_NAME] = String(agentName);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Retriever-specific attributes
|
|
225
|
+
if (type === "retriever") {
|
|
226
|
+
const documents = (attributes as Record<string, unknown>)?.documents;
|
|
227
|
+
const query = (attributes as Record<string, unknown>)?.query;
|
|
228
|
+
|
|
229
|
+
if (documents !== undefined && Array.isArray(documents)) {
|
|
230
|
+
const docAttrs = _flattenDocuments(documents as Document[]);
|
|
231
|
+
Object.assign(otelAttributes, docAttrs);
|
|
232
|
+
}
|
|
233
|
+
if (query !== undefined) {
|
|
234
|
+
otelAttributes["retriever.query"] = String(query);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
159
238
|
// Add metadata (use OpenInference metadata convention)
|
|
160
239
|
const metadataAttrs = _flattenAndSerializeMetadata(
|
|
161
240
|
metadata,
|
|
@@ -163,13 +242,21 @@ export function createObservationAttributes(
|
|
|
163
242
|
);
|
|
164
243
|
Object.assign(otelAttributes, metadataAttrs);
|
|
165
244
|
|
|
166
|
-
// Also add
|
|
245
|
+
// Also add observation.metadata for compatibility
|
|
167
246
|
const obsetvabilityMetadataAttrs = _flattenAndSerializeMetadata(
|
|
168
247
|
metadata,
|
|
169
248
|
OtelSpanAttributes.OBSERVATION_METADATA
|
|
170
249
|
);
|
|
171
250
|
Object.assign(otelAttributes, obsetvabilityMetadataAttrs);
|
|
172
251
|
|
|
252
|
+
// Pass through any additional custom attributes (e.g., agui.thread_id, agui.run_id)
|
|
253
|
+
// Only pass through attributes that haven't been set yet to avoid overwriting internal ones
|
|
254
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
255
|
+
if (!(key in otelAttributes) && value !== undefined && value !== null) {
|
|
256
|
+
otelAttributes[key] = typeof value === 'string' ? value : _serialize(value);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
173
260
|
// Filter out null/undefined values
|
|
174
261
|
return Object.fromEntries(
|
|
175
262
|
Object.entries(otelAttributes).filter(([_, v]) => v != null),
|
|
@@ -198,10 +285,10 @@ function _serialize(obj: unknown): string | undefined {
|
|
|
198
285
|
*
|
|
199
286
|
* Converts nested metadata objects into dot-notation attribute keys.
|
|
200
287
|
* For example, `{ database: { host: 'localhost' } }` becomes
|
|
201
|
-
* `{ 'metadata.database.host': 'localhost' }` (or '
|
|
288
|
+
* `{ 'metadata.database.host': 'localhost' }` (or 'observation.metadata.database.host').
|
|
202
289
|
*
|
|
203
290
|
* @param metadata - Metadata object to flatten
|
|
204
|
-
* @param prefix - Attribute prefix (e.g., 'metadata' or '
|
|
291
|
+
* @param prefix - Attribute prefix (e.g., 'metadata' or 'observation.metadata')
|
|
205
292
|
* @returns Flattened metadata attributes
|
|
206
293
|
* @internal
|
|
207
294
|
*/
|
|
@@ -231,3 +318,161 @@ function _flattenAndSerializeMetadata(
|
|
|
231
318
|
|
|
232
319
|
return metadataAttributes;
|
|
233
320
|
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Flattens LLM messages into OpenTelemetry attributes.
|
|
324
|
+
*
|
|
325
|
+
* Follows OpenInference convention:
|
|
326
|
+
* - llm.input_messages.{i}.message.role
|
|
327
|
+
* - llm.input_messages.{i}.message.content
|
|
328
|
+
* - llm.input_messages.{i}.message.tool_calls
|
|
329
|
+
*
|
|
330
|
+
* @param messages - Array of LLM messages
|
|
331
|
+
* @param prefix - Attribute prefix ('llm.input_messages' or 'llm.output_messages')
|
|
332
|
+
* @returns Flattened message attributes
|
|
333
|
+
* @internal
|
|
334
|
+
*/
|
|
335
|
+
function _flattenLLMMessages(
|
|
336
|
+
messages: LLMMessage[] | undefined,
|
|
337
|
+
prefix: string,
|
|
338
|
+
): Record<string, string> {
|
|
339
|
+
const attributes: Record<string, string> = {};
|
|
340
|
+
|
|
341
|
+
if (!messages || !Array.isArray(messages)) {
|
|
342
|
+
return attributes;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
messages.forEach((msg, index) => {
|
|
347
|
+
if (!msg || typeof msg !== "object") return;
|
|
348
|
+
|
|
349
|
+
const baseKey = `${prefix}.${index}.message`;
|
|
350
|
+
|
|
351
|
+
if (msg.role !== undefined) {
|
|
352
|
+
attributes[`${baseKey}.role`] = String(msg.role);
|
|
353
|
+
}
|
|
354
|
+
if (msg.content !== undefined) {
|
|
355
|
+
attributes[`${baseKey}.content`] = String(msg.content);
|
|
356
|
+
}
|
|
357
|
+
if (msg.toolCallId !== undefined) {
|
|
358
|
+
attributes[`${baseKey}.tool_call_id`] = String(msg.toolCallId);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Flatten tool calls if present
|
|
362
|
+
if (msg.toolCalls && Array.isArray(msg.toolCalls)) {
|
|
363
|
+
msg.toolCalls.forEach((toolCall, tcIndex) => {
|
|
364
|
+
if (!toolCall || typeof toolCall !== "object") return;
|
|
365
|
+
|
|
366
|
+
const tcKey = `${baseKey}.tool_calls.${tcIndex}`;
|
|
367
|
+
|
|
368
|
+
if (toolCall.id !== undefined) {
|
|
369
|
+
attributes[`${tcKey}.id`] = String(toolCall.id);
|
|
370
|
+
}
|
|
371
|
+
if (toolCall.function !== undefined) {
|
|
372
|
+
if (toolCall.function.name !== undefined) {
|
|
373
|
+
attributes[`${tcKey}.function.name`] = String(toolCall.function.name);
|
|
374
|
+
}
|
|
375
|
+
if (toolCall.function.arguments !== undefined) {
|
|
376
|
+
attributes[`${tcKey}.function.arguments`] = String(toolCall.function.arguments);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
} catch (e) {
|
|
383
|
+
// Silent fail - don't block business logic for observability
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return attributes;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Flattens documents into OpenTelemetry attributes.
|
|
391
|
+
*
|
|
392
|
+
* Follows OpenInference convention:
|
|
393
|
+
* - retrieval.documents.{i}.document.id
|
|
394
|
+
* - retrieval.documents.{i}.document.content
|
|
395
|
+
* - retrieval.documents.{i}.document.score
|
|
396
|
+
* - retrieval.documents.{i}.document.metadata
|
|
397
|
+
*
|
|
398
|
+
* @param documents - Array of documents
|
|
399
|
+
* @returns Flattened document attributes
|
|
400
|
+
* @internal
|
|
401
|
+
*/
|
|
402
|
+
function _flattenDocuments(
|
|
403
|
+
documents: Document[] | undefined,
|
|
404
|
+
): Record<string, string> {
|
|
405
|
+
const attributes: Record<string, string> = {};
|
|
406
|
+
|
|
407
|
+
if (!documents || !Array.isArray(documents)) {
|
|
408
|
+
return attributes;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
try {
|
|
412
|
+
documents.forEach((doc, index) => {
|
|
413
|
+
if (!doc || typeof doc !== "object") return;
|
|
414
|
+
|
|
415
|
+
const baseKey = `retrieval.documents.${index}.document`;
|
|
416
|
+
|
|
417
|
+
if (doc.id !== undefined) {
|
|
418
|
+
attributes[`${baseKey}.id`] = String(doc.id);
|
|
419
|
+
}
|
|
420
|
+
if (doc.content !== undefined) {
|
|
421
|
+
attributes[`${baseKey}.content`] = String(doc.content);
|
|
422
|
+
}
|
|
423
|
+
if (doc.score !== undefined) {
|
|
424
|
+
attributes[`${baseKey}.score`] = String(doc.score);
|
|
425
|
+
}
|
|
426
|
+
if (doc.metadata !== undefined && typeof doc.metadata === "object") {
|
|
427
|
+
const metadataSerialized = _serialize(doc.metadata);
|
|
428
|
+
if (metadataSerialized) {
|
|
429
|
+
attributes[`${baseKey}.metadata`] = metadataSerialized;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
} catch (e) {
|
|
434
|
+
// Silent fail - don't block business logic for observability
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return attributes;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Flattens tool call into OpenTelemetry attributes.
|
|
442
|
+
*
|
|
443
|
+
* Follows OpenInference convention:
|
|
444
|
+
* - tool_call.id
|
|
445
|
+
* - tool_call.function.name
|
|
446
|
+
* - tool_call.function.arguments
|
|
447
|
+
*
|
|
448
|
+
* @param toolCall - Tool call object
|
|
449
|
+
* @returns Flattened tool call attributes
|
|
450
|
+
* @internal
|
|
451
|
+
*/
|
|
452
|
+
function _flattenToolCall(
|
|
453
|
+
toolCall: ToolCall | undefined,
|
|
454
|
+
): Record<string, string> {
|
|
455
|
+
const attributes: Record<string, string> = {};
|
|
456
|
+
|
|
457
|
+
if (!toolCall || typeof toolCall !== "object") {
|
|
458
|
+
return attributes;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
if (toolCall.id !== undefined) {
|
|
463
|
+
attributes["tool_call.id"] = String(toolCall.id);
|
|
464
|
+
}
|
|
465
|
+
if (toolCall.function !== undefined) {
|
|
466
|
+
if (toolCall.function.name !== undefined) {
|
|
467
|
+
attributes["tool_call.function.name"] = String(toolCall.function.name);
|
|
468
|
+
}
|
|
469
|
+
if (toolCall.function.arguments !== undefined) {
|
|
470
|
+
attributes["tool_call.function.arguments"] = String(toolCall.function.arguments);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
} catch (e) {
|
|
474
|
+
// Silent fail - don't block business logic for observability
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return attributes;
|
|
478
|
+
}
|