@elastic/opentelemetry-instrumentation-openai
Version:
OpenTelemetry instrumentation for the `openai` OpenAI client library
577 lines • 30.3 kB
JavaScript
;
/*
* Copyright Elasticsearch B.V. and contributors
* SPDX-License-Identifier: Apache-2.0
*/
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenAIInstrumentation = void 0;
// avoids a dependency on @opentelemetry/core for hrTime utilities
const perf_hooks_1 = require("perf_hooks");
const api_1 = require("@opentelemetry/api");
const instrumentation_1 = require("@opentelemetry/instrumentation");
const api_logs_1 = require("@opentelemetry/api-logs");
const semconv_1 = require("./semconv");
const version_1 = require("./version");
const utils_1 = require("./utils");
// Use `DEBUG=elastic-opentelemetry-instrumentation-openai ...` for debug output.
// Or use `node --env-file ./dev.env ...` in this repo.
const debug_1 = require("debug");
const debug = (0, debug_1.default)('elastic-opentelemetry-instrumentation-openai');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
debug.inspectOpts = { depth: 50, colors: true };
class OpenAIInstrumentation extends instrumentation_1.InstrumentationBase {
constructor(config = {}) {
super(version_1.PACKAGE_NAME, version_1.PACKAGE_VERSION, config);
// Possible environment variable overrides for config.
const cfg = this.getConfig();
const envCC = (0, utils_1.getEnvBool)('OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT', this._diag);
if (envCC !== undefined) {
cfg.captureMessageContent = envCC;
}
}
// Override InstrumentationAbtract.setConfig so we can normalize config.
setConfig(config = {}) {
const { captureMessageContent } = config, validConfig = __rest(config, ["captureMessageContent"]);
validConfig.captureMessageContent =
!!captureMessageContent;
super.setConfig(validConfig);
}
init() {
return [
new instrumentation_1.InstrumentationNodeModuleDefinition('openai', ['>=4.19.0 <5'], (modExports, modVer) => {
debug('instrument openai@%s (isESM=%s), config=%o', modVer, modExports[Symbol.toStringTag] === 'Module', this.getConfig());
this._wrap(modExports.OpenAI.Chat.Completions.prototype, 'create', this._getPatchedChatCompletionsCreate());
this._wrap(modExports.OpenAI.Embeddings.prototype, 'create', this._getPatchedEmbeddingsCreate());
return modExports;
}),
];
}
// This is a 'protected' method on class `InstrumentationAbstract`.
_updateMetricInstruments() {
this._genaiClientOperationDuration = this.meter.createHistogram(semconv_1.METRIC_GEN_AI_CLIENT_OPERATION_DURATION, {
description: 'GenAI operation duration',
unit: 's',
advice: {
explicitBucketBoundaries: [
0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.56, 5.12, 10.24,
20.48, 40.96, 81.92,
],
},
});
this._genaiClientTokenUsage = this.meter.createHistogram(semconv_1.METRIC_GEN_AI_CLIENT_TOKEN_USAGE, {
description: 'Measures number of input and output tokens used',
unit: '{token}',
advice: {
explicitBucketBoundaries: [
1, 4, 16, 64, 256, 1024, 4096, 16384, 65536, 262144, 1048576,
4194304, 16777216, 67108864,
],
},
});
}
_getPatchedChatCompletionsCreate() {
const self = this;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (original) => {
// https://platform.openai.com/docs/api-reference/chat/create
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return function patchedCreate(...args) {
var _a;
if (!self.isEnabled) {
return original.apply(this, args);
}
debug('OpenAI.Chat.Completions.create args: %O', args);
/** type ChatCompletionCreateParamsStreaming */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const params = args[0];
const config = self.getConfig();
const startNow = perf_hooks_1.performance.now();
let startInfo;
try {
startInfo = self._startChatCompletionsSpan(params, config, (_a = this === null || this === void 0 ? void 0 : this._client) === null || _a === void 0 ? void 0 : _a.baseURL);
}
catch (err) {
self._diag.error('unexpected error starting span:', err);
return original.apply(this, args);
}
const { span, ctx, commonAttrs } = startInfo;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const apiPromise = api_1.context.with(ctx, () => original.apply(this, args));
// Streaming.
if (params && params.stream) {
// When streaming, `apiPromise` resolves to `import('openai/streaming').Stream`,
// an async iterable (i.e. has a `Symbol.asyncIterator` method). We
// want to wrap that iteration to gather telemetry. Instead of wrapping
// `Symbol.asyncIterator`, which would be nice, we wrap the `iterator`
// method because it is used internally by `Stream#tee()`.
return apiPromise.then(stream => {
self._wrap(stream, 'iterator', origIterator => {
return () => {
return self._onChatCompletionsStreamIterator(origIterator(), span, startNow, config, commonAttrs, ctx);
};
});
return stream;
});
}
// Non-streaming.
apiPromise
.then(result => {
self._onChatCompletionsCreateResult(span, startNow, commonAttrs, result, config, ctx);
})
.catch(self._createAPIPromiseRejectionHandler(startNow, span, commonAttrs));
return apiPromise;
};
};
}
/**
* Start a span for this chat-completion API call. This also emits log events
* as appropriate for the request params.
*
* @param {import('openai').OpenAI.ChatCompletionCreateParams} params
*/
_startChatCompletionsSpan(params, // eslint-disable-line @typescript-eslint/no-explicit-any
config, baseURL) {
// Attributes common to span, metrics, log events.
const commonAttrs = {
[semconv_1.ATTR_GEN_AI_OPERATION_NAME]: 'chat',
[semconv_1.ATTR_GEN_AI_REQUEST_MODEL]: params.model,
[semconv_1.ATTR_GEN_AI_SYSTEM]: 'openai',
};
Object.assign(commonAttrs, (0, utils_1.getAttrsFromBaseURL)(baseURL, this._diag));
// Span attributes.
const attrs = Object.assign({}, commonAttrs);
if (params.frequency_penalty != null) {
attrs[semconv_1.ATTR_GEN_AI_REQUEST_FREQUENCY_PENALTY] = params.frequency_penalty;
}
if (params.max_completion_tokens != null) {
attrs[semconv_1.ATTR_GEN_AI_REQUEST_MAX_TOKENS] = params.max_completion_tokens;
}
else if (params.max_tokens != null) {
// `max_tokens` is deprecated in favour of `max_completion_tokens`.
attrs[semconv_1.ATTR_GEN_AI_REQUEST_MAX_TOKENS] = params.max_tokens;
}
if (params.presence_penalty != null) {
attrs[semconv_1.ATTR_GEN_AI_REQUEST_PRESENCE_PENALTY] = params.presence_penalty;
}
if (params.top_p != null) {
attrs[semconv_1.ATTR_GEN_AI_REQUEST_TOP_P] = params.top_p;
}
const span = this.tracer.startSpan(`${attrs[semconv_1.ATTR_GEN_AI_OPERATION_NAME]} ${attrs[semconv_1.ATTR_GEN_AI_REQUEST_MODEL]}`, {
kind: api_1.SpanKind.CLIENT,
attributes: attrs,
});
const ctx = api_1.trace.setSpan(api_1.context.active(), span);
// Capture prompts as log events.
const timestamp = Date.now();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
params.messages.forEach((msg) => {
// `msg` is Array<import('openai/resources/chat/completions').ChatCompletionMessageParam>
let body;
switch (msg.role) {
case 'system':
if (config.captureMessageContent) {
this.logger.emit({
timestamp,
context: ctx,
severityNumber: api_logs_1.SeverityNumber.INFO,
attributes: {
[semconv_1.ATTR_EVENT_NAME]: semconv_1.EVENT_GEN_AI_SYSTEM_MESSAGE,
[semconv_1.ATTR_GEN_AI_SYSTEM]: 'openai',
},
body: {
role: msg.role,
content: msg.content,
},
});
}
break;
case 'user':
if (config.captureMessageContent) {
this.logger.emit({
timestamp,
context: ctx,
severityNumber: api_logs_1.SeverityNumber.INFO,
attributes: {
[semconv_1.ATTR_EVENT_NAME]: semconv_1.EVENT_GEN_AI_USER_MESSAGE,
[semconv_1.ATTR_GEN_AI_SYSTEM]: 'openai',
},
body: {
role: msg.role,
content: msg.content,
},
});
}
break;
case 'assistant':
if (config.captureMessageContent) {
body = {
content: msg.content,
tool_calls: msg.tool_calls,
};
}
else {
body = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
tool_calls: msg.tool_calls.map((tc) => {
return {
id: tc.id,
type: tc.type,
function: { name: tc.function.name },
};
}),
};
}
this.logger.emit({
timestamp,
context: ctx,
severityNumber: api_logs_1.SeverityNumber.INFO,
attributes: {
[semconv_1.ATTR_EVENT_NAME]: semconv_1.EVENT_GEN_AI_ASSISTANT_MESSAGE,
[semconv_1.ATTR_GEN_AI_SYSTEM]: 'openai',
},
body,
});
break;
case 'tool':
if (config.captureMessageContent) {
body = {
content: msg.content,
id: msg.tool_call_id,
};
}
else {
body = {
id: msg.tool_call_id,
};
}
this.logger.emit({
timestamp,
context: ctx,
severityNumber: api_logs_1.SeverityNumber.INFO,
attributes: {
[semconv_1.ATTR_EVENT_NAME]: semconv_1.EVENT_GEN_AI_TOOL_MESSAGE,
[semconv_1.ATTR_GEN_AI_SYSTEM]: 'openai',
},
body,
});
break;
default:
debug(`unknown message role in OpenAI.Chat.Completions.create: ${msg.role}`);
}
});
return { span, ctx, commonAttrs };
}
/**
* This wraps an instance of a `openai/streaming.Stream.iterator()`, an
* async iterator. It should yield the chunks unchanged, and gather telemetry
* data from those chunks, then end the span.
*
* @param {OpenAIInstrumentationConfig} config
*/
_onChatCompletionsStreamIterator(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
streamIter, span, startNow, config, commonAttrs, ctx) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
return __asyncGenerator(this, arguments, function* _onChatCompletionsStreamIterator_1() {
var e_1, _p;
let id;
let model;
let role;
let finishReason;
const contentParts = [];
const toolCalls = [];
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
for (var _q = __asyncValues(streamIter), _r; _r = yield __await(_q.next()), !_r.done;) {
const chunk = _r.value;
yield yield __await(chunk);
// Gather telemetry from this chunk.
debug('OpenAI.Chat.Completions.create stream chunk: %O', chunk);
if (config.captureMessageContent) {
const contentPart = (_b = (_a = chunk.choices[0]) === null || _a === void 0 ? void 0 : _a.delta) === null || _b === void 0 ? void 0 : _b.content;
if (contentPart) {
contentParts.push(contentPart);
}
}
// Assume delta.tool_calls, if exists, is an array of length 1.
const toolCallPart = (_e = (_d = (_c = chunk.choices[0]) === null || _c === void 0 ? void 0 : _c.delta) === null || _d === void 0 ? void 0 : _d.tool_calls) === null || _e === void 0 ? void 0 : _e[0];
if (toolCallPart) {
if (toolCallPart.id) {
// First chunk in a tool call.
toolCalls.push({
id: toolCallPart.id,
type: toolCallPart.type,
function: {
name: (_f = toolCallPart.function) === null || _f === void 0 ? void 0 : _f.name,
arguments: (_h = (_g = toolCallPart.function) === null || _g === void 0 ? void 0 : _g.arguments) !== null && _h !== void 0 ? _h : '',
},
});
}
else if (toolCalls.length > 0) {
// A tool call chunk with more of the `function.arguments`.
toolCalls[toolCalls.length - 1].function.arguments +=
(_k = (_j = toolCallPart.function) === null || _j === void 0 ? void 0 : _j.arguments) !== null && _k !== void 0 ? _k : '';
}
}
if (!id && chunk.id) {
id = chunk.id;
span.setAttribute(semconv_1.ATTR_GEN_AI_RESPONSE_ID, id);
}
if (!model && chunk.model) {
model = chunk.model;
span.setAttribute(semconv_1.ATTR_GEN_AI_RESPONSE_MODEL, model);
}
if (!role) {
role = (_m = (_l = chunk.choices[0]) === null || _l === void 0 ? void 0 : _l.delta) === null || _m === void 0 ? void 0 : _m.role;
}
if (!finishReason) {
finishReason = (_o = chunk.choices[0]) === null || _o === void 0 ? void 0 : _o.finish_reason;
if (finishReason) {
span.setAttribute(semconv_1.ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [
finishReason,
]);
}
}
if (chunk.usage) {
// A final usage chunk if `stream_options.include_usage: true`.
span.setAttribute(semconv_1.ATTR_GEN_AI_USAGE_INPUT_TOKENS, chunk.usage.prompt_tokens);
span.setAttribute(semconv_1.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, chunk.usage.completion_tokens);
this._genaiClientTokenUsage.record(chunk.usage.prompt_tokens, Object.assign(Object.assign({}, commonAttrs), { [semconv_1.ATTR_GEN_AI_RESPONSE_MODEL]: model, [semconv_1.ATTR_GEN_AI_TOKEN_TYPE]: 'input' }));
this._genaiClientTokenUsage.record(chunk.usage.completion_tokens, Object.assign(Object.assign({}, commonAttrs), { [semconv_1.ATTR_GEN_AI_RESPONSE_MODEL]: model, [semconv_1.ATTR_GEN_AI_TOKEN_TYPE]: 'output' }));
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_r && !_r.done && (_p = _q.return)) yield __await(_p.call(_q));
}
finally { if (e_1) throw e_1.error; }
}
// Capture choices as log events.
const message = { role };
if (config.captureMessageContent && contentParts.length > 0) {
message.content = contentParts.join('');
}
if (toolCalls.length > 0) {
message.tool_calls = toolCalls;
if (!config.captureMessageContent) {
toolCalls.forEach(tc => {
var _a;
(_a = tc.function) === null || _a === void 0 ? true : delete _a.arguments;
});
}
}
this.logger.emit({
timestamp: Date.now(),
context: ctx,
severityNumber: api_logs_1.SeverityNumber.INFO,
attributes: {
[semconv_1.ATTR_EVENT_NAME]: semconv_1.EVENT_GEN_AI_CHOICE,
[semconv_1.ATTR_GEN_AI_SYSTEM]: 'openai',
},
body: {
finish_reason: finishReason,
index: 0,
message,
},
});
this._genaiClientOperationDuration.record((perf_hooks_1.performance.now() - startNow) / 1000, Object.assign(Object.assign({}, commonAttrs), { [semconv_1.ATTR_GEN_AI_RESPONSE_MODEL]: model }));
span.end();
});
}
/**
* @param {import('openai').OpenAI.ChatCompletion} result
* @param {OpenAIInstrumentationConfig} config
*/
_onChatCompletionsCreateResult(span, startNow, commonAttrs,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result, config, ctx) {
debug('OpenAI.Chat.Completions.create result: %O', result);
try {
span.setAttribute(semconv_1.ATTR_GEN_AI_RESPONSE_FINISH_REASONS,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result.choices.map((c) => c.finish_reason));
span.setAttribute(semconv_1.ATTR_GEN_AI_RESPONSE_ID, result.id);
span.setAttribute(semconv_1.ATTR_GEN_AI_RESPONSE_MODEL, result.model);
span.setAttribute(semconv_1.ATTR_GEN_AI_USAGE_INPUT_TOKENS, result.usage.prompt_tokens);
span.setAttribute(semconv_1.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, result.usage.completion_tokens);
// Capture choices as log events.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result.choices.forEach((choice) => {
let message;
if (config.captureMessageContent) {
// TODO: telemetry diff with streaming case: content=null, no 'role: assistant', 'tool calls (enableCaptureContent=true)' test case
message = { content: choice.message.content };
if (choice.message.tool_calls) {
message.tool_calls = choice.message.tool_calls;
}
}
else {
message = {};
if (choice.tool_calls) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
message.tool_calls = choice.message.tool_calls.map((tc) => {
return {
id: tc.id,
type: tc.type,
function: { name: tc.function.name },
};
});
}
}
this.logger.emit({
timestamp: Date.now(),
context: ctx,
severityNumber: api_logs_1.SeverityNumber.INFO,
attributes: {
[semconv_1.ATTR_EVENT_NAME]: semconv_1.EVENT_GEN_AI_CHOICE,
[semconv_1.ATTR_GEN_AI_SYSTEM]: 'openai',
},
body: {
finish_reason: choice.finish_reason,
index: choice.index,
message,
},
});
});
this._genaiClientOperationDuration.record((perf_hooks_1.performance.now() - startNow) / 1000, Object.assign(Object.assign({}, commonAttrs), { [semconv_1.ATTR_GEN_AI_RESPONSE_MODEL]: result.model }));
this._genaiClientTokenUsage.record(result.usage.prompt_tokens, Object.assign(Object.assign({}, commonAttrs), { [semconv_1.ATTR_GEN_AI_RESPONSE_MODEL]: result.model, [semconv_1.ATTR_GEN_AI_TOKEN_TYPE]: 'input' }));
this._genaiClientTokenUsage.record(result.usage.completion_tokens, Object.assign(Object.assign({}, commonAttrs), { [semconv_1.ATTR_GEN_AI_RESPONSE_MODEL]: result.model, [semconv_1.ATTR_GEN_AI_TOKEN_TYPE]: 'output' }));
}
catch (err) {
this._diag.error('unexpected error getting telemetry from chat result:', err);
}
span.end();
}
_createAPIPromiseRejectionHandler(startNow, span, commonAttrs) {
return (err) => {
var _a;
debug('OpenAI APIPromise rejection: %O', err);
// https://github.com/openai/openai-node/blob/master/src/error.ts
// The most reliable low cardinality string for errors seems to be
// the class name. See also:
// https://platform.openai.com/docs/guides/error-codes
const errorType = (_a = err === null || err === void 0 ? void 0 : err.constructor) === null || _a === void 0 ? void 0 : _a.name;
this._genaiClientOperationDuration.record((perf_hooks_1.performance.now() - startNow) / 1000, Object.assign(Object.assign({}, commonAttrs), { 'error.type': errorType }));
span.setStatus({
code: api_1.SpanStatusCode.ERROR,
message: err.message,
});
span.setAttribute('error.type', errorType);
span.end();
};
}
_getPatchedEmbeddingsCreate() {
const self = this;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (original) => {
// https://platform.openai.com/docs/api-reference/embeddings/create
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return function patchedCreate(...args) {
var _a;
if (!self.isEnabled) {
return original.apply(this, args);
}
debug('OpenAI.Chat.Embeddings.create args: %O', args);
const params = args[0];
const startNow = perf_hooks_1.performance.now();
let startInfo;
try {
startInfo = self._startEmbeddingsSpan(params, (_a = this === null || this === void 0 ? void 0 : this._client) === null || _a === void 0 ? void 0 : _a.baseURL);
}
catch (err) {
self._diag.error('unexpected error starting span:', err);
return original.apply(this, args);
}
const { span, ctx, commonAttrs } = startInfo;
/** @type {import('openai/core').APIPromise} */
const apiPromise = api_1.context.with(ctx, () => original.apply(this, args));
apiPromise
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.then((result) => {
self._onEmbeddingsCreateResult(span, startNow, commonAttrs, result);
})
.catch(self._createAPIPromiseRejectionHandler(startNow, span, commonAttrs));
return apiPromise;
};
};
}
/**
* Start a span for this chat-completion API call. This also emits log events
* as appropriate for the request params.
*
* @param {OpenAIInstrumentationConfig} config
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_startEmbeddingsSpan(params, baseURL) {
// Attributes common to span, metrics, log events.
const commonAttrs = {
[semconv_1.ATTR_GEN_AI_OPERATION_NAME]: 'embeddings',
[semconv_1.ATTR_GEN_AI_REQUEST_MODEL]: params.model,
[semconv_1.ATTR_GEN_AI_SYSTEM]: 'openai',
};
Object.assign(commonAttrs, (0, utils_1.getAttrsFromBaseURL)(baseURL, this._diag));
// Span attributes.
const attrs = Object.assign({}, commonAttrs);
if (params.encoding_format != null) {
attrs[semconv_1.ATTR_GEN_AI_REQUEST_ENCODING_FORMATS] = [params.encoding_format];
}
const span = this.tracer.startSpan(`${attrs[semconv_1.ATTR_GEN_AI_OPERATION_NAME]} ${attrs[semconv_1.ATTR_GEN_AI_REQUEST_MODEL]}`, {
kind: api_1.SpanKind.CLIENT,
attributes: attrs,
});
const ctx = api_1.trace.setSpan(api_1.context.active(), span);
return { span, ctx, commonAttrs };
}
/**
* @param {import('openai').OpenAI.CreateEmbeddingResponse} result
*/
_onEmbeddingsCreateResult(span, startNow, commonAttrs,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result) {
debug('OpenAI.Embeddings.create result: %O', result);
try {
span.setAttribute(semconv_1.ATTR_GEN_AI_RESPONSE_MODEL, result.model);
this._genaiClientOperationDuration.record((perf_hooks_1.performance.now() - startNow) / 1000, Object.assign(Object.assign({}, commonAttrs), { [semconv_1.ATTR_GEN_AI_RESPONSE_MODEL]: result.model }));
span.setAttribute(semconv_1.ATTR_GEN_AI_USAGE_INPUT_TOKENS, result.usage.prompt_tokens);
this._genaiClientTokenUsage.record(result.usage.prompt_tokens, Object.assign(Object.assign({}, commonAttrs), { [semconv_1.ATTR_GEN_AI_RESPONSE_MODEL]: result.model, [semconv_1.ATTR_GEN_AI_TOKEN_TYPE]: 'input' }));
}
catch (err) {
this._diag.error('unexpected error getting telemetry from embeddings result:', err);
}
span.end();
}
}
exports.OpenAIInstrumentation = OpenAIInstrumentation;
//# sourceMappingURL=instrumentation.js.map