@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.
241 lines • 13.7 kB
JavaScript
"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.LangChainInstrumentation = exports.INSTRUMENTATION_SHORT_NAME = exports.INSTRUMENTATION_NAME = void 0;
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-this-alias */
const api_1 = require("@opentelemetry/api");
const instrumentation_1 = require("@opentelemetry/instrumentation");
const version_1 = require("../../version");
const utils_1 = require("../../utils");
exports.INSTRUMENTATION_NAME = '@aws/aws-distro-opentelemetry-instrumentation-langchain';
exports.INSTRUMENTATION_SHORT_NAME = 'aws_langchain';
const SUPPORTED_VERSIONS = ['>=1.0.0 <2.0.0'];
class LangChainInstrumentation extends instrumentation_1.InstrumentationBase {
constructor(config = {}) {
super(exports.INSTRUMENTATION_NAME, version_1.LIB_VERSION, config);
this._patchedCallbackManagers = new Set();
this._patchedChatModelsProtos = new Set();
this._patchedToolsProtos = new Set();
this._wrappedChatProtos = new Set();
this._wrappedToolProtos = new Set();
this._handler = undefined;
}
setConfig(config = {}) {
super.setConfig(Object.assign(Object.assign({}, config), { captureMessageContent: !!config.captureMessageContent }));
this._handler = undefined;
}
enable() {
if ((0, utils_1.isInstrumentationDisabled)(exports.INSTRUMENTATION_SHORT_NAME)) {
this._diag.debug(`${exports.INSTRUMENTATION_NAME} is disabled`);
return;
}
if (!(0, utils_1.isAgenticInstrumentationOptIn)()) {
const conflict = (0, utils_1.detectConflictingInstrumentation)(exports.INSTRUMENTATION_SHORT_NAME);
if (conflict) {
this._diag.info(`Skipping ${exports.INSTRUMENTATION_NAME}: third-party '${conflict}' detected. ` +
'Set AWS_AGENTIC_INSTRUMENTATION_OPT_IN=true to override.');
return;
}
}
super.enable();
}
init() {
const moduleFiles = (path, patch, unpatch) => [
new instrumentation_1.InstrumentationNodeModuleFile(`${path}.cjs`, SUPPORTED_VERSIONS, patch, unpatch),
new instrumentation_1.InstrumentationNodeModuleFile(`${path}.js`, SUPPORTED_VERSIONS, patch, unpatch),
];
// to ensure these patches work for both CJS and ESM and to ensure we are setting the
// proper trace context for downstream instrumentations we MUST monkey-patch _generate() and _call() to wrap
// them in context.with(), which makes our callback handler's span the active context in AsyncLocalStorage. Unfortunately,
// in js the only way to set the active context is by wrapping the callback itself.
// see: https://github.com/open-telemetry/opentelemetry-js/issues/3558
return [
new instrumentation_1.InstrumentationNodeModuleDefinition('@langchain/core', SUPPORTED_VERSIONS, (m) => m, () => { }, [
...moduleFiles('@langchain/core/dist/callbacks/manager', (m) => {
this._patchCallbackManager(m === null || m === void 0 ? void 0 : m.CallbackManager);
return m;
}, (m) => {
this._unpatchCallbackManager(m === null || m === void 0 ? void 0 : m.CallbackManager);
return m;
}),
...moduleFiles('@langchain/core/dist/language_models/chat_models', (m) => this._patchChatModelsModule(m), (m) => this._unpatchChatModelsModule(m)),
...moduleFiles('@langchain/core/dist/tools/index', (m) => this._patchToolsModule(m), (m) => this._unpatchToolsModule(m)),
]),
];
}
_patchCallbackManager(CallbackManager) {
if (!CallbackManager || this._patchedCallbackManagers.has(CallbackManager))
return;
if (typeof CallbackManager._configureSync !== 'function')
return;
const langChainInstrumentation = this;
this._wrap(CallbackManager, '_configureSync', (original) => {
return function (...args) {
if (!langChainInstrumentation._handler) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { OpenTelemetryCallbackHandler } = require('./callback-handler');
langChainInstrumentation._handler = new OpenTelemetryCallbackHandler(langChainInstrumentation.tracer, !!langChainInstrumentation.getConfig().captureMessageContent);
langChainInstrumentation._diag.debug('Lazily loaded OTel callback handler');
}
// OTel handler must be first so that span context is set before
// other handlers that are registered are executed so that we can
// propagate to downstream instrumentations.
// see: https://github.com/aws-observability/aws-otel-python-instrumentation/blob/e729533/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/instrumentation/langchain/callback_handler.py#L78-L82
args[0] = LangChainInstrumentation._injectHandler(args[0], langChainInstrumentation._handler);
return original.apply(this, args);
};
});
this._patchedCallbackManagers.add(CallbackManager);
this._diag.debug('Patched CallbackManager._configureSync');
}
_unpatchCallbackManager(CallbackManager) {
if (!CallbackManager || !this._patchedCallbackManagers.has(CallbackManager))
return;
this._unwrap(CallbackManager, '_configureSync');
this._patchedCallbackManagers.clear();
this._handler = undefined;
this._diag.debug('Unpatched CallbackManager');
}
_patchChatModelsModule(modExports) {
var _a;
const proto = (_a = modExports === null || modExports === void 0 ? void 0 : modExports.BaseChatModel) === null || _a === void 0 ? void 0 : _a.prototype;
if (!proto || this._patchedChatModelsProtos.has(proto))
return modExports;
const langChainInstrumentation = this;
this._wrap(proto, '_generateUncached', (original) => {
return function (...args) {
langChainInstrumentation._propagateContextOnChatProto(Object.getPrototypeOf(this));
return original.apply(this, args);
};
});
this._patchedChatModelsProtos.add(proto);
this._diag.debug('Patched BaseChatModel.prototype._generateUncached');
return modExports;
}
_unpatchChatModelsModule(modExports) {
var _a;
const proto = (_a = modExports === null || modExports === void 0 ? void 0 : modExports.BaseChatModel) === null || _a === void 0 ? void 0 : _a.prototype;
if (!proto || !this._patchedChatModelsProtos.has(proto))
return modExports;
this._unwrap(proto, '_generateUncached');
for (const p of this._wrappedChatProtos) {
if (typeof p._generate === 'function')
this._unwrap(p, '_generate');
if (typeof p._streamResponseChunks === 'function')
this._unwrap(p, '_streamResponseChunks');
}
this._wrappedChatProtos.clear();
this._patchedChatModelsProtos.clear();
this._diag.debug('Unpatched BaseChatModel and all concrete chat model prototypes');
return modExports;
}
_patchToolsModule(modExports) {
var _a;
const proto = (_a = modExports === null || modExports === void 0 ? void 0 : modExports.StructuredTool) === null || _a === void 0 ? void 0 : _a.prototype;
if (!proto || this._patchedToolsProtos.has(proto))
return modExports;
const langChainInstrumentation = this;
this._wrap(proto, 'call', (original) => {
return function (...args) {
langChainInstrumentation._propagateContextOnToolProto(Object.getPrototypeOf(this));
return original.apply(this, args);
};
});
this._patchedToolsProtos.add(proto);
this._diag.debug('Patched StructuredTool.prototype.call');
return modExports;
}
_unpatchToolsModule(modExports) {
var _a;
const proto = (_a = modExports === null || modExports === void 0 ? void 0 : modExports.StructuredTool) === null || _a === void 0 ? void 0 : _a.prototype;
if (!proto || !this._patchedToolsProtos.has(proto))
return modExports;
this._unwrap(proto, 'call');
for (const p of this._wrappedToolProtos) {
if (typeof p._call === 'function')
this._unwrap(p, '_call');
}
this._wrappedToolProtos.clear();
this._patchedToolsProtos.clear();
this._diag.debug('Unpatched StructuredTool and all concrete tool prototypes');
return modExports;
}
// for propagating context in non-streaming and streaming calls to LLMs.
// These are the base methods all chat classes must implement
// see: _generate: https://github.com/langchain-ai/langchainjs/blob/0bf9d7e/libs/langchain-core/src/language_models/chat_models.ts#L896
// see: _streamResponseChunks: https://github.com/langchain-ai/langchainjs/blob/0bf9d7e/libs/langchain-core/src/language_models/chat_models.ts#L145
_propagateContextOnChatProto(concreteProto) {
var _a;
if (!concreteProto || this._wrappedChatProtos.has(concreteProto))
return;
this._wrappedChatProtos.add(concreteProto);
const langChainInstrumentation = this;
if (typeof concreteProto._generate === 'function') {
this._wrap(concreteProto, '_generate', (original) => {
return function (...genArgs) {
var _a, _b, _c, _d;
const spanCtx = (_d = (_b = (_a = langChainInstrumentation._handler) === null || _a === void 0 ? void 0 : _a.runIdToSpanMap) === null || _b === void 0 ? void 0 : _b.get((_c = genArgs[2]) === null || _c === void 0 ? void 0 : _c.runId)) === null || _d === void 0 ? void 0 : _d.context;
if (spanCtx)
return api_1.context.with(spanCtx, () => original.apply(this, genArgs));
return original.apply(this, genArgs);
};
});
}
if (typeof concreteProto._streamResponseChunks === 'function') {
this._wrap(concreteProto, '_streamResponseChunks', (original) => {
return function (...streamArgs) {
var _a, _b, _c, _d;
const spanCtx = (_d = (_b = (_a = langChainInstrumentation._handler) === null || _a === void 0 ? void 0 : _a.runIdToSpanMap) === null || _b === void 0 ? void 0 : _b.get((_c = streamArgs[2]) === null || _c === void 0 ? void 0 : _c.runId)) === null || _d === void 0 ? void 0 : _d.context;
if (!spanCtx)
return original.apply(this, streamArgs);
const boundOriginal = api_1.context.bind(spanCtx, original.bind(this, ...streamArgs));
return boundOriginal();
};
});
}
this._diag.debug(`Wrapped context propagation on ${((_a = concreteProto.constructor) === null || _a === void 0 ? void 0 : _a.name) || 'unknown'} prototype`);
}
// for propagating context in tool calls
// see: _call: https://github.com/langchain-ai/langchainjs/blob/0bf9d7e/libs/langchain-core/src/tools/index.ts#L163
_propagateContextOnToolProto(concreteProto) {
var _a;
if (!concreteProto || this._wrappedToolProtos.has(concreteProto))
return;
this._wrappedToolProtos.add(concreteProto);
const langChainInstrumentation = this;
if (typeof concreteProto._call === 'function') {
this._wrap(concreteProto, '_call', (original) => {
return function (...callArgs) {
var _a, _b, _c, _d;
const spanCtx = (_d = (_b = (_a = langChainInstrumentation._handler) === null || _a === void 0 ? void 0 : _a.runIdToSpanMap) === null || _b === void 0 ? void 0 : _b.get((_c = callArgs[1]) === null || _c === void 0 ? void 0 : _c.runId)) === null || _d === void 0 ? void 0 : _d.context;
if (spanCtx)
return api_1.context.with(spanCtx, () => original.apply(this, callArgs));
return original.apply(this, callArgs);
};
});
}
this._diag.debug(`Wrapped context propagation on ${((_a = concreteProto.constructor) === null || _a === void 0 ? void 0 : _a.name) || 'unknown'} tool prototype`);
}
static _injectHandler(handlersOrManager, handler) {
if (Array.isArray(handlersOrManager)) {
if (!handlersOrManager.includes(handler)) {
handlersOrManager.unshift(handler);
}
return handlersOrManager;
}
const manager = handlersOrManager;
if (manager && typeof manager === 'object' && Array.isArray(manager.handlers)) {
if (!manager.handlers.includes(handler)) {
manager.handlers.unshift(handler);
if (Array.isArray(manager.inheritableHandlers)) {
manager.inheritableHandlers.unshift(handler);
}
}
return manager;
}
return [handler];
}
}
exports.LangChainInstrumentation = LangChainInstrumentation;
//# sourceMappingURL=instrumentation.js.map