@maximai/maxim-js
Version:
Maxim AI JS SDK. Visit https://getmaxim.ai for more info.
446 lines • 16.7 kB
JavaScript
;
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