@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
314 lines (283 loc) • 8.48 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const exports$1 = require('../../exports.js');
const semanticAttributes = require('../../semanticAttributes.js');
const spanstatus = require('../spanstatus.js');
const trace = require('../trace.js');
const genAiAttributes = require('../ai/gen-ai-attributes.js');
const constants = require('./constants.js');
const utils = require('./utils.js');
/**
* Creates a Sentry callback handler for LangChain
* Returns a plain object that LangChain will call via duck-typing
*
* This is a stateful handler that tracks spans across multiple LangChain executions.
*/
function createLangChainCallbackHandler(options = {}) {
const recordInputs = options.recordInputs ?? false;
const recordOutputs = options.recordOutputs ?? false;
// Internal state - single instance tracks all spans
const spanMap = new Map();
/**
* Exit a span and clean up
*/
const exitSpan = (runId) => {
const span = spanMap.get(runId);
if (span?.isRecording()) {
span.end();
spanMap.delete(runId);
}
};
/**
* Handler for LLM Start
* This handler will be called by LangChain's callback handler when an LLM event is detected.
*/
const handler = {
// Required LangChain BaseCallbackHandler properties
lc_serializable: false,
lc_namespace: ['langchain_core', 'callbacks', 'sentry'],
lc_secrets: undefined,
lc_attributes: undefined,
lc_aliases: undefined,
lc_serializable_keys: undefined,
lc_id: ['langchain_core', 'callbacks', 'sentry'],
lc_kwargs: {},
name: 'SentryCallbackHandler',
// BaseCallbackHandlerInput boolean flags
ignoreLLM: false,
ignoreChain: false,
ignoreAgent: false,
ignoreRetriever: false,
ignoreCustomEvent: false,
raiseError: false,
awaitHandlers: true,
handleLLMStart(
llm,
prompts,
runId,
_parentRunId,
_extraParams,
tags,
metadata,
_runName,
) {
const invocationParams = utils.getInvocationParams(tags);
const attributes = utils.extractLLMRequestAttributes(
llm ,
prompts,
recordInputs,
invocationParams,
metadata,
);
const modelName = attributes[genAiAttributes.GEN_AI_REQUEST_MODEL_ATTRIBUTE];
const operationName = attributes[genAiAttributes.GEN_AI_OPERATION_NAME_ATTRIBUTE];
trace.startSpanManual(
{
name: `${operationName} ${modelName}`,
op: 'gen_ai.pipeline',
attributes: {
...attributes,
[semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.pipeline',
},
},
span => {
spanMap.set(runId, span);
return span;
},
);
},
// Chat Model Start Handler
handleChatModelStart(
llm,
messages,
runId,
_parentRunId,
_extraParams,
tags,
metadata,
_runName,
) {
const invocationParams = utils.getInvocationParams(tags);
const attributes = utils.extractChatModelRequestAttributes(
llm ,
messages ,
recordInputs,
invocationParams,
metadata,
);
const modelName = attributes[genAiAttributes.GEN_AI_REQUEST_MODEL_ATTRIBUTE];
const operationName = attributes[genAiAttributes.GEN_AI_OPERATION_NAME_ATTRIBUTE];
trace.startSpanManual(
{
name: `${operationName} ${modelName}`,
op: 'gen_ai.chat',
attributes: {
...attributes,
[semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.chat',
},
},
span => {
spanMap.set(runId, span);
return span;
},
);
},
// LLM End Handler - note: handleLLMEnd with capital LLM (used by both LLMs and chat models!)
handleLLMEnd(
output,
runId,
_parentRunId,
_tags,
_extraParams,
) {
const span = spanMap.get(runId);
if (span?.isRecording()) {
const attributes = utils.extractLlmResponseAttributes(output , recordOutputs);
if (attributes) {
span.setAttributes(attributes);
}
exitSpan(runId);
}
},
// LLM Error Handler - note: handleLLMError with capital LLM
handleLLMError(error, runId) {
const span = spanMap.get(runId);
if (span?.isRecording()) {
span.setStatus({ code: spanstatus.SPAN_STATUS_ERROR, message: 'llm_error' });
exitSpan(runId);
}
exports$1.captureException(error, {
mechanism: {
handled: false,
type: `${constants.LANGCHAIN_ORIGIN}.llm_error_handler`,
},
});
},
// Chain Start Handler
handleChainStart(chain, inputs, runId, _parentRunId) {
const chainName = chain.name || 'unknown_chain';
const attributes = {
[semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ai.langchain',
'langchain.chain.name': chainName,
};
// Add inputs if recordInputs is enabled
if (recordInputs) {
attributes['langchain.chain.inputs'] = JSON.stringify(inputs);
}
trace.startSpanManual(
{
name: `chain ${chainName}`,
op: 'gen_ai.invoke_agent',
attributes: {
...attributes,
[semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.invoke_agent',
},
},
span => {
spanMap.set(runId, span);
return span;
},
);
},
// Chain End Handler
handleChainEnd(outputs, runId) {
const span = spanMap.get(runId);
if (span?.isRecording()) {
// Add outputs if recordOutputs is enabled
if (recordOutputs) {
span.setAttributes({
'langchain.chain.outputs': JSON.stringify(outputs),
});
}
exitSpan(runId);
}
},
// Chain Error Handler
handleChainError(error, runId) {
const span = spanMap.get(runId);
if (span?.isRecording()) {
span.setStatus({ code: spanstatus.SPAN_STATUS_ERROR, message: 'chain_error' });
exitSpan(runId);
}
exports$1.captureException(error, {
mechanism: {
handled: false,
type: `${constants.LANGCHAIN_ORIGIN}.chain_error_handler`,
},
});
},
// Tool Start Handler
handleToolStart(tool, input, runId, _parentRunId) {
const toolName = tool.name || 'unknown_tool';
const attributes = {
[semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: constants.LANGCHAIN_ORIGIN,
'gen_ai.tool.name': toolName,
};
// Add input if recordInputs is enabled
if (recordInputs) {
attributes['gen_ai.tool.input'] = input;
}
trace.startSpanManual(
{
name: `execute_tool ${toolName}`,
op: 'gen_ai.execute_tool',
attributes: {
...attributes,
[semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.execute_tool',
},
},
span => {
spanMap.set(runId, span);
return span;
},
);
},
// Tool End Handler
handleToolEnd(output, runId) {
const span = spanMap.get(runId);
if (span?.isRecording()) {
// Add output if recordOutputs is enabled
if (recordOutputs) {
span.setAttributes({
'gen_ai.tool.output': JSON.stringify(output),
});
}
exitSpan(runId);
}
},
// Tool Error Handler
handleToolError(error, runId) {
const span = spanMap.get(runId);
if (span?.isRecording()) {
span.setStatus({ code: spanstatus.SPAN_STATUS_ERROR, message: 'tool_error' });
exitSpan(runId);
}
exports$1.captureException(error, {
mechanism: {
handled: false,
type: `${constants.LANGCHAIN_ORIGIN}.tool_error_handler`,
},
});
},
// LangChain BaseCallbackHandler required methods
copy() {
return handler;
},
toJSON() {
return {
lc: 1,
type: 'not_implemented',
id: handler.lc_id,
};
},
toJSONNotImplemented() {
return {
lc: 1,
type: 'not_implemented',
id: handler.lc_id,
};
},
};
return handler;
}
exports.createLangChainCallbackHandler = createLangChainCallbackHandler;
//# sourceMappingURL=index.js.map