@traceloop/instrumentation-langchain
Version: 
OpenTelemetry instrumentation for LangchainJS
208 lines (203 loc) • 10.1 kB
JavaScript
import { trace, context, SpanStatusCode } from '@opentelemetry/api';
import { safeExecuteInTheMiddle, InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation';
import { TraceloopSpanKindValues, SpanAttributes, CONTEXT_KEY_ALLOW_TRACE_CONTENT } from '@traceloop/ai-semantic-conventions';
function genericWrapper(tracer, shouldSendPrompts, spanKind, spanName) {
    // eslint-disable-next-line
    return (original) => {
        return function method(...args) {
            var _a, _b;
            const span = tracer().startSpan(spanName || `${this.constructor.name}.${spanKind}`);
            span.setAttribute(SpanAttributes.TRACELOOP_SPAN_KIND, spanKind);
            if (shouldSendPrompts) {
                try {
                    if (args.length === 1 &&
                        typeof args[0] === "object" &&
                        !(args[0] instanceof Map)) {
                        span.setAttribute(SpanAttributes.TRACELOOP_ENTITY_INPUT, JSON.stringify({ args: [], kwargs: args[0] }));
                    }
                    else {
                        span.setAttribute(SpanAttributes.TRACELOOP_ENTITY_INPUT, JSON.stringify({
                            args: args.map((arg) => arg instanceof Map ? Array.from(arg.entries()) : arg),
                            kwargs: {},
                        }));
                    }
                }
                catch (e) {
                    this._diag.debug(e);
                    (_b = (_a = this._config).exceptionLogger) === null || _b === void 0 ? void 0 : _b.call(_a, e);
                }
            }
            const execContext = trace.setSpan(context.active(), span);
            const execPromise = safeExecuteInTheMiddle(() => {
                return context.with(execContext, () => {
                    return original.apply(this, args);
                });
            }, 
            // eslint-disable-next-line @typescript-eslint/no-empty-function
            () => { });
            const wrappedPromise = execPromise
                .then((result) => {
                return new Promise((resolve) => {
                    var _a, _b;
                    span.setStatus({ code: SpanStatusCode.OK });
                    try {
                        if (shouldSendPrompts) {
                            if (result instanceof Map) {
                                span.setAttribute(SpanAttributes.TRACELOOP_ENTITY_OUTPUT, JSON.stringify(Array.from(result.entries())));
                            }
                            else {
                                span.setAttribute(SpanAttributes.TRACELOOP_ENTITY_OUTPUT, JSON.stringify(result));
                            }
                        }
                    }
                    catch (e) {
                        this._diag.debug(e);
                        (_b = (_a = this._config).exceptionLogger) === null || _b === void 0 ? void 0 : _b.call(_a, e);
                    }
                    finally {
                        span.end();
                        resolve(result);
                    }
                });
            })
                .catch((error) => {
                return new Promise((_, reject) => {
                    span.setStatus({
                        code: SpanStatusCode.ERROR,
                        message: error.message,
                    });
                    span.end();
                    reject(error);
                });
            });
            return context.bind(execContext, wrappedPromise);
        };
    };
}
function taskWrapper(tracer, shouldSendPrompts, spanName) {
    return genericWrapper(tracer, shouldSendPrompts, TraceloopSpanKindValues.TASK, spanName);
}
function workflowWrapper(tracer, shouldSendPrompts, spanName) {
    return genericWrapper(tracer, shouldSendPrompts, TraceloopSpanKindValues.WORKFLOW, spanName);
}
var version = "0.12.0";
/*
 * Copyright Traceloop
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
class LangChainInstrumentation extends InstrumentationBase {
    constructor(config = {}) {
        super("@traceloop/instrumentation-langchain", version, config);
    }
    manuallyInstrument({ chainsModule, agentsModule, toolsModule, vectorStoreModule, runnablesModule, }) {
        if (chainsModule) {
            this._diag.debug("Manually instrumenting langchain chains");
            this.patchChainModule(chainsModule);
        }
        if (agentsModule) {
            this._diag.debug("Manually instrumenting langchain agents");
            this.patchAgentModule(agentsModule);
        }
        if (toolsModule) {
            this._diag.debug("Manually instrumenting langchain tools");
            this.patchToolsModule(toolsModule);
        }
        if (vectorStoreModule) {
            this._diag.debug("Manually instrumenting langchain vector stores");
            this.patchVectorStoreModule(vectorStoreModule);
        }
        if (runnablesModule) {
            this._diag.debug("Manually instrumenting @langchain/core/runnables");
            this.patchRunnablesModule(runnablesModule);
        }
    }
    init() {
        const chainModule = new InstrumentationNodeModuleDefinition("langchain/chains.cjs", [">=0.3.0"], this.patchChainModule.bind(this), this.unpatchChainModule.bind(this));
        const agentModule = new InstrumentationNodeModuleDefinition("langchain/agents.cjs", [">=0.3.0"], this.patchAgentModule.bind(this), this.unpatchAgentModule.bind(this));
        const toolsModule = new InstrumentationNodeModuleDefinition("langchain/tools.cjs", [">=0.3.0"], this.patchToolsModule.bind(this), this.unpatchToolsModule.bind(this));
        const vectorStoreModule = new InstrumentationNodeModuleDefinition("langchain/core/vectorstores.cjs", [">=0.3.0"], this.patchVectorStoreModule.bind(this), this.unpatchVectorStoreModule.bind(this));
        const runnablesModule = new InstrumentationNodeModuleDefinition("@langchain/core/runnables.cjs", [">=0.3.0"], this.patchRunnablesModule.bind(this), this.unpatchRunnablesModule.bind(this));
        return [
            chainModule,
            agentModule,
            toolsModule,
            vectorStoreModule,
            runnablesModule,
        ];
    }
    patchChainModule(moduleExports, moduleVersion) {
        this._diag.debug(`Patching langchain/chains.cjs@${moduleVersion}`);
        this._wrap(moduleExports.RetrievalQAChain.prototype, "_call", workflowWrapper(() => this.tracer, this._shouldSendPrompts(), "retrieval_qa.workflow"));
        this._wrap(moduleExports.BaseChain.prototype, "call", taskWrapper(() => this.tracer, this._shouldSendPrompts()));
        return moduleExports;
    }
    patchAgentModule(moduleExports, moduleVersion) {
        this._diag.debug(`Patching langchain/agents.cjs@${moduleVersion}`);
        this._wrap(moduleExports.AgentExecutor.prototype, "_call", workflowWrapper(() => this.tracer, this._shouldSendPrompts(), "langchain.agent"));
        return moduleExports;
    }
    patchToolsModule(moduleExports, moduleVersion) {
        this._diag.debug(`Patching langchain/tools.cjs@${moduleVersion}`);
        this._wrap(moduleExports.Tool.prototype, "call", taskWrapper(() => this.tracer, this._shouldSendPrompts()));
        return moduleExports;
    }
    patchVectorStoreModule(moduleExports, moduleVersion) {
        this._diag.debug(`Patching langchain/vectorstores.cjs@${moduleVersion}`);
        this._wrap(moduleExports.VectorStoreRetriever.prototype, "_getRelevantDocuments", taskWrapper(() => this.tracer, this._shouldSendPrompts()));
        return moduleExports;
    }
    patchRunnablesModule(moduleExports, moduleVersion) {
        this._diag.debug(`Patching /core/runnables@${moduleVersion}`);
        this._wrap(moduleExports.RunnableSequence.prototype, "invoke", taskWrapper(() => this.tracer, this._shouldSendPrompts()));
        return moduleExports;
    }
    unpatchChainModule(moduleExports, moduleVersion) {
        this._diag.debug(`Unpatching langchain/chains.cjs@${moduleVersion}`);
        this._unwrap(moduleExports.RetrievalQAChain.prototype, "_call");
        this._unwrap(moduleExports.BaseChain.prototype, "call");
        return moduleExports;
    }
    unpatchAgentModule(moduleExports, moduleVersion) {
        this._diag.debug(`Unpatching langchain/agents.cjs@${moduleVersion}`);
        this._unwrap(moduleExports.AgentExecutor.prototype, "_call");
        return moduleExports;
    }
    unpatchToolsModule(moduleExports) {
        this._diag.debug(`Unpatching langchain/tools.cjs`);
        this._unwrap(moduleExports.Tool.prototype, "call");
        return moduleExports;
    }
    unpatchVectorStoreModule(moduleExports) {
        this._diag.debug(`Unpatching langchain/vectorstores.cjs`);
        this._unwrap(moduleExports.VectorStoreRetriever.prototype, "_getRelevantDocuments");
        return moduleExports;
    }
    unpatchRunnablesModule(moduleExports) {
        this._diag.debug(`Unpatching /core/runnables`);
        this._unwrap(moduleExports.Runnable.prototype, "invoke");
        return moduleExports;
    }
    _shouldSendPrompts() {
        const contextShouldSendPrompts = context
            .active()
            .getValue(CONTEXT_KEY_ALLOW_TRACE_CONTENT);
        if (contextShouldSendPrompts !== undefined) {
            return !!contextShouldSendPrompts;
        }
        return this._config.traceContent !== undefined
            ? this._config.traceContent
            : true;
    }
}
export { LangChainInstrumentation };
//# sourceMappingURL=index.mjs.map