@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
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.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