UNPKG

openlit

Version:

OpenTelemetry-native Auto instrumentation library for monitoring LLM Applications, facilitating the integration of observability into your GenAI-driven projects

249 lines 11.7 kB
"use strict"; /** * OpenLIT OpenAI Agents TracingProcessor implementation. * * Integrates with the @openai/agents TracingProcessor interface. * All span data fields are read at onSpanEnd (when fully populated). * Compliant with OTel GenAI semantic conventions. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OpenLITTracingProcessor = void 0; const api_1 = require("@opentelemetry/api"); const semantic_conventions_1 = require("@opentelemetry/semantic-conventions"); const semantic_convention_1 = __importDefault(require("../../semantic-convention")); const constant_1 = require("../../constant"); const config_1 = __importDefault(require("../../config")); const helpers_1 = require("../../helpers"); const utils_1 = require("./utils"); const [OPENAI_SERVER_ADDRESS, OPENAI_SERVER_PORT] = (0, helpers_1.getServerAddressForProvider)('openai'); const LLM_SPAN_TYPES = new Set(['response', 'generation']); /** * TracingProcessor that emits OTel GenAI-compliant spans from * the @openai/agents SDK tracing lifecycle. * * Thread-safe by design: each trace/span entry is keyed independently. * LLM span types (response, generation) are skipped -- the OpenAI * provider instrumentation handles those with richer telemetry. */ class OpenLITTracingProcessor { constructor(tracer, agentCreationRegistry = null) { // SDK span_id -> SpanEntry this._otelSpans = new Map(); // SDK trace_id -> TraceEntry this._rootSpans = new Map(); // trace_id -> group_id (conversation id) this._traceGroupIds = new Map(); // Agent handoff tracker (bounded Map) this._handoffTracker = new Map(); this._tracer = tracer; this._agentCreationRegistry = agentCreationRegistry; } // ------------------------------------------------------------------ // Trace lifecycle // ------------------------------------------------------------------ async onTraceStart(sdkTrace) { try { const traceId = sdkTrace.traceId ?? 'unknown'; const traceName = sdkTrace.name ?? 'workflow'; const groupId = sdkTrace.groupId ?? null; const operation = semantic_convention_1.default.GEN_AI_OPERATION_TYPE_FRAMEWORK; const spanName = `${operation} ${traceName}`; const otelSpan = this._tracer.startSpan(spanName, { kind: api_1.SpanKind.INTERNAL, attributes: { [semantic_convention_1.default.GEN_AI_OPERATION]: operation, [semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL]: semantic_convention_1.default.GEN_AI_SYSTEM_OPENAI, }, }); const startTime = Date.now(); this._rootSpans.set(traceId, { otelSpan, startTime }); if (groupId) { this._traceGroupIds.set(traceId, String(groupId)); } (0, helpers_1.setFrameworkParentContext)(api_1.trace.setSpan(api_1.context.active(), otelSpan)); } catch { // swallow } } async onTraceEnd(sdkTrace) { try { const traceId = sdkTrace.traceId ?? 'unknown'; const traceName = sdkTrace.name ?? 'workflow'; const entry = this._rootSpans.get(traceId); this._rootSpans.delete(traceId); const groupId = this._traceGroupIds.get(traceId) ?? null; this._traceGroupIds.delete(traceId); if (!entry) return; const { otelSpan, startTime } = entry; const endTime = Date.now(); const durationMs = endTime - startTime; // Set common framework attributes otelSpan.setAttribute(semantic_conventions_1.ATTR_TELEMETRY_SDK_NAME, constant_1.SDK_NAME); otelSpan.setAttribute(semantic_convention_1.default.GEN_AI_SDK_VERSION, constant_1.SDK_VERSION); otelSpan.setAttribute(semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL, semantic_convention_1.default.GEN_AI_SYSTEM_OPENAI); otelSpan.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_FRAMEWORK); if (OPENAI_SERVER_ADDRESS) { otelSpan.setAttribute(semantic_convention_1.default.SERVER_ADDRESS, OPENAI_SERVER_ADDRESS); if (OPENAI_SERVER_PORT) { otelSpan.setAttribute(semantic_convention_1.default.SERVER_PORT, OPENAI_SERVER_PORT); } } otelSpan.setAttribute(semantic_convention_1.default.ATTR_DEPLOYMENT_ENVIRONMENT, config_1.default.environment ?? 'default'); otelSpan.setAttribute(semantic_conventions_1.ATTR_SERVICE_NAME, config_1.default.applicationName ?? 'default'); otelSpan.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_OPERATION_DURATION, durationMs / 1000); otelSpan.setAttribute(semantic_convention_1.default.GEN_AI_WORKFLOW_NAME, traceName); if (groupId) { otelSpan.setAttribute(semantic_convention_1.default.GEN_AI_CONVERSATION_ID, groupId); } (0, helpers_1.applyCustomSpanAttributes)(otelSpan); // Error handling const error = sdkTrace.error; if (error) { const errorType = typeof error === 'object' && error !== null ? error.constructor?.name || error.code || '_OTHER' : '_OTHER'; const errorMsg = typeof error === 'object' && error !== null ? error.message ?? String(error) : String(error); otelSpan.setAttribute(semantic_convention_1.default.ERROR_TYPE, errorType); otelSpan.setStatus({ code: api_1.SpanStatusCode.ERROR, message: errorMsg }); } else { otelSpan.setStatus({ code: api_1.SpanStatusCode.OK }); } // Metrics if (!config_1.default.disableMetrics) { (0, utils_1.recordMetrics)(semantic_convention_1.default.GEN_AI_OPERATION_TYPE_FRAMEWORK, durationMs / 1000, null); } otelSpan.end(); (0, helpers_1.clearFrameworkParentContext)(); } catch { // swallow } } // ------------------------------------------------------------------ // Span lifecycle // ------------------------------------------------------------------ async onSpanStart(sdkSpan) { try { const spanData = sdkSpan.spanData; const spanType = spanData?.type ?? 'unknown'; // Skip LLM span types -- let the OpenAI provider instrumentation handle them if (LLM_SPAN_TYPES.has(spanType)) return; const traceId = sdkSpan.traceId ?? 'unknown'; const sdkSpanId = sdkSpan.spanId ?? null; const parentSdkId = sdkSpan.parentId ?? null; const operation = (0, utils_1.getOperationType)(spanType); const kind = (0, utils_1.getSpanKind)(operation); const spanName = (0, utils_1.generateSpanName)(spanData); // Find parent OTel span context let parentCtx = api_1.context.active(); if (parentSdkId && this._otelSpans.has(parentSdkId)) { const parentEntry = this._otelSpans.get(parentSdkId); parentCtx = api_1.trace.setSpan(api_1.context.active(), parentEntry.otelSpan); } else if (this._rootSpans.has(traceId)) { const rootEntry = this._rootSpans.get(traceId); parentCtx = api_1.trace.setSpan(api_1.context.active(), rootEntry.otelSpan); } // Span links: connect invoke_agent back to create_agent const links = []; if (spanType === 'agent' && this._agentCreationRegistry) { const agentName = spanData.name; if (agentName) { const creationCtx = this._agentCreationRegistry.get(String(agentName)); if (creationCtx) { links.push({ context: creationCtx, attributes: {} }); } } } const otelSpan = this._tracer.startSpan(spanName, { kind, links, attributes: { [semantic_convention_1.default.GEN_AI_OPERATION]: operation, [semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL]: semantic_convention_1.default.GEN_AI_SYSTEM_OPENAI, }, }, parentCtx); const startTime = Date.now(); if (sdkSpanId) { this._otelSpans.set(sdkSpanId, { otelSpan, startTime }); } (0, helpers_1.setFrameworkParentContext)(api_1.trace.setSpan(parentCtx, otelSpan)); } catch { // swallow } } async onSpanEnd(sdkSpan) { try { const spanData = sdkSpan.spanData; const spanType = spanData?.type ?? 'unknown'; // Skip LLM span types if (LLM_SPAN_TYPES.has(spanType)) return; const sdkSpanId = sdkSpan.spanId ?? null; const traceId = sdkSpan.traceId ?? 'unknown'; if (!sdkSpanId) return; const entry = this._otelSpans.get(sdkSpanId); this._otelSpans.delete(sdkSpanId); if (!entry) return; const { otelSpan, startTime } = entry; const conversationId = this._traceGroupIds.get(traceId) ?? null; (0, utils_1.processSpanEnd)(otelSpan, sdkSpan, startTime, conversationId, this._handoffTracker); otelSpan.end(); // Restore parent context so subsequent provider spans nest correctly const parentSdkId = sdkSpan.parentId ?? null; if (parentSdkId && this._otelSpans.has(parentSdkId)) { const parentEntry = this._otelSpans.get(parentSdkId); (0, helpers_1.setFrameworkParentContext)(api_1.trace.setSpan(api_1.context.active(), parentEntry.otelSpan)); } else if (this._rootSpans.has(traceId)) { const rootEntry = this._rootSpans.get(traceId); (0, helpers_1.setFrameworkParentContext)(api_1.trace.setSpan(api_1.context.active(), rootEntry.otelSpan)); } } catch { // swallow } } // ------------------------------------------------------------------ // Lifecycle management // ------------------------------------------------------------------ async forceFlush() { try { for (const [, { otelSpan }] of this._otelSpans) { try { otelSpan.end(); } catch { /* ignore */ } } this._otelSpans.clear(); for (const [, { otelSpan }] of this._rootSpans) { try { otelSpan.end(); } catch { /* ignore */ } } this._rootSpans.clear(); this._traceGroupIds.clear(); } catch { // swallow } } async shutdown() { await this.forceFlush(); } } exports.OpenLITTracingProcessor = OpenLITTracingProcessor; //# sourceMappingURL=processor.js.map