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
JavaScript
;
/**
* 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