Class: DSPy::Storage::StorageManager

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

Overview

High-level storage manager that integrates with the teleprompter system Provides easy saving/loading of optimization results

Defined Under Namespace

Classes: StorageConfig

Constant Summary collapse

@@instance =

Global storage instance

T.let(nil, T.nilable(StorageManager))

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config: nil) ⇒ StorageManager

Returns a new instance of StorageManager.



60
61
62
63
64
65
66
# File 'lib/dspy/storage/storage_manager.rb', line 60

def initialize(config: nil)
  @config = config || StorageConfig.new
  @storage = ProgramStorage.new(
    storage_path: @config.storage_path,
    create_directories: true
  )
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



54
55
56
# File 'lib/dspy/storage/storage_manager.rb', line 54

def config
  @config
end

#storageObject (readonly)

Returns the value of attribute storage.



57
58
59
# File 'lib/dspy/storage/storage_manager.rb', line 57

def storage
  @storage
end

Class Method Details

.best(signature_class) ⇒ Object



325
326
327
# File 'lib/dspy/storage/storage_manager.rb', line 325

def self.best(signature_class)
  instance.get_best_program(signature_class)
end

.configure(config) ⇒ Object



309
310
311
# File 'lib/dspy/storage/storage_manager.rb', line 309

def self.configure(config)
  @@instance = new(config: config)
end

.instanceObject



303
304
305
# File 'lib/dspy/storage/storage_manager.rb', line 303

def self.instance
  @@instance ||= new
end

.load(program_id) ⇒ Object



320
321
322
# File 'lib/dspy/storage/storage_manager.rb', line 320

def self.load(program_id)
  instance.storage.load_program(program_id)
end

.save(optimization_result, metadata: {}) ⇒ Object



315
316
317
# File 'lib/dspy/storage/storage_manager.rb', line 315

def self.save(optimization_result, metadata: {})
  instance.save_optimization_result(optimization_result, metadata: )
end

Instance Method Details

#cleanup_old_programsObject



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/dspy/storage/storage_manager.rb', line 229

def cleanup_old_programs
  return 0 unless @config.max_stored_programs > 0
  
  programs = @storage.list_programs
  return 0 if programs.size <= @config.max_stored_programs
  
  # Sort by score (keep best) and recency (keep recent)
  sorted_programs = programs.sort_by do |p|
    score_rank = p[:best_score] || 0
    time_rank = Time.parse(p[:saved_at]).to_f / 1_000_000 # Convert to smaller number
    
    # Weighted combination: 70% score, 30% recency
    -(score_rank * 0.7 + time_rank * 0.3)
  end
  
  programs_to_delete = sorted_programs.drop(@config.max_stored_programs)
  deleted_count = 0
  
  programs_to_delete.each do |program|
    if @storage.delete_program(program[:program_id])
      deleted_count += 1
    end
  end
  
  DSPy.log('storage.cleanup', **{
    'storage.deleted_count' => deleted_count,
    'storage.remaining_count' => @config.max_stored_programs
  })
  
  deleted_count
end

#compare_programs(program_id_1, program_id_2) ⇒ Object



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/dspy/storage/storage_manager.rb', line 268

def compare_programs(program_id_1, program_id_2)
  program1 = @storage.load_program(program_id_1)
  program2 = @storage.load_program(program_id_2)
  
  return nil unless program1 && program2
  
  {
    program_1: {
      id: program1.program_id,
      score: program1.optimization_result[:best_score_value],
      optimizer: program1.optimization_result[:metadata]&.dig(:optimizer),
      saved_at: program1.saved_at.iso8601
    },
    program_2: {
      id: program2.program_id,
      score: program2.optimization_result[:best_score_value],
      optimizer: program2.optimization_result[:metadata]&.dig(:optimizer),
      saved_at: program2.saved_at.iso8601
    },
    comparison: {
      score_difference: (program1.optimization_result[:best_score_value] || 0) - 
                      (program2.optimization_result[:best_score_value] || 0),
      better_program: (program1.optimization_result[:best_score_value] || 0) > 
                    (program2.optimization_result[:best_score_value] || 0) ? 
                    program_id_1 : program_id_2,
      age_difference_hours: ((program1.saved_at - program2.saved_at) / 3600).round(2)
    }
  }
end

#create_checkpoint(optimization_result, checkpoint_name, metadata: {}) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/dspy/storage/storage_manager.rb', line 158

