UNPKG

@maximai/maxim-js

Version:

Maxim AI JS SDK. Visit https://getmaxim.ai for more info.

446 lines 16.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Generation = void 0; const utils_1 = require("../utils"); const base_1 = require("./base"); const types_1 = require("./types"); /** * Represents an LLM generation or completion. * * The Generation class tracks the complete lifecycle of LLM requests, * including input messages, model parameters, results, and any errors. * It supports both chat and text completion formats. * * @class Generation * @extends EvaluatableBaseContainer * @example * const generation = container.generation({ * id: 'gen-001', * name: 'User Query Response', * provider: 'openai', * model: 'gpt-4', * messages: [ * { role: 'system', content: 'You are a helpful assistant.' }, * { role: 'user', content: 'What is the capital of France?' } * ], * modelParameters: { temperature: 0.7, max_tokens: 150 } * }); * * // Record the result * generation.result({ * id: 'cmpl-123', * object: 'chat.completion', * created: Date.now(), * model: 'gpt-4', * choices: [{ * index: 0, * message: { role: 'assistant', content: 'The capital of France is Paris.' }, * finish_reason: 'stop', * logprobs: null * }], * usage: { prompt_tokens: 25, completion_tokens: 8, total_tokens: 33 } * }); */ class Generation extends base_1.EvaluatableBaseContainer { /** * Creates a new generation log entry. * * @param config - Configuration object defining the generation * @param writer - Log writer instance for persisting generation data * @example * const generation = container.generation({ * id: 'response-gen-001', * name: 'Customer Query Response', * provider: 'openai', * model: 'gpt-4', * messages: [ * { role: 'system', content: 'You are a helpful assistant.' }, * { role: 'user', content: 'How do I reset my password?' } * ], * modelParameters: { temperature: 0.7, max_tokens: 200 } * }); */ constructor(config, writer) { // Extract attachments from messages before calling super constructor const [processedMessages, attachments] = parseAttachmentsFromMessages(config.messages); // Create modified config with processed messages const processedConfig = { ...config, messages: processedMessages, }; super(types_1.Entity.GENERATION, processedConfig, writer); this.model = config.model; this.provider = config.provider; this.maximPromptId = config.maximPromptId; this.maximPromptVersionId = config.maximPromptVersionId; this.modelParameters = config.modelParameters; // Add extracted attachments for (const attachment of attachments) { this.addAttachment(attachment); } } /** * Updates the model being used for this generation. * * @param model - The new model name or identifier * @returns void * @example * generation.setModel('gpt-4-turbo'); */ setModel(model) { this.model = model; this.commit("update", { model }); } /** * Static method to update the model for any generation by ID. * * @param writer - The log writer instance * @param id - The generation ID * @param model - The new model name * @returns void */ static setModel_(writer, id, model) { base_1.EvaluatableBaseContainer.commit_(writer, types_1.Entity.GENERATION, id, "update", { model }); } /** * Updates the name being used for this generation. * * @param name - The new name * @returns void * @example * generation.setName('Customer Query Response'); */ setName(name) { this._name = name; this.commit("update", { name }); } /** * Static method to update the name for any generation by ID. * * @param writer - The log writer instance * @param id - The generation ID * @param name - The new name * @returns void */ static setName_(writer, id, name) { base_1.EvaluatableBaseContainer.commit_(writer, types_1.Entity.GENERATION, id, "update", { name }); } /** * Adds additional messages to this generation's conversation. * * @param messages - Array of messages to add * @returns void * @example * generation.addMessages([ * { role: 'user', content: 'Can you clarify that?' }, * ]); */ addMessages(messages) { // Extract attachments from messages const [processedMessages, attachments] = parseAttachmentsFromMessages(messages); // Add the processed messages this.commit("update", { messages: processedMessages }); // Add extracted attachments for (const attachment of attachments) { this.addAttachment(attachment); } } /** * Static method to add messages to any generation by ID. * * @param writer - The log writer instance * @param id - The generation ID * @param messages - Array of messages to add * @returns void */ static addMessages_(writer, id, messages) { // Extract attachments from messages const [processedMessages, attachments] = parseAttachmentsFromMessages(messages); // Add the processed messages base_1.EvaluatableBaseContainer.commit_(writer, types_1.Entity.GENERATION, id, "update", { messages: processedMessages }); // Add extracted attachments for (const attachment of attachments) { Generation.addAttachment_(writer, id, attachment); } } /** * Updates the model parameters for this generation. * * @param modelParameters - Object containing model-specific parameters * @returns void * @example * generation.setModelParameters({ * temperature: 0.9, * max_tokens: 500, * top_p: 0.95, * frequency_penalty: 0.2 * }); */ setModelParameters(modelParameters) { this.commit("update", { modelParameters }); } /** * Static method to update model parameters for any generation by ID. * * @param writer - The log writer instance * @param id - The generation ID * @param modelParameters - Model parameters to update * @returns void */ static setModelParameters_(writer, id, modelParameters) { base_1.EvaluatableBaseContainer.commit_(writer, types_1.Entity.GENERATION, id, "update", { modelParameters }); } /** * Records the successful result of this generation and ends it. * * @param result - The completion result from the LLM * @returns void * @example * generation.result({ * id: 'cmpl-123', * object: 'chat.completion', * created: Date.now(), * model: 'gpt-4', * choices: [{ * index: 0, * message: { role: 'assistant', content: 'Here is the answer...' }, * finish_reason: 'stop', * logprobs: null * }], * usage: { prompt_tokens: 50, completion_tokens: 25, total_tokens: 75 } * }); */ result(result) { this.commit("result", { result }); this.end(); } /** * Static method to record a result for any generation by ID. * * @param writer - The log writer instance * @param id - The generation ID * @param result - The completion result * @returns void */ static result_(writer, id, result) { base_1.EvaluatableBaseContainer.commit_(writer, types_1.Entity.GENERATION, id, "result", { result }); base_1.EvaluatableBaseContainer.end_(writer, types_1.Entity.GENERATION, id); } /** * Records an error that occurred during this generation. * * @param error - Error information including message, code, and type * @returns void * @example * generation.error({ * message: 'API request timed out', * code: 'TIMEOUT_ERROR', * type: 'NetworkError' * }); */ error(error) { this.commit("result", { result: { error: error, id: (0, utils_1.uniqueId)() } }); this.end(); } /** * Static method to record an error for any generation by ID. * * @param writer - The log writer instance * @param id - The generation ID * @param error - Error information * @returns void */ static error_(writer, id, error) { base_1.EvaluatableBaseContainer.commit_(writer, types_1.Entity.GENERATION, id, "result", { result: { error: error, id: (0, utils_1.uniqueId)() } }); base_1.EvaluatableBaseContainer.end_(writer, types_1.Entity.GENERATION, id); } /** * Adds a numeric metric to this generation. * * Records quantitative values such as generation quality metrics, token accounting, * and streaming/throughput characteristics under a named metric. Each call adds * or updates a single metric entry. * * Common examples include: `tokens_in`, `tokens_out`, `output_tokens`, `ttft_ms` (Time To First Token), * `tps` (tokens per second), `avg_logprob`. * * @param name - Name of the metric * @param value - Numeric value of the metric (numeric) * @returns void * @example * generation.addMetric('tokens_in', 312); * generation.addMetric('tokens_out', 87); * generation.addMetric('output_tokens', 87); * generation.addMetric('ttft_ms', 180.5); * generation.addMetric('tps', 15.8); * generation.addMetric('avg_logprob', -0.32); */ addMetric(name, value) { this.commit("update", { metrics: { [name]: value } }); } /** * Static method to add a metric to any generation by ID. * * @param writer - The log writer instance * @param id - The generation ID * @param name - Name of the metric * @param value - Numeric value of the metric (float/number) * @returns void */ static addMetric_(writer, id, name, value) { base_1.EvaluatableBaseContainer.commit_(writer, types_1.Entity.GENERATION, id, "update", { metrics: { [name]: value } }); } /** * Adds an attachment to this generation (can be of type `file`, `data`, or `url`). * * @param attachment - The attachment to add (file, data, or URL) * @returns void * @example * generation.addAttachment({ * id: 'input-document', * type: 'file', * path: './uploads/user_document.pdf', * name: 'User Document' * }); */ addAttachment(attachment) { this.commit("upload-attachment", attachment); } /** * Static method to add an attachment to any generation by ID. * * @param writer - The log writer instance * @param id - The generation ID * @param attachment - The attachment to add * @returns void */ static addAttachment_(writer, id, attachment) { base_1.EvaluatableBaseContainer.commit_(writer, types_1.Entity.GENERATION, id, "upload-attachment", attachment); } /** * Returns the complete data representation of this generation. * * @returns Generation data. * @example * const genData = generation.data(); */ data() { return { ...super.data(), provider: this.provider, model: this.model, maximPromptId: this.maximPromptId, maximPromptVersionId: this.maximPromptVersionId, modelParameters: this.modelParameters, }; } } exports.Generation = Generation; function parseAttachmentsFromMessages(messages) { const attachments = []; const modifiedMessages = []; for (const message of messages) { const content = message.content; // Determine attachedTo value based on message role const attachedTo = message.role === "assistant" ? "output" : "input"; // If content is a string, no attachments to extract if (typeof content === "string") { modifiedMessages.push(message); continue; } // Handle array content (multimodal) if (Array.isArray(content)) { const filteredContent = []; for (const item of content) { if (typeof item === "string") { // Convert string items to text content objects filteredContent.push({ type: "text", text: item }); continue; } if (item.type === "image_url") { const imageUrl = item.image_url.url; if (imageUrl) { // Check if it's a base64 encoded data URI if (imageUrl.startsWith("data:image")) { // Extract base64 data from data URI const match = imageUrl.match(/^data:image\/(\w+);base64,(.+)$/); if (match) { const ext = match[1]; const base64Data = match[2]; try { // Convert base64 to binary data const binaryData = atob(base64Data); const bytes = new Uint8Array(binaryData.length); for (let i = 0; i < binaryData.length; i++) { bytes[i] = binaryData.charCodeAt(i); } const attachment = { type: "fileData", id: (0, utils_1.uniqueId)(), name: `image.${ext}`, data: Buffer.from(bytes), mimeType: `image/${ext}`, tags: { attachedTo: attachedTo }, }; attachments.push(attachment); } catch (error) { console.error("[MaximSDK] Error while parsing base64 attachment:", error); // Keep the image content if parsing fails filteredContent.push(item); } } else { // Keep the image content if regex doesn't match filteredContent.push(item); } } else { // For regular URLs, create URL attachment const attachment = { type: "url", id: (0, utils_1.uniqueId)(), url: imageUrl, mimeType: "image/*", tags: { attachedTo: attachedTo }, }; attachments.push(attachment); } // Note: We remove the image content from the message since it's now an attachment } } else { // Keep other content types (text, etc.) filteredContent.push(item); } } // Convert the filtered content back to a string if it only contains text if (filteredContent.length === 1 && filteredContent[0].type === "text") { modifiedMessages.push({ ...message, content: filteredContent[0].text, }); } else if (filteredContent.length === 0) { // If all content was images, keep an empty string modifiedMessages.push({ ...message, content: "", }); } else { // Keep as array if multiple content items or non-text items modifiedMessages.push({ ...message, content: filteredContent, }); } } else { // Fallback for any other content type modifiedMessages.push(message); } } return [modifiedMessages, attachments]; } //# sourceMappingURL=generation.js.map