UNPKG

@traceloop/instrumentation-anthropic

Version:
309 lines (305 loc) 13.8 kB
import { __asyncGenerator, __asyncValues, __await } from 'tslib'; import { trace, context, SpanKind, SpanStatusCode } from '@opentelemetry/api'; import { InstrumentationBase, InstrumentationNodeModuleDefinition, safeExecuteInTheMiddle } from '@opentelemetry/instrumentation'; import { SpanAttributes, CONTEXT_KEY_ALLOW_TRACE_CONTENT } from '@traceloop/ai-semantic-conventions'; var version = "0.12.0"; class AnthropicInstrumentation extends 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 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 = trace.setSpan(context.active(), span); const execPromise = safeExecuteInTheMiddle(() => { return 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 context.bind(execContext, plugin._streamingWrapPromise({ span, type, promise: execPromise, })); } const wrappedPromise = plugin._wrapPromise(type, span, execPromise); return context.bind(execContext, wrappedPromise); }; }; } startSpan({ type, params, }) { var _a, _b; const attributes = { [SpanAttributes.LLM_SYSTEM]: "Anthropic", [SpanAttributes.LLM_REQUEST_TYPE]: type, }; try { attributes[SpanAttributes.LLM_REQUEST_MODEL] = params.model; attributes[SpanAttributes.LLM_REQUEST_TEMPERATURE] = params.temperature; attributes[SpanAttributes.LLM_REQUEST_TOP_P] = params.top_p; attributes[SpanAttributes.LLM_TOP_K] = params.top_k; if (type === "completion") { attributes[SpanAttributes.LLM_REQUEST_MAX_TOKENS] = params.max_tokens_to_sample; } else { attributes[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[`${SpanAttributes.LLM_PROMPTS}.${index}.role`] = message.role; if (typeof message.content === "string") { attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.content`] = message.content || ""; } else { attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.content`] = JSON.stringify(message.content); } }); } else { attributes[`${SpanAttributes.LLM_PROMPTS}.0.role`] = "user"; attributes[`${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: SpanKind.CLIENT, attributes, }); } _streamingWrapPromise(_a) { return __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 = __asyncValues(yield __await(promise)), _p; _p = yield __await(_o.next()), _b = _p.done, !_b; _m = true) { _d = _p.value; _m = false; const chunk = _d; yield yield __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 __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 = __asyncValues(yield __await(promise)), _s; _s = yield __await(_r.next()), _e = _s.done, !_e; _q = true) { _g = _s.value; _q = false; const chunk = _g; yield yield __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 __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: 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(SpanAttributes.LLM_RESPONSE_MODEL, result.model); if (type === "chat" && result.usage) { span.setAttribute(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(SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, (_c = result.usage) === null || _c === void 0 ? void 0 : _c.output_tokens); span.setAttribute(SpanAttributes.LLM_USAGE_PROMPT_TOKENS, (_d = result.usage) === null || _d === void 0 ? void 0 : _d.input_tokens); } if (result.stop_reason) { span.setAttribute(`${SpanAttributes.LLM_COMPLETIONS}.0.finish_reason`, result.stop_reason); } if (this._shouldSendPrompts()) { if (type === "chat") { span.setAttribute(`${SpanAttributes.LLM_COMPLETIONS}.0.role`, "assistant"); span.setAttribute(`${SpanAttributes.LLM_COMPLETIONS}.0.content`, JSON.stringify(result.content)); } else { span.setAttribute(`${SpanAttributes.LLM_COMPLETIONS}.0.role`, "assistant"); span.setAttribute(`${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 = context .active() .getValue(CONTEXT_KEY_ALLOW_TRACE_CONTENT); if (contextShouldSendPrompts !== undefined) { return contextShouldSendPrompts; } return this._config.traceContent !== undefined ? this._config.traceContent : true; } } export { AnthropicInstrumentation }; //# sourceMappingURL=index.mjs.map