@cloudbase/agent-observability 1.0.1-alpha.27 → 1.0.1-alpha.29
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/chunk-PJ5HSKYS.mjs +80 -0
- package/dist/chunk-PJ5HSKYS.mjs.map +1 -0
- package/dist/{chunk-43GF2BGQ.mjs → chunk-Q5P7VQEQ.mjs} +230 -204
- package/dist/chunk-Q5P7VQEQ.mjs.map +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +201 -134
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/langchain.js +206 -134
- package/dist/langchain.js.map +1 -1
- package/dist/langchain.mjs +7 -2
- package/dist/langchain.mjs.map +1 -1
- package/dist/server.js +79 -1364
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +46 -46
- package/dist/server.mjs.map +1 -1
- package/package.json +7 -10
- package/src/index.ts +259 -160
- package/src/langchain/CallbackHandler.ts +6 -0
- package/src/server/SingleLineConsoleSpanExporter.ts +47 -49
- package/dist/chunk-43GF2BGQ.mjs.map +0 -1
- package/dist/chunk-NFEGQTCC.mjs +0 -27
- package/dist/chunk-NFEGQTCC.mjs.map +0 -1
- package/dist/esm-PGEDANAI.mjs +0 -1030
- package/dist/esm-PGEDANAI.mjs.map +0 -1
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 * as fs from 'fs';\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 // SCF environment: use sync write to ensure logs are flushed before freeze\n const isSCF = process.env.TENCENTCLOUD_RUNENV === 'SCF';\n\n for (const span of spans) {\n const spanDict = this.spanToDict(span);\n const jsonLine = JSON.stringify(spanDict) + '\\n';\n\n if (isSCF) {\n try {\n fs.writeSync(1, jsonLine); // fd 1 = stdout, sync for SCF reliability\n } catch {\n console.log(jsonLine.trim()); // Fallback to async\n }\n } else {\n console.log(jsonLine.trim()); // Normal async output\n }\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 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 return {\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}\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":[]}
|
|
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": "1.0.1-alpha.
|
|
3
|
+
"version": "1.0.1-alpha.29",
|
|
4
4
|
"description": "OpenInference-compatible observability for AG-Kit",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -64,19 +64,16 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@arizeai/openinference-semantic-conventions": "^2.1.7",
|
|
66
66
|
"@opentelemetry/api": "^1.9.0",
|
|
67
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.211.0",
|
|
68
|
+
"@opentelemetry/resources": "^2.5.0",
|
|
69
|
+
"@opentelemetry/sdk-trace-base": "^2.5.0",
|
|
70
|
+
"@opentelemetry/sdk-trace-node": "^2.5.0",
|
|
67
71
|
"@opentelemetry/semantic-conventions": "^1.39.0",
|
|
68
|
-
"@cloudbase/agent-shared": "^1.0.1-alpha.
|
|
69
|
-
},
|
|
70
|
-
"optionalDependencies": {
|
|
71
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.210.0",
|
|
72
|
-
"@opentelemetry/resources": "^2.4.0",
|
|
73
|
-
"@opentelemetry/sdk-node": "^0.210.0",
|
|
74
|
-
"@opentelemetry/sdk-trace-base": "^2.4.0",
|
|
75
|
-
"@opentelemetry/sdk-trace-node": "^2.4.0"
|
|
72
|
+
"@cloudbase/agent-shared": "^1.0.1-alpha.29"
|
|
76
73
|
},
|
|
77
74
|
"devDependencies": {
|
|
78
75
|
"@langchain/core": "^1.0.2",
|
|
79
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.
|
|
76
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.211.0",
|
|
80
77
|
"@types/node": "^20.0.0",
|
|
81
78
|
"tsup": "^8.5.0",
|
|
82
79
|
"typescript": "^5.0.0"
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { trace, context, TimeInput, SpanStatusCode, Span, SpanContext, Link } from "@opentelemetry/api";
|
|
7
|
+
import { trace, context, TimeInput, SpanStatusCode, Span, SpanContext, Link, INVALID_SPAN_CONTEXT } from "@opentelemetry/api";
|
|
8
|
+
|
|
9
|
+
// Re-export SpanContext type for adapters to use without depending on @opentelemetry/api directly
|
|
10
|
+
export type { SpanContext } from "@opentelemetry/api";
|
|
8
11
|
|
|
9
12
|
import {
|
|
10
13
|
createObservationAttributes,
|
|
@@ -164,6 +167,50 @@ function createParentContext(
|
|
|
164
167
|
return trace.setSpanContext(context.active(), parentSpanContext);
|
|
165
168
|
}
|
|
166
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Creates a NonRecordingSpan that safely accepts all Span operations as no-ops.
|
|
172
|
+
* Used as a fallback when observation creation fails, ensuring observability
|
|
173
|
+
* never blocks business logic.
|
|
174
|
+
*
|
|
175
|
+
* @internal
|
|
176
|
+
*/
|
|
177
|
+
function createNoopSpan(): Span {
|
|
178
|
+
return trace.wrapSpanContext(INVALID_SPAN_CONTEXT);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Creates a no-op observation of the specified type.
|
|
183
|
+
* All methods (update, end, setErrorStatus, etc.) are safe to call but do nothing.
|
|
184
|
+
*
|
|
185
|
+
* @param asType - The observation type to create
|
|
186
|
+
* @returns A no-op observation instance
|
|
187
|
+
* @internal
|
|
188
|
+
*/
|
|
189
|
+
function createNoopObservation(asType: ObservationType = "chain"): Observation {
|
|
190
|
+
const otelSpan = createNoopSpan();
|
|
191
|
+
switch (asType) {
|
|
192
|
+
case "llm":
|
|
193
|
+
return new ObservationLLM({ otelSpan });
|
|
194
|
+
case "embedding":
|
|
195
|
+
return new ObservationEmbedding({ otelSpan });
|
|
196
|
+
case "agent":
|
|
197
|
+
return new ObservationAgent({ otelSpan });
|
|
198
|
+
case "tool":
|
|
199
|
+
return new ObservationTool({ otelSpan });
|
|
200
|
+
case "retriever":
|
|
201
|
+
return new ObservationRetriever({ otelSpan });
|
|
202
|
+
case "reranker":
|
|
203
|
+
return new ObservationReranker({ otelSpan });
|
|
204
|
+
case "evaluator":
|
|
205
|
+
return new ObservationEvaluator({ otelSpan });
|
|
206
|
+
case "guardrail":
|
|
207
|
+
return new ObservationGuardrail({ otelSpan });
|
|
208
|
+
case "chain":
|
|
209
|
+
default:
|
|
210
|
+
return new ObservationChain({ otelSpan });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
167
214
|
// Function overloads for proper type inference
|
|
168
215
|
// Generic overload for dynamic asType (returns Observation union)
|
|
169
216
|
export function startObservation(
|
|
@@ -284,71 +331,80 @@ export function startObservation(
|
|
|
284
331
|
): Observation {
|
|
285
332
|
const { asType = "chain", ...observationOptions } = options || {};
|
|
286
333
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
334
|
+
try {
|
|
335
|
+
const otelSpan = createOtelSpan({
|
|
336
|
+
name,
|
|
337
|
+
...observationOptions,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
switch (asType) {
|
|
341
|
+
case "llm":
|
|
342
|
+
return new ObservationLLM({
|
|
343
|
+
otelSpan,
|
|
344
|
+
attributes: attributes as LLMAttributes,
|
|
345
|
+
});
|
|
298
346
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
347
|
+
case "embedding":
|
|
348
|
+
return new ObservationEmbedding({
|
|
349
|
+
otelSpan,
|
|
350
|
+
attributes: attributes as EmbeddingAttributes,
|
|
351
|
+
});
|
|
304
352
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
353
|
+
case "agent":
|
|
354
|
+
return new ObservationAgent({
|
|
355
|
+
otelSpan,
|
|
356
|
+
attributes: attributes as AgentAttributes,
|
|
357
|
+
});
|
|
310
358
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
359
|
+
case "tool":
|
|
360
|
+
return new ObservationTool({
|
|
361
|
+
otelSpan,
|
|
362
|
+
attributes: attributes as ToolAttributes,
|
|
363
|
+
});
|
|
316
364
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
365
|
+
case "chain":
|
|
366
|
+
return new ObservationChain({
|
|
367
|
+
otelSpan,
|
|
368
|
+
attributes: attributes as ChainAttributes,
|
|
369
|
+
});
|
|
322
370
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
371
|
+
case "retriever":
|
|
372
|
+
return new ObservationRetriever({
|
|
373
|
+
otelSpan,
|
|
374
|
+
attributes: attributes as RetrieverAttributes,
|
|
375
|
+
});
|
|
328
376
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
377
|
+
case "reranker":
|
|
378
|
+
return new ObservationReranker({
|
|
379
|
+
otelSpan,
|
|
380
|
+
attributes: attributes as RerankerAttributes,
|
|
381
|
+
});
|
|
334
382
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
383
|
+
case "evaluator":
|
|
384
|
+
return new ObservationEvaluator({
|
|
385
|
+
otelSpan,
|
|
386
|
+
attributes: attributes as EvaluatorAttributes,
|
|
387
|
+
});
|
|
340
388
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
389
|
+
case "guardrail":
|
|
390
|
+
return new ObservationGuardrail({
|
|
391
|
+
otelSpan,
|
|
392
|
+
attributes: attributes as GuardrailAttributes,
|
|
393
|
+
});
|
|
346
394
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
395
|
+
default:
|
|
396
|
+
return new ObservationChain({
|
|
397
|
+
otelSpan,
|
|
398
|
+
attributes: attributes as BaseSpanAttributes,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
} catch (err) {
|
|
402
|
+
// Observability must never block business logic - return a no-op observation
|
|
403
|
+
console.warn(
|
|
404
|
+
`[Observability] Failed to create observation "${name}":`,
|
|
405
|
+
err instanceof Error ? err.message : err,
|
|
406
|
+
);
|
|
407
|
+
return createNoopObservation(asType);
|
|
352
408
|
}
|
|
353
409
|
}
|
|
354
410
|
|
|
@@ -547,71 +603,92 @@ export function startActiveObservation<
|
|
|
547
603
|
>(name: string, fn: F, options?: StartActiveObservationOpts): ReturnType<F> {
|
|
548
604
|
const { asType = "chain", endOnExit, ...observationOptions } = options || {};
|
|
549
605
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
context.active(),
|
|
555
|
-
(span) => {
|
|
556
|
-
try {
|
|
557
|
-
let observation: Observation;
|
|
558
|
-
|
|
559
|
-
switch (asType) {
|
|
560
|
-
case "llm":
|
|
561
|
-
observation = new ObservationLLM({ otelSpan: span });
|
|
562
|
-
break;
|
|
563
|
-
case "embedding":
|
|
564
|
-
observation = new ObservationEmbedding({ otelSpan: span });
|
|
565
|
-
break;
|
|
566
|
-
case "agent":
|
|
567
|
-
observation = new ObservationAgent({ otelSpan: span });
|
|
568
|
-
break;
|
|
569
|
-
case "tool":
|
|
570
|
-
observation = new ObservationTool({ otelSpan: span });
|
|
571
|
-
break;
|
|
572
|
-
case "retriever":
|
|
573
|
-
observation = new ObservationRetriever({ otelSpan: span });
|
|
574
|
-
break;
|
|
575
|
-
case "reranker":
|
|
576
|
-
observation = new ObservationReranker({ otelSpan: span });
|
|
577
|
-
break;
|
|
578
|
-
case "evaluator":
|
|
579
|
-
observation = new ObservationEvaluator({ otelSpan: span });
|
|
580
|
-
break;
|
|
581
|
-
case "guardrail":
|
|
582
|
-
observation = new ObservationGuardrail({ otelSpan: span });
|
|
583
|
-
break;
|
|
584
|
-
case "chain":
|
|
585
|
-
default:
|
|
586
|
-
observation = new ObservationChain({ otelSpan: span });
|
|
587
|
-
}
|
|
606
|
+
// Track whether fn has been called to distinguish observability errors from business errors.
|
|
607
|
+
// If fn was called and threw, we must re-throw (business error).
|
|
608
|
+
// If fn was never called, the error is from observability infrastructure and we can fall back.
|
|
609
|
+
let fnCalled = false;
|
|
588
610
|
|
|
589
|
-
|
|
611
|
+
try {
|
|
612
|
+
return getTracer().startActiveSpan(
|
|
613
|
+
name,
|
|
614
|
+
{ startTime: observationOptions?.startTime },
|
|
615
|
+
createParentContext(observationOptions?.parentSpanContext) ??
|
|
616
|
+
context.active(),
|
|
617
|
+
(span) => {
|
|
618
|
+
try {
|
|
619
|
+
let observation: Observation;
|
|
620
|
+
|
|
621
|
+
switch (asType) {
|
|
622
|
+
case "llm":
|
|
623
|
+
observation = new ObservationLLM({ otelSpan: span });
|
|
624
|
+
break;
|
|
625
|
+
case "embedding":
|
|
626
|
+
observation = new ObservationEmbedding({ otelSpan: span });
|
|
627
|
+
break;
|
|
628
|
+
case "agent":
|
|
629
|
+
observation = new ObservationAgent({ otelSpan: span });
|
|
630
|
+
break;
|
|
631
|
+
case "tool":
|
|
632
|
+
observation = new ObservationTool({ otelSpan: span });
|
|
633
|
+
break;
|
|
634
|
+
case "retriever":
|
|
635
|
+
observation = new ObservationRetriever({ otelSpan: span });
|
|
636
|
+
break;
|
|
637
|
+
case "reranker":
|
|
638
|
+
observation = new ObservationReranker({ otelSpan: span });
|
|
639
|
+
break;
|
|
640
|
+
case "evaluator":
|
|
641
|
+
observation = new ObservationEvaluator({ otelSpan: span });
|
|
642
|
+
break;
|
|
643
|
+
case "guardrail":
|
|
644
|
+
observation = new ObservationGuardrail({ otelSpan: span });
|
|
645
|
+
break;
|
|
646
|
+
case "chain":
|
|
647
|
+
default:
|
|
648
|
+
observation = new ObservationChain({ otelSpan: span });
|
|
649
|
+
}
|
|
590
650
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
651
|
+
fnCalled = true;
|
|
652
|
+
const result = fn(observation as Parameters<F>[0]);
|
|
653
|
+
|
|
654
|
+
if (result instanceof Promise) {
|
|
655
|
+
return wrapPromise(
|
|
656
|
+
result,
|
|
657
|
+
span,
|
|
658
|
+
endOnExit,
|
|
659
|
+
) as ReturnType<F>;
|
|
660
|
+
} else {
|
|
661
|
+
if (endOnExit !== false) {
|
|
662
|
+
span.end();
|
|
663
|
+
}
|
|
664
|
+
return result as ReturnType<F>;
|
|
665
|
+
}
|
|
666
|
+
} catch (err) {
|
|
667
|
+
span.setStatus({
|
|
668
|
+
code: SpanStatusCode.ERROR,
|
|
669
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
670
|
+
});
|
|
598
671
|
if (endOnExit !== false) {
|
|
599
672
|
span.end();
|
|
600
673
|
}
|
|
601
|
-
|
|
602
|
-
}
|
|
603
|
-
} catch (err) {
|
|
604
|
-
span.setStatus({
|
|
605
|
-
code: SpanStatusCode.ERROR,
|
|
606
|
-
message: err instanceof Error ? err.message : "Unknown error",
|
|
607
|
-
});
|
|
608
|
-
if (endOnExit !== false) {
|
|
609
|
-
span.end();
|
|
674
|
+
throw err;
|
|
610
675
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
676
|
+
},
|
|
677
|
+
);
|
|
678
|
+
} catch (err) {
|
|
679
|
+
// If fn was already called, the error is from business logic - re-throw
|
|
680
|
+
if (fnCalled) {
|
|
681
|
+
throw err;
|
|
682
|
+
}
|
|
683
|
+
// Error is from observability infrastructure (getTracer, startActiveSpan) -
|
|
684
|
+
// fall back to executing fn with a no-op observation
|
|
685
|
+
console.warn(
|
|
686
|
+
`[Observability] startActiveObservation "${name}" failed, falling back to no-op:`,
|
|
687
|
+
err instanceof Error ? err.message : err,
|
|
688
|
+
);
|
|
689
|
+
const noopObservation = createNoopObservation(asType);
|
|
690
|
+
return fn(noopObservation as Parameters<F>[0]) as ReturnType<F>;
|
|
691
|
+
}
|
|
615
692
|
}
|
|
616
693
|
|
|
617
694
|
/**
|
|
@@ -732,53 +809,75 @@ export function observe<T extends (...args: any[]) => any>(
|
|
|
732
809
|
): ReturnType<T> {
|
|
733
810
|
const name = fn.name || "anonymous-function";
|
|
734
811
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
// Set the observation span as active in the context
|
|
749
|
-
const activeContext = trace.setSpan(context.active(), observation.otelSpan);
|
|
750
|
-
|
|
751
|
-
// Execute the function within the observation context
|
|
752
|
-
const result = context.with(activeContext, () => fn.apply(this, args));
|
|
753
|
-
|
|
754
|
-
// Handle promises
|
|
755
|
-
if (result instanceof Promise) {
|
|
756
|
-
return result.then(
|
|
757
|
-
(value) => {
|
|
758
|
-
if (captureOutput) {
|
|
759
|
-
observation.update({ output: value });
|
|
760
|
-
}
|
|
761
|
-
observation.end();
|
|
762
|
-
return value;
|
|
812
|
+
let observation: Observation;
|
|
813
|
+
let fnCalled = false;
|
|
814
|
+
try {
|
|
815
|
+
// Prepare input data
|
|
816
|
+
const inputData = captureInput ? _captureArguments(args) : undefined;
|
|
817
|
+
|
|
818
|
+
// Create the observation (safe - returns no-op on failure)
|
|
819
|
+
observation = startObservation(
|
|
820
|
+
name,
|
|
821
|
+
inputData ? { input: inputData } : {},
|
|
822
|
+
{
|
|
823
|
+
...observationOptions,
|
|
824
|
+
asType: asType as "chain",
|
|
763
825
|
},
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
// Set the observation span as active in the context
|
|
829
|
+
const activeContext = trace.setSpan(context.active(), observation.otelSpan);
|
|
830
|
+
|
|
831
|
+
// Execute the function within the observation context
|
|
832
|
+
fnCalled = true;
|
|
833
|
+
const result = context.with(activeContext, () => fn.apply(this, args));
|
|
834
|
+
|
|
835
|
+
// Handle promises
|
|
836
|
+
if (result instanceof Promise) {
|
|
837
|
+
return result.then(
|
|
838
|
+
(value) => {
|
|
839
|
+
if (captureOutput) {
|
|
840
|
+
observation.update({ output: value });
|
|
841
|
+
}
|
|
842
|
+
observation.end();
|
|
843
|
+
return value;
|
|
844
|
+
},
|
|
845
|
+
(err: unknown) => {
|
|
846
|
+
observation.update({
|
|
847
|
+
level: "ERROR",
|
|
848
|
+
statusMessage: err instanceof Error ? err.message : "Unknown error",
|
|
849
|
+
});
|
|
850
|
+
observation.end();
|
|
851
|
+
throw err;
|
|
852
|
+
},
|
|
853
|
+
) as ReturnType<T>;
|
|
854
|
+
}
|
|
774
855
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
856
|
+
// Handle synchronous functions
|
|
857
|
+
if (captureOutput) {
|
|
858
|
+
observation.update({ output: result });
|
|
859
|
+
}
|
|
860
|
+
observation.end();
|
|
780
861
|
|
|
781
|
-
|
|
862
|
+
return result as ReturnType<T>;
|
|
863
|
+
} catch (err) {
|
|
864
|
+
// If fn was already called, the error is from business logic - re-throw
|
|
865
|
+
if (fnCalled) {
|
|
866
|
+
throw err;
|
|
867
|
+
}
|
|
868
|
+
// Observability infra error (trace.setSpan, context.with) -
|
|
869
|
+
// fall back to calling fn directly without observability context
|
|
870
|
+
console.warn(
|
|
871
|
+
`[Observability] observe "${name}" failed, falling back to direct call:`,
|
|
872
|
+
err instanceof Error ? (err as Error).message : err,
|
|
873
|
+
);
|
|
874
|
+
try {
|
|
875
|
+
observation!?.end();
|
|
876
|
+
} catch {
|
|
877
|
+
// Ignore end() failures on cleanup
|
|
878
|
+
}
|
|
879
|
+
return fn.apply(this, args);
|
|
880
|
+
}
|
|
782
881
|
};
|
|
783
882
|
|
|
784
883
|
// Preserve function properties
|
|
@@ -766,6 +766,12 @@ export class CallbackHandler extends BaseCallbackHandler {
|
|
|
766
766
|
);
|
|
767
767
|
this.runMap.set(runId, observation);
|
|
768
768
|
|
|
769
|
+
if (this.runMap.size > 1000) {
|
|
770
|
+
this.logger.warn?.(
|
|
771
|
+
`runMap size (${this.runMap.size}) exceeds 1000. Possible missing end callbacks.`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
|
|
769
775
|
return observation;
|
|
770
776
|
}
|
|
771
777
|
|