@traceloop/instrumentation-anthropic
Version:
361 lines (356 loc) • 18.8 kB
JavaScript
;
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