@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
JavaScript
;
// 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