@traceloop/instrumentation-anthropic
Version:
Anthropic Instrumentaion
311 lines (306 loc) • 14.4 kB
JavaScript
;
var tslib = require('tslib');
var api = require('@opentelemetry/api');
var instrumentation = require('@opentelemetry/instrumentation');
var aiSemanticConventions = require('@traceloop/ai-semantic-conventions');
var version = "0.12.0";
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("completion"));
this._wrap(module.Anthropic.Messages.prototype, "create", this.patchAnthropic("chat"));
}
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("completion"));
this._wrap(moduleExports.Anthropic.Messages.prototype, "create", this.patchAnthropic("chat"));
return moduleExports;
}
unpatch(moduleExports, moduleVersion) {
this._diag.debug(`Unpatching @azure/openai@${moduleVersion}`);
this._unwrap(moduleExports.Anthropic.Completions.prototype, "create");
this._unwrap(moduleExports.Anthropic.Messages.prototype, "create");
}
patchAnthropic(type) {
// 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 === "chat"
? plugin.startSpan({
type,
params: args[0],
})
: plugin.startSpan({
type,
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 &&
type === "completion" // For some reason, this causes an exception with chat, so disabled for now
) {
return api.context.bind(execContext, plugin._streamingWrapPromise({
span,
type,
promise: execPromise,
}));
}
const wrappedPromise = plugin._wrapPromise(type, span, execPromise);
return api.context.bind(execContext, wrappedPromise);
};
};
}
startSpan({ type, params, }) {
var _a, _b;
const attributes = {
[aiSemanticConventions.SpanAttributes.LLM_SYSTEM]: "Anthropic",
[aiSemanticConventions.SpanAttributes.LLM_REQUEST_TYPE]: type,
};
try {
attributes[aiSemanticConventions.SpanAttributes.LLM_REQUEST_MODEL] = params.model;
attributes[aiSemanticConventions.SpanAttributes.LLM_REQUEST_TEMPERATURE] = params.temperature;
attributes[aiSemanticConventions.SpanAttributes.LLM_REQUEST_TOP_P] = params.top_p;
attributes[aiSemanticConventions.SpanAttributes.LLM_TOP_K] = params.top_k;
if (type === "completion") {
attributes[aiSemanticConventions.SpanAttributes.LLM_REQUEST_MAX_TOKENS] =
params.max_tokens_to_sample;
}
else {
attributes[aiSemanticConventions.SpanAttributes.LLM_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 === "chat") {
params.messages.forEach((message, index) => {
attributes[`${aiSemanticConventions.SpanAttributes.LLM_PROMPTS}.${index}.role`] =
message.role;
if (typeof message.content === "string") {
attributes[`${aiSemanticConventions.SpanAttributes.LLM_PROMPTS}.${index}.content`] =
message.content || "";
}
else {
attributes[`${aiSemanticConventions.SpanAttributes.LLM_PROMPTS}.${index}.content`] =
JSON.stringify(message.content);
}
});
}
else {
attributes[`${aiSemanticConventions.SpanAttributes.LLM_PROMPTS}.0.role`] = "user";
attributes[`${aiSemanticConventions.SpanAttributes.LLM_PROMPTS}.0.content`] = 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(`anthropic.${type}`, {
kind: api.SpanKind.CLIENT,
attributes,
});
}
_streamingWrapPromise(_a) {
return tslib.__asyncGenerator(this, arguments, function* _streamingWrapPromise_1({ span, type, promise, }) {
var _b, e_1, _c, _d, _e, e_2, _f, _g;
var _h, _j, _k, _l;
if (type === "chat") {
const result = {
id: "0",
type: "message",
model: "",
role: "assistant",
stop_reason: null,
stop_sequence: null,
usage: { input_tokens: 0, output_tokens: 0 },
content: [],
};
try {
for (var _m = true, _o = tslib.__asyncValues(yield tslib.__await(promise)), _p; _p = yield tslib.__await(_o.next()), _b = _p.done, !_b; _m = true) {
_d = _p.value;
_m = false;
const chunk = _d;
yield yield tslib.__await(chunk);
try {
switch (chunk.type) {
case "content_block_start":
if (result.content.length <= chunk.index) {
result.content.push(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,
};
}
}
}
}
catch (e) {
this._diag.debug(e);
(_j = (_h = this._config).exceptionLogger) === null || _j === void 0 ? void 0 : _j.call(_h, e);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_m && !_b && (_c = _o.return)) yield tslib.__await(_c.call(_o));
}
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 _q = true, _r = tslib.__asyncValues(yield tslib.__await(promise)), _s; _s = yield tslib.__await(_r.next()), _e = _s.done, !_e; _q = true) {
_g = _s.value;
_q = false;
const chunk = _g;
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);
(_l = (_k = this._config).exceptionLogger) === null || _l === void 0 ? void 0 : _l.call(_k, e);
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (!_q && !_e && (_f = _r.return)) yield tslib.__await(_f.call(_r));
}
finally { if (e_2) throw e_2.error; }
}
this._endSpan({ span, type, result });
}
});
}
_wrapPromise(type, span, promise) {
return promise
.then((result) => {
return new Promise((resolve) => {
if (type === "chat") {
this._endSpan({
type,
span,
result: result,
});
}
else {
this._endSpan({
type,
span,
result: result,
});
}
resolve(result);
});
})
.catch((error) => {
return new Promise((_, reject) => {
span.setStatus({
code: api.SpanStatusCode.ERROR,
message: error.message,
});
span.recordException(error);
span.end();
reject(error);
});
});
}
_endSpan({ span, type, result, }) {
var _a, _b, _c, _d, _e, _f;
try {
span.setAttribute(aiSemanticConventions.SpanAttributes.LLM_RESPONSE_MODEL, result.model);
if (type === "chat" && result.usage) {
span.setAttribute(aiSemanticConventions.SpanAttributes.LLM_USAGE_TOTAL_TOKENS, ((_a = result.usage) === null || _a === void 0 ? void 0 : _a.input_tokens) + ((_b = result.usage) === null || _b === void 0 ? void 0 : _b.output_tokens));
span.setAttribute(aiSemanticConventions.SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, (_c = result.usage) === null || _c === void 0 ? void 0 : _c.output_tokens);
span.setAttribute(aiSemanticConventions.SpanAttributes.LLM_USAGE_PROMPT_TOKENS, (_d = result.usage) === null || _d === void 0 ? void 0 : _d.input_tokens);
}
if (result.stop_reason) {
span.setAttribute(`${aiSemanticConventions.SpanAttributes.LLM_COMPLETIONS}.0.finish_reason`, result.stop_reason);
}
if (this._shouldSendPrompts()) {
if (type === "chat") {
span.setAttribute(`${aiSemanticConventions.SpanAttributes.LLM_COMPLETIONS}.0.role`, "assistant");
span.setAttribute(`${aiSemanticConventions.SpanAttributes.LLM_COMPLETIONS}.0.content`, JSON.stringify(result.content));
}
else {
span.setAttribute(`${aiSemanticConventions.SpanAttributes.LLM_COMPLETIONS}.0.role`, "assistant");
span.setAttribute(`${aiSemanticConventions.SpanAttributes.LLM_COMPLETIONS}.0.content`, result.completion);
}
}
}
catch (e) {
this._diag.debug(e);
(_f = (_e = this._config).exceptionLogger) === null || _f === void 0 ? void 0 : _f.call(_e, 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;
//# sourceMappingURL=index.js.map