def create_checkpoint(optimization_result, checkpoint_name, metadata: {})
   = .merge({
    checkpoint: true,
    checkpoint_name: checkpoint_name,
    created_at: Time.now.iso8601
  })

  save_optimization_result(
    optimization_result,
    tags: ["checkpoint"],
    description: "Checkpoint: #{checkpoint_name}",
    metadata: 
  )
end

#find_programs(optimizer: nil, min_score: nil, max_age_days: nil, tags: [], signature_class: nil) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/dspy/storage/storage_manager.rb', line 110

def find_programs(optimizer: nil, min_score: nil, max_age_days: nil, tags: [], signature_class: nil)
  programs = @storage.list_programs
  
  programs.select do |program|
    # Filter by optimizer
    next false if optimizer && program[:optimizer] != optimizer
    
    # Filter by minimum score
    next false if min_score && (program[:best_score] || 0) < min_score
    
    # Filter by age
    if max_age_days
      saved_at = Time.parse(program[:saved_at])
      age_days = (Time.now - saved_at) / (24 * 60 * 60)
      next false if age_days > max_age_days
    end
    
    # Filter by signature class
    next false if signature_class && program[:signature_class] != signature_class
    
    # Filter by tags (if any tags specified, program must have at least one)
    if tags.any?
      program_tags = program.dig(:metadata, :tags) || []
      next false unless (tags & program_tags).any?
    end
    
    true
  end
end

#get_best_program(signature_class) ⇒ Object



142
143
144
145
146
147
148
# File 'lib/dspy/storage/storage_manager.rb', line 142

def get_best_program(signature_class)
  matching_programs = find_programs(signature_class: signature_class)
  return nil if matching_programs.empty?
  
  best_program_info = matching_programs.max_by { |p| p[:best_score] || 0 }
  @storage.load_program(best_program_info[:program_id])
end

#get_optimization_historyObject



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/dspy/storage/storage_manager.rb', line 189

def get_optimization_history
  history = @storage.get_history
  
  # Calculate trends
  programs = history[:programs] || []
  return history if programs.empty?
  
  # Group by optimizer
  by_optimizer = programs.group_by { |p| p[:optimizer] }
  optimizer_stats = by_optimizer.transform_values do |progs|
    scores = progs.map { |p| p[:best_score] }.compact
    {
      count: progs.size,
      avg_score: scores.sum.to_f / scores.size,
      best_score: scores.max,
      latest: progs.max_by { |p| Time.parse(p[:saved_at]) }
    }
  end
  
  # Calculate improvement trends
  sorted_programs = programs.sort_by { |p| Time.parse(p[:saved_at]) }
  recent_programs = sorted_programs.last(10)
  older_programs = sorted_programs.first([sorted_programs.size - 10, 1].max)
  
  recent_avg = recent_programs.map { |p| p[:best_score] }.compact.sum.to_f / recent_programs.size
  older_avg = older_programs.map { |p| p[:best_score] }.compact.sum.to_f / older_programs.size
  improvement_trend = older_avg > 0 ? ((recent_avg - older_avg) / older_avg * 100).round(2) : 0

  history.merge({
    optimizer_stats: optimizer_stats,
    trends: {
      improvement_percentage: improvement_trend,
      recent_avg_score: recent_avg.round(4),
      older_avg_score: older_avg.round(4)
    }
  })
end

#restore_checkpoint(checkpoint_name) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
# File 'lib/dspy/storage/storage_manager.rb', line 175

def restore_checkpoint(checkpoint_name)
  programs = find_programs(tags: ["checkpoint"])
  checkpoint = programs.find do |p| 
    # Check both top-level and nested metadata
    p[:checkpoint_name] == checkpoint_name || 
    p.dig(:metadata, :checkpoint_name) == checkpoint_name
  end
  
  return nil unless checkpoint
  @storage.load_program(checkpoint[:program_id])
end

#save_optimization_result(optimization_result, tags: [], description: nil, metadata: {}) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/dspy/storage/storage_manager.rb', line 77

def save_optimization_result(optimization_result, tags: [], description: nil, metadata: {})
  return nil unless @config.auto_save
  
  program = optimization_result.respond_to?(:optimized_program) ? 
           optimization_result.optimized_program : nil
  return nil unless program

   = .merge({
    tags: tags,
    description: description,
    optimizer_class: optimization_result.class.name,
    saved_by: "StorageManager",
    optimization_timestamp: optimization_result.respond_to?(:metadata) ? 
                           optimization_result.[:optimization_timestamp] : nil
  })

  @storage.save_program(
    program,
    optimization_result,
    metadata: 
  )
end