openlit
Version:
OpenTelemetry-native Auto instrumentation library for monitoring LLM Applications, facilitating the integration of observability into your GenAI-driven projects
963 lines (962 loc) • 51.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const api_1 = require("@opentelemetry/api");
const config_1 = __importDefault(require("../../config"));
const helpers_1 = __importStar(require("../../helpers"));
const semantic_convention_1 = __importDefault(require("../../semantic-convention"));
const base_wrapper_1 = __importDefault(require("../base-wrapper"));
function spanCreationAttrs(operationName, requestModel) {
return {
[semantic_convention_1.default.GEN_AI_OPERATION]: operationName,
[semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL]: semantic_convention_1.default.GEN_AI_SYSTEM_OPENAI,
[semantic_convention_1.default.GEN_AI_REQUEST_MODEL]: requestModel,
[semantic_convention_1.default.SERVER_ADDRESS]: OpenAIWrapper.serverAddress,
[semantic_convention_1.default.SERVER_PORT]: OpenAIWrapper.serverPort,
};
}
class OpenAIWrapper extends base_wrapper_1.default {
static _patchChatCompletionCreate(tracer) {
const genAIEndpoint = 'openai.resources.chat.completions';
return (originalMethod) => {
return async function (...args) {
if ((0, helpers_1.isFrameworkLlmActive)())
return originalMethod.apply(this, args);
const requestModel = args[0]?.model || 'gpt-4o';
const spanName = `${semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT} ${requestModel}`;
const effectiveCtx = (0, helpers_1.getFrameworkParentContext)() ?? api_1.context.active();
const span = tracer.startSpan(spanName, {
kind: api_1.SpanKind.CLIENT,
attributes: spanCreationAttrs(semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT, requestModel),
}, effectiveCtx);
return api_1.context
.with(api_1.trace.setSpan(effectiveCtx, span), async () => {
return originalMethod.apply(this, args);
})
.then((response) => {
const { stream = false } = args[0];
if (stream) {
return helpers_1.default.createStreamProxy(response, OpenAIWrapper._chatCompletionGenerator({
args,
genAIEndpoint,
response,
span,
}));
}
return OpenAIWrapper._chatCompletion({ args, genAIEndpoint, response, span });
})
.catch((e) => {
helpers_1.default.handleException(span, e);
base_wrapper_1.default.recordMetrics(span, {
genAIEndpoint,
model: requestModel,
aiSystem: OpenAIWrapper.aiSystem,
serverAddress: OpenAIWrapper.serverAddress,
serverPort: OpenAIWrapper.serverPort,
errorType: e?.constructor?.name || '_OTHER',
});
span.end();
throw e;
});
};
};
}
static async _chatCompletion({ args, genAIEndpoint, response, span, }) {
let metricParams;
try {
metricParams = await OpenAIWrapper._chatCompletionCommonSetter({
args,
genAIEndpoint,
result: response,
span,
});
return response;
}
catch (e) {
helpers_1.default.handleException(span, e);
throw e;
}
finally {
span.end();
if (metricParams) {
base_wrapper_1.default.recordMetrics(span, metricParams);
}
}
}
static async *_chatCompletionGenerator({ args, genAIEndpoint, response, span, }) {
let metricParams;
const timestamps = [];
const startTime = Date.now();
try {
const { messages } = args[0];
let { tools } = args[0];
const result = {
id: '0',
created: -1,
model: '',
system_fingerprint: '',
service_tier: 'auto',
choices: [
{
index: 0,
logprobs: null,
finish_reason: 'stop',
message: { role: 'assistant', content: '' },
},
],
usage: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
completion_tokens_details: {
reasoning_tokens: 0,
audio_tokens: 0,
},
prompt_tokens_details: {
cached_tokens: 0,
audio_tokens: 0,
},
},
};
const toolCalls = [];
for await (const chunk of response) {
timestamps.push(Date.now());
result.id = chunk.id;
result.created = chunk.created;
result.model = chunk.model;
if (chunk.system_fingerprint) {
result.system_fingerprint = chunk.system_fingerprint;
}
if (chunk.service_tier) {
result.service_tier = chunk.service_tier;
}
if (chunk.choices[0]?.finish_reason) {
result.choices[0].finish_reason = chunk.choices[0].finish_reason;
}
if (chunk.choices[0]?.logprobs) {
result.choices[0].logprobs = chunk.choices[0].logprobs;
}
if (chunk.choices[0]?.delta.content) {
result.choices[0].message.content += chunk.choices[0].delta.content;
}
if (chunk.choices[0]?.delta.tool_calls) {
const deltaTools = chunk.choices[0].delta.tool_calls;
for (const tool of deltaTools) {
const idx = tool.index || 0;
while (toolCalls.length <= idx) {
toolCalls.push({
id: '',
type: 'function',
function: { name: '', arguments: '' }
});
}
if (tool.id) {
toolCalls[idx].id = tool.id;
toolCalls[idx].type = tool.type || 'function';
if (tool.function?.name) {
toolCalls[idx].function.name = tool.function.name;
}
if (tool.function?.arguments) {
toolCalls[idx].function.arguments = tool.function.arguments;
}
}
else if (tool.function?.arguments) {
toolCalls[idx].function.arguments += tool.function.arguments;
}
}
tools = true;
}
yield chunk;
}
if (toolCalls.length > 0) {
result.choices[0].message = {
...result.choices[0].message,
tool_calls: toolCalls
};
}
let promptTokens = 0;
for (const message of messages || []) {
promptTokens += helpers_1.default.openaiTokens(message.content, result.model) ?? 0;
}
const completionTokens = helpers_1.default.openaiTokens(result.choices[0].message.content ?? '', result.model);
if (completionTokens) {
result.usage = {
prompt_tokens: promptTokens,
completion_tokens: completionTokens,
total_tokens: promptTokens + completionTokens,
completion_tokens_details: result.usage.completion_tokens_details,
prompt_tokens_details: result.usage.prompt_tokens_details,
};
}
args[0].tools = tools;
const ttft = timestamps.length > 0 ? (timestamps[0] - startTime) / 1000 : 0;
let tbt = 0;
if (timestamps.length > 1) {
const timeDiffs = timestamps.slice(1).map((t, i) => t - timestamps[i]);
tbt = timeDiffs.reduce((a, b) => a + b, 0) / timeDiffs.length / 1000;
}
metricParams = await OpenAIWrapper._chatCompletionCommonSetter({
args,
genAIEndpoint,
result,
span,
ttft,
tbt,
});
return result;
}
catch (e) {
helpers_1.default.handleException(span, e);
throw e;
}
finally {
span.end();
if (metricParams) {
base_wrapper_1.default.recordMetrics(span, metricParams);
}
}
}
static async _chatCompletionCommonSetter({ args, genAIEndpoint, result, span, ttft = 0, tbt = 0, }) {
const captureContent = config_1.default.captureMessageContent;
const requestModel = args[0]?.model || 'gpt-4o';
const { messages, frequency_penalty = 0, max_tokens = null, n = 1, presence_penalty = 0, seed = null, stop = null, temperature = 1, top_p, user, stream = false, tools: _tools, service_tier, } = args[0];
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TOP_P, top_p || 1);
if (max_tokens != null) {
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MAX_TOKENS, max_tokens);
}
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TEMPERATURE, temperature);
if (presence_penalty) {
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty);
}
if (frequency_penalty) {
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty);
}
if (seed != null) {
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_SEED, Number(seed));
}
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IS_STREAM, stream);
if (stop) {
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_STOP_SEQUENCES, Array.isArray(stop) ? stop : [stop]);
}
if (n && n !== 1) {
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_CHOICE_COUNT, n);
}
if (service_tier && service_tier !== 'auto') {
span.setAttribute(semantic_convention_1.default.OPENAI_REQUEST_SERVICE_TIER, service_tier);
}
if (captureContent) {
span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, helpers_1.default.buildInputMessages(messages || []));
}
span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, result.id);
const responseModel = result.model || requestModel;
const pricingInfo = config_1.default.pricingInfo || {};
const cost = helpers_1.default.getChatModelCost(requestModel, pricingInfo, result.usage.prompt_tokens, result.usage.completion_tokens);
OpenAIWrapper.setBaseSpanAttributes(span, {
genAIEndpoint,
model: requestModel,
user,
cost,
aiSystem: OpenAIWrapper.aiSystem,
serverAddress: OpenAIWrapper.serverAddress,
serverPort: OpenAIWrapper.serverPort,
});
span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_MODEL, responseModel);
span.setAttribute(semantic_convention_1.default.OPENAI_API_TYPE, 'chat_completions');
if (result.system_fingerprint) {
span.setAttribute(semantic_convention_1.default.OPENAI_RESPONSE_SYSTEM_FINGERPRINT, result.system_fingerprint);
}
if (result.service_tier) {
span.setAttribute(semantic_convention_1.default.OPENAI_RESPONSE_SERVICE_TIER, result.service_tier);
}
const inputTokens = result.usage.prompt_tokens;
const outputTokens = result.usage.completion_tokens;
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, inputTokens);
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS, outputTokens);
if (result.usage.prompt_tokens_details?.cached_tokens) {
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, result.usage.prompt_tokens_details.cached_tokens);
}
if (result.usage.prompt_tokens_details?.cache_creation_tokens) {
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, result.usage.prompt_tokens_details.cache_creation_tokens);
}
if (ttft > 0) {
span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TTFT, ttft);
}
if (tbt > 0) {
span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TBT, tbt);
}
if (result.choices[0].finish_reason) {
span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON, [result.choices[0].finish_reason]);
}
const outputType = typeof result.choices[0].message.content === 'string'
? semantic_convention_1.default.GEN_AI_OUTPUT_TYPE_TEXT
: semantic_convention_1.default.GEN_AI_OUTPUT_TYPE_JSON;
span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_TYPE, outputType);
if (result.choices[0].message.tool_calls) {
const toolCalls = result.choices[0].message.tool_calls;
const toolNames = toolCalls.map((t) => t.function?.name || '').filter(Boolean);
const toolIds = toolCalls.map((t) => t.id || '').filter(Boolean);
const toolArgs = toolCalls.map((t) => t.function?.arguments || '').filter(Boolean);
if (toolNames.length > 0) {
span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_NAME, toolNames.join(', '));
}
if (toolIds.length > 0) {
span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ID, toolIds.join(', '));
}
if (toolArgs.length > 0) {
span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_ARGS, toolArgs.join(', '));
}
}
let inputMessagesJson;
let outputMessagesJson;
if (captureContent) {
const toolCalls = result.choices[0].message.tool_calls;
outputMessagesJson = helpers_1.default.buildOutputMessages(result.choices[0].message.content || '', result.choices[0].finish_reason || 'stop', toolCalls);
span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_MESSAGES, outputMessagesJson);
inputMessagesJson = helpers_1.default.buildInputMessages(messages || []);
}
if (!config_1.default.disableEvents) {
const eventAttrs = {
[semantic_convention_1.default.GEN_AI_OPERATION]: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT,
[semantic_convention_1.default.GEN_AI_REQUEST_MODEL]: requestModel,
[semantic_convention_1.default.GEN_AI_RESPONSE_MODEL]: responseModel,
[semantic_convention_1.default.SERVER_ADDRESS]: OpenAIWrapper.serverAddress,
[semantic_convention_1.default.SERVER_PORT]: OpenAIWrapper.serverPort,
[semantic_convention_1.default.GEN_AI_RESPONSE_ID]: result.id,
[semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON]: [result.choices[0].finish_reason],
[semantic_convention_1.default.GEN_AI_OUTPUT_TYPE]: outputType,
[semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS]: inputTokens,
[semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS]: outputTokens,
};
if (captureContent) {
if (inputMessagesJson)
eventAttrs[semantic_convention_1.default.GEN_AI_INPUT_MESSAGES] = inputMessagesJson;
if (outputMessagesJson)
eventAttrs[semantic_convention_1.default.GEN_AI_OUTPUT_MESSAGES] = outputMessagesJson;
}
helpers_1.default.emitInferenceEvent(span, eventAttrs);
}
return {
genAIEndpoint,
model: requestModel,
user,
cost,
aiSystem: OpenAIWrapper.aiSystem,
};
}
static _patchEmbedding(tracer) {
const genAIEndpoint = 'openai.resources.embeddings';
return (originalMethod) => {
return async function (...args) {
if ((0, helpers_1.isFrameworkLlmActive)())
return originalMethod.apply(this, args);
const requestModel = args[0]?.model || 'text-embedding-ada-002';
const spanName = `${semantic_convention_1.default.GEN_AI_OPERATION_TYPE_EMBEDDING} ${requestModel}`;
const effectiveCtx = (0, helpers_1.getFrameworkParentContext)() ?? api_1.context.active();
const span = tracer.startSpan(spanName, {
kind: api_1.SpanKind.CLIENT,
attributes: spanCreationAttrs(semantic_convention_1.default.GEN_AI_OPERATION_TYPE_EMBEDDING, requestModel),
}, effectiveCtx);
return api_1.context.with(api_1.trace.setSpan(effectiveCtx, span), async () => {
const captureContent = config_1.default.captureMessageContent;
let metricParams;
try {
const response = await originalMethod.apply(this, args);
const _responseModel = response.model || requestModel;
const pricingInfo = config_1.default.pricingInfo || {};
const cost = helpers_1.default.getEmbedModelCost(requestModel, pricingInfo, response.usage.prompt_tokens);
const { dimensions, encoding_format = 'float', input, user } = args[0];
OpenAIWrapper.setBaseSpanAttributes(span, {
genAIEndpoint,
model: requestModel,
user,
cost,
aiSystem: OpenAIWrapper.aiSystem,
serverAddress: OpenAIWrapper.serverAddress,
serverPort: OpenAIWrapper.serverPort,
});
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IS_STREAM, false);
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_ENCODING_FORMATS, [encoding_format]);
if (dimensions) {
span.setAttribute(semantic_convention_1.default.GEN_AI_EMBEDDINGS_DIMENSION_COUNT, dimensions);
}
if (captureContent) {
const formattedInput = typeof input === 'string' ? input : JSON.stringify(input);
span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, formattedInput);
}
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, response.usage.prompt_tokens);
metricParams = {
genAIEndpoint,
model: requestModel,
user,
cost,
aiSystem: OpenAIWrapper.aiSystem,
};
return response;
}
catch (e) {
helpers_1.default.handleException(span, e);
throw e;
}
finally {
span.end();
if (metricParams) {
base_wrapper_1.default.recordMetrics(span, metricParams);
}
}
});
};
};
}
static _patchFineTune(tracer) {
const genAIEndpoint = 'openai.resources.fine_tuning.jobs';
return (originalMethod) => {
return async function (...args) {
if ((0, helpers_1.isFrameworkLlmActive)())
return originalMethod.apply(this, args);
const requestModel = args[0]?.model || 'gpt-3.5-turbo';
const spanName = `${semantic_convention_1.default.GEN_AI_OPERATION_TYPE_FINETUNING} ${requestModel}`;
const effectiveCtx = (0, helpers_1.getFrameworkParentContext)() ?? api_1.context.active();
const span = tracer.startSpan(spanName, {
kind: api_1.SpanKind.CLIENT,
attributes: spanCreationAttrs(semantic_convention_1.default.GEN_AI_OPERATION_TYPE_FINETUNING, requestModel),
}, effectiveCtx);
return api_1.context.with(api_1.trace.setSpan(effectiveCtx, span), async () => {
let metricParams;
try {
const response = await originalMethod.apply(this, args);
const { hyperparameters = {}, suffix = '', training_file, user, validation_file, } = args[0];
OpenAIWrapper.setBaseSpanAttributes(span, {
genAIEndpoint,
model: requestModel,
user,
aiSystem: OpenAIWrapper.aiSystem,
serverAddress: OpenAIWrapper.serverAddress,
serverPort: OpenAIWrapper.serverPort,
});
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TRAINING_FILE, training_file);
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_VALIDATION_FILE, validation_file);
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_FINETUNE_BATCH_SIZE, hyperparameters?.batch_size || 'auto');
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_FINETUNE_MODEL_LRM, hyperparameters?.learning_rate_multiplier || 'auto');
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_FINETUNE_MODEL_EPOCHS, hyperparameters?.n_epochs || 'auto');
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_FINETUNE_MODEL_SUFFIX, suffix);
span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, response.id);
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, response.usage.prompt_tokens);
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_FINETUNE_STATUS, response.status);
metricParams = {
genAIEndpoint,
model: requestModel,
user,
aiSystem: OpenAIWrapper.aiSystem,
};
return response;
}
catch (e) {
helpers_1.default.handleException(span, e);
throw e;
}
finally {
span.end();
if (metricParams) {
base_wrapper_1.default.recordMetrics(span, metricParams);
}
}
});
};
};
}
static _patchImageGenerate(tracer) {
const genAIEndpoint = 'openai.resources.images';
return (originalMethod) => {
return async function (...args) {
if ((0, helpers_1.isFrameworkLlmActive)())
return originalMethod.apply(this, args);
const requestModel = args[0]?.model || 'dall-e-2';
const spanName = `${semantic_convention_1.default.GEN_AI_OPERATION_TYPE_IMAGE} ${requestModel}`;
const effectiveCtx = (0, helpers_1.getFrameworkParentContext)() ?? api_1.context.active();
const span = tracer.startSpan(spanName, {
kind: api_1.SpanKind.CLIENT,
attributes: spanCreationAttrs(semantic_convention_1.default.GEN_AI_OPERATION_TYPE_IMAGE, requestModel),
}, effectiveCtx);
return api_1.context.with(api_1.trace.setSpan(effectiveCtx, span), async () => {
const captureContent = config_1.default.captureMessageContent;
let metricParams;
try {
const response = await originalMethod.apply(this, args);
const { prompt, quality = 'standard', response_format = 'url', size = '1024x1024', style = 'vivid', user, } = args[0];
const responseModel = response.model || requestModel;
const pricingInfo = config_1.default.pricingInfo || {};
const cost = (response.data?.length || 1) *
helpers_1.default.getImageModelCost(responseModel, pricingInfo, size, quality);
OpenAIWrapper.setBaseSpanAttributes(span, {
genAIEndpoint,
model: requestModel,
user,
cost,
aiSystem: OpenAIWrapper.aiSystem,
serverAddress: OpenAIWrapper.serverAddress,
serverPort: OpenAIWrapper.serverPort,
});
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IMAGE_SIZE, size);
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IMAGE_QUALITY, quality);
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IMAGE_STYLE, style);
if (captureContent) {
span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, prompt);
}
if (response.data) {
const imageUrls = [];
const revisedPrompts = [];
for (const items of response.data) {
revisedPrompts.push(items.revised_prompt || '');
const value = items[response_format];
imageUrls.push(value && !String(value).startsWith('data:') ? value : '[base64_image_data]');
}
span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_IMAGE, imageUrls);
span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_REVISED_PROMPT, revisedPrompts);
}
metricParams = {
genAIEndpoint,
model: requestModel,
user,
cost,
aiSystem: OpenAIWrapper.aiSystem,
};
return response;
}
catch (e) {
helpers_1.default.handleException(span, e);
throw e;
}
finally {
span.end();
if (metricParams) {
base_wrapper_1.default.recordMetrics(span, metricParams);
}
}
});
};
};
}
static _patchImageVariation(tracer) {
const genAIEndpoint = 'openai.resources.images';
return (originalMethod) => {
return async function (...args) {
if ((0, helpers_1.isFrameworkLlmActive)())
return originalMethod.apply(this, args);
const requestModel = args[0]?.model || 'dall-e-2';
const spanName = `${semantic_convention_1.default.GEN_AI_OPERATION_TYPE_IMAGE} ${requestModel}`;
const effectiveCtx = (0, helpers_1.getFrameworkParentContext)() ?? api_1.context.active();
const span = tracer.startSpan(spanName, {
kind: api_1.SpanKind.CLIENT,
attributes: spanCreationAttrs(semantic_convention_1.default.GEN_AI_OPERATION_TYPE_IMAGE, requestModel),
}, effectiveCtx);
return api_1.context.with(api_1.trace.setSpan(effectiveCtx, span), async () => {
const captureContent = config_1.default.captureMessageContent;
let metricParams;
try {
const response = await originalMethod.apply(this, args);
const { prompt, quality = 'standard', response_format = 'url', size = '1024x1024', style = 'vivid', user, } = args[0];
span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, response.created);
const responseModel = response.model || requestModel;
const pricingInfo = config_1.default.pricingInfo || {};
const cost = (response.data?.length || 1) *
helpers_1.default.getImageModelCost(responseModel, pricingInfo, size, quality);
OpenAIWrapper.setBaseSpanAttributes(span, {
genAIEndpoint,
model: requestModel,
user,
cost,
aiSystem: OpenAIWrapper.aiSystem,
serverAddress: OpenAIWrapper.serverAddress,
serverPort: OpenAIWrapper.serverPort,
});
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IMAGE_SIZE, size);
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IMAGE_QUALITY, quality);
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IMAGE_STYLE, style);
if (captureContent) {
span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, prompt);
}
if (response.data) {
const imageUrls = [];
const revisedPrompts = [];
for (const items of response.data) {
revisedPrompts.push(items.revised_prompt || '');
const value = items[response_format];
imageUrls.push(value && !String(value).startsWith('data:') ? value : '[base64_image_data]');
}
span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_IMAGE, imageUrls);
span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_REVISED_PROMPT, revisedPrompts);
}
metricParams = {
genAIEndpoint,
model: requestModel,
user,
cost,
aiSystem: OpenAIWrapper.aiSystem,
};
return response;
}
catch (e) {
helpers_1.default.handleException(span, e);
throw e;
}
finally {
span.end();
if (metricParams) {
base_wrapper_1.default.recordMetrics(span, metricParams);
}
}
});
};
};
}
static _patchAudioCreate(tracer) {
const genAIEndpoint = 'openai.resources.audio.speech';
return (originalMethod) => {
return async function (...args) {
if ((0, helpers_1.isFrameworkLlmActive)())
return originalMethod.apply(this, args);
const requestModel = args[0]?.model || 'tts-1';
const spanName = `${semantic_convention_1.default.GEN_AI_OPERATION_TYPE_AUDIO} ${requestModel}`;
const effectiveCtx = (0, helpers_1.getFrameworkParentContext)() ?? api_1.context.active();
const span = tracer.startSpan(spanName, {
kind: api_1.SpanKind.CLIENT,
attributes: spanCreationAttrs(semantic_convention_1.default.GEN_AI_OPERATION_TYPE_AUDIO, requestModel),
}, effectiveCtx);
return api_1.context.with(api_1.trace.setSpan(effectiveCtx, span), async () => {
const captureContent = config_1.default.captureMessageContent;
let metricParams;
try {
const response = await originalMethod.apply(this, args);
const { input, user, voice, response_format = 'mp3', speed = 1 } = args[0];
const responseModel = response.model || requestModel;
const pricingInfo = config_1.default.pricingInfo || {};
const cost = helpers_1.default.getAudioModelCost(responseModel, pricingInfo, input);
OpenAIWrapper.setBaseSpanAttributes(span, {
genAIEndpoint,
model: requestModel,
user,
cost,
aiSystem: OpenAIWrapper.aiSystem,
serverAddress: OpenAIWrapper.serverAddress,
serverPort: OpenAIWrapper.serverPort,
});
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_AUDIO_VOICE, voice);
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_AUDIO_RESPONSE_FORMAT, response_format);
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_AUDIO_SPEED, speed);
if (captureContent) {
span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, input);
}
metricParams = {
genAIEndpoint,
model: requestModel,
user,
cost,
aiSystem: OpenAIWrapper.aiSystem,
};
return response;
}
catch (e) {
helpers_1.default.handleException(span, e);
throw e;
}
finally {
span.end();
if (metricParams) {
base_wrapper_1.default.recordMetrics(span, metricParams);
}
}
});
};
};
}
static _patchResponsesCreate(tracer) {
const genAIEndpoint = 'openai.resources.responses';
return (originalMethod) => {
return async function (...args) {
if ((0, helpers_1.isFrameworkLlmActive)())
return originalMethod.apply(this, args);
const requestModel = args[0]?.model || 'gpt-4o';
const spanName = `${semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT} ${requestModel}`;
const effectiveCtx = (0, helpers_1.getFrameworkParentContext)() ?? api_1.context.active();
const span = tracer.startSpan(spanName, {
kind: api_1.SpanKind.CLIENT,
attributes: spanCreationAttrs(semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT, requestModel),
}, effectiveCtx);
return api_1.context
.with(api_1.trace.setSpan(effectiveCtx, span), async () => {
return originalMethod.apply(this, args);
})
.then((response) => {
const { stream = false } = args[0];
if (stream) {
return helpers_1.default.createStreamProxy(response, OpenAIWrapper._responsesGenerator({
args,
genAIEndpoint,
response,
span,
}));
}
return OpenAIWrapper._responsesComplete({ args, genAIEndpoint, response, span });
})
.catch((e) => {
helpers_1.default.handleException(span, e);
base_wrapper_1.default.recordMetrics(span, {
genAIEndpoint,
model: requestModel,
aiSystem: OpenAIWrapper.aiSystem,
serverAddress: OpenAIWrapper.serverAddress,
serverPort: OpenAIWrapper.serverPort,
errorType: e?.constructor?.name || '_OTHER',
});
span.end();
throw e;
});
};
};
}
static async _responsesComplete({ args, genAIEndpoint, response, span, }) {
let metricParams;
try {
metricParams = await OpenAIWrapper._responsesCommonSetter({
args,
genAIEndpoint,
result: response,
span,
});
return response;
}
catch (e) {
helpers_1.default.handleException(span, e);
throw e;
}
finally {
span.end();
if (metricParams) {
base_wrapper_1.default.recordMetrics(span, metricParams);
}
}
}
static async *_responsesGenerator({ args, genAIEndpoint, response, span, }) {
let metricParams;
const timestamps = [];
const startTime = Date.now();
try {
const result = {
id: '',
model: '',
service_tier: 'default',
status: 'completed',
output: [],
usage: {
input_tokens: 0,
output_tokens: 0,
output_tokens_details: {
reasoning_tokens: 0,
},
},
};
let llmResponse = '';
const responseTools = [];
for await (const chunk of response) {
timestamps.push(Date.now());
if (chunk.type === 'response.output_text.delta') {
llmResponse += chunk.delta || '';
}
else if (chunk.type === 'response.output_item.added') {
const item = chunk.item;
if (item?.type === 'function_call') {
responseTools.push({
id: item.id,
call_id: item.call_id,
name: item.name,
type: item.type,
arguments: item.arguments || '',
status: item.status,
});
}
}
else if (chunk.type === 'response.function_call_arguments.delta') {
const itemId = chunk.item_id;
const delta = chunk.delta || '';
const tool = responseTools.find(t => t.id === itemId);
if (tool) {
tool.arguments += delta;
}
}
else if (chunk.type === 'response.completed') {
const responseData = chunk.response;
result.id = responseData.id;
result.model = responseData.model;
result.status = responseData.status;
const usage = responseData.usage || {};
result.usage.input_tokens = usage.input_tokens || 0;
result.usage.output_tokens = usage.output_tokens || 0;
result.usage.output_tokens_details.reasoning_tokens =
usage.output_tokens_details?.reasoning_tokens || 0;
}
yield chunk;
}
if (llmResponse) {
result.output.push({
type: 'message',
content: [{ type: 'text', text: llmResponse }],
});
}
if (responseTools.length > 0) {
result.output.push(...responseTools);
}
const ttft = timestamps.length > 0 ? (timestamps[0] - startTime) / 1000 : 0;
let tbt = 0;
if (timestamps.length > 1) {
const timeDiffs = timestamps.slice(1).map((t, i) => t - timestamps[i]);
tbt = timeDiffs.reduce((a, b) => a + b, 0) / timeDiffs.length / 1000;
}
metricParams = await OpenAIWrapper._responsesCommonSetter({
args,
genAIEndpoint,
result,
span,
ttft,
tbt,
});
return result;
}
catch (e) {
helpers_1.default.handleException(span, e);
throw e;
}
finally {
span.end();
if (metricParams) {
base_wrapper_1.default.recordMetrics(span, metricParams);
}
}
}
static async _responsesCommonSetter({ args, genAIEndpoint, result, span, ttft = 0, tbt = 0, }) {
const captureContent = config_1.default.captureMessageContent;
const requestModel = args[0]?.model || 'gpt-4o';
const { input, temperature = 1.0, top_p = 1.0, max_output_tokens, reasoning, stream = false, } = args[0];
const responsesMessages = typeof input === 'string'
? [{ role: 'user', content: input }]
: (Array.isArray(input) ? input : []);
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TEMPERATURE, temperature);
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TOP_P, top_p);
if (max_output_tokens != null) {
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MAX_TOKENS, max_output_tokens);
}
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IS_STREAM, stream);
if (reasoning?.effort) {
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_REASONING_EFFORT, reasoning.effort);
}
if (captureContent) {
span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, helpers_1.default.buildInputMessages(responsesMessages));
}
const responseModel = result.model || requestModel;
const pricingInfo = config_1.default.pricingInfo || {};
const inputTokens = result.usage?.input_tokens || 0;
const outputTokens = result.usage?.output_tokens || 0;
const cost = helpers_1.default.getChatModelCost(requestModel, pricingInfo, inputTokens, outputTokens);
OpenAIWrapper.setBaseSpanAttributes(span, {
genAIEndpoint,
model: requestModel,
user: '',
cost,
aiSystem: OpenAIWrapper.aiSystem,
serverAddress: OpenAIWrapper.serverAddress,
serverPort: OpenAIWrapper.serverPort,
});
span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, result.id);
span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_MODEL, responseModel);
span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON, [result.status || 'completed']);
span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_TYPE, semantic_convention_1.default.GEN_AI_OUTPUT_TYPE_TEXT);
span.setAttribute(semantic_convention_1.default.OPENAI_API_TYPE, 'responses');
if (result.service_tier) {
span.setAttribute(semantic_convention_1.default.OPENAI_RESPONSE_SERVICE_TIER, result.service_tier);
}
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, inputTokens);
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS, outputTokens);
if (result.usage?.output_tokens_details?.reasoning_tokens) {
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_REASONING_TOKENS, result.usage.output_tokens_details.reasoning_tokens);
}
if (ttft > 0) {
span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TTFT, ttft);
}
if (tbt > 0) {
span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TBT, tbt);
}
let completionText = '';
if (result.output && Array.isArray(result.output)) {
for (const item of result.output) {
if (item.type === 'message' && item.content) {
for (const content of item.content) {
if (content.type === 'text' || content.type === 'output_text') {
completionText += content.text || '';
}
}
}
}
}
const toolCalls = result.tools || [];
if (toolCalls.length > 0) {
const toolNames = toolCalls.map((t) => t.name || '').filter(Boolean);
const toolIds = toolCalls.map((t) => t.call_id || '').filter(Boolean);
const toolArgs = toolCalls.map((t) => t.arguments || '').filter(Boolean);
if (toolNames.length > 0) {
span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_NAME, toolNames.join(', '));
}
if (toolIds.length > 0) {
span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ID, toolIds.join(', '));
}
if (toolArgs.length > 0) {
span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_ARGS, toolArgs.join(', '));
}
}
let inputMessagesJson;
let outputMessagesJson;
if (captureContent) {
outputMessagesJson = helpers_1.default.buildOutputMessages(completionText, result.status || 'stop');
span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_MESSAGES, outputMessagesJson);
inputMessagesJson = helpers_1.default.buildInputMessages(responsesMessages);
}
if (!config_1.default.disableEvents) {
const eventAttrs = {
[semantic_convention_1.default.GEN_AI_OPERATION]: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT,
[semantic_convention_1.default.GEN_AI_REQUEST_MODEL]: requestModel,
[semantic_convention_1.default.GEN_AI_RESPONSE_MODEL]: responseModel,
[semantic_convention_1.default.SERVER_ADDRESS]: OpenAIWrapper.serverAddress,
[semantic_convention_1.default.SERVER_PORT]: OpenAIWrapper.serverPort,