UNPKG

openlit

Version:

OpenTelemetry-native Auto instrumentation library for monitoring LLM Applications, facilitating the integration of observability into your GenAI-driven projects

436 lines 23.5 kB
"use strict"; 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 = __importDefault(require("../../helpers")); const semantic_convention_1 = __importDefault(require("../../semantic-convention")); const base_wrapper_1 = __importDefault(require("../base-wrapper")); class OpenAIWrapper extends base_wrapper_1.default { static _patchChatCompletionCreate(tracer) { const genAIEndpoint = 'openai.resources.chat.completions'; return (originalMethod) => { return async function (...args) { const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT }); return api_1.context .with(api_1.trace.setSpan(api_1.context.active(), 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); span.end(); }); }; }; } static async _chatCompletion({ args, genAIEndpoint, response, span, }) { try { await OpenAIWrapper._chatCompletionCommonSetter({ args, genAIEndpoint, result: response, span, }); return response; } catch (e) { helpers_1.default.handleException(span, e); } finally { span.end(); } } static async *_chatCompletionGenerator({ args, genAIEndpoint, response, span, }) { try { const { messages } = args[0]; let { tools } = args[0]; const result = { id: '0', created: -1, model: '', choices: [ { index: 0, logprobs: null, finish_reason: 'stop', message: { role: 'assistant', content: '' }, }, ], usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, }, }; for await (const chunk of response) { result.id = chunk.id; result.created = chunk.created; result.model = chunk.model; 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) { tools = true; } yield chunk; } 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, }; } args[0].tools = tools; await OpenAIWrapper._chatCompletionCommonSetter({ args, genAIEndpoint, result, span, }); return result; } catch (e) { helpers_1.default.handleException(span, e); } finally { span.end(); } } static async _chatCompletionCommonSetter({ args, genAIEndpoint, result, span, }) { const traceContent = config_1.default.traceContent; const { messages, frequency_penalty = 0, max_tokens = null, n = 1, presence_penalty = 0, seed = null, temperature = 1, top_p, user, stream = false, tools, } = args[0]; // Request Params attributes : Start span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TOP_P, top_p || 1); span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MAX_TOKENS, max_tokens); span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TEMPERATURE, temperature); span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty); span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty); span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_SEED, seed); span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IS_STREAM, stream); if (traceContent) { // Format 'messages' into a single string const messagePrompt = messages || []; const formattedMessages = []; for (const message of messagePrompt) { const role = message.role; const content = message.content; if (Array.isArray(content)) { const contentStr = content .map((item) => { if ('type' in item) { return `${item.type}: ${item.text ? item.text : item.image_url}`; } else { return `text: ${item.text}`; } }) .join(', '); formattedMessages.push(`${role}: ${contentStr}`); } else { formattedMessages.push(`${role}: ${content}`); } } const prompt = formattedMessages.join('\n'); span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, prompt); } // Request Params attributes : End span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT); span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, result.id); const model = result.model || 'gpt-3.5-turbo'; const pricingInfo = await config_1.default.updatePricingJson(config_1.default.pricing_json); // Calculate cost of the operation const cost = helpers_1.default.getChatModelCost(model, pricingInfo, result.usage.prompt_tokens, result.usage.completion_tokens); OpenAIWrapper.setBaseSpanAttributes(span, { genAIEndpoint, model, user, cost, aiSystem: OpenAIWrapper.aiSystem, }); span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, result.usage.prompt_tokens); span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS, result.usage.completion_tokens); span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_TOTAL_TOKENS, result.usage.total_tokens); if (result.choices[0].finish_reason) { span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON, result.choices[0].finish_reason); } if (tools) { span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION, 'Function called with tools'); } else { if (traceContent) { if (n === 1) { span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION, result.choices[0].message.content); } else { let i = 0; while (i < n) { const attribute_name = `${semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION}.[i]`; span.setAttribute(attribute_name, result.choices[i].message.content); i += 1; } } } } } static _patchEmbedding(tracer) { const genAIEndpoint = 'openai.resources.embeddings'; const traceContent = config_1.default.traceContent; return (originalMethod) => { return async function (...args) { const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT }); return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => { try { const response = await originalMethod.apply(this, args); const model = response.model || 'text-embedding-ada-002'; const pricingInfo = await config_1.default.updatePricingJson(config_1.default.pricing_json); const cost = helpers_1.default.getEmbedModelCost(model, pricingInfo, response.usage.prompt_tokens); span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_EMBEDDING); const { dimensions, encoding_format = 'float', input, user } = args[0]; // Set base span attribues OpenAIWrapper.setBaseSpanAttributes(span, { genAIEndpoint, model, user, cost, aiSystem: OpenAIWrapper.aiSystem, }); // Request Params attributes : Start span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_ENCODING_FORMATS, encoding_format); span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_EMBEDDING_DIMENSION, dimensions); if (traceContent) { span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, input); } // Request Params attributes : End span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, response.usage.prompt_tokens); span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_TOTAL_TOKENS, response.usage.total_tokens); return response; } catch (e) { helpers_1.default.handleException(span, e); } finally { span.end(); } }); }; }; } static _patchFineTune(tracer) { const genAIEndpoint = 'openai.resources.fine_tuning.jobs'; return (originalMethod) => { return async function (...args) { const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT }); return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => { try { const response = await originalMethod.apply(this, args); const model = response.model || 'gpt-3.5-turbo'; const { hyperparameters = {}, suffix = '', training_file, user, validation_file, } = args[0]; // Set base span attribues OpenAIWrapper.setBaseSpanAttributes(span, { genAIEndpoint, model, user, aiSystem: OpenAIWrapper.aiSystem, }); span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_FINETUNING); // Request Params attributes : Start 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); // Request Params attributes : End 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); return response; } catch (e) { helpers_1.default.handleException(span, e); } finally { span.end(); } }); }; }; } static _patchImageGenerate(tracer) { const genAIEndpoint = 'openai.resources.images'; const traceContent = config_1.default.traceContent; return (originalMethod) => { return async function (...args) { const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT }); return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => { 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_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_IMAGE); const model = response.model || 'dall-e-2'; const pricingInfo = await config_1.default.updatePricingJson(config_1.default.pricing_json); // Calculate cost of the operation const cost = (response.data?.length || 1) * helpers_1.default.getImageModelCost(model, pricingInfo, size, quality); OpenAIWrapper.setBaseSpanAttributes(span, { genAIEndpoint, model, user, cost, aiSystem: OpenAIWrapper.aiSystem, }); // Request Params attributes : Start 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 (traceContent) { span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, prompt); } // Request Params attributes : End let imagesCount = 0; if (response.data) { for (const items of response.data) { span.setAttribute(`${semantic_convention_1.default.GEN_AI_CONTENT_REVISED_PROMPT}.${imagesCount}`, items.revised_prompt || ''); const attributeName = `${semantic_convention_1.default.GEN_AI_RESPONSE_IMAGE}.${imagesCount}`; span.setAttribute(attributeName, items[response_format]); imagesCount++; } } return response; } catch (e) { helpers_1.default.handleException(span, e); } finally { span.end(); } }); }; }; } static _patchImageVariation(tracer) { const genAIEndpoint = 'openai.resources.images'; const traceContent = config_1.default.traceContent; return (originalMethod) => { return async function (...args) { const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT }); return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => { 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_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_IMAGE); span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, response.created); const model = response.model || 'dall-e-2'; const pricingInfo = await config_1.default.updatePricingJson(config_1.default.pricing_json); // Calculate cost of the operation const cost = (response.data?.length || 1) * helpers_1.default.getImageModelCost(model, pricingInfo, size, quality); OpenAIWrapper.setBaseSpanAttributes(span, { genAIEndpoint, model, user, cost, aiSystem: OpenAIWrapper.aiSystem, }); // Request Params attributes : Start 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 (traceContent) { span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, prompt); } // Request Params attributes : End let imagesCount = 0; if (response.data) { for (const items of response.data) { span.setAttribute(`${semantic_convention_1.default.GEN_AI_CONTENT_REVISED_PROMPT}.${imagesCount}`, items.revised_prompt || ''); const attributeName = `${semantic_convention_1.default.GEN_AI_RESPONSE_IMAGE}.${imagesCount}`; span.setAttribute(attributeName, items[response_format]); imagesCount++; } } return response; } catch (e) { helpers_1.default.handleException(span, e); } finally { span.end(); } }); }; }; } static _patchAudioCreate(tracer) { const genAIEndpoint = 'openai.resources.audio.speech'; const traceContent = config_1.default.traceContent; return (originalMethod) => { return async function (...args) { const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT }); return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => { try { const response = await originalMethod.apply(this, args); const { input, user, voice, response_format = 'mp3', speed = 1 } = args[0]; span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_AUDIO); const model = response.model || 'tts-1'; const pricingInfo = await config_1.default.updatePricingJson(config_1.default.pricing_json); // Calculate cost of the operation const cost = helpers_1.default.getAudioModelCost(model, pricingInfo, input); OpenAIWrapper.setBaseSpanAttributes(span, { genAIEndpoint, model, user, cost, aiSystem: OpenAIWrapper.aiSystem, }); // Request Params attributes : Start 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 (traceContent) { span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, input); } // Request Params attributes : End return response; } catch (e) { helpers_1.default.handleException(span, e); } finally { span.end(); } }); }; }; } } OpenAIWrapper.aiSystem = semantic_convention_1.default.GEN_AI_SYSTEM_OPENAI; exports.default = OpenAIWrapper; //# sourceMappingURL=wrapper.js.map