Class: Looped::Optimizer

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/looped/optimizer.rb

Constant Summary collapse

DEFAULT_BATCH_SIZE =
5
DEFAULT_MAX_METRIC_CALLS =
32
DEFAULT_CHECK_INTERVAL =
60

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(state:, batch_size: DEFAULT_BATCH_SIZE, max_metric_calls: DEFAULT_MAX_METRIC_CALLS, check_interval: DEFAULT_CHECK_INTERVAL, reflection_model: nil) ⇒ Optimizer

Returns a new instance of Optimizer.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/looped/optimizer.rb', line 29

def initialize(
  state:,
  batch_size: DEFAULT_BATCH_SIZE,
  max_metric_calls: DEFAULT_MAX_METRIC_CALLS,
  check_interval: DEFAULT_CHECK_INTERVAL,
  reflection_model: nil
)
  @state = state
  @batch_size = batch_size
  @max_metric_calls = max_metric_calls
  @check_interval = check_interval
  @reflection_model = T.let(
    reflection_model || ENV.fetch('LOOPED_REFLECTION_MODEL', 'openai/gpt-4o-mini'),
    String
  )
  @running = T.let(false, T::Boolean)
end

Instance Attribute Details

#runningObject (readonly)

Returns the value of attribute running.



18
19
20
# File 'lib/looped/optimizer.rb', line 18

def running
  @running
end

#stateObject (readonly)

Returns the value of attribute state.



15
16
17
# File 'lib/looped/optimizer.rb', line 15

def state
  @state
end

Instance Method Details

#check_and_optimizeObject



65
66
67
68
69
70
# File 'lib/looped/optimizer.rb', line 65

def check_and_optimize
  buffer = @state.peek_training_buffer
  return if buffer.length < @batch_size

  optimize(buffer)
end

#optimize(training_results) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/looped/optimizer.rb', line 73

def optimize(training_results)
  # Build trainset from training results
  trainset = build_trainset(training_results)
  return if trainset.empty?

  # Load current instructions
  current_instructions = @state.load_instructions

  # Build the base agent for optimization
  agent = build_agent_for_optimization(current_instructions)

  # Configure GEPA metric
  metric = build_metric

  # Create reflection LM for GEPA
  reflection_lm = DSPy::ReflectionLM.new(@reflection_model, api_key: resolve_api_key(@reflection_model))

  # Build feedback map for multi-predictor optimization
  feedback_map = build_feedback_map

  # Run GEPA optimization
  gepa = DSPy::Teleprompt::GEPA.new(
    metric: metric,
    reflection_lm: reflection_lm,
    feedback_map: feedback_map,
    config: {
      max_metric_calls: @max_metric_calls,
      minibatch_size: [@batch_size, trainset.length].min,
      perfect_score: 10.0,
      skip_perfect_score: true,
      use_merge: trainset.length >= 4
    }
  )

  # Split trainset - use majority for training, rest for validation
  split_point = [(trainset.length * 0.8).ceil, trainset.length - 1].min
  train_examples = trainset[0...split_point]
  val_examples = trainset[split_point..]

  result = gepa.compile(agent.react, trainset: train_examples, valset: val_examples)

  # Extract optimized instructions from the best program
  save_optimized_instructions(result, current_instructions)

  # Consume the training buffer after successful optimization
  @state.consume_training_buffer
end

#startObject



48
49
50
51
52
53
54
55
56
57
# File 'lib/looped/optimizer.rb', line 48

def start
  @running = true

  Async do |task|
    while @running
      check_and_optimize
      task.sleep(@check_interval)
    end
  end
end

#stopObject



60
61
62
# File 'lib/looped/optimizer.rb', line 60

def stop
  @running = false
end