UNPKG

ai-patterns

Version:

Production-ready TypeScript patterns to build solid and robust AI applications. Retry logic, circuit breakers, rate limiting, human-in-the-loop escalation, prompt versioning, response validation, context window management, and more—all with complete type

459 lines 19.1 kB
"use strict"; /** * Reflection Loop Pattern * * Enables AI agents to self-critique and iteratively improve their responses. * The AI generates a response, reflects on it, and regenerates based on feedback * until a target quality score is reached or max iterations is hit. * * @example * ```typescript * const result = await reflectionLoop({ * execute: async (ctx) => { * const prompt = ctx.iteration === 1 * ? 'Write a blog post about TypeScript' * : `Previous: ${ctx.previousResponse} * Feedback: ${ctx.previousCritique?.feedback} * Improve based on this feedback.`; * return await generateText({ prompt }); * }, * reflect: async (text) => { * const critique = await generateText({ * prompt: `Rate this (1-10) and suggest improvements: ${text}` * }); * return { * score: extractScore(critique), * feedback: critique, * shouldContinue: extractScore(critique) < 8 * }; * }, * maxIterations: 5, * targetScore: 8 * }); * ``` */ Object.defineProperty(exports, "__esModule", { value: true }); exports.InMemoryReflectionStorage = void 0; exports.reflectionLoop = reflectionLoop; const common_1 = require("../types/common"); const errors_1 = require("../types/errors"); const storage_1 = require("../common/storage"); /** * In-memory storage implementation for reflection history using GlobalStorage */ class InMemoryReflectionStorage { constructor() { this.namespace = storage_1.StorageNamespace.REFLECTION; this.storage = storage_1.GlobalStorage.getInstance(); } async save(sessionId, iteration) { const existing = await this.storage.get(this.namespace, sessionId); const iterations = existing || []; iterations.push(iteration); await this.storage.set(this.namespace, sessionId, iterations); } async load(sessionId) { const iterations = await this.storage.get(this.namespace, sessionId); return iterations || []; } async delete(sessionId) { await this.storage.delete(this.namespace, sessionId); } async listSessions() { return this.storage.keys(this.namespace); } } exports.InMemoryReflectionStorage = InMemoryReflectionStorage; /** * Create a ReflectionHistory object from iterations */ function createHistory(iterations) { if (iterations.length === 0) { throw new errors_1.PatternError("Cannot create history from empty iterations array", errors_1.ErrorCode.INVALID_ARGUMENT); } const scores = iterations.map((i) => i.critique.score); const best = iterations.reduce((best, curr) => curr.critique.score > best.critique.score ? curr : best); const last = iterations[iterations.length - 1]; const stats = { totalIterations: iterations.length, totalTime: iterations.reduce((sum, i) => sum + i.metrics.totalTime, 0), totalCost: iterations.reduce((sum, i) => sum + (i.metrics.cost || 0), 0) || undefined, totalTokens: iterations.reduce((sum, i) => sum + (i.metrics.tokens || 0), 0) || undefined, averageScore: scores.reduce((a, b) => a + b, 0) / scores.length, scoreImprovement: best.critique.score - iterations[0].critique.score, bestScore: Math.max(...scores), worstScore: Math.min(...scores), }; return { iterations, stats, best, last, getIteration: (n) => iterations.find((i) => i.iteration === n), getScoreProgression: () => scores, wasImproving: () => { if (iterations.length < 2) return false; const first = iterations[0].critique.score; const lastScore = iterations[iterations.length - 1].critique.score; return lastScore > first; }, }; } /** * Execute a reflection loop where the AI iteratively improves its response * through self-critique and regeneration. */ async function reflectionLoop(config) { const { execute, reflect, maxIterations = 5, targetScore = 10, onMaxIterationsReached = "return-best", onStart, onBeforeExecute, onAfterExecute, onBeforeReflect, onAfterReflect, onIterationComplete, onImprovement, onStagnation, onTargetReached, onMaxIterations, onError, onComplete, enableHistory = true, historyStorage, sessionId = `reflection-${Date.now()}-${Math.random().toString(36).slice(2)}`, costPerToken, includeHistoryInContext = false, maxHistoryInContext = 3, logger = common_1.defaultLogger, } = config; // Validate configuration if (maxIterations < 1) { throw new errors_1.PatternError("maxIterations must be at least 1", errors_1.ErrorCode.INVALID_ARGUMENT, undefined, { maxIterations }); } if (!execute || typeof execute !== "function") { throw new errors_1.PatternError("execute must be a function", errors_1.ErrorCode.INVALID_ARGUMENT); } if (!reflect || typeof reflect !== "function") { throw new errors_1.PatternError("reflect must be a function", errors_1.ErrorCode.INVALID_ARGUMENT); } const loopStartTime = performance.now(); const iterations = []; const storage = historyStorage || new InMemoryReflectionStorage(); // Hook: onStart if (onStart) { try { await onStart(config); } catch (error) { logger.error("onStart hook failed", { error: error instanceof Error ? error.message : String(error), }); } } let iteration = 0; let bestIteration = null; try { while (iteration < maxIterations) { iteration++; const iterationStartTime = performance.now(); logger.debug(`Reflection iteration ${iteration}/${maxIterations}`); // Build context const previousIterations = includeHistoryInContext ? iterations.slice(-maxHistoryInContext).map((iter) => ({ response: iter.response, critique: iter.critique, })) : []; const context = { iteration, previousResponse: iterations[iterations.length - 1]?.response, previousCritique: iterations[iterations.length - 1]?.critique, history: previousIterations, }; // Hook: onBeforeExecute if (onBeforeExecute) { try { await onBeforeExecute(context); } catch (error) { logger.error("onBeforeExecute hook failed", { error: error instanceof Error ? error.message : String(error), }); } } // Execute const executeStart = performance.now(); let response; try { response = await execute(context); } catch (error) { const execError = error instanceof Error ? error : new Error(String(error)); logger.error(`Execution failed at iteration ${iteration}`, { error: execError.message, }); // Hook: onError if (onError) { try { await onError(execError, context, iteration); } catch (hookError) { logger.error("onError hook failed", { error: hookError instanceof Error ? hookError.message : String(hookError), }); } } throw new errors_1.PatternError(`Execution failed at iteration ${iteration}: ${execError.message}`, errors_1.ErrorCode.EXECUTION_FAILED, execError, { iteration, sessionId }); } const executeEnd = performance.now(); const executionTime = executeEnd - executeStart; // Hook: onAfterExecute if (onAfterExecute) { try { await onAfterExecute(response, context, executionTime); } catch (error) { logger.error("onAfterExecute hook failed", { error: error instanceof Error ? error.message : String(error), }); } } // Hook: onBeforeReflect if (onBeforeReflect) { try { await onBeforeReflect(response, context); } catch (error) { logger.error("onBeforeReflect hook failed", { error: error instanceof Error ? error.message : String(error), }); } } // Reflect const reflectStart = performance.now(); let critique; try { critique = await reflect(response, context); } catch (error) { const reflectError = error instanceof Error ? error : new Error(String(error)); logger.error(`Reflection failed at iteration ${iteration}`, { error: reflectError.message, }); // Hook: onError if (onError) { try { await onError(reflectError, context, iteration); } catch (hookError) { logger.error("onError hook failed", { error: hookError instanceof Error ? hookError.message : String(hookError), }); } } throw new errors_1.PatternError(`Reflection failed at iteration ${iteration}: ${reflectError.message}`, errors_1.ErrorCode.EXECUTION_FAILED, reflectError, { iteration, sessionId }); } const reflectEnd = performance.now(); const reflectionTime = reflectEnd - reflectStart; // Hook: onAfterReflect if (onAfterReflect) { try { await onAfterReflect(critique, context, reflectionTime); } catch (error) { logger.error("onAfterReflect hook failed", { error: error instanceof Error ? error.message : String(error), }); } } // Calculate metrics const iterationEndTime = performance.now(); const totalTime = iterationEndTime - iterationStartTime; // Extract tokens from response if available (like costTracking pattern) let tokens = undefined; let cost = undefined; if (response && typeof response === "object" && "tokens" in response) { tokens = response.tokens; // Calculate cost if costPerToken is provided if (tokens !== undefined && costPerToken !== undefined) { cost = tokens * costPerToken; } } // Create iteration record const currentIteration = { iteration, response, critique, metrics: { executionTime, reflectionTime, totalTime, tokens, cost, }, startTime: iterationStartTime, endTime: iterationEndTime, }; iterations.push(currentIteration); // Save to storage if (enableHistory) { try { await storage.save(sessionId, currentIteration); } catch (error) { logger.warn("Failed to save iteration to storage", { error: error instanceof Error ? error.message : String(error), }); } } // Hook: onIterationComplete if (onIterationComplete) { try { await onIterationComplete(currentIteration); } catch (error) { logger.error("onIterationComplete hook failed", { error: error instanceof Error ? error.message : String(error), }); } } logger.info(`Iteration ${iteration} complete`, { score: critique.score, executionTime, reflectionTime, totalTime, tokens, cost, }); // Check for improvement if (!bestIteration || critique.score > bestIteration.critique.score) { const previousBest = bestIteration; bestIteration = currentIteration; logger.info(`New best score: ${critique.score}`); // Hook: onImprovement if (onImprovement && previousBest) { try { await onImprovement(currentIteration, previousBest); } catch (error) { logger.error("onImprovement hook failed", { error: error instanceof Error ? error.message : String(error), }); } } } else if (onStagnation && iterations.length > 1) { // Hook: onStagnation const previousScore = iterations[iterations.length - 2]?.critique.score || 0; try { await onStagnation(critique.score, previousScore, iteration); } catch (error) { logger.error("onStagnation hook failed", { error: error instanceof Error ? error.message : String(error), }); } } // Check if target reached if (critique.score >= targetScore) { logger.info(`Target score ${targetScore} reached at iteration ${iteration}`); const history = createHistory(iterations); // Hook: onTargetReached if (onTargetReached) { try { await onTargetReached(currentIteration, history); } catch (error) { logger.error("onTargetReached hook failed", { error: error instanceof Error ? error.message : String(error), }); } } const loopEndTime = performance.now(); const result = { value: response, finalScore: critique.score, iterations: iteration, targetReached: true, history, timestamp: Date.now(), metrics: { totalTime: loopEndTime - loopStartTime, totalCost: history.stats.totalCost, totalTokens: history.stats.totalTokens, averageIterationTime: history.stats.totalTime / iteration, scoreProgression: history.getScoreProgression(), }, }; // Hook: onComplete if (onComplete) { try { await onComplete(result, history); } catch (error) { logger.error("onComplete hook failed", { error: error instanceof Error ? error.message : String(error), }); } } return result; } // Check if should continue if (!critique.shouldContinue) { logger.info(`Reflection stopped by reflect function at iteration ${iteration}`); break; } } // Max iterations reached logger.warn(`Max iterations (${maxIterations}) reached`); const history = createHistory(iterations); // Hook: onMaxIterations if (onMaxIterations) { try { await onMaxIterations(history); } catch (error) { logger.error("onMaxIterations hook failed", { error: error instanceof Error ? error.message : String(error), }); } } if (onMaxIterationsReached === "throw") { throw new errors_1.PatternError(`Reflection loop failed to reach target score (${targetScore}) after ${maxIterations} iterations`, errors_1.ErrorCode.ALL_RETRIES_FAILED, undefined, { maxIterations, bestScore: bestIteration?.critique.score, targetScore, sessionId, stats: history.stats, }); } const finalIteration = onMaxIterationsReached === "return-best" ? bestIteration : iterations[iterations.length - 1]; const loopEndTime = performance.now(); const result = { value: finalIteration.response, finalScore: finalIteration.critique.score, iterations: iteration, targetReached: false, history, timestamp: Date.now(), metrics: { totalTime: loopEndTime - loopStartTime, totalCost: history.stats.totalCost, totalTokens: history.stats.totalTokens, averageIterationTime: history.stats.totalTime / iteration, scoreProgression: history.getScoreProgression(), }, }; // Hook: onComplete if (onComplete) { try { await onComplete(result, history); } catch (error) { logger.error("onComplete hook failed", { error: error instanceof Error ? error.message : String(error), }); } } return result; } catch (error) { // If error wasn't already handled, handle it here if (!(error instanceof errors_1.PatternError)) { const err = error instanceof Error ? error : new Error(String(error)); logger.error("Unexpected error in reflection loop", { error: err.message, }); throw new errors_1.PatternError(`Reflection loop encountered an unexpected error: ${err.message}`, errors_1.ErrorCode.EXECUTION_FAILED, err, { iteration, sessionId }); } throw error; } } //# sourceMappingURL=reflection-loop.js.map