/**
* Unified Prompt Management System
*
* This class serves as the central coordinator for all prompt operations in Semem.
* It provides a unified interface while maintaining backward compatibility with
* existing systems (PromptTemplates, PromptFormatter, MCP workflows).
*/
import logger from 'loglevel';
import {
PromptContext,
PromptTemplate,
PromptOptions,
PromptResult,
PromptValidation
} from './interfaces.js';
import TemplateLoader from './TemplateLoader.js';
export default class PromptManager {
constructor(options = {}) {
this.options = {
enableLegacySupport: options.enableLegacySupport !== false,
cacheTemplates: options.cacheTemplates !== false,
validateTemplates: options.validateTemplates !== false,
logLevel: options.logLevel || 'info',
templatesPath: options.templatesPath,
...options
};
// Template storage
this.templates = new Map();
this.templateCache = new Map();
// Template loader for external files
this.templateLoader = new TemplateLoader(this.options.templatesPath);
// Update options to reflect the actual path being used
this.options.templatesPath = this.templateLoader.templatesPath;
// Format handlers
this.formatters = new Map();
this.initializeFormatters();
// Model-specific handlers
this.modelHandlers = new Map();
// Legacy compatibility
this.legacyAdapters = new Map();
this.initializeLegacyAdapters();
// Statistics
this.stats = {
templatesLoaded: 0,
promptsGenerated: 0,
cacheHits: 0,
errors: 0
};
logger.info('PromptManager initialized with options:', this.options);
}
/**
* Initialize format handlers
*/
initializeFormatters() {
this.formatters.set('structured', this.formatStructured.bind(this));
this.formatters.set('conversational', this.formatConversational.bind(this));
this.formatters.set('json', this.formatJSON.bind(this));
this.formatters.set('markdown', this.formatMarkdown.bind(this));
this.formatters.set('chat', this.formatChat.bind(this));
this.formatters.set('completion', this.formatCompletion.bind(this));
}
/**
* Initialize legacy adapters for backward compatibility
*/
initializeLegacyAdapters() {
// PromptTemplates adapter
this.legacyAdapters.set('PromptTemplates', {
formatChatPrompt: this.legacyFormatChatPrompt.bind(this),
formatCompletionPrompt: this.legacyFormatCompletionPrompt.bind(this),
formatConceptPrompt: this.legacyFormatConceptPrompt.bind(this),
getTemplateForModel: this.legacyGetTemplateForModel.bind(this)
});
// PromptFormatter adapter
this.legacyAdapters.set('PromptFormatter', {
format: this.legacyFormat.bind(this),
formatAsStructured: this.formatStructured.bind(this),
formatAsConversational: this.formatConversational.bind(this),
formatAsJSON: this.formatJSON.bind(this)
});
// MCP adapter
this.legacyAdapters.set('MCP', {
executePromptWorkflow: this.legacyExecuteWorkflow.bind(this),
validatePromptArguments: this.legacyValidateArguments.bind(this)
});
}
/**
* Register a template
*/
registerTemplate(template, options = {}) {
try {
// Convert from various formats to PromptTemplate
const promptTemplate = this.normalizeTemplate(template);
// Validate if requested
if (this.options.validateTemplates) {
PromptValidation.validateTemplate(promptTemplate);
}
// Store template
this.templates.set(promptTemplate.name, promptTemplate);
this.stats.templatesLoaded++;
logger.debug(`Template registered: ${promptTemplate.name}`);
return promptTemplate;
} catch (error) {
logger.error(`Failed to register template: ${error.message}`);
this.stats.errors++;
throw error;
}
}
/**
* Get a template by name
*/
getTemplate(name) {
return this.templates.get(name);
}
/**
* List all templates
*/
listTemplates() {
return Array.from(this.templates.values());
}
/**
* Generate a prompt using a template
*/
async generatePrompt(templateName, context, options = {}) {
const startTime = Date.now();
try {
// Get template
const template = this.getTemplate(templateName);
if (!template) {
throw new Error(`Template not found: ${templateName}`);
}
// Normalize context
const promptContext = this.normalizeContext(context);
// Normalize options
const promptOptions = this.normalizeOptions(options);
// Generate prompt
const result = await this.executeTemplate(template, promptContext, promptOptions);
// Create result object
const promptResult = new PromptResult({
id: promptContext.id,
content: result.content,
format: result.format,
metadata: result.metadata,
executionTime: Date.now() - startTime,
model: promptContext.model,
temperature: promptContext.temperature,
originalContext: promptContext,
template: template
});
this.stats.promptsGenerated++;
return promptResult;
} catch (error) {
logger.error(`Failed to generate prompt: ${error.message}`);
this.stats.errors++;
return new PromptResult({
success: false,
error: error.message,
executionTime: Date.now() - startTime
});
}
}
/**
* Execute a template with context and options
*/
async executeTemplate(template, context, options) {
// Check cache first
const cacheKey = this.getCacheKey(template.name, context, options);
if (this.options.cacheTemplates && this.templateCache.has(cacheKey)) {
this.stats.cacheHits++;
return this.templateCache.get(cacheKey);
}
// Render template
const rendered = template.render(context);
// Apply formatting
const formatted = await this.applyFormatting(rendered, context, options);
// Cache result
if (this.options.cacheTemplates) {
this.templateCache.set(cacheKey, formatted);
}
return formatted;
}
/**
* Apply formatting to rendered content
*/
async applyFormatting(rendered, context, options) {
const formatter = this.formatters.get(options.format);
if (!formatter) {
throw new Error(`Unknown format: ${options.format}`);
}
return await formatter(rendered, context, options);
}
/**
* Format handlers
*/
async formatStructured(rendered, context, options) {
let content = '';
// Header
if (rendered.metadata?.title) {
content += `# ${rendered.metadata.title}\n\n`;
}
// System prompt
if (rendered.systemPrompt) {
content += `## System Instructions\n${rendered.systemPrompt}\n\n`;
}
// Context
if (context.context) {
content += `## Context\n${context.context}\n\n`;
}
// Main content
content += `## Query\n${rendered.content}\n\n`;
// Metadata
if (options.includeMetadata && rendered.metadata) {
content += `## Metadata\n\`\`\`json\n${JSON.stringify(rendered.metadata, null, 2)}\n\`\`\`\n`;
}
return {
content,
format: 'structured',
metadata: rendered.metadata
};
}
async formatConversational(rendered, context, options) {
let content = '';
if (context.context) {
content += `Based on the following context: ${context.context}\n\n`;
}
content += rendered.content;
return {
content,
format: 'conversational',
metadata: rendered.metadata
};
}
async formatJSON(rendered, context, options) {
const jsonContent = {
query: rendered.content,
context: context.context || null,
systemPrompt: rendered.systemPrompt || null,
metadata: options.includeMetadata ? rendered.metadata : null
};
return {
content: JSON.stringify(jsonContent, null, 2),
format: 'json',
metadata: rendered.metadata
};
}
async formatMarkdown(rendered, context, options) {
let content = '# Prompt\n\n';
if (rendered.systemPrompt) {
content += `## System\n${rendered.systemPrompt}\n\n`;
}
if (context.context) {
content += `## Context\n${context.context}\n\n`;
}
content += `## Query\n${rendered.content}\n\n`;
return {
content,
format: 'markdown',
metadata: rendered.metadata
};
}
async formatChat(rendered, context, options) {
const messages = [];
if (rendered.systemPrompt) {
messages.push({
role: 'system',
content: rendered.systemPrompt
});
}
let userContent = rendered.content;
if (context.context) {
userContent = `Context: ${context.context}\n\nQuery: ${userContent}`;
}
messages.push({
role: 'user',
content: userContent
});
return {
content: messages,
format: 'chat',
metadata: rendered.metadata
};
}
async formatCompletion(rendered, context, options) {
let content = '';
if (context.context) {
content += `Context: ${context.context}\n\n`;
}
content += `Query: ${rendered.content}`;
return {
content,
format: 'completion',
metadata: rendered.metadata
};
}
/**
* Normalize various input formats to standard interfaces
*/
normalizeTemplate(template) {
if (template instanceof PromptTemplate) {
return template;
}
// Convert from various formats
if (typeof template === 'string') {
return new PromptTemplate({
name: 'inline',
content: template
});
}
if (typeof template === 'object') {
return new PromptTemplate(template);
}
throw new Error(`Cannot normalize template: ${typeof template}`);
}
normalizeContext(context) {
if (context instanceof PromptContext) {
return context;
}
if (typeof context === 'object') {
return new PromptContext(context);
}
if (typeof context === 'string') {
return new PromptContext({ query: context });
}
throw new Error(`Cannot normalize context: ${typeof context}`);
}
normalizeOptions(options) {
if (options instanceof PromptOptions) {
return options;
}
return new PromptOptions(options);
}
/**
* Legacy compatibility methods
*/
legacyFormatChatPrompt(modelName, system, context, query) {
const template = new PromptTemplate({
name: 'legacy-chat',
content: query,
systemPrompt: system,
format: 'chat'
});
const promptContext = new PromptContext({
query,
systemPrompt: system,
context,
model: modelName
});
const options = new PromptOptions({ format: 'chat' });
return this.executeTemplate(template, promptContext, options)
.then(result => result.content);
}
legacyFormatCompletionPrompt(modelName, context, query) {
const template = new PromptTemplate({
name: 'legacy-completion',
content: query,
format: 'completion'
});
const promptContext = new PromptContext({
query,
context,
model: modelName
});
const options = new PromptOptions({ format: 'completion' });
return this.executeTemplate(template, promptContext, options)
.then(result => result.content);
}
legacyFormatConceptPrompt(modelName, text) {
const template = new PromptTemplate({
name: 'legacy-concept',
content: `Extract key concepts from the following text and return them as a JSON array: "${text}"`,
format: 'completion'
});
const promptContext = new PromptContext({
query: text,
model: modelName
});
const options = new PromptOptions({ format: 'completion' });
return this.executeTemplate(template, promptContext, options)
.then(result => result.content);
}
legacyGetTemplateForModel(modelName) {
// Return a legacy-compatible template object
return {
chat: (system, context, query) => this.legacyFormatChatPrompt(modelName, system, context, query),
completion: (context, query) => this.legacyFormatCompletionPrompt(modelName, context, query),
extractConcepts: (text) => this.legacyFormatConceptPrompt(modelName, text)
};
}
async legacyFormat(projectedContent, navigationContext, options = {}) {
const template = new PromptTemplate({
name: 'legacy-format',
content: JSON.stringify(projectedContent),
format: options.format || 'structured'
});
const promptContext = new PromptContext({
query: JSON.stringify(projectedContent),
context: JSON.stringify(navigationContext),
arguments: options
});
const promptOptions = new PromptOptions(options);
const result = await this.executeTemplate(template, promptContext, promptOptions);
return {
content: result.content,
format: result.format,
metadata: result.metadata
};
}
async legacyExecuteWorkflow(prompt, args, toolExecutor) {
// Convert MCP workflow to unified format
const template = new PromptTemplate({
name: prompt.name,
description: prompt.description,
workflow: prompt.workflow,
arguments: prompt.arguments,
isWorkflow: true
});
const context = new PromptContext({
workflow: prompt.name,
arguments: args
});
const options = new PromptOptions({
workflow: prompt.name,
stepByStep: true
});
return await this.executeTemplate(template, context, options);
}
legacyValidateArguments(prompt, providedArgs) {
const template = new PromptTemplate({
name: prompt.name,
arguments: prompt.arguments
});
try {
template.validate();
return { valid: true, errors: [], processedArgs: providedArgs };
} catch (error) {
return { valid: false, errors: [error.message], processedArgs: {} };
}
}
/**
* Utility methods
*/
getCacheKey(templateName, context, options) {
return `${templateName}:${context.id}:${options.format}`;
}
clearCache() {
this.templateCache.clear();
logger.info('Template cache cleared');
}
getStats() {
return { ...this.stats };
}
/**
* Load external templates from prompts/templates/ directory
*/
async loadExternalTemplates() {
try {
const externalTemplates = await this.templateLoader.loadAllTemplates();
// Register all loaded templates
for (const [name, template] of externalTemplates) {
this.templates.set(name, template);
this.stats.templatesLoaded++;
}
logger.info(`Loaded ${externalTemplates.size} external templates from filesystem`);
return externalTemplates;
} catch (error) {
console.error('\n' + '='.repeat(80));
console.error('⚠️ CRITICAL: PROMPT MANAGER TEMPLATE LOADING FAILED');
console.error('='.repeat(80));
console.error('❌ Cannot load external prompt templates');
console.error('📁 Template directory:', this.templatesPath);
console.error('💥 Error:', error.message);
console.error('🔧 Check that prompts/templates/ directory exists and contains valid JSON files');
console.error('='.repeat(80) + '\n');
logger.error('Failed to load external templates:', error.message);
throw error;
}
}
/**
* Load a specific template from external files
*/
async loadExternalTemplate(category, name) {
try {
const template = await this.templateLoader.loadTemplateByName(category, name);
if (template) {
this.registerTemplate(template);
logger.debug(`Loaded external template: ${template.name}`);
}
return template;
} catch (error) {
logger.error(`Failed to load external template ${category}/${name}:`, error.message);
return null;
}
}
/**
* Reload all external templates (useful for development)
*/
async reloadExternalTemplates() {
try {
// Clear existing external templates (keep registered ones)
const externalTemplateNames = [];
for (const [name, template] of this.templates) {
if (template.metadata?.external) {
externalTemplateNames.push(name);
}
}
// Remove external templates
externalTemplateNames.forEach(name => this.templates.delete(name));
// Reload from filesystem
const reloadedTemplates = await this.loadExternalTemplates();
logger.info(`Reloaded ${reloadedTemplates.size} external templates`);
return reloadedTemplates;
} catch (error) {
logger.error('Failed to reload external templates:', error.message);
throw error;
}
}
/**
* List available external template files
*/
async listExternalTemplateFiles() {
try {
return await this.templateLoader.listTemplateFiles();
} catch (error) {
logger.error('Failed to list external template files:', error.message);
return [];
}
}
/**
* Load templates from various sources
*/
async loadTemplatesFromPromptTemplates(promptTemplates) {
// Load from existing PromptTemplates.js
const models = ['llama2', 'mistral'];
for (const model of models) {
const template = promptTemplates.getTemplateForModel(model);
// Convert to unified format
const chatTemplate = new PromptTemplate({
name: `${model}-chat`,
description: `Chat template for ${model}`,
content: '${query}',
systemPrompt: '${system}',
format: 'chat',
supportedModels: [model]
});
const completionTemplate = new PromptTemplate({
name: `${model}-completion`,
description: `Completion template for ${model}`,
content: '${query}',
format: 'completion',
supportedModels: [model]
});
const conceptTemplate = new PromptTemplate({
name: `${model}-concept`,
description: `Concept extraction template for ${model}`,
content: 'Extract key concepts from: ${text}',
format: 'completion',
supportedModels: [model]
});
this.registerTemplate(chatTemplate);
this.registerTemplate(completionTemplate);
this.registerTemplate(conceptTemplate);
}
logger.info('Loaded templates from PromptTemplates');
}
async loadTemplatesFromMCP(mcpRegistry) {
// Load from MCP prompt registry
const prompts = mcpRegistry.listPrompts();
for (const prompt of prompts) {
const template = new PromptTemplate({
name: prompt.name,
description: prompt.description,
workflow: prompt.workflow,
arguments: prompt.arguments,
isWorkflow: true,
category: 'mcp'
});
this.registerTemplate(template);
}
logger.info(`Loaded ${prompts.length} templates from MCP registry`);
}
/**
* Get legacy adapter for backward compatibility
*/
getLegacyAdapter(adapterName) {
return this.legacyAdapters.get(adapterName);
}
/**
* Health check
*/
healthCheck() {
return {
status: 'healthy',
templates: this.templates.size,
formatters: this.formatters.size,
stats: this.stats,
cacheSize: this.templateCache.size
};
}
}