Class: DSPy::Memory::MemoryCompactor

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/dspy/memory/memory_compactor.rb

Overview

Simple memory compaction system with inline triggers Handles deduplication, relevance pruning, and conflict resolution

Constant Summary collapse

DEFAULT_MAX_MEMORIES =

Compaction thresholds

1000
DEFAULT_MAX_AGE_DAYS =
90
DEFAULT_SIMILARITY_THRESHOLD =
0.95
DEFAULT_LOW_ACCESS_THRESHOLD =
0.1

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_memories: DEFAULT_MAX_MEMORIES, max_age_days: DEFAULT_MAX_AGE_DAYS, similarity_threshold: DEFAULT_SIMILARITY_THRESHOLD, low_access_threshold: DEFAULT_LOW_ACCESS_THRESHOLD) ⇒ MemoryCompactor

Returns a new instance of MemoryCompactor.



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/dspy/memory/memory_compactor.rb', line 38

def initialize(
  max_memories: DEFAULT_MAX_MEMORIES,
  max_age_days: DEFAULT_MAX_AGE_DAYS,
  similarity_threshold: DEFAULT_SIMILARITY_THRESHOLD,
  low_access_threshold: DEFAULT_LOW_ACCESS_THRESHOLD
)
  @max_memories = max_memories
  @max_age_days = max_age_days
  @similarity_threshold = similarity_threshold
  @low_access_threshold = low_access_threshold
end

Instance Attribute Details

#low_access_thresholdObject (readonly)

Returns the value of attribute low_access_threshold.



28
29
30
# File 'lib/dspy/memory/memory_compactor.rb', line 28

def low_access_threshold
  @low_access_threshold
end

#max_age_daysObject (readonly)

Returns the value of attribute max_age_days.



22
23
24
# File 'lib/dspy/memory/memory_compactor.rb', line 22

def max_age_days
  @max_age_days
end

#max_memoriesObject (readonly)

Returns the value of attribute max_memories.



19
20
21
# File 'lib/dspy/memory/memory_compactor.rb', line 19

def max_memories
  @max_memories
end

#similarity_thresholdObject (readonly)

Returns the value of attribute similarity_threshold.



25
26
27
# File 'lib/dspy/memory/memory_compactor.rb', line 25

def similarity_threshold
  @similarity_threshold
end

Instance Method Details

#age_compaction_needed?(store, user_id) ⇒ Boolean

Returns:

  • (Boolean)


86
87
88
89
90
91
92
# File 'lib/dspy/memory/memory_compactor.rb', line 86

def age_compaction_needed?(store, user_id)
  memories = store.list(user_id: user_id)
  return false if memories.empty?
  
  # Check if any memory exceeds the age limit
  memories.any? { |memory| memory.age_in_days > @max_age_days }
end

#compact_if_needed!(store, embedding_engine, user_id: nil) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/dspy/memory/memory_compactor.rb', line 52

def compact_if_needed!(store, embedding_engine, user_id: nil)
  DSPy::Context.with_span(operation: 'memory.compaction_check', 'memory.user_id' => user_id) do
    results = {}
    
    # Check triggers in order of impact
    if size_compaction_needed?(store, user_id)
      results[:size_compaction] = perform_size_compaction!(store, user_id)
    end
    
    if age_compaction_needed?(store, user_id)
      results[:age_compaction] = perform_age_compaction!(store, user_id)
    end
    
    if duplication_compaction_needed?(store, embedding_engine, user_id)
      results[:deduplication] = perform_deduplication!(store, embedding_engine, user_id)
    end
    
    if relevance_compaction_needed?(store, user_id)
      results[:relevance_pruning] = perform_relevance_pruning!(store, user_id)
    end
    
    results[:total_compacted] = results.values.sum { |r| r.is_a?(Hash) ? r[:removed_count] || 0 : 0 }
    results
  end
end

#duplication_compaction_needed?(store, embedding_engine, user_id) ⇒ Boolean

Returns:

  • (Boolean)


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/dspy/memory/memory_compactor.rb', line 96

def duplication_compaction_needed?(store, embedding_engine, user_id)
  # Sample recent memories to check for duplicates
  recent_memories = store.list(user_id: user_id, limit: 50)
  return false if recent_memories.length < 10
  
  # Quick duplicate check on a sample
  sample_size = [recent_memories.length / 4, 10].max
  sample = recent_memories.sample(sample_size)
  
  duplicate_count = 0
  sample.each_with_index do |memory1, i|
    sample[(i+1)..-1].each do |memory2|
      next unless memory1.embedding && memory2.embedding
      
      similarity = embedding_engine.cosine_similarity(memory1.embedding, memory2.embedding)
      duplicate_count += 1 if similarity > @similarity_threshold
    end
  end
  
  # Need deduplication if > 20% of sample has duplicates
  (duplicate_count.to_f / sample_size) > 0.2
end

#relevance_compaction_needed?(store, user_id) ⇒ Boolean

Returns:

  • (Boolean)


121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/dspy/memory/memory_compactor.rb', line 121

def relevance_compaction_needed?(store, user_id)
  memories = store.list(user_id: user_id, limit: 100)
  return false if memories.length < 50
  
  # Check if many memories have low access counts
  total_access = memories.sum(&:access_count)
  return false if total_access == 0
  
  # Calculate relative access for each memory
  low_access_count = memories.count do |memory|
    relative_access = memory.access_count.to_f / total_access
    relative_access < @low_access_threshold
  end
  
  # Need pruning if > 30% of memories have low relative access
  low_access_ratio = low_access_count.to_f / memories.length
  low_access_ratio > 0.3
end

#size_compaction_needed?(store, user_id) ⇒ Boolean

Returns:

  • (Boolean)


80
81
82
# File 'lib/dspy/memory/memory_compactor.rb', line 80

def size_compaction_needed?(store, user_id)
  store.count(user_id: user_id) > @max_memories
end