@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
241 lines (238 loc) • 9.74 kB
JavaScript
import { captureException } from '../../exports.js';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes.js';
import { SPAN_STATUS_ERROR } from '../spanstatus.js';
import { startSpan, startSpanManual } from '../trace.js';
import { GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, GEN_AI_REQUEST_MODEL_ATTRIBUTE, GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, GEN_AI_REQUEST_TOP_P_ATTRIBUTE, GEN_AI_REQUEST_STREAM_ATTRIBUTE, GEN_AI_REQUEST_TOP_K_ATTRIBUTE, GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE, GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, GEN_AI_PROMPT_ATTRIBUTE, GEN_AI_OPERATION_NAME_ATTRIBUTE, GEN_AI_SYSTEM_ATTRIBUTE, GEN_AI_RESPONSE_TEXT_ATTRIBUTE, GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, GEN_AI_RESPONSE_MODEL_ATTRIBUTE, GEN_AI_RESPONSE_ID_ATTRIBUTE } from '../ai/gen-ai-attributes.js';
import { resolveAIRecordingOptions, shouldEnableTruncation, wrapPromiseWithMethods, buildMethodPath, setTokenUsageAttributes } from '../ai/utils.js';
import { ANTHROPIC_METHOD_REGISTRY } from './constants.js';
import { instrumentAsyncIterableStream, instrumentMessageStream } from './streaming.js';
import { messagesFromParams, setMessagesAttribute, handleResponseError } from './utils.js';
function extractRequestAttributes(args, methodPath, operationName) {
const attributes = {
[GEN_AI_SYSTEM_ATTRIBUTE]: "anthropic",
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: operationName,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.ai.anthropic"
};
if (args.length > 0 && typeof args[0] === "object" && args[0] !== null) {
const params = args[0];
if (params.tools && Array.isArray(params.tools)) {
attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE] = JSON.stringify(params.tools);
}
attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = params.model ?? "unknown";
if ("temperature" in params) attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE] = params.temperature;
if ("top_p" in params) attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE] = params.top_p;
if ("stream" in params) attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE] = params.stream;
if ("top_k" in params) attributes[GEN_AI_REQUEST_TOP_K_ATTRIBUTE] = params.top_k;
if ("frequency_penalty" in params)
attributes[GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE] = params.frequency_penalty;
if ("max_tokens" in params) attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE] = params.max_tokens;
} else {
if (methodPath === "models.retrieve" || methodPath === "models.get") {
attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = args[0];
} else {
attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = "unknown";
}
}
return attributes;
}
function addPrivateRequestAttributes(span, params, enableTruncation) {
const messages = messagesFromParams(params);
setMessagesAttribute(span, messages, enableTruncation);
if ("prompt" in params) {
span.setAttributes({ [GEN_AI_PROMPT_ATTRIBUTE]: JSON.stringify(params.prompt) });
}
}
function addContentAttributes(span, response) {
if ("content" in response) {
if (Array.isArray(response.content)) {
span.setAttributes({
[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: response.content.map((item) => item.text).filter((text) => !!text).join("")
});
const toolCalls = [];
for (const item of response.content) {
if (item.type === "tool_use" || item.type === "server_tool_use") {
toolCalls.push(item);
}
}
if (toolCalls.length > 0) {
span.setAttributes({ [GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]: JSON.stringify(toolCalls) });
}
}
}
if ("completion" in response) {
span.setAttributes({ [GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: response.completion });
}
if ("input_tokens" in response) {
span.setAttributes({ [GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: JSON.stringify(response.input_tokens) });
}
}
function addMetadataAttributes(span, response) {
if ("id" in response && "model" in response) {
span.setAttributes({
[GEN_AI_RESPONSE_ID_ATTRIBUTE]: response.id,
[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: response.model
});
if ("usage" in response && response.usage) {
setTokenUsageAttributes(
span,
response.usage.input_tokens,
response.usage.output_tokens,
response.usage.cache_creation_input_tokens,
response.usage.cache_read_input_tokens
);
}
}
}
function addResponseAttributes(span, response, recordOutputs) {
if (!response || typeof response !== "object") return;
if ("type" in response && response.type === "error") {
handleResponseError(span, response);
return;
}
if (recordOutputs) {
addContentAttributes(span, response);
}
addMetadataAttributes(span, response);
}
function handleStreamingError(error, span, methodPath) {
captureException(error, {
mechanism: { handled: false, type: "auto.ai.anthropic", data: { function: methodPath } }
});
if (span.isRecording()) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: "internal_error" });
span.end();
}
throw error;
}
function handleStreamingRequest(originalMethod, target, context, args, requestAttributes, operationName, methodPath, params, options, isStreamRequested, isStreamingMethod) {
const model = requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] ?? "unknown";
const spanConfig = {
name: `${operationName} ${model}`,
op: `gen_ai.${operationName}`,
attributes: requestAttributes
};
if (isStreamRequested && !isStreamingMethod) {
let originalResult;
const instrumentedPromise = startSpanManual(spanConfig, (span) => {
originalResult = originalMethod.apply(context, args);
if (options.recordInputs && params) {
addPrivateRequestAttributes(span, params, shouldEnableTruncation(options.enableTruncation));
}
return (async () => {
try {
const result = await originalResult;
return instrumentAsyncIterableStream(
result,
span,
options.recordOutputs ?? false
);
} catch (error) {
return handleStreamingError(error, span, methodPath);
}
})();
});
return wrapPromiseWithMethods(originalResult, instrumentedPromise, "auto.ai.anthropic");
} else {
return startSpanManual(spanConfig, (span) => {
try {
if (options.recordInputs && params) {
addPrivateRequestAttributes(span, params, shouldEnableTruncation(options.enableTruncation));
}
const messageStream = target.apply(context, args);
return instrumentMessageStream(messageStream, span, options.recordOutputs ?? false);
} catch (error) {
return handleStreamingError(error, span, methodPath);
}
});
}
}
function instrumentMethod(originalMethod, methodPath, instrumentedMethod, context, options) {
return new Proxy(originalMethod, {
apply(target, thisArg, args) {
const operationName = instrumentedMethod.operation || "unknown";
const requestAttributes = extractRequestAttributes(args, methodPath, operationName);
const model = requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] ?? "unknown";
const params = typeof args[0] === "object" ? args[0] : void 0;
const isStreamRequested = Boolean(params?.stream);
const isStreamingMethod = instrumentedMethod.streaming === true;
if (isStreamRequested || isStreamingMethod) {
return handleStreamingRequest(
originalMethod,
target,
context,
args,
requestAttributes,
operationName,
methodPath,
params,
options,
isStreamRequested,
isStreamingMethod
);
}
let originalResult;
const instrumentedPromise = startSpan(
{
name: `${operationName} ${model}`,
op: `gen_ai.${operationName}`,
attributes: requestAttributes
},
(span) => {
originalResult = target.apply(context, args);
if (options.recordInputs && params) {
addPrivateRequestAttributes(span, params, shouldEnableTruncation(options.enableTruncation));
}
return originalResult.then(
(result) => {
addResponseAttributes(span, result, options.recordOutputs);
return result;
},
(error) => {
captureException(error, {
mechanism: {
handled: false,
type: "auto.ai.anthropic",
data: {
function: methodPath
}
}
});
throw error;
}
);
}
);
return wrapPromiseWithMethods(originalResult, instrumentedPromise, "auto.ai.anthropic");
}
});
}
function createDeepProxy(target, currentPath = "", options) {
return new Proxy(target, {
get(obj, prop) {
const value = obj[prop];
const methodPath = buildMethodPath(currentPath, String(prop));
const instrumentedMethod = ANTHROPIC_METHOD_REGISTRY[methodPath];
if (typeof value === "function" && instrumentedMethod) {
return instrumentMethod(
value,
methodPath,
instrumentedMethod,
obj,
options
);
}
if (typeof value === "function") {
return value.bind(obj);
}
if (value && typeof value === "object") {
return createDeepProxy(value, methodPath, options);
}
return value;
}
});
}
function instrumentAnthropicAiClient(anthropicAiClient, options) {
return createDeepProxy(anthropicAiClient, "", resolveAIRecordingOptions(options));
}
export { instrumentAnthropicAiClient };
//# sourceMappingURL=index.js.map