UNPKG

@callmedayz/ai-prompt-toolkit

Version:

Professional AI prompt engineering toolkit with advanced template features, real-time dashboards, conditional logic, template inheritance, live monitoring, OpenRouter integration, and 310+ model support

467 lines 19.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AdvancedPromptTemplate = void 0; const prompt_template_1 = require("./prompt-template"); /** * Advanced Prompt Template System with Conditional Logic and Branching * Extends the basic PromptTemplate with support for: * - Conditional blocks (if/else) * - Loops (for each) * - Template inheritance * - Custom functions * - Dynamic composition */ class AdvancedPromptTemplate extends prompt_template_1.PromptTemplate { constructor(options) { super(options); this.advancedOptions = { enableConditionals: true, enableLoops: true, enableInheritance: true, customFunctions: {}, ...options }; this.context = { variables: options.variables || {}, functions: this.buildDefaultFunctions(), metadata: {} }; // Merge custom functions if (options.customFunctions) { this.context.functions = { ...this.context.functions, ...options.customFunctions }; } // Set up inheritance if base template is provided if (options.baseTemplate) { this.setupInheritance(options.baseTemplate); } } /** * Render the advanced template with conditional logic and loops */ render(variables) { const allVariables = { ...this.context.variables, ...variables }; this.context.variables = allVariables; let template = this.getEffectiveTemplate(); // Process inheritance blocks first if (this.advancedOptions.enableInheritance) { template = this.processInheritance(template); } // Process loop blocks first (they may contain conditionals and functions) if (this.advancedOptions.enableLoops) { template = this.processLoops(template); } // Process function calls (for functions outside of loops) template = this.processFunctions(template); // Process conditional blocks (for conditionals outside of loops) if (this.advancedOptions.enableConditionals) { template = this.processConditionals(template); } // Use parent render for basic variable substitution const basicTemplate = new prompt_template_1.PromptTemplate({ template, variables: allVariables, escapeHtml: this.advancedOptions.escapeHtml, preserveWhitespace: this.advancedOptions.preserveWhitespace }); return basicTemplate.render(); } /** * Process conditional blocks in the template * Syntax: {{#if condition}}content{{#else}}alternative{{/if}} */ processConditionals(template) { const conditionalRegex = /\{\{#if\s+([^}]+)\}\}([\s\S]*?)(?:\{\{#else\}\}([\s\S]*?))?\{\{\/if\}\}/g; return template.replace(conditionalRegex, (match, condition, trueContent, falseContent = '') => { // Process any function calls in the condition first let processedCondition = condition.trim(); // Handle function calls within the condition expression const functionCallRegex = /(\w+)\((.*?)\)/g; processedCondition = processedCondition.replace(functionCallRegex, (funcMatch, functionName, argsString) => { const func = this.context.functions[functionName]; if (func) { const args = this.parseArguments(argsString); const result = func(...args); return String(result); } return funcMatch; // Return unchanged if function not found }); const result = this.evaluateCondition(processedCondition); return result ? trueContent : falseContent; }); } /** * Process loop blocks in the template * Syntax: {{#each items as item}}{{item.name}}{{/each}} */ processLoops(template) { const loopRegex = /\{\{#each\s+(\w+)\s+as\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g; return template.replace(loopRegex, (match, arrayName, itemName, loopTemplate) => { const array = this.context.variables[arrayName]; if (!Array.isArray(array)) { throw new Error(`Variable '${arrayName}' is not an array for loop`); } return array.map((item, index) => { const loopContext = { ...this.context.variables, [itemName]: item, [`${itemName}_index`]: index, [`${itemName}_first`]: index === 0, [`${itemName}_last`]: index === array.length - 1 }; // Handle object property access in loop template let processedLoopTemplate = loopTemplate; // Replace object property access like {item.name} and {{item.name}} with actual values if (typeof item === 'object' && item !== null) { Object.keys(item).forEach(key => { // Handle both {item.property} and {{item.property}} patterns const singleBraceRegex = new RegExp(`\\{${itemName}\\.${key}\\}`, 'g'); const doubleBraceRegex = new RegExp(`\\{\\{${itemName}\\.${key}\\}\\}`, 'g'); processedLoopTemplate = processedLoopTemplate.replace(singleBraceRegex, String(item[key])); processedLoopTemplate = processedLoopTemplate.replace(doubleBraceRegex, String(item[key])); }); } // Also handle property access within function calls like {{upper(task.priority)}} if (typeof item === 'object' && item !== null) { Object.keys(item).forEach(key => { const functionPropertyRegex = new RegExp(`${itemName}\\.${key}`, 'g'); processedLoopTemplate = processedLoopTemplate.replace(functionPropertyRegex, String(item[key])); }); } // Handle conditionals that reference the loop item properties if (typeof item === 'object' && item !== null) { Object.keys(item).forEach(key => { const conditionalPropertyRegex = new RegExp(`${itemName}\\.${key}`, 'g'); const value = item[key]; // For boolean values, don't use JSON.stringify as it adds quotes const replacement = typeof value === 'boolean' ? String(value) : JSON.stringify(value); processedLoopTemplate = processedLoopTemplate.replace(conditionalPropertyRegex, replacement); }); } // Create a new instance with the loop context to process conditionals and functions const loopProcessor = new AdvancedPromptTemplate({ template: processedLoopTemplate, variables: loopContext, escapeHtml: this.advancedOptions.escapeHtml, preserveWhitespace: this.advancedOptions.preserveWhitespace, enableConditionals: true, enableLoops: false, // Prevent infinite recursion enableInheritance: false }); return loopProcessor.render(); }).join(''); }); } /** * Process function calls in the template * Syntax: {{function_name(arg1, arg2)}} */ processFunctions(template) { const functionRegex = /\{\{(\w+)\((.*?)\)\}\}/g; return template.replace(functionRegex, (match, functionName, argsString) => { const func = this.context.functions[functionName]; if (!func) { throw new Error(`Function '${functionName}' not found`); } const args = this.parseArguments(argsString); const result = func(...args); return String(result); }); } /** * Evaluate a conditional expression */ evaluateCondition(condition) { try { // Simple condition evaluation - supports basic comparisons // Security note: In production, use a proper expression parser const context = this.context.variables; // Replace variable names with their values let expression = condition; Object.keys(context).forEach(key => { const value = context[key]; const regex = new RegExp(`\\b${key}\\b`, 'g'); if (typeof value === 'string') { expression = expression.replace(regex, `"${value}"`); } else { expression = expression.replace(regex, String(value)); } }); // Evaluate the expression (basic implementation) return this.safeEvaluate(expression); } catch (error) { console.warn(`Failed to evaluate condition: ${condition}`, error); return false; } } /** * Safe evaluation of simple expressions */ safeEvaluate(expression) { const trimmed = expression.trim(); // Handle direct boolean values first if (trimmed === 'true') return true; if (trimmed === 'false') return false; if (trimmed === '"true"') return true; if (trimmed === '"false"') return false; // Only allow safe operations for complex expressions const safeExpression = expression.replace(/[^a-zA-Z0-9\s"'<>=!&|()]/g, ''); // Basic comparison operators if (safeExpression.includes('==')) { const [left, right] = safeExpression.split('==').map(s => s.trim()); return this.parseValue(left) == this.parseValue(right); } if (safeExpression.includes('!=')) { const [left, right] = safeExpression.split('!=').map(s => s.trim()); return this.parseValue(left) != this.parseValue(right); } if (safeExpression.includes('>=')) { const [left, right] = safeExpression.split('>=').map(s => s.trim()); return Number(this.parseValue(left)) >= Number(this.parseValue(right)); } if (safeExpression.includes('<=')) { const [left, right] = safeExpression.split('<=').map(s => s.trim()); return Number(this.parseValue(left)) <= Number(this.parseValue(right)); } if (safeExpression.includes('>')) { const [left, right] = safeExpression.split('>').map(s => s.trim()); return Number(this.parseValue(left)) > Number(this.parseValue(right)); } if (safeExpression.includes('<')) { const [left, right] = safeExpression.split('<').map(s => s.trim()); return Number(this.parseValue(left)) < Number(this.parseValue(right)); } // Boolean values const value = this.parseValue(safeExpression); return Boolean(value); } /** * Parse a value from string representation */ parseValue(value) { const trimmed = value.trim(); // String literals if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) { return trimmed.slice(1, -1); } // Numbers if (!isNaN(Number(trimmed))) { return Number(trimmed); } // Booleans if (trimmed === 'true') return true; if (trimmed === 'false') return false; return trimmed; } /** * Parse function arguments */ parseArguments(argsString) { if (!argsString.trim()) return []; // Split arguments while respecting quoted strings const args = []; let current = ''; let inQuotes = false; let quoteChar = ''; for (let i = 0; i < argsString.length; i++) { const char = argsString[i]; if ((char === '"' || char === "'") && !inQuotes) { inQuotes = true; quoteChar = char; current += char; } else if (char === quoteChar && inQuotes) { inQuotes = false; quoteChar = ''; current += char; } else if (char === ',' && !inQuotes) { args.push(current.trim()); current = ''; } else { current += char; } } if (current.trim()) { args.push(current.trim()); } return args.map(arg => { // Check if it's a variable reference if (this.context.variables.hasOwnProperty(arg)) { return this.context.variables[arg]; } return this.parseValue(arg); }); } /** * Build default functions available in templates */ buildDefaultFunctions() { return { // String functions upper: (str) => String(str).toUpperCase(), lower: (str) => String(str).toLowerCase(), capitalize: (str) => String(str).charAt(0).toUpperCase() + String(str).slice(1), length: (str) => str.length, // Array functions join: (arr, separator = ', ') => Array.isArray(arr) ? arr.join(separator) : String(arr), first: (arr) => Array.isArray(arr) ? arr[0] : arr, last: (arr) => Array.isArray(arr) ? arr[arr.length - 1] : arr, // Math functions add: (a, b) => Number(a) + Number(b), subtract: (a, b) => Number(a) - Number(b), multiply: (a, b) => Number(a) * Number(b), divide: (a, b) => Number(a) / Number(b), // Utility functions default: (value, defaultValue) => value || defaultValue, format: (template, ...args) => { return template.replace(/{(\d+)}/g, (match, index) => args[index] || match); } }; } /** * Set up template inheritance */ setupInheritance(baseTemplateString) { this.baseTemplate = new AdvancedPromptTemplate({ template: baseTemplateString, variables: this.context.variables, enableConditionals: this.advancedOptions.enableConditionals, enableLoops: this.advancedOptions.enableLoops, enableInheritance: false // Prevent infinite recursion }); } /** * Process template inheritance * Syntax: {{#extends base}}...{{#block name}}content{{/block}}...{{/extends}} */ processInheritance(template) { if (!this.baseTemplate) { return template; } // Extract blocks from child template const blockRegex = /\{\{#block\s+(\w+)\}\}([\s\S]*?)\{\{\/block\}\}/g; const blocks = {}; let match; while ((match = blockRegex.exec(template)) !== null) { blocks[match[1]] = match[2]; } // Render base template with block replacements let baseContent = this.baseTemplate.render(this.context.variables); // Replace block placeholders in base template Object.keys(blocks).forEach(blockName => { const blockPlaceholder = new RegExp(`\\{\\{#block\\s+${blockName}\\}\\}[\\s\\S]*?\\{\\{\\/block\\}\\}`, 'g'); baseContent = baseContent.replace(blockPlaceholder, blocks[blockName]); }); return baseContent; } /** * Get the effective template (considering inheritance) */ getEffectiveTemplate() { return this.advancedOptions.template; } /** * Add custom function to the template context */ addFunction(name, func) { this.context.functions[name] = func; } /** * Get all variable names used in the template (overrides parent method) */ getVariables() { const template = this.advancedOptions.template; const variables = new Set(); // Basic variable patterns {var} and {{var}} const basicVarRegex = /\{\{?(\w+)\}?\}/g; let match; while ((match = basicVarRegex.exec(template)) !== null) { // Skip control structures and functions if (!['if', 'else', 'each', 'block', 'section', 'extends'].includes(match[1]) && !match[0].includes('(')) { variables.add(match[1]); } } // Variables in conditionals const conditionalRegex = /\{\{#if\s+([^}]+)\}\}/g; while ((match = conditionalRegex.exec(template)) !== null) { const condition = match[1].trim(); const varMatches = condition.match(/\b(\w+)\b/g); if (varMatches) { varMatches.forEach(varName => { if (!['true', 'false', 'null', 'undefined'].includes(varName) && isNaN(Number(varName))) { variables.add(varName); } }); } } // Variables in loops const loopRegex = /\{\{#each\s+(\w+)\s+as\s+(\w+)\}\}/g; while ((match = loopRegex.exec(template)) !== null) { variables.add(match[1]); // array variable } // Variables in function calls const functionRegex = /\{\{(\w+)\(([^)]*)\)\}\}/g; while ((match = functionRegex.exec(template)) !== null) { const args = match[2].split(',').map(arg => arg.trim()); args.forEach(arg => { if (this.context.variables.hasOwnProperty(arg)) { variables.add(arg); } }); } return Array.from(variables); } /** * Get template analysis including conditional and loop blocks */ analyze() { const variables = this.getVariables(); const template = this.advancedOptions.template; // Analyze conditionals const conditionalRegex = /\{\{#if\s+([^}]+)\}\}/g; const conditionals = []; let match; while ((match = conditionalRegex.exec(template)) !== null) { conditionals.push({ condition: match[1].trim(), result: this.evaluateCondition(match[1].trim()), evaluatedContent: '' }); } // Analyze loops const loopRegex = /\{\{#each\s+(\w+)\s+as\s+(\w+)\}\}/g; const loops = []; while ((match = loopRegex.exec(template)) !== null) { const array = this.context.variables[match[1]]; loops.push({ variable: match[1], iterations: Array.isArray(array) ? array.length : 0, content: '' }); } // Analyze functions const functionRegex = /\{\{(\w+)\(/g; const functions = []; while ((match = functionRegex.exec(template)) !== null) { if (!functions.includes(match[1])) { functions.push(match[1]); } } return { variables, conditionals, loops, functions }; } } exports.AdvancedPromptTemplate = AdvancedPromptTemplate; //# sourceMappingURL=advanced-prompt-template.js.map