UNPKG

@aws/aws-distro-opentelemetry-node-autoinstrumentation

Version:

This package provides Amazon Web Services distribution of the OpenTelemetry Node Instrumentation, which allows for auto-instrumentation of NodeJS applications.

312 lines 14.7 kB
"use strict"; // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 Object.defineProperty(exports, "__esModule", { value: true }); exports.OpenTelemetryTracingProcessor = void 0; /* eslint-disable @typescript-eslint/no-explicit-any */ const api_1 = require("@opentelemetry/api"); const semconv_1 = require("../common/semconv"); const instrumentation_utils_1 = require("../common/instrumentation-utils"); class OpenTelemetryTracingProcessor { constructor(tracer, captureMessageContent) { this._spanMap = new Map(); this._disabled = false; this._tracer = tracer; this._captureMessageContent = captureMessageContent; } get disabled() { return this._disabled; } disable() { this._disabled = true; } enable() { this._disabled = false; } getOtelContext(spanId) { var _a; return (_a = this._spanMap.get(spanId)) === null || _a === void 0 ? void 0 : _a.otelContext; } async onTraceStart(_trace) { } async onTraceEnd(_trace) { } async onSpanStart(sdkSpan) { var _a; if (this._disabled) return; const existing = this._spanMap.get(sdkSpan.spanId); if (existing) return; const spanData = sdkSpan.spanData; if (!(spanData === null || spanData === void 0 ? void 0 : spanData.type)) return; const parentContext = (sdkSpan.parentId && ((_a = this._spanMap.get(sdkSpan.parentId)) === null || _a === void 0 ? void 0 : _a.otelContext)) || api_1.context.active(); const { name, kind } = this._getSpanNameAndKind(spanData); const otelSpan = this._tracer.startSpan(name, { kind }, parentContext); this._setStartAttributes(otelSpan, spanData); const otelContext = api_1.trace.setSpan(parentContext, otelSpan); this._spanMap.set(sdkSpan.spanId, { otelSpan, otelContext }); } async onSpanEnd(span) { if (this._disabled) return; const entry = this._spanMap.get(span.spanId); if (!entry) return; const { otelSpan } = entry; const spanData = span.spanData; this._setEndAttributes(otelSpan, spanData, span.parentId); if (span.error) { otelSpan.setStatus({ code: api_1.SpanStatusCode.ERROR, message: span.error.message }); otelSpan.recordException({ message: span.error.message }); } otelSpan.end(); this._spanMap.delete(span.spanId); } async shutdown() { this._spanMap.clear(); } async forceFlush() { this._spanMap.clear(); } _getSpanNameAndKind(spanData) { var _a, _b, _c; const data = spanData; switch (spanData.type) { case 'agent': return { name: `${semconv_1.GEN_AI_OPERATION_NAME_VALUE_INVOKE_AGENT} ${data.name}`, kind: api_1.SpanKind.INTERNAL }; case 'response': { const model = (_a = spanData._response) === null || _a === void 0 ? void 0 : _a.model; const name = model ? `${semconv_1.GEN_AI_OPERATION_NAME_VALUE_CHAT} ${model}` : semconv_1.GEN_AI_OPERATION_NAME_VALUE_CHAT; return { name, kind: api_1.SpanKind.CLIENT }; } case 'function': return { name: `${semconv_1.GEN_AI_OPERATION_NAME_VALUE_EXECUTE_TOOL} ${data.name}`, kind: api_1.SpanKind.INTERNAL }; default: { const label = (_c = (_b = data.name) !== null && _b !== void 0 ? _b : data.server) !== null && _c !== void 0 ? _c : data.to_agent; const name = label ? `${spanData.type} ${label}` : spanData.type; return { name, kind: api_1.SpanKind.INTERNAL }; } } } _setStartAttributes(otelSpan, spanData) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_PROVIDER_NAME, semconv_1.GEN_AI_PROVIDER_NAME_VALUE_OPENAI); switch (spanData.type) { case 'agent': otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_OPERATION_NAME, semconv_1.GEN_AI_OPERATION_NAME_VALUE_INVOKE_AGENT); break; case 'response': otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_OPERATION_NAME, semconv_1.GEN_AI_OPERATION_NAME_VALUE_CHAT); break; case 'function': otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_OPERATION_NAME, semconv_1.GEN_AI_OPERATION_NAME_VALUE_EXECUTE_TOOL); otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_TOOL_TYPE, 'function'); break; } this._mapSdkFieldsToAttributes(otelSpan, spanData); } _setEndAttributes(otelSpan, spanData, parentId) { switch (spanData.type) { case 'response': this._setResponseEndAttributes(otelSpan, spanData, parentId); break; case 'function': this._setFunctionEndAttributes(otelSpan, spanData); break; } this._mapSdkFieldsToAttributes(otelSpan, spanData); } _setResponseEndAttributes(otelSpan, spanData, parentId) { const response = spanData._response; if (spanData.response_id) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_RESPONSE_ID, spanData.response_id); } if (!response) return; const model = response.model; if (model) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_RESPONSE_MODEL, model); otelSpan.name = `${semconv_1.GEN_AI_OPERATION_NAME_VALUE_CHAT} ${model}`; this._propagateModelToAgent(parentId, model); } if (response.usage) { if (response.usage.input_tokens != null) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_USAGE_INPUT_TOKENS, response.usage.input_tokens); } if (response.usage.output_tokens != null) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, response.usage.output_tokens); } } if (response.temperature != null) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_REQUEST_TEMPERATURE, response.temperature); } if (response.top_p != null) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_REQUEST_TOP_P, response.top_p); } const finishReasons = this._getFinishReasons(response); if (finishReasons.length > 0) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_RESPONSE_FINISH_REASONS, finishReasons); } if (response.tools && Array.isArray(response.tools)) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_TOOL_DEFINITIONS, (0, instrumentation_utils_1.serializeToJson)(response.tools)); } if (this._captureMessageContent) { if (response.instructions) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_SYSTEM_INSTRUCTIONS, (0, instrumentation_utils_1.serializeToJson)([{ type: 'text', content: response.instructions }])); } const inputMessages = this._formatInputMessages(spanData._input); if (inputMessages) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_INPUT_MESSAGES, inputMessages); } const outputMessages = this._formatOutputMessages(response.output, finishReasons); if (outputMessages) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_OUTPUT_MESSAGES, outputMessages); } } } _setFunctionEndAttributes(otelSpan, spanData) { if (this._captureMessageContent) { if (spanData.input) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_TOOL_CALL_ARGUMENTS, spanData.input); } if (spanData.output) { otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_TOOL_CALL_RESULT, spanData.output); } } } _mapSdkFieldsToAttributes(otelSpan, spanData) { var _a; const type = spanData.type; const data = spanData; for (const field of Object.keys(data)) { const value = data[field]; if (value == null) continue; const mapKey = `${type}.${field}`; const mapping = OpenTelemetryTracingProcessor.ATTRIBUTE_MAP.find(m => m.from === mapKey || m.from === `*.${field}`); if (mapping && !mapping.to) continue; const attrValue = (mapping === null || mapping === void 0 ? void 0 : mapping.transform) ? mapping.transform(value, data) : value; // for attributes we don't have a equivalent OTel mapping to, prepend open_ai to the attribute // name to avoid dropping the data const attrName = (_a = mapping === null || mapping === void 0 ? void 0 : mapping.to) !== null && _a !== void 0 ? _a : `open_ai.${mapKey}`; if (typeof attrValue === 'string' || typeof attrValue === 'number' || typeof attrValue === 'boolean') { otelSpan.setAttribute(attrName, attrValue); } else if (Array.isArray(attrValue) && attrValue.every(v => typeof v === 'string')) { otelSpan.setAttribute(attrName, attrValue); } else { otelSpan.setAttribute(attrName, (0, instrumentation_utils_1.serializeToJson)(attrValue)); } } } _getFinishReasons(response) { if (!response.output || !Array.isArray(response.output)) return []; const hasToolCalls = response.output.some((item) => item.type === 'function_call'); const hasMessages = response.output.some((item) => item.type === 'message'); if (hasToolCalls) return ['tool_calls']; if (hasMessages) return ['stop']; return []; } _formatInputMessages(input) { if (!input || !Array.isArray(input)) return undefined; const formatted = input.map((item) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; if (item.type === 'message') { return { role: (_a = item.role) !== null && _a !== void 0 ? _a : 'user', parts: [{ type: 'text', content: (_b = item.content) !== null && _b !== void 0 ? _b : '' }], }; } if (item.type === 'function_call') { return { role: 'assistant', parts: [ { type: 'tool_call', id: (_d = (_c = item.callId) !== null && _c !== void 0 ? _c : item.call_id) !== null && _d !== void 0 ? _d : null, name: (_e = item.name) !== null && _e !== void 0 ? _e : '', arguments: (0, instrumentation_utils_1.tryParseJson)((_f = item.arguments) !== null && _f !== void 0 ? _f : ''), }, ], }; } if (item.type === 'function_call_result') { return { role: 'tool', parts: [ { type: 'tool_call_response', id: (_h = (_g = item.callId) !== null && _g !== void 0 ? _g : item.call_id) !== null && _h !== void 0 ? _h : null, response: (_l = (_k = (_j = item.output) === null || _j === void 0 ? void 0 : _j.text) !== null && _k !== void 0 ? _k : item.output) !== null && _l !== void 0 ? _l : '', }, ], }; } return { role: 'user', parts: [{ type: 'text', content: JSON.stringify(item) }] }; }); return (0, instrumentation_utils_1.serializeToJson)(formatted); } _formatOutputMessages(output, finishReasons) { var _a, _b, _c, _d, _e, _f; if (!output || !Array.isArray(output)) return undefined; const parts = []; for (const item of output) { if (item.type === 'message' && item.content) { for (const content of item.content) { if (content.type === 'output_text') { parts.push({ type: 'text', content: (_a = content.text) !== null && _a !== void 0 ? _a : '' }); } } } else if (item.type === 'function_call') { parts.push({ type: 'tool_call', id: (_c = (_b = item.call_id) !== null && _b !== void 0 ? _b : item.id) !== null && _c !== void 0 ? _c : null, name: (_d = item.name) !== null && _d !== void 0 ? _d : '', arguments: (0, instrumentation_utils_1.tryParseJson)((_e = item.arguments) !== null && _e !== void 0 ? _e : ''), }); } } if (parts.length === 0) return undefined; return (0, instrumentation_utils_1.serializeToJson)([ { role: 'assistant', parts, finish_reason: (_f = finishReasons[0]) !== null && _f !== void 0 ? _f : 'stop', }, ]); } _propagateModelToAgent(parentId, model) { if (!parentId) return; const parentEntry = this._spanMap.get(parentId); if (!(parentEntry === null || parentEntry === void 0 ? void 0 : parentEntry.otelSpan.isRecording())) return; parentEntry.otelSpan.setAttribute(semconv_1.ATTR_GEN_AI_RESPONSE_MODEL, model); } } // An adapter class for OpenAI Agents' TracingProcessor to intercept SDK spans // and create corresponding OTel spans with OTel GenAI semantic convention attributes. // see: https://github.com/openai/openai-agents-js/blob/v0.8.5/packages/agents-core/src/tracing/processor.ts#L16-L53 OpenTelemetryTracingProcessor.ATTRIBUTE_MAP = [ { from: 'agent.name', to: semconv_1.ATTR_GEN_AI_AGENT_NAME }, { from: 'agent.output_type', to: semconv_1.ATTR_GEN_AI_OUTPUT_TYPE }, { from: 'function.name', to: semconv_1.ATTR_GEN_AI_TOOL_NAME }, { from: 'transcription.model', to: semconv_1.ATTR_GEN_AI_REQUEST_MODEL }, { from: 'speech.model', to: semconv_1.ATTR_GEN_AI_REQUEST_MODEL }, { from: '*.type' }, { from: 'response._response' }, { from: 'response._input' }, { from: 'response.response_id' }, { from: 'function.input' }, { from: 'function.output' }, ]; exports.OpenTelemetryTracingProcessor = OpenTelemetryTracingProcessor; //# sourceMappingURL=tracing-processor.js.map