crewai-ts
Version:
TypeScript port of crewAI for agent-based workflows
282 lines (281 loc) • 9.26 kB
JavaScript
/**
* Memory retention policies implementation
* Provides flexible control over memory retention with various strategies
*/
/**
* Time-based retention policy
* Retains items based on their age (creation or access time)
*/
export class TimeBasedRetentionPolicy {
maxAgeMs;
timeField;
name;
constructor(options) {
this.maxAgeMs = options.maxAgeMs;
this.timeField = options.timeField ?? 'createdAt';
this.name = options.name;
}
shouldRetain(item) {
const now = Date.now();
let timestamp;
switch (this.timeField) {
case 'lastAccessedAt':
timestamp = item.lastAccessedAt ?? item.createdAt;
break;
case 'updatedAt':
// Safe access with type casting
timestamp = item.updatedAt ?? item.createdAt;
break;
case 'createdAt':
default:
timestamp = item.createdAt;
}
return now - timestamp < this.maxAgeMs;
}
async applyToMemory(memory) {
// Get all items
// This implementation assumes memory has a method to get all items
// In a real implementation, we would use an internal method or property
// that efficiently allows us to iterate through items
// For now, we'll implement a simple version that assumes a search method
// This might not be efficient for large memories
const allItems = await memory.search({ limit: Number.MAX_SAFE_INTEGER });
let removedCount = 0;
// Check each item and remove if it doesn't meet retention criteria
for (const item of allItems.items) {
if (!this.shouldRetain(item)) {
await memory.remove(item.id);
removedCount++;
}
}
return removedCount;
}
}
/**
* Count-based retention policy
* Retains up to a maximum number of items
*/
export class CountBasedRetentionPolicy {
maxItems;
strategy;
name;
constructor(options) {
this.maxItems = options.maxItems;
this.strategy = options.strategy ?? 'oldest';
this.name = options.name;
}
shouldRetain(item) {
// This method is not useful for CountBasedRetentionPolicy
// since retention depends on comparing with other items
// Always return true to defer the decision to applyToMemory
return true;
}
async applyToMemory(memory) {
// Get all items
const allItems = await memory.search({ limit: Number.MAX_SAFE_INTEGER });
// If we're under the limit, do nothing
if (allItems.items.length <= this.maxItems) {
return 0;
}
// Sort based on strategy
const items = [...allItems.items];
if (this.strategy === 'oldest') {
items.sort((a, b) => a.createdAt - b.createdAt);
}
else {
// leastAccessed strategy
items.sort((a, b) => {
const aTime = a.lastAccessedAt ?? a.createdAt;
const bTime = b.lastAccessedAt ?? b.createdAt;
return aTime - bTime;
});
}
// Calculate how many to remove
const countToRemove = items.length - this.maxItems;
// Remove excess items
const itemsToRemove = items.slice(0, countToRemove);
let removedCount = 0;
for (const item of itemsToRemove) {
await memory.remove(item.id);
removedCount++;
}
return removedCount;
}
}
/**
* Relevance-based retention policy
* Retains items based on their calculated relevance score
*/
export class RelevanceBasedRetentionPolicy {
threshold;
relevanceFn;
name;
constructor(options) {
this.threshold = options.threshold;
this.relevanceFn = options.relevanceFn;
this.name = options.name;
}
shouldRetain(item) {
// Calculate relevance
let relevance;
if (this.relevanceFn) {
// Use custom relevance function
relevance = this.relevanceFn(item);
}
else if (item.relevance !== undefined) {
// Use item's relevance property if available with safe type casting
relevance = item.relevance || 0;
}
else {
// Default to high relevance if no method to determine
return true;
}
return relevance >= this.threshold;
}
async applyToMemory(memory) {
// Get all items
const allItems = await memory.search({ limit: Number.MAX_SAFE_INTEGER });
let removedCount = 0;
// Check each item and remove if it doesn't meet relevance threshold
for (const item of allItems.items) {
if (!this.shouldRetain(item)) {
await memory.remove(item.id);
removedCount++;
}
}
return removedCount;
}
}
/**
* Composite retention policy
* Combines multiple retention policies with AND/OR logic
*/
export class CompositeRetentionPolicy {
policies;
mode;
name;
constructor(options) {
this.policies = options.policies;
this.mode = options.mode ?? 'any';
this.name = options.name;
}
shouldRetain(item) {
if (this.mode === 'all') {
// Item must be retained by all policies (AND)
return this.policies.every(policy => policy.shouldRetain(item));
}
else {
// Item must be retained by at least one policy (OR)
return this.policies.some(policy => policy.shouldRetain(item));
}
}
async applyToMemory(memory) {
// Get all items
const allItems = await memory.search({ limit: Number.MAX_SAFE_INTEGER });
let removedCount = 0;
// Check each item against the composite policy
for (const item of allItems.items) {
if (!this.shouldRetain(item)) {
await memory.remove(item.id);
removedCount++;
}
}
return removedCount;
}
}
/**
* Metadata-based retention policy
* Retains items based on their metadata properties
*/
export class MetadataBasedRetentionPolicy {
criteria;
invert;
name;
constructor(options) {
this.criteria = options.criteria;
this.invert = options.invert ?? false;
this.name = options.name;
}
shouldRetain(item) {
// Item must have metadata
if (!item.metadata) {
return !this.invert; // If inverted, remove items without metadata
}
// Check if item matches all criteria
const matches = Object.entries(this.criteria).every(([key, value]) => {
return item.metadata?.[key] === value;
});
// Apply inversion if needed
return this.invert ? !matches : matches;
}
async applyToMemory(memory) {
// Get all items
const allItems = await memory.search({ limit: Number.MAX_SAFE_INTEGER });
let removedCount = 0;
// Check each item against metadata criteria
for (const item of allItems.items) {
if (!this.shouldRetain(item)) {
await memory.remove(item.id);
removedCount++;
}
}
return removedCount;
}
}
/**
* Retention policy factory with common policy creation methods
*/
export class RetentionPolicyFactory {
/**
* Create a policy that retains items based on age
*/
static createTimeBasedPolicy(maxAgeMs, timeField = 'createdAt') {
return new TimeBasedRetentionPolicy({
maxAgeMs,
timeField,
name: `TimeBasedRetention(${timeField}, ${maxAgeMs}ms)`
});
}
/**
* Create a policy that retains a maximum number of items
*/
static createCountBasedPolicy(maxItems, strategy = 'oldest') {
return new CountBasedRetentionPolicy({
maxItems,
strategy,
name: `CountBasedRetention(${maxItems}, ${strategy})`
});
}
/**
* Create a policy that retains items based on relevance score
*/
static createRelevanceBasedPolicy(threshold, relevanceFn) {
return new RelevanceBasedRetentionPolicy({
threshold,
relevanceFn,
name: `RelevanceBasedRetention(${threshold})`
});
}
/**
* Create a policy that combines multiple policies
*/
static createCompositePolicy(policies, mode = 'any') {
const policyNames = policies.map(p => p.name || 'unnamed').join(',');
return new CompositeRetentionPolicy({
policies,
mode,
name: `CompositeRetention(${mode}, [${policyNames}])`
});
}
/**
* Create a policy that retains items based on metadata
*/
static createMetadataBasedPolicy(criteria, invert = false) {
const criteriaStr = JSON.stringify(criteria).slice(0, 50) + (JSON.stringify(criteria).length > 50 ? '...' : '');
return new MetadataBasedRetentionPolicy({
criteria,
invert,
name: `MetadataBasedRetention(${criteriaStr}, invert=${invert})`
});
}
}