UNPKG

@traceloop/instrumentation-anthropic

Version:
361 lines (356 loc) 18.8 kB
'use strict'; var tslib = require('tslib'); var api = require('@opentelemetry/api'); var instrumentation = require('@opentelemetry/instrumentation'); var aiSemanticConventions = require('@traceloop/ai-semantic-conventions'); var instrumentationUtils = require('@traceloop/instrumentation-utils'); var incubating = require('@opentelemetry/semantic-conventions/incubating'); var version = "0.26.0"; // Mapping of Anthropic-specific stop reasons to standardized finish reasons const anthropicFinishReasonMap = { end_turn: aiSemanticConventions.FinishReasons.STOP, max_tokens: aiSemanticConventions.FinishReasons.LENGTH, stop_sequence: aiSemanticConventions.FinishReasons.STOP, tool_use: aiSemanticConventions.FinishReasons.TOOL_CALL, }; class AnthropicInstrumentation extends instrumentation.InstrumentationBase { constructor(config = {}) { super("@traceloop/instrumentation-anthropic", version, config); } setConfig(config = {}) { super.setConfig(config); } manuallyInstrument(module) { this._diag.debug(`Patching @anthropic-ai/sdk manually`); this._wrap(module.Anthropic.Completions.prototype, "create", this.patchAnthropic(incubating.GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION, module)); this._wrap(module.Anthropic.Messages.prototype, "create", this.patchAnthropic(incubating.GEN_AI_OPERATION_NAME_VALUE_CHAT, module)); this._wrap(module.Anthropic.Beta.Messages.prototype, "create", this.patchAnthropic(incubating.GEN_AI_OPERATION_NAME_VALUE_CHAT, module)); } init() { const module = new instrumentation.InstrumentationNodeModuleDefinition("@anthropic-ai/sdk", [">=0.9.1"], this.patch.bind(this), this.unpatch.bind(this)); return module; } patch(moduleExports, moduleVersion) { this._diag.debug(`Patching @anthropic-ai/sdk@${moduleVersion}`); this._wrap(moduleExports.Anthropic.Completions.prototype, "create", this.patchAnthropic(incubating.GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION, moduleExports)); this._wrap(moduleExports.Anthropic.Messages.prototype, "create", this.patchAnthropic(incubating.GEN_AI_OPERATION_NAME_VALUE_CHAT, moduleExports)); this._wrap(moduleExports.Anthropic.Beta.Messages.prototype, "create", this.patchAnthropic(incubating.GEN_AI_OPERATION_NAME_VALUE_CHAT, moduleExports)); return moduleExports; } unpatch(moduleExports, moduleVersion) { this._diag.debug(`Unpatching @anthropic-ai/sdk@${moduleVersion}`); this._unwrap(moduleExports.Anthropic.Completions.prototype, "create"); this._unwrap(moduleExports.Anthropic.Messages.prototype, "create"); this._unwrap(moduleExports.Anthropic.Beta.Messages.prototype, "create"); } patchAnthropic(type, moduleExports) { // eslint-disable-next-line @typescript-eslint/no-this-alias const plugin = this; // eslint-disable-next-line return (original) => { return function method(...args) { const span = type === incubating.GEN_AI_OPERATION_NAME_VALUE_CHAT ? plugin.startSpan({ type, params: args[0], }) : plugin.startSpan({ type: incubating.GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION, params: args[0], }); const execContext = api.trace.setSpan(api.context.active(), span); const execPromise = instrumentation.safeExecuteInTheMiddle(() => { return api.context.with(execContext, () => { var _a; if ((_a = args === null || args === void 0 ? void 0 : args[0]) === null || _a === void 0 ? void 0 : _a.extraAttributes) { delete args[0].extraAttributes; } return original.apply(this, args); }); }, (e) => { if (e) { plugin._diag.error("Error in Anthropic instrumentation", e); } }); if (args[0].stream) { return api.context.bind(execContext, plugin._streamingWrapPromise(this._client, moduleExports, { span, type, promise: execPromise, })); } const wrappedPromise = plugin._wrapPromise(type, span, execPromise); return api.context.bind(execContext, wrappedPromise); }; }; } startSpan({ type, params, }) { var _a, _b, _c; const attributes = { [incubating.ATTR_GEN_AI_PROVIDER_NAME]: incubating.GEN_AI_PROVIDER_NAME_VALUE_ANTHROPIC, [incubating.ATTR_GEN_AI_OPERATION_NAME]: type, }; try { attributes[incubating.ATTR_GEN_AI_REQUEST_MODEL] = params.model; attributes[incubating.ATTR_GEN_AI_REQUEST_TEMPERATURE] = params.temperature; attributes[incubating.ATTR_GEN_AI_REQUEST_TOP_P] = params.top_p; attributes[incubating.ATTR_GEN_AI_REQUEST_TOP_K] = params.top_k; // Handle thinking parameters (for beta messages) const betaParams = params; if (betaParams.thinking && betaParams.thinking.type === "enabled") { attributes[aiSemanticConventions.SpanAttributes.GEN_AI_REQUEST_THINKING_TYPE] = betaParams.thinking.type; attributes[aiSemanticConventions.SpanAttributes.GEN_AI_REQUEST_THINKING_BUDGET_TOKENS] = betaParams.thinking.budget_tokens; } if (type === incubating.GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION) { attributes[incubating.ATTR_GEN_AI_REQUEST_MAX_TOKENS] = params.max_tokens_to_sample; } else { attributes[incubating.ATTR_GEN_AI_REQUEST_MAX_TOKENS] = params.max_tokens; } if (params.extraAttributes !== undefined && typeof params.extraAttributes === "object") { Object.keys(params.extraAttributes).forEach((key) => { attributes[key] = params.extraAttributes[key]; }); } if (this._shouldSendPrompts()) { if (type === incubating.GEN_AI_OPERATION_NAME_VALUE_CHAT) { if ("system" in params && params.system !== undefined) { attributes[incubating.ATTR_GEN_AI_SYSTEM_INSTRUCTIONS] = instrumentationUtils.formatSystemInstructions(params.system); } attributes[incubating.ATTR_GEN_AI_INPUT_MESSAGES] = instrumentationUtils.formatInputMessages(params.messages, instrumentationUtils.mapAnthropicContentBlock); } else { attributes[incubating.ATTR_GEN_AI_INPUT_MESSAGES] = instrumentationUtils.formatInputMessagesFromPrompt(params.prompt); } } } catch (e) { this._diag.debug(e); (_b = (_a = this._config).exceptionLogger) === null || _b === void 0 ? void 0 : _b.call(_a, e); } return this.tracer.startSpan(`${type} ${(_c = params === null || params === void 0 ? void 0 : params.model) !== null && _c !== void 0 ? _c : "unknown"}`, { kind: api.SpanKind.CLIENT, attributes, }); } _streamingWrapPromise(client, moduleExports, { span, type, promise, }) { function iterateStream(stream) { return tslib.__asyncGenerator(this, arguments, function* iterateStream_1() { var _a, e_1, _b, _c, _d, e_2, _e, _f; var _g, _h, _j, _k; try { if (type === incubating.GEN_AI_OPERATION_NAME_VALUE_CHAT) { const result = { id: "0", type: "message", model: "", role: "assistant", stop_reason: null, stop_sequence: null, usage: { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: null, cache_read_input_tokens: null, }, content: [], }; try { for (var _l = true, stream_1 = tslib.__asyncValues(stream), stream_1_1; stream_1_1 = yield tslib.__await(stream_1.next()), _a = stream_1_1.done, !_a; _l = true) { _c = stream_1_1.value; _l = false; const chunk = _c; yield yield tslib.__await(chunk); try { switch (chunk.type) { case "message_start": result.id = chunk.message.id; result.model = chunk.message.model; Object.assign(result.usage, chunk.message.usage); break; case "message_delta": if (chunk.usage) { Object.assign(result.usage, chunk.usage); } break; case "content_block_start": if (result.content.length <= chunk.index) { result.content.push(Object.assign({}, chunk.content_block)); } break; case "content_block_delta": if (chunk.index < result.content.length) { const current = result.content[chunk.index]; if (current.type === "text" && chunk.delta.type === "text_delta") { result.content[chunk.index] = { type: "text", text: current.text + chunk.delta.text, citations: current.citations, }; } } break; } } catch (e) { this._diag.debug(e); (_h = (_g = this._config).exceptionLogger) === null || _h === void 0 ? void 0 : _h.call(_g, e); } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_l && !_a && (_b = stream_1.return)) yield tslib.__await(_b.call(stream_1)); } finally { if (e_1) throw e_1.error; } } this._endSpan({ span, type, result }); } else { const result = { id: "0", type: "completion", model: "", completion: "", stop_reason: null, }; try { for (var _m = true, _o = tslib.__asyncValues(stream), _p; _p = yield tslib.__await(_o.next()), _d = _p.done, !_d; _m = true) { _f = _p.value; _m = false; const chunk = _f; yield yield tslib.__await(chunk); try { result.id = chunk.id; result.model = chunk.model; if (chunk.stop_reason) { result.stop_reason = chunk.stop_reason; } if (chunk.model) { result.model = chunk.model; } if (chunk.completion) { result.completion += chunk.completion; } } catch (e) { this._diag.debug(e); (_k = (_j = this._config).exceptionLogger) === null || _k === void 0 ? void 0 : _k.call(_j, e); } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (!_m && !_d && (_e = _o.return)) yield tslib.__await(_e.call(_o)); } finally { if (e_2) throw e_2.error; } } this._endSpan({ span, type, result }); } } catch (error) { span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message, }); span.recordException(error); span.end(); throw error; } }); } return new moduleExports.APIPromise(client, promise.responsePromise, (client, props) => tslib.__awaiter(this, void 0, void 0, function* () { const realStream = yield promise.parseResponse(client, props); // take the incoming stream, iterate it using our instrumented function, and wrap it in a new stream to keep the rich object type the same return new realStream.constructor(() => iterateStream.call(this, realStream), realStream.controller); })); } _wrapPromise(type, span, promise) { return promise .then((result) => { if (type === incubating.GEN_AI_OPERATION_NAME_VALUE_CHAT) { this._endSpan({ type, span, result: result, }); } else { this._endSpan({ type, span, result: result, }); } return result; }) .catch((error) => { span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message, }); span.recordException(error); span.end(); throw error; }); } _endSpan({ span, type, result, }) { var _a, _b, _c; try { span.setAttribute(incubating.ATTR_GEN_AI_RESPONSE_MODEL, result.model); // Always set finish_reason — it's metadata, not user content if (result.stop_reason) { const mappedReason = (_a = anthropicFinishReasonMap[result.stop_reason]) !== null && _a !== void 0 ? _a : result.stop_reason; span.setAttribute(incubating.ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [mappedReason]); } if (type === incubating.GEN_AI_OPERATION_NAME_VALUE_CHAT && result.usage) { span.setAttribute(aiSemanticConventions.SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS, result.usage.input_tokens + result.usage.output_tokens); span.setAttribute(incubating.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, result.usage.output_tokens); span.setAttribute(incubating.ATTR_GEN_AI_USAGE_INPUT_TOKENS, result.usage.input_tokens); // Cache token attributes (v1.40) if (result.usage.cache_creation_input_tokens != null) { span.setAttribute(incubating.ATTR_GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, result.usage.cache_creation_input_tokens); } if (result.usage.cache_read_input_tokens != null) { span.setAttribute(incubating.ATTR_GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, result.usage.cache_read_input_tokens); } } // Only set output message content when tracing content if (this._shouldSendPrompts()) { const content = type === incubating.GEN_AI_OPERATION_NAME_VALUE_CHAT ? result.content : result.completion; const outputMessages = instrumentationUtils.formatOutputMessage(content, result.stop_reason, anthropicFinishReasonMap, type, instrumentationUtils.mapAnthropicContentBlock); span.setAttribute(incubating.ATTR_GEN_AI_OUTPUT_MESSAGES, outputMessages); } } catch (e) { this._diag.debug(e); (_c = (_b = this._config).exceptionLogger) === null || _c === void 0 ? void 0 : _c.call(_b, e); } span.end(); } _shouldSendPrompts() { const contextShouldSendPrompts = api.context .active() .getValue(aiSemanticConventions.CONTEXT_KEY_ALLOW_TRACE_CONTENT); if (contextShouldSendPrompts !== undefined) { return contextShouldSendPrompts; } return this._config.traceContent !== undefined ? this._config.traceContent : true; } } exports.AnthropicInstrumentation = AnthropicInstrumentation; exports.anthropicFinishReasonMap = anthropicFinishReasonMap; //# sourceMappingURL=index.js.map