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