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.
@@ -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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'core/example'
4
+ require_relative 'core/prediction'
5
+ require_relative 'core/trace'
6
+ require_relative 'core/traceable'
7
+ require_relative 'core/compiler'
8
+
9
+ module Desiru
10
+ module Core
11
+ end
12
+ end
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?('?')