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

259 lines 9.7 kB
"use strict"; /** * Human-in-the-Loop Pattern - AI → Human escalation */ Object.defineProperty(exports, "__esModule", { value: true }); exports.HumanInTheLoop = exports.CommonEscalationRules = void 0; exports.humanInTheLoop = humanInTheLoop; const common_1 = require("../types/common"); const errors_1 = require("../types/errors"); const human_in_the_loop_1 = require("../types/human-in-the-loop"); Object.defineProperty(exports, "CommonEscalationRules", { enumerable: true, get: function () { return human_in_the_loop_1.CommonEscalationRules; } }); /** * Human-in-the-Loop - Enables AI → Human escalation (internal class) */ class HumanInTheLoop { constructor(aiFn, options) { this.aiFn = aiFn; this.options = options; this.reviews = new Map(); this.reviewCounter = 0; } /** * Execute AI function with possible escalation */ async execute(input) { const logger = this.options.logger ?? common_1.defaultLogger; let aiOutput; let error; // Try to execute AI function try { aiOutput = await this.aiFn(input); } catch (err) { error = err instanceof Error ? err : new Error(String(err)); logger.warn("Error during AI execution", { error: error.message }); } // Check escalation rules const escalationRule = await this.checkEscalationRules(input, aiOutput, error); if (escalationRule) { logger.info(`Escalation triggered: ${escalationRule.name}`); // Create review const review = this.createReview(input, aiOutput, escalationRule.reason); if (this.options.onEscalate) { this.options.onEscalate(review); } // Request human review const humanOutput = await this.requestReview(review); if (this.options.onReviewComplete) { this.options.onReviewComplete(review); } return humanOutput; } // No escalation, return AI output (or throw error) if (error) { throw error; } return aiOutput; } /** * Check escalation rules */ async checkEscalationRules(input, output, error) { const rules = this.options.escalationRules ?? []; // Sort by priority const sortedRules = [...rules].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)); for (const rule of sortedRules) { const shouldEscalate = await rule.shouldEscalate(input, output, error); if (shouldEscalate) { return rule; } } return null; } /** * Create a review */ createReview(input, aiOutput, reason) { const id = `review-${++this.reviewCounter}-${Date.now()}`; const review = { id, input, aiOutput, reason, createdAt: Date.now(), status: human_in_the_loop_1.ReviewStatus.PENDING, }; this.reviews.set(id, review); return review; } /** * Request human review with timeout */ async requestReview(review) { const logger = this.options.logger ?? common_1.defaultLogger; const timeout = this.options.reviewTimeout ?? 300000; const timeoutFallback = this.options.timeoutFallback ?? human_in_the_loop_1.TimeoutFallback.THROW; try { const humanOutput = await Promise.race([ this.options.requestHumanReview(review), new Promise((_, reject) => setTimeout(() => reject(new errors_1.PatternError(`Review timeout after ${timeout}ms`, errors_1.ErrorCode.REVIEW_TIMEOUT, undefined, { timeout, reviewId: review.id })), timeout)), ]); review.status = human_in_the_loop_1.ReviewStatus.APPROVED; review.humanOutput = humanOutput; review.resolvedAt = Date.now(); logger.info(`Review ${review.id} completed`); return humanOutput; } catch (error) { if (error instanceof errors_1.PatternError && error.code === errors_1.ErrorCode.REVIEW_TIMEOUT) { logger.warn(`Review ${review.id} timed out`); switch (timeoutFallback) { case human_in_the_loop_1.TimeoutFallback.USE_AI: if (review.aiOutput !== undefined) { logger.info("Using AI output after timeout"); return review.aiOutput; } throw new errors_1.PatternError("No AI output available after timeout", errors_1.ErrorCode.NO_AI_OUTPUT); case human_in_the_loop_1.TimeoutFallback.RETRY: logger.info("Retrying review request"); return this.requestReview(review); case human_in_the_loop_1.TimeoutFallback.THROW: default: throw error; } } throw error; } } /** * Get all reviews */ getReviews() { return Array.from(this.reviews.values()); } /** * Get review by ID */ getReview(id) { return this.reviews.get(id); } /** * Get pending reviews */ getPendingReviews() { return this.getReviews().filter((r) => r.status === human_in_the_loop_1.ReviewStatus.PENDING); } } exports.HumanInTheLoop = HumanInTheLoop; /** * Human-in-the-Loop Pattern with single parameter API */ async function humanInTheLoop(options) { const { execute: aiFn, input, escalationRules = [], reviewTimeout = 300000, requestHumanReview, onEscalate, onReviewComplete, logger = common_1.defaultLogger, timeoutFallback = human_in_the_loop_1.TimeoutFallback.THROW, } = options; let aiOutput; let error; // Try to execute AI function try { aiOutput = await aiFn(); } catch (err) { error = err instanceof Error ? err : new Error(String(err)); logger.warn("Error during AI execution", { error: error.message }); } // Check escalation rules const escalationRule = await checkEscalationRules(input, aiOutput, error, escalationRules); if (escalationRule) { logger.info(`Escalation triggered: ${escalationRule.name}`); // Create review const review = createReview(input, aiOutput, escalationRule.reason); if (onEscalate) { onEscalate(review); } // Request human review const humanOutput = await requestReviewWithTimeout(review, requestHumanReview, reviewTimeout, timeoutFallback, logger); if (onReviewComplete) { onReviewComplete(review); } return { value: humanOutput, escalated: true, escalationReason: escalationRule.reason, }; } // No escalation, return AI output (or throw error) if (error) { throw error; } return { value: aiOutput, escalated: false, }; } /** * Check escalation rules */ async function checkEscalationRules(input, output, error, rules) { // Sort by priority const sortedRules = [...rules].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)); for (const rule of sortedRules) { const shouldEscalate = await rule.shouldEscalate(input, output, error); if (shouldEscalate) { return rule; } } return null; } /** * Create a review */ let reviewCounter = 0; function createReview(input, aiOutput, reason) { const id = `review-${++reviewCounter}-${Date.now()}`; return { id, input: input, aiOutput, reason, createdAt: Date.now(), status: human_in_the_loop_1.ReviewStatus.PENDING, }; } /** * Request human review with timeout */ async function requestReviewWithTimeout(review, requestHumanReview, timeout, timeoutFallback, logger) { try { const humanOutput = await Promise.race([ requestHumanReview(review), new Promise((_, reject) => setTimeout(() => reject(new errors_1.PatternError(`Review timeout after ${timeout}ms`, errors_1.ErrorCode.REVIEW_TIMEOUT, undefined, { timeout, reviewId: review.id })), timeout)), ]); review.status = human_in_the_loop_1.ReviewStatus.APPROVED; review.humanOutput = humanOutput; review.resolvedAt = Date.now(); logger.info(`Review ${review.id} completed`); return humanOutput; } catch (error) { if (error instanceof errors_1.PatternError && error.code === errors_1.ErrorCode.REVIEW_TIMEOUT) { logger.warn(`Review ${review.id} timed out`); switch (timeoutFallback) { case human_in_the_loop_1.TimeoutFallback.USE_AI: if (review.aiOutput !== undefined) { logger.info("Using AI output after timeout"); return review.aiOutput; } throw new errors_1.PatternError("No AI output available after timeout", errors_1.ErrorCode.NO_AI_OUTPUT); case human_in_the_loop_1.TimeoutFallback.RETRY: logger.info("Retrying review request"); return requestReviewWithTimeout(review, requestHumanReview, timeout, timeoutFallback, logger); case human_in_the_loop_1.TimeoutFallback.THROW: default: throw error; } } throw error; } } //# sourceMappingURL=human-in-the-loop.js.map