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.

587 lines 33.5 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.OpenTelemetryCallbackHandler = void 0; const api_1 = require("@opentelemetry/api"); const semantic_conventions_1 = require("@opentelemetry/semantic-conventions"); const semconv_1 = require("../common/semconv"); const instrumentation_utils_1 = require("../common/instrumentation-utils"); const base_1 = require("@langchain/core/callbacks/base"); const messages_1 = require("@langchain/core/messages"); const LANGGRAPH_STEP_SPAN_ATTR = 'langgraph.step'; const LANGGRAPH_NODE_SPAN_ATTR = 'langgraph.node'; class OpenTelemetryCallbackHandler extends base_1.BaseCallbackHandler { constructor(tracer, captureMessageContent = false, shouldSuppressInternalChains = true) { super(); this.name = 'otel-callback-handler'; // Ensures the OTel callback is executed synchronously and not in an async thread. // This is to ensure that we are ALWAYS setting this instrumentation's spans as the current span in context to make // sure we propagate the trace to downstream spans. // https://github.com/langchain-ai/langchainjs/blob/0c799481f691e046a4533588fc96e190669fa16e/libs/langchain-core/src/callbacks/manager.ts#L124-L143 this.awaitHandlers = true; this.runIdToSpanMap = new Map(); this.tracer = tracer; this.captureMessageContent = captureMessageContent; this.shouldSuppressInternalChains = shouldSuppressInternalChains; } handleChatModelStart(serialized, messages, runId, parentRunId, extraParams, _tags, metadata) { var _a; const config = (_a = OpenTelemetryCallbackHandler._getSerializedConfig(serialized)) !== null && _a !== void 0 ? _a : {}; const modelName = OpenTelemetryCallbackHandler._extractModelId(extraParams, config, metadata, serialized); const provider = OpenTelemetryCallbackHandler._extractModelProvider(serialized, extraParams, metadata); const spanName = modelName ? `${semconv_1.GEN_AI_OPERATION_NAME_VALUE_CHAT} ${modelName}` : semconv_1.GEN_AI_OPERATION_NAME_VALUE_CHAT; const span = this._startSpan(runId, parentRunId, spanName, api_1.SpanKind.CLIENT); this._setLanggraphAttributes(span, metadata); this._setAttribute(span, semconv_1.ATTR_GEN_AI_PROVIDER_NAME, provider); this._setAttribute(span, semconv_1.ATTR_GEN_AI_OPERATION_NAME, semconv_1.GEN_AI_OPERATION_NAME_VALUE_CHAT); this._setModelRequestAttributes(span, extraParams, config, modelName); if (this.captureMessageContent && messages.length > 0) { const { systemInstructions, conversation } = OpenTelemetryCallbackHandler._formatMessages(messages); if (conversation.length > 0) { this._setAttribute(span, semconv_1.ATTR_GEN_AI_INPUT_MESSAGES, (0, instrumentation_utils_1.serializeToJson)(conversation)); } if (systemInstructions.length > 0) { this._setAttribute(span, semconv_1.ATTR_GEN_AI_SYSTEM_INSTRUCTIONS, (0, instrumentation_utils_1.serializeToJson)(systemInstructions)); } } this._propagateToAgentSpan(runId, provider, modelName, extraParams, config); } handleLLMStart(serialized, prompts, runId, parentRunId, extraParams, _tags, metadata) { var _a; const config = (_a = OpenTelemetryCallbackHandler._getSerializedConfig(serialized)) !== null && _a !== void 0 ? _a : {}; const modelName = OpenTelemetryCallbackHandler._extractModelId(extraParams, config, metadata, serialized); const provider = OpenTelemetryCallbackHandler._extractModelProvider(serialized, extraParams, metadata); const spanName = modelName ? `${semconv_1.GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION} ${modelName}` : semconv_1.GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION; const span = this._startSpan(runId, parentRunId, spanName, api_1.SpanKind.CLIENT); this._setLanggraphAttributes(span, metadata); this._setAttribute(span, semconv_1.ATTR_GEN_AI_PROVIDER_NAME, provider); this._setAttribute(span, semconv_1.ATTR_GEN_AI_OPERATION_NAME, semconv_1.GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION); this._setModelRequestAttributes(span, extraParams, config, modelName); if (this.captureMessageContent && prompts.length > 0) { const conversation = prompts.map(prompt => ({ role: 'user', parts: [{ type: 'text', content: prompt }] })); this._setAttribute(span, semconv_1.ATTR_GEN_AI_INPUT_MESSAGES, (0, instrumentation_utils_1.serializeToJson)(conversation)); } this._propagateToAgentSpan(runId, provider, modelName, extraParams, config); } handleLLMEnd(response, runId, _parentRunId) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p; const entry = this.runIdToSpanMap.get(runId); if (!(entry === null || entry === void 0 ? void 0 : entry.span)) return; const { span } = entry; const llmOutput = (_a = response.llmOutput) !== null && _a !== void 0 ? _a : {}; const usage = (_c = (_b = llmOutput.token_usage) !== null && _b !== void 0 ? _b : llmOutput.usage) !== null && _c !== void 0 ? _c : {}; const model = (_d = llmOutput.model_name) !== null && _d !== void 0 ? _d : llmOutput.model_id; let responseId = llmOutput.id; let inputTokens = (_f = (_e = usage.prompt_tokens) !== null && _e !== void 0 ? _e : usage.input_token_count) !== null && _f !== void 0 ? _f : usage.input_tokens; let outputTokens = (_h = (_g = usage.completion_tokens) !== null && _g !== void 0 ? _g : usage.generated_token_count) !== null && _h !== void 0 ? _h : usage.output_tokens; const firstGeneration = (_k = (_j = response.generations) === null || _j === void 0 ? void 0 : _j[0]) === null || _k === void 0 ? void 0 : _k[0]; const message = firstGeneration && 'message' in firstGeneration ? firstGeneration.message : undefined; if (message && (0, messages_1.isAIMessage)(message)) { const usageMeta = message.usage_metadata; inputTokens = (_l = usageMeta === null || usageMeta === void 0 ? void 0 : usageMeta.input_tokens) !== null && _l !== void 0 ? _l : inputTokens; outputTokens = (_m = usageMeta === null || usageMeta === void 0 ? void 0 : usageMeta.output_tokens) !== null && _m !== void 0 ? _m : outputTokens; responseId = (_o = message.id) !== null && _o !== void 0 ? _o : responseId; } this._setAttribute(span, semconv_1.ATTR_GEN_AI_USAGE_INPUT_TOKENS, inputTokens); this._setAttribute(span, semconv_1.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, outputTokens); this._setAttribute(span, semconv_1.ATTR_GEN_AI_RESPONSE_ID, responseId); this._setAttribute(span, semconv_1.ATTR_GEN_AI_RESPONSE_MODEL, model); if (((_p = response.generations) === null || _p === void 0 ? void 0 : _p.length) > 0) { if (this.captureMessageContent) { const outputMessages = OpenTelemetryCallbackHandler._formatOutputMessages(response); if (outputMessages.length > 0) { this._setAttribute(span, semconv_1.ATTR_GEN_AI_OUTPUT_MESSAGES, (0, instrumentation_utils_1.serializeToJson)(outputMessages)); } } const finishReasons = OpenTelemetryCallbackHandler._extractFinishReasons(response); if (finishReasons.length > 0) { this._setAttribute(span, semconv_1.ATTR_GEN_AI_RESPONSE_FINISH_REASONS, finishReasons); } } this._endSpan(runId); } handleLLMError(err, runId, _parentRunId) { this._handleError(err, runId); } handleChainStart(serialized, _inputs, runId, parentRunId, _tags, metadata, _runType, runName) { const name = OpenTelemetryCallbackHandler._extractLCName(serialized, undefined, runName); if (this._shouldSkipChain(serialized, name, metadata)) { const parentEntry = parentRunId ? this.runIdToSpanMap.get(parentRunId) : undefined; if (parentEntry) { this.runIdToSpanMap.set(runId, { context: parentEntry.context, agentSpan: parentEntry.agentSpan, }); } return; } // AgentExecutor is the legacy LangChain agent node. lcAgentName metadata is only set // when a custom name is given to the agent, otherwise it defaults to "LangGraph". // langgraphNode check ensures we only match against agent nodes, not unwanted // internal nodes. const isLangGraphPregelAgent = !!name && Array.isArray(serialized.id) && serialized.id.some(part => part === 'langgraph') && serialized.id.some(part => part === 'pregel' || part === 'CompiledStateGraph' || part === 'Pregel') && _inputs != null && typeof _inputs === 'object' && 'messages' in _inputs; const isAgentChain = !!name && (name.includes('AgentExecutor') || name === 'LangGraph' || name === (metadata === null || metadata === void 0 ? void 0 : metadata.lc_agent_name) || isLangGraphPregelAgent); const provider = OpenTelemetryCallbackHandler._extractModelProvider(serialized); const lcAgentName = metadata === null || metadata === void 0 ? void 0 : metadata.lc_agent_name; const agentName = (typeof lcAgentName === 'string' ? lcAgentName : undefined) || (isAgentChain ? name : undefined); const operation = isAgentChain ? semconv_1.GEN_AI_OPERATION_NAME_VALUE_INVOKE_AGENT : 'chain'; const spanName = name ? `${operation} ${name}` : operation; const span = this._startSpan(runId, parentRunId, spanName); const entry = this.runIdToSpanMap.get(runId); if (entry && isAgentChain) { entry.agentSpan = span; } this._setLanggraphAttributes(span, metadata); this._setAttribute(span, semconv_1.ATTR_GEN_AI_PROVIDER_NAME, provider); if (isAgentChain) { this._setAttribute(span, semconv_1.ATTR_GEN_AI_OPERATION_NAME, semconv_1.GEN_AI_OPERATION_NAME_VALUE_INVOKE_AGENT); } this._setAttribute(span, semconv_1.ATTR_GEN_AI_AGENT_NAME, agentName); } handleChainEnd(_outputs, runId, _parentRunId) { this._endSpan(runId); } handleChainError(err, runId, _parentRunId) { this._handleError(err, runId); } handleToolStart(serialized, input, runId, parentRunId, _tags, metadata, runName, toolCallId) { const name = runName || OpenTelemetryCallbackHandler._extractLCName(serialized); const provider = OpenTelemetryCallbackHandler._extractModelProvider(serialized); const spanName = name ? `${semconv_1.GEN_AI_OPERATION_NAME_VALUE_EXECUTE_TOOL} ${name}` : semconv_1.GEN_AI_OPERATION_NAME_VALUE_EXECUTE_TOOL; const span = this._startSpan(runId, parentRunId, spanName); this._setLanggraphAttributes(span, metadata); this._setAttribute(span, semconv_1.ATTR_GEN_AI_PROVIDER_NAME, provider); this._setAttribute(span, semconv_1.ATTR_GEN_AI_OPERATION_NAME, semconv_1.GEN_AI_OPERATION_NAME_VALUE_EXECUTE_TOOL); this._setAttribute(span, semconv_1.ATTR_GEN_AI_TOOL_NAME, name); this._setAttribute(span, semconv_1.ATTR_GEN_AI_TOOL_TYPE, 'function'); this._setAttribute(span, semconv_1.ATTR_GEN_AI_TOOL_CALL_ID, toolCallId); if (this.captureMessageContent) { this._setAttribute(span, semconv_1.ATTR_GEN_AI_TOOL_CALL_ARGUMENTS, input); } } handleToolEnd(output, runId, _parentRunId) { if (this.captureMessageContent) { const entry = this.runIdToSpanMap.get(runId); if (entry === null || entry === void 0 ? void 0 : entry.span) { const content = output && typeof output === 'object' && 'content' in output ? output.content : output; const outputStr = typeof content === 'string' ? content : (0, instrumentation_utils_1.serializeToJson)(content); this._setAttribute(entry.span, semconv_1.ATTR_GEN_AI_TOOL_CALL_RESULT, outputStr); } } this._endSpan(runId); } handleToolError(err, runId, _parentRunId) { this._handleError(err, runId); } _handleError(error, runId) { const entry = this.runIdToSpanMap.get(runId); if (!entry) return; if (entry.span) { const { span } = entry; if (span.isRecording()) { const err = error instanceof Error ? error : new Error(String(error)); span.recordException(err); span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: err.message }); this._setAttribute(span, semantic_conventions_1.ATTR_ERROR_TYPE, err.constructor.name); } } this._endSpan(runId); } _startSpan(runId, parentRunId, spanName, kind = api_1.SpanKind.INTERNAL) { const parentEntry = parentRunId ? this.runIdToSpanMap.get(parentRunId) : undefined; const parentCtx = parentEntry ? parentEntry.context : api_1.context.active(); const span = this.tracer.startSpan(spanName, { kind }, parentCtx); const ctx = api_1.trace.setSpan(parentCtx, span); this.runIdToSpanMap.set(runId, { span, context: ctx, agentSpan: parentEntry === null || parentEntry === void 0 ? void 0 : parentEntry.agentSpan }); return span; } _endSpan(runId) { var _a; const entry = this.runIdToSpanMap.get(runId); if (!entry) return; this.runIdToSpanMap.delete(runId); (_a = entry.span) === null || _a === void 0 ? void 0 : _a.end(); } // handleChainStart/End callbacks will contain internal chain types showing the // internal agent orchestration workflow which can cause a lot of noisy spans // except for chains with "AgentExecutor or LangGraph" in the name as // those are used for invoke_agent spans: // - "runnables": internal orchestration, see: // https://github.com/langchain-ai/langchainjs/blob/0c799481f691e046a4533588fc96e190669fa16e/libs/langchain-core/src/runnables/base.ts#L124 // - "prompts": string formatting, see: // https://github.com/langchain-ai/langchainjs/blob/0c799481f691e046a4533588fc96e190669fa16e/libs/langchain-core/src/prompts/index.ts#L1 // - "output_parsers": text parsing, see: // https://github.com/langchain-ai/langchainjs/blob/0c799481f691e046a4533588fc96e190669fa16e/libs/langchain-core/src/output_parsers/index.ts#L1 _shouldSkipChain(serialized, name, metadata) { var _a, _b; if (!this.shouldSuppressInternalChains) return false; const isAgentNode = (!!name && (name === 'LangGraph' || name.includes('AgentExecutor'))) || (!!metadata && 'lc_agent_name' in metadata); if (isAgentNode) return false; const idPath = (_b = (_a = serialized.id) === null || _a === void 0 ? void 0 : _a.join('.')) !== null && _b !== void 0 ? _b : ''; if (idPath.includes('runnables') || idPath.includes('prompts') || idPath.includes('output_parsers')) { return true; } // Legacy agent name patterns for supporting pre-langgraph orchestration if (name && (name.startsWith('Runnable') || name.endsWith('OutputParser') || name.endsWith('PromptTemplate'))) { return true; } // In @langchain/core >= 1.0.0, the agent creation logic changed to depend on langgraph. // We suppress internal nodes that have langgraph metadata, except for nodes that // contain the agent name metadata as those are used for invoke_agent spans. if (metadata && Object.keys(metadata).some(k => k.startsWith('langgraph_'))) { return true; } return false; } _setModelRequestAttributes(span, extraParams, config, // eslint-disable-line @typescript-eslint/no-explicit-any modelName) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p; const resolvedConfig = config !== null && config !== void 0 ? config : {}; const model = OpenTelemetryCallbackHandler._extractModelId(extraParams, resolvedConfig) || modelName; if (model) { this._setAttribute(span, semconv_1.ATTR_GEN_AI_REQUEST_MODEL, model); } const invocationParams = ((_a = extraParams === null || extraParams === void 0 ? void 0 : extraParams.invocation_params) !== null && _a !== void 0 ? _a : {}); const params = ((_b = invocationParams.params) !== null && _b !== void 0 ? _b : invocationParams); const inferenceConfig = ((_c = params.inferenceConfig) !== null && _c !== void 0 ? _c : {}); this._setAttribute(span, semconv_1.ATTR_GEN_AI_REQUEST_MAX_TOKENS, (_f = (_e = (_d = params.max_tokens) !== null && _d !== void 0 ? _d : params.max_new_tokens) !== null && _e !== void 0 ? _e : inferenceConfig.maxTokens) !== null && _f !== void 0 ? _f : resolvedConfig.max_tokens); this._setAttribute(span, semconv_1.ATTR_GEN_AI_REQUEST_TEMPERATURE, (_h = (_g = params.temperature) !== null && _g !== void 0 ? _g : inferenceConfig.temperature) !== null && _h !== void 0 ? _h : resolvedConfig.temperature); this._setAttribute(span, semconv_1.ATTR_GEN_AI_REQUEST_TOP_P, (_k = (_j = params.top_p) !== null && _j !== void 0 ? _j : inferenceConfig.topP) !== null && _k !== void 0 ? _k : resolvedConfig.top_p); this._setAttribute(span, semconv_1.ATTR_GEN_AI_REQUEST_TOP_K, (_l = params.top_k) !== null && _l !== void 0 ? _l : resolvedConfig.top_k); this._setAttribute(span, semconv_1.ATTR_GEN_AI_REQUEST_FREQUENCY_PENALTY, (_m = params.frequency_penalty) !== null && _m !== void 0 ? _m : resolvedConfig.frequency_penalty); this._setAttribute(span, semconv_1.ATTR_GEN_AI_REQUEST_PRESENCE_PENALTY, (_o = params.presence_penalty) !== null && _o !== void 0 ? _o : resolvedConfig.presence_penalty); const stop = (_p = params.stop) !== null && _p !== void 0 ? _p : resolvedConfig.stop; if (stop) { this._setAttribute(span, semconv_1.ATTR_GEN_AI_REQUEST_STOP_SEQUENCES, stop); } } // propagates LLM model span attributes to the parent invoke_agent span. // These are OTel attributes that the invoke_agent span are recommended to have. _propagateToAgentSpan(runId, provider, modelName, extraParams, config) { var _a, _b, _c, _d, _e, _f; const entry = this.runIdToSpanMap.get(runId); if (!((_a = entry === null || entry === void 0 ? void 0 : entry.agentSpan) === null || _a === void 0 ? void 0 : _a.isRecording())) return; const agentSpan = entry.agentSpan; this._setAttribute(agentSpan, semconv_1.ATTR_GEN_AI_PROVIDER_NAME, provider); this._setAttribute(agentSpan, semconv_1.ATTR_GEN_AI_REQUEST_MODEL, modelName); const invocationParams = ((_b = extraParams === null || extraParams === void 0 ? void 0 : extraParams.invocation_params) !== null && _b !== void 0 ? _b : {}); const params = ((_c = invocationParams.params) !== null && _c !== void 0 ? _c : invocationParams); const inferenceConfig = ((_d = params.inferenceConfig) !== null && _d !== void 0 ? _d : {}); const temperature = (_f = (_e = params.temperature) !== null && _e !== void 0 ? _e : inferenceConfig.temperature) !== null && _f !== void 0 ? _f : config === null || config === void 0 ? void 0 : config.temperature; this._setAttribute(agentSpan, semconv_1.ATTR_GEN_AI_REQUEST_TEMPERATURE, temperature); } _setLanggraphAttributes(span, metadata) { if (!metadata) return; this._setAttribute(span, LANGGRAPH_STEP_SPAN_ATTR, metadata.langgraph_step); this._setAttribute(span, LANGGRAPH_NODE_SPAN_ATTR, metadata.langgraph_node); } static _extractLCName(serialized, extraParams, runName) { var _a; if (runName) return runName; const config = OpenTelemetryCallbackHandler._getSerializedConfig(serialized); if (config === null || config === void 0 ? void 0 : config.name) return config.name; if (serialized.name) return serialized.name; if ((_a = serialized.id) === null || _a === void 0 ? void 0 : _a.length) return serialized.id[serialized.id.length - 1]; return extraParams === null || extraParams === void 0 ? void 0 : extraParams.name; } static _extractModelId(extraParams, config, // eslint-disable-line @typescript-eslint/no-explicit-any metadata, serialized) { var _a, _b, _c, _d; if (typeof (metadata === null || metadata === void 0 ? void 0 : metadata.ls_model_name) === 'string') return metadata.ls_model_name; const invocationParams = ((_a = extraParams === null || extraParams === void 0 ? void 0 : extraParams.invocation_params) !== null && _a !== void 0 ? _a : {}); const sources = [extraParams, invocationParams, config]; for (const source of sources) { if (!source) continue; const model = (_d = (_c = (_b = source.model) !== null && _b !== void 0 ? _b : source.model_name) !== null && _c !== void 0 ? _c : source.model_id) !== null && _d !== void 0 ? _d : source.base_model_id; if (typeof model === 'string' && model) return model; } if (serialized) return OpenTelemetryCallbackHandler._extractLCName(serialized, extraParams); return undefined; } static _extractModelProvider(serialized, extraParams, metadata) { var _a, _b; if (serialized.id) { for (const part of serialized.id) { const provider = instrumentation_utils_1.PROVIDER_MAP[part.toLowerCase()]; if (provider) return provider; } } if (typeof (metadata === null || metadata === void 0 ? void 0 : metadata.ls_provider) === 'string') { const provider = instrumentation_utils_1.PROVIDER_MAP[metadata.ls_provider.toLowerCase()]; if (provider) return provider; } const invocationParams = ((_a = extraParams === null || extraParams === void 0 ? void 0 : extraParams.invocation_params) !== null && _a !== void 0 ? _a : {}); const invType = invocationParams._type; if (typeof invType === 'string' && invType) { const prefix = invType.split('-')[0].toLowerCase(); const provider = instrumentation_utils_1.PROVIDER_MAP[prefix]; if (provider) return provider; } const modelId = (_b = invocationParams.model_id) !== null && _b !== void 0 ? _b : extraParams === null || extraParams === void 0 ? void 0 : extraParams.model_id; if (typeof modelId === 'string' && modelId.includes('/')) { const prefix = modelId.split('/')[0].toLowerCase(); const provider = instrumentation_utils_1.PROVIDER_MAP[prefix]; if (provider) return provider; } return undefined; } // eslint-disable-next-line @typescript-eslint/no-explicit-any static _getSerializedConfig(serialized) { return 'kwargs' in serialized ? serialized.kwargs : undefined; } // Converts LangChain messages to OTel format conversation and system instructions format based on // the following schemas: // https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-input-messages.json // https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-output-messages.json // https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-system-instructions.json // // Example LangChain input based on: // https://github.com/langchain-ai/langchainjs/blob/0c799481f691e046a4533588fc96e190669fa16e/libs/langchain-core/src/messages/human.ts#L18 // https://github.com/langchain-ai/langchainjs/blob/0c799481f691e046a4533588fc96e190669fa16e/libs/langchain-core/src/messages/system.ts#L18 // https://github.com/langchain-ai/langchainjs/blob/0c799481f691e046a4533588fc96e190669fa16e/libs/langchain-core/src/messages/ai.ts#L33 // https://github.com/langchain-ai/langchainjs/blob/0c799481f691e046a4533588fc96e190669fa16e/libs/langchain-core/src/messages/tool.ts#L53 // // [[ // SystemMessage({ content: "You are a helpful assistant." }), // HumanMessage({ content: "What is the weather in Paris?" }), // AIMessage({ content: "Let me check.", tool_calls: [ // { name: "get_weather", args: { city: "Paris" }, id: "call_abc123", type: "tool_call" } // ] }), // ]] // // // Example OTel output: // // systemInstructions: // [{ type: "text", content: "You are a helpful assistant." }] // // conversation: // [ // { role: "user", parts: [{ type: "text", content: "What is the weather in Paris?" }] }, // { role: "assistant", parts: [ // { type: "text", content: "Let me check." }, // { type: "tool_call", id: "call_abc123", name: "get_weather", arguments: {...} }, // ] }, // { role: "tool", parts: [ // { type: "tool_call_response", id: "call_abc123", response: "72°F and sunny" }, // ] }, // ] static _formatMessages(messages) { var _a; const systemInstructions = []; const conversation = []; for (const messageGroup of messages) { for (const message of messageGroup) { const messageType = message.getType(); const role = OpenTelemetryCallbackHandler._normalizeRole(messageType); const parts = []; const textContent = OpenTelemetryCallbackHandler._extractTextContent(message.content); const isToolMessage = role === 'tool' && 'tool_call_id' in message && message.tool_call_id; if (textContent && !isToolMessage) { parts.push({ type: 'text', content: textContent }); } if ((0, messages_1.isAIMessage)(message) && message.tool_calls) { for (const toolCall of message.tool_calls) { parts.push({ type: 'tool_call', id: (_a = toolCall.id) !== null && _a !== void 0 ? _a : '', name: toolCall.name, arguments: toolCall.args, }); } } if (isToolMessage) { parts.push({ type: 'tool_call_response', id: message.tool_call_id, response: textContent, }); } if (role === 'system') { systemInstructions.push({ type: 'text', content: textContent }); } else if (parts.length > 0) { conversation.push({ role, parts }); } } } return { systemInstructions, conversation }; } // Converts the result of LLM to OTel output messages format. // https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-output-messages.json // // Example LangChain input based on: // https://github.com/langchain-ai/langchainjs/blob/0c799481f691e046a4533588fc96e190669fa16e/libs/langchain-core/src/outputs.ts#L55 // https://github.com/langchain-ai/langchainjs/blob/0c799481f691e046a4533588fc96e190669fa16e/libs/langchain-core/src/outputs.ts#L72 // // [[ChatGeneration({ // message: AIMessage({ content: "The weather is sunny.", tool_calls: [...] }), // generationInfo: { finish_reason: "end_turn" }, // })]] // // Example OTel output: // // [{ role: "assistant", parts: [ // { type: "text", content: "The weather is sunny." }, // { type: "tool_call", id: "call_abc", name: "get_weather", arguments: {...} }, // ], finish_reason: "stop" }] static _formatOutputMessages(response) { var _a; const outputMessages = []; for (const generationGroup of response.generations) { for (const generation of generationGroup) { const parts = []; let finishReason; if ('message' in generation) { const message = generation.message; const textContent = OpenTelemetryCallbackHandler._extractTextContent(message.content); if (textContent) { parts.push({ type: 'text', content: textContent }); } if ((0, messages_1.isAIMessage)(message) && message.tool_calls) { for (const toolCall of message.tool_calls) { parts.push({ type: 'tool_call', id: (_a = toolCall.id) !== null && _a !== void 0 ? _a : '', name: toolCall.name, arguments: toolCall.args, }); } } finishReason = OpenTelemetryCallbackHandler._extractFinishReason(generation); } else { if (generation.text) { parts.push({ type: 'text', content: generation.text }); } } if (parts.length > 0) { outputMessages.push({ role: 'assistant', parts, finish_reason: finishReason !== null && finishReason !== void 0 ? finishReason : 'stop', }); } } } return outputMessages; } static _extractTextContent(content) { if (typeof content === 'string') return content; if (Array.isArray(content)) { return content .filter((block) => typeof block === 'object' && block !== null && 'type' in block && block.type === 'text') .map(block => block.text) .join(''); } return ''; } static _extractFinishReasons(response) { const reasons = []; for (const generationGroup of response.generations) { for (const generation of generationGroup) { const reason = OpenTelemetryCallbackHandler._extractFinishReason(generation); if (reason) reasons.push(reason); } } return reasons; } static _extractFinishReason(generation) { var _a, _b, _c, _d, _e, _f; if (!('message' in generation)) return undefined; const message = generation.message; const metadata = ((_a = message.response_metadata) !== null && _a !== void 0 ? _a : {}); const rawReason = (_f = (_e = (_d = (_c = (_b = generation.generationInfo) === null || _b === void 0 ? void 0 : _b.finish_reason) !== null && _c !== void 0 ? _c : metadata.finish_reason) !== null && _d !== void 0 ? _d : metadata.stop_reason) !== null && _e !== void 0 ? _e : metadata.stopReason) !== null && _f !== void 0 ? _f : metadata.finishReason; return typeof rawReason === 'string' ? OpenTelemetryCallbackHandler._normalizeFinishReason(rawReason) : undefined; } static _normalizeRole(messageType) { switch (messageType) { case 'human': case 'generic': return 'user'; case 'ai': return 'assistant'; case 'system': return 'system'; case 'tool': case 'function': return 'tool'; default: return messageType; } } static _normalizeFinishReason(raw) { switch (raw) { case 'stop': case 'end_turn': case 'STOP': case 'COMPLETE': return 'stop'; case 'length': case 'max_tokens': case 'MAX_TOKENS': case 'ERROR_LIMIT': return 'length'; case 'content_filter': case 'SAFETY': case 'RECITATION': case 'ERROR_TOXIC': return 'content_filter'; case 'tool_use': case 'tool_calls': case 'function_call': return 'tool_call'; case 'error': case 'ERROR': return 'error'; default: return raw; } } // eslint-disable-next-line @typescript-eslint/no-explicit-any _setAttribute(span, name, value) { if (span.isRecording() && value !== undefined && value !== null && value !== '') { span.setAttribute(name, value); } } } exports.OpenTelemetryCallbackHandler = OpenTelemetryCallbackHandler; //# sourceMappingURL=callback-handler.js.map