desiru 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/settings.local.json +11 -0
- data/CHANGELOG.md +73 -0
- data/CLAUDE.local.md +3 -0
- data/CLAUDE.md +6 -1
- data/Gemfile.lock +1 -1
- data/README.md +7 -1
- data/desiru-development-swarm.yml +185 -0
- data/lib/desiru/core/compiler.rb +231 -0
- data/lib/desiru/core/example.rb +96 -0
- data/lib/desiru/core/prediction.rb +108 -0
- data/lib/desiru/core/trace.rb +330 -0
- data/lib/desiru/core/traceable.rb +61 -0
- data/lib/desiru/core.rb +12 -0
- data/lib/desiru/module.rb +8 -0
- data/lib/desiru/modules/best_of_n.rb +306 -0
- data/lib/desiru/modules/multi_chain_comparison.rb +72 -20
- data/lib/desiru/modules/predict.rb +7 -0
- data/lib/desiru/modules/program_of_thought.rb +227 -28
- data/lib/desiru/optimizers/base.rb +31 -1
- data/lib/desiru/optimizers/mipro_v2.rb +889 -0
- data/lib/desiru/persistence/repositories/base_repository.rb +1 -1
- data/lib/desiru/version.rb +1 -1
- data/lib/desiru.rb +10 -0
- metadata +13 -1
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Desiru
|
4
|
+
module Core
|
5
|
+
class Prediction
|
6
|
+
attr_reader :completions, :example
|
7
|
+
|
8
|
+
def initialize(example = nil, **kwargs)
|
9
|
+
@example = example || Example.new
|
10
|
+
@completions = {}
|
11
|
+
@metadata = {}
|
12
|
+
|
13
|
+
kwargs.each do |key, value|
|
14
|
+
if key == :completions
|
15
|
+
@completions = value
|
16
|
+
elsif key == :metadata
|
17
|
+
@metadata = value
|
18
|
+
else
|
19
|
+
@completions[key] = value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](key)
|
25
|
+
if @completions.key?(key)
|
26
|
+
@completions[key]
|
27
|
+
elsif @example
|
28
|
+
# First check the raw data in the example
|
29
|
+
@example[key] ||
|
30
|
+
# Then check the inputs and labels
|
31
|
+
@example.inputs[key] ||
|
32
|
+
@example.labels[key]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def []=(key, value)
|
37
|
+
@completions[key] = value
|
38
|
+
end
|
39
|
+
|
40
|
+
def get(key, default = nil)
|
41
|
+
@completions.fetch(key) { @example[key] || default }
|
42
|
+
end
|
43
|
+
|
44
|
+
def keys
|
45
|
+
(@completions.keys + (@example&.keys || [])).uniq
|
46
|
+
end
|
47
|
+
|
48
|
+
def values
|
49
|
+
keys.map { |k| self[k] }
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_h
|
53
|
+
result = @example ? @example.to_h : {}
|
54
|
+
result.merge(@completions)
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_example
|
58
|
+
Example.new(**to_h)
|
59
|
+
end
|
60
|
+
|
61
|
+
def metadata
|
62
|
+
@metadata.dup
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_metadata(key, value)
|
66
|
+
@metadata[key] = value
|
67
|
+
end
|
68
|
+
|
69
|
+
def method_missing(method_name, *args, &)
|
70
|
+
if method_name.to_s.end_with?('=')
|
71
|
+
key = method_name.to_s.chop.to_sym
|
72
|
+
self[key] = args.first
|
73
|
+
elsif @completions.key?(method_name)
|
74
|
+
@completions[method_name]
|
75
|
+
elsif @example.respond_to?(method_name)
|
76
|
+
@example.send(method_name, *args, &)
|
77
|
+
else
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def respond_to_missing?(method_name, include_private = false)
|
83
|
+
method_name.to_s.end_with?('=') ||
|
84
|
+
@completions.key?(method_name) ||
|
85
|
+
@example.respond_to?(method_name) ||
|
86
|
+
super
|
87
|
+
end
|
88
|
+
|
89
|
+
def ==(other)
|
90
|
+
return false unless other.is_a?(self.class)
|
91
|
+
|
92
|
+
@completions == other.completions &&
|
93
|
+
@example == other.example &&
|
94
|
+
@metadata == other.instance_variable_get(:@metadata)
|
95
|
+
end
|
96
|
+
|
97
|
+
def inspect
|
98
|
+
"#<#{self.class.name} completions=#{@completions.inspect} " \
|
99
|
+
"example=#{@example.inspect} metadata=#{@metadata.inspect}>"
|
100
|
+
end
|
101
|
+
|
102
|
+
# Class method to create a Prediction from an Example
|
103
|
+
def self.from_example(example)
|
104
|
+
new(example)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,330 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Desiru
|
4
|
+
module Core
|
5
|
+
class Trace
|
6
|
+
attr_reader :module_name, :signature, :inputs, :outputs, :metadata, :timestamp
|
7
|
+
|
8
|
+
def initialize(module_name:, signature:, inputs: {}, outputs: {}, metadata: {})
|
9
|
+
@module_name = module_name
|
10
|
+
@signature = signature
|
11
|
+
@inputs = inputs
|
12
|
+
@outputs = outputs
|
13
|
+
@metadata = metadata
|
14
|
+
@timestamp = Time.now
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_h
|
18
|
+
{
|
19
|
+
module_name: @module_name,
|
20
|
+
signature: @signature.is_a?(String) ? @signature : @signature.to_h,
|
21
|
+
inputs: @inputs,
|
22
|
+
outputs: @outputs,
|
23
|
+
metadata: @metadata,
|
24
|
+
timestamp: @timestamp
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_example
|
29
|
+
Example.new(**@inputs, **@outputs)
|
30
|
+
end
|
31
|
+
|
32
|
+
def success?
|
33
|
+
@metadata[:success] != false
|
34
|
+
end
|
35
|
+
|
36
|
+
def error?
|
37
|
+
@metadata.key?(:error)
|
38
|
+
end
|
39
|
+
|
40
|
+
def error
|
41
|
+
@metadata[:error]
|
42
|
+
end
|
43
|
+
|
44
|
+
def duration
|
45
|
+
@metadata[:duration]
|
46
|
+
end
|
47
|
+
|
48
|
+
def duration_ms
|
49
|
+
return 0 unless duration
|
50
|
+
|
51
|
+
(duration * 1000).to_f
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(other)
|
55
|
+
return false unless other.is_a?(self.class)
|
56
|
+
|
57
|
+
@module_name == other.module_name &&
|
58
|
+
@signature == other.signature &&
|
59
|
+
@inputs == other.inputs &&
|
60
|
+
@outputs == other.outputs &&
|
61
|
+
@metadata == other.metadata
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class TraceCollector
|
66
|
+
attr_reader :traces
|
67
|
+
|
68
|
+
def initialize
|
69
|
+
@traces = []
|
70
|
+
@enabled = true
|
71
|
+
@filters = []
|
72
|
+
end
|
73
|
+
|
74
|
+
def collect(trace)
|
75
|
+
return unless @enabled && trace.is_a?(Trace)
|
76
|
+
return if @filters.any? { |filter| !filter.call(trace) }
|
77
|
+
|
78
|
+
@traces << trace
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_filter(&block)
|
82
|
+
@filters << block if block_given?
|
83
|
+
end
|
84
|
+
|
85
|
+
def clear_filters
|
86
|
+
@filters.clear
|
87
|
+
end
|
88
|
+
|
89
|
+
def enable
|
90
|
+
@enabled = true
|
91
|
+
end
|
92
|
+
|
93
|
+
def disable
|
94
|
+
@enabled = false
|
95
|
+
end
|
96
|
+
|
97
|
+
def enabled?
|
98
|
+
@enabled
|
99
|
+
end
|
100
|
+
|
101
|
+
def clear
|
102
|
+
@traces.clear
|
103
|
+
end
|
104
|
+
|
105
|
+
def size
|
106
|
+
@traces.size
|
107
|
+
end
|
108
|
+
|
109
|
+
def empty?
|
110
|
+
@traces.empty?
|
111
|
+
end
|
112
|
+
|
113
|
+
def recent(count = 10)
|
114
|
+
@traces.last(count)
|
115
|
+
end
|
116
|
+
|
117
|
+
def by_module(module_name)
|
118
|
+
@traces.select { |trace| trace.module_name == module_name }
|
119
|
+
end
|
120
|
+
|
121
|
+
def successful
|
122
|
+
@traces.select(&:success?)
|
123
|
+
end
|
124
|
+
|
125
|
+
def failed
|
126
|
+
@traces.reject(&:success?)
|
127
|
+
end
|
128
|
+
|
129
|
+
def to_examples
|
130
|
+
@traces.map(&:to_example)
|
131
|
+
end
|
132
|
+
|
133
|
+
def export
|
134
|
+
@traces.map(&:to_h)
|
135
|
+
end
|
136
|
+
|
137
|
+
def filter_by_module(module_name)
|
138
|
+
@traces.select { |trace| trace.module_name == module_name }
|
139
|
+
end
|
140
|
+
|
141
|
+
def filter_by_success(success: true)
|
142
|
+
if success
|
143
|
+
@traces.select(&:success?)
|
144
|
+
else
|
145
|
+
@traces.reject(&:success?)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def filter_by_time_range(start_time, end_time)
|
150
|
+
@traces.select { |trace| trace.timestamp.between?(start_time, end_time) }
|
151
|
+
end
|
152
|
+
|
153
|
+
def statistics
|
154
|
+
return default_statistics if @traces.empty?
|
155
|
+
|
156
|
+
total = @traces.size
|
157
|
+
successful = @traces.count(&:success?)
|
158
|
+
|
159
|
+
# Group by module
|
160
|
+
by_module = @traces.group_by(&:module_name).transform_values do |module_traces|
|
161
|
+
durations = module_traces.map(&:duration_ms).compact
|
162
|
+
{
|
163
|
+
count: module_traces.size,
|
164
|
+
avg_duration_ms: durations.empty? ? 0 : durations.sum / durations.size.to_f
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
# Calculate average duration
|
169
|
+
all_durations = @traces.map(&:duration_ms).compact
|
170
|
+
avg_duration = all_durations.empty? ? 0 : all_durations.sum / all_durations.size.to_f
|
171
|
+
|
172
|
+
{
|
173
|
+
total_traces: total,
|
174
|
+
success_rate: total.positive? ? successful.to_f / total : 0,
|
175
|
+
average_duration_ms: avg_duration,
|
176
|
+
by_module: by_module
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
def default_statistics
|
183
|
+
{
|
184
|
+
total_traces: 0,
|
185
|
+
success_rate: 0,
|
186
|
+
average_duration_ms: 0,
|
187
|
+
by_module: {}
|
188
|
+
}
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class TraceContext
|
193
|
+
attr_reader :collector
|
194
|
+
|
195
|
+
def initialize(collector = nil)
|
196
|
+
@collector = collector || TraceCollector.new
|
197
|
+
@stack = []
|
198
|
+
end
|
199
|
+
|
200
|
+
# Class method to temporarily use a specific collector
|
201
|
+
def self.with_collector(collector)
|
202
|
+
# Save current context if it exists
|
203
|
+
previous_context = Thread.current[:desiru_trace_context]
|
204
|
+
|
205
|
+
# Create new context with the provided collector
|
206
|
+
context = new(collector)
|
207
|
+
Thread.current[:desiru_trace_context] = context
|
208
|
+
|
209
|
+
# Store context for thread propagation
|
210
|
+
Thread.current[:desiru_trace_collector_for_threads] = collector
|
211
|
+
|
212
|
+
# Execute the block
|
213
|
+
yield
|
214
|
+
ensure
|
215
|
+
# Restore previous context
|
216
|
+
Thread.current[:desiru_trace_context] = previous_context
|
217
|
+
Thread.current[:desiru_trace_collector_for_threads] = nil
|
218
|
+
end
|
219
|
+
|
220
|
+
# Get the current trace context
|
221
|
+
def self.current
|
222
|
+
Core.trace_context
|
223
|
+
end
|
224
|
+
|
225
|
+
def start_trace(module_name:, signature:, inputs: {})
|
226
|
+
trace_data = {
|
227
|
+
module_name: module_name,
|
228
|
+
signature: signature,
|
229
|
+
inputs: inputs,
|
230
|
+
start_time: Time.now,
|
231
|
+
metadata: {}
|
232
|
+
}
|
233
|
+
@stack.push(trace_data)
|
234
|
+
end
|
235
|
+
|
236
|
+
def add_metadata(metadata)
|
237
|
+
return if @stack.empty?
|
238
|
+
|
239
|
+
current_trace = @stack.last
|
240
|
+
current_trace[:metadata] = (current_trace[:metadata] || {}).merge(metadata)
|
241
|
+
end
|
242
|
+
|
243
|
+
def end_trace(outputs: {}, metadata: {})
|
244
|
+
return if @stack.empty?
|
245
|
+
|
246
|
+
trace_data = @stack.pop
|
247
|
+
duration = Time.now - trace_data[:start_time]
|
248
|
+
|
249
|
+
# Merge accumulated metadata with end trace metadata
|
250
|
+
combined_metadata = (trace_data[:metadata] || {}).merge(metadata).merge(
|
251
|
+
duration: duration,
|
252
|
+
success: true
|
253
|
+
)
|
254
|
+
|
255
|
+
trace = Trace.new(
|
256
|
+
module_name: trace_data[:module_name],
|
257
|
+
signature: trace_data[:signature],
|
258
|
+
inputs: trace_data[:inputs],
|
259
|
+
outputs: outputs,
|
260
|
+
metadata: combined_metadata
|
261
|
+
)
|
262
|
+
|
263
|
+
@collector.collect(trace)
|
264
|
+
trace
|
265
|
+
end
|
266
|
+
|
267
|
+
def record_error(error, outputs: {}, metadata: {})
|
268
|
+
return if @stack.empty?
|
269
|
+
|
270
|
+
trace_data = @stack.pop
|
271
|
+
duration = Time.now - trace_data[:start_time]
|
272
|
+
|
273
|
+
# Merge accumulated metadata with error metadata
|
274
|
+
combined_metadata = (trace_data[:metadata] || {}).merge(metadata).merge(
|
275
|
+
duration: duration,
|
276
|
+
success: false,
|
277
|
+
error: error.message,
|
278
|
+
error_class: error.class.name
|
279
|
+
)
|
280
|
+
|
281
|
+
trace = Trace.new(
|
282
|
+
module_name: trace_data[:module_name],
|
283
|
+
signature: trace_data[:signature],
|
284
|
+
inputs: trace_data[:inputs],
|
285
|
+
outputs: outputs,
|
286
|
+
metadata: combined_metadata
|
287
|
+
)
|
288
|
+
|
289
|
+
@collector.collect(trace)
|
290
|
+
trace
|
291
|
+
end
|
292
|
+
|
293
|
+
def with_trace(module_name:, signature:, inputs: {})
|
294
|
+
start_trace(module_name: module_name, signature: signature, inputs: inputs)
|
295
|
+
|
296
|
+
begin
|
297
|
+
outputs = yield
|
298
|
+
end_trace(outputs: outputs)
|
299
|
+
outputs
|
300
|
+
rescue StandardError => e
|
301
|
+
record_error(e)
|
302
|
+
raise
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
class << self
|
308
|
+
def trace_collector
|
309
|
+
@trace_collector ||= TraceCollector.new
|
310
|
+
end
|
311
|
+
|
312
|
+
def trace_context
|
313
|
+
# Check for thread-local context first (set by with_collector)
|
314
|
+
if Thread.current[:desiru_trace_context]
|
315
|
+
Thread.current[:desiru_trace_context]
|
316
|
+
elsif Thread.current[:desiru_trace_collector_for_threads]
|
317
|
+
# Create a new context for this thread using the parent's collector
|
318
|
+
Thread.current[:desiru_trace_context] = TraceContext.new(Thread.current[:desiru_trace_collector_for_threads])
|
319
|
+
else
|
320
|
+
@trace_context ||= TraceContext.new(trace_collector)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def reset_traces!
|
325
|
+
@trace_collector = TraceCollector.new
|
326
|
+
@trace_context = TraceContext.new(@trace_collector)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Desiru
|
4
|
+
module Core
|
5
|
+
module Traceable
|
6
|
+
def call(inputs = {})
|
7
|
+
return super unless trace_enabled?
|
8
|
+
|
9
|
+
# Handle anonymous classes
|
10
|
+
class_name = self.class.name || "AnonymousModule"
|
11
|
+
module_name = class_name.split('::').last
|
12
|
+
|
13
|
+
Core.trace_context.start_trace(
|
14
|
+
module_name: module_name,
|
15
|
+
signature: signature,
|
16
|
+
inputs: inputs
|
17
|
+
)
|
18
|
+
|
19
|
+
begin
|
20
|
+
result = super
|
21
|
+
|
22
|
+
outputs = if result.is_a?(ModuleResult)
|
23
|
+
result.outputs
|
24
|
+
else
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
metadata = if result.is_a?(ModuleResult)
|
29
|
+
result.metadata
|
30
|
+
else
|
31
|
+
{}
|
32
|
+
end
|
33
|
+
|
34
|
+
Core.trace_context.end_trace(
|
35
|
+
outputs: outputs,
|
36
|
+
metadata: metadata
|
37
|
+
)
|
38
|
+
|
39
|
+
result
|
40
|
+
rescue StandardError => e
|
41
|
+
Core.trace_context.record_error(e)
|
42
|
+
raise
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def trace_enabled?
|
47
|
+
return true unless defined?(@trace_enabled)
|
48
|
+
|
49
|
+
@trace_enabled
|
50
|
+
end
|
51
|
+
|
52
|
+
def enable_trace!
|
53
|
+
@trace_enabled = true
|
54
|
+
end
|
55
|
+
|
56
|
+
def disable_trace!
|
57
|
+
@trace_enabled = false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/desiru/core.rb
ADDED
data/lib/desiru/module.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'async_capable'
|
4
4
|
require_relative 'assertions'
|
5
|
+
require_relative 'core/traceable'
|
5
6
|
|
6
7
|
module Desiru
|
7
8
|
# Base class for all Desiru modules
|
@@ -9,6 +10,7 @@ module Desiru
|
|
9
10
|
class Module
|
10
11
|
extend Forwardable
|
11
12
|
include AsyncCapable
|
13
|
+
prepend Core::Traceable
|
12
14
|
|
13
15
|
attr_reader :signature, :model, :config, :demos, :metadata
|
14
16
|
|
@@ -216,6 +218,12 @@ module Desiru
|
|
216
218
|
end
|
217
219
|
end
|
218
220
|
|
221
|
+
def key?(key)
|
222
|
+
@data.key?(key.to_sym) || @data.key?(key.to_s)
|
223
|
+
end
|
224
|
+
|
225
|
+
alias has_key? key?
|
226
|
+
|
219
227
|
def method_missing(method_name, *args, &)
|
220
228
|
method_str = method_name.to_s
|
221
229
|
if method_str.end_with?('?')
|