UNPKG

@elastic/opentelemetry-instrumentation-openai

Version:

OpenTelemetry instrumentation for the `openai` OpenAI client library

577 lines 30.3 kB
"use strict"; /* * 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