dspy 0.3.1 → 0.4.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,725 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sorbet-runtime'
4
+ require 'yaml'
5
+ require 'fileutils'
6
+ require 'digest'
7
+
8
+ module DSPy
9
+ module Registry
10
+ # Registry for managing signature versions and deployments
11
+ # Provides version control, rollback capabilities, and deployment tracking
12
+ class SignatureRegistry
13
+ extend T::Sig
14
+
15
+ # Represents a versioned signature with deployment information
16
+ class SignatureVersion
17
+ extend T::Sig
18
+
19
+ sig { returns(String) }
20
+ attr_reader :signature_name
21
+
22
+ sig { returns(String) }
23
+ attr_reader :version
24
+
25
+ sig { returns(String) }
26
+ attr_reader :version_hash
27
+
28
+ sig { returns(T::Hash[Symbol, T.untyped]) }
29
+ attr_reader :configuration
30
+
31
+ sig { returns(T::Hash[Symbol, T.untyped]) }
32
+ attr_reader :metadata
33
+
34
+ sig { returns(Time) }
35
+ attr_reader :created_at
36
+
37
+ sig { returns(T.nilable(String)) }
38
+ attr_reader :program_id
39
+
40
+ sig { returns(T::Boolean) }
41
+ attr_reader :is_deployed
42
+
43
+ sig { returns(T.nilable(Float)) }
44
+ attr_reader :performance_score
45
+
46
+ sig do
47
+ params(
48
+ signature_name: String,
49
+ version: String,
50
+ configuration: T::Hash[Symbol, T.untyped],
51
+ metadata: T::Hash[Symbol, T.untyped],
52
+ program_id: T.nilable(String),
53
+ is_deployed: T::Boolean,
54
+ performance_score: T.nilable(Float)
55
+ ).void
56
+ end
57
+ def initialize(signature_name:, version:, configuration:, metadata: {}, program_id: nil, is_deployed: false, performance_score: nil)
58
+ @signature_name = signature_name
59
+ @version = version
60
+ @configuration = configuration.freeze
61
+ @metadata = metadata.merge({
62
+ created_at: Time.now.iso8601,
63
+ registry_version: "1.0"
64
+ }).freeze
65
+ @created_at = Time.now
66
+ @program_id = program_id
67
+ @is_deployed = is_deployed
68
+ @performance_score = performance_score
69
+ @version_hash = generate_version_hash
70
+ end
71
+
72
+ sig { returns(T::Hash[Symbol, T.untyped]) }
73
+ def to_h
74
+ {
75
+ signature_name: @signature_name,
76
+ version: @version,
77
+ version_hash: @version_hash,
78
+ configuration: @configuration,
79
+ metadata: @metadata,
80
+ created_at: @created_at.iso8601,
81
+ program_id: @program_id,
82
+ is_deployed: @is_deployed,
83
+ performance_score: @performance_score
84
+ }
85
+ end
86
+
87
+ sig { params(data: T::Hash[Symbol, T.untyped]).returns(SignatureVersion) }
88
+ def self.from_h(data)
89
+ version = new(
90
+ signature_name: data[:signature_name],
91
+ version: data[:version],
92
+ configuration: data[:configuration] || {},
93
+ metadata: data[:metadata] || {},
94
+ program_id: data[:program_id],
95
+ is_deployed: data[:is_deployed] || false,
96
+ performance_score: data[:performance_score]
97
+ )
98
+ version.instance_variable_set(:@created_at, Time.parse(data[:created_at])) if data[:created_at]
99
+ version.instance_variable_set(:@version_hash, data[:version_hash]) if data[:version_hash]
100
+ version
101
+ end
102
+
103
+ sig { params(score: Float).returns(SignatureVersion) }
104
+ def with_performance_score(score)
105
+ self.class.new(
106
+ signature_name: @signature_name,
107
+ version: @version,
108
+ configuration: @configuration,
109
+ metadata: @metadata,
110
+ program_id: @program_id,
111
+ is_deployed: @is_deployed,
112
+ performance_score: score
113
+ )
114
+ end
115
+
116
+ sig { returns(SignatureVersion) }
117
+ def deploy
118
+ self.class.new(
119
+ signature_name: @signature_name,
120
+ version: @version,
121
+ configuration: @configuration,
122
+ metadata: @metadata,
123
+ program_id: @program_id,
124
+ is_deployed: true,
125
+ performance_score: @performance_score
126
+ )
127
+ end
128
+
129
+ sig { returns(SignatureVersion) }
130
+ def undeploy
131
+ self.class.new(
132
+ signature_name: @signature_name,
133
+ version: @version,
134
+ configuration: @configuration,
135
+ metadata: @metadata,
136
+ program_id: @program_id,
137
+ is_deployed: false,
138
+ performance_score: @performance_score
139
+ )
140
+ end
141
+
142
+ private
143
+
144
+ sig { returns(String) }
145
+ def generate_version_hash
146
+ content = "#{@signature_name}_#{@version}_#{@configuration.hash}_#{@created_at.to_f}"
147
+ Digest::SHA256.hexdigest(content)[0, 12]
148
+ end
149
+ end
150
+
151
+ # Configuration for the registry
152
+ class RegistryConfig
153
+ extend T::Sig
154
+
155
+ sig { returns(String) }
156
+ attr_accessor :registry_path
157
+
158
+ sig { returns(String) }
159
+ attr_accessor :config_file
160
+
161
+ sig { returns(T::Boolean) }
162
+ attr_accessor :auto_version
163
+
164
+ sig { returns(Integer) }
165
+ attr_accessor :max_versions_per_signature
166
+
167
+ sig { returns(T::Boolean) }
168
+ attr_accessor :backup_on_deploy
169
+
170
+ sig { returns(String) }
171
+ attr_accessor :version_format
172
+
173
+ sig { void }
174
+ def initialize
175
+ @registry_path = "./dspy_registry"
176
+ @config_file = "registry.yml"
177
+ @auto_version = true
178
+ @max_versions_per_signature = 10
179
+ @backup_on_deploy = true
180
+ @version_format = "v%Y%m%d_%H%M%S" # timestamp-based versions
181
+ end
182
+
183
+ sig { returns(T::Hash[Symbol, T.untyped]) }
184
+ def to_h
185
+ {
186
+ registry_path: @registry_path,
187
+ config_file: @config_file,
188
+ auto_version: @auto_version,
189
+ max_versions_per_signature: @max_versions_per_signature,
190
+ backup_on_deploy: @backup_on_deploy,
191
+ version_format: @version_format
192
+ }
193
+ end
194
+
195
+ sig { params(data: T::Hash[Symbol, T.untyped]).void }
196
+ def from_h(data)
197
+ @registry_path = data[:registry_path] if data[:registry_path]
198
+ @config_file = data[:config_file] if data[:config_file]
199
+ @auto_version = data[:auto_version] if data.key?(:auto_version)
200
+ @max_versions_per_signature = data[:max_versions_per_signature] if data[:max_versions_per_signature]
201
+ @backup_on_deploy = data[:backup_on_deploy] if data.key?(:backup_on_deploy)
202
+ @version_format = data[:version_format] if data[:version_format]
203
+ end
204
+ end
205
+
206
+ sig { returns(RegistryConfig) }
207
+ attr_reader :config
208
+
209
+ sig { params(config: T.nilable(RegistryConfig)).void }
210
+ def initialize(config: nil)
211
+ @config = config || RegistryConfig.new
212
+ setup_registry_directory
213
+ load_or_create_config
214
+ end
215
+
216
+ # Register a new signature version
217
+ sig do
218
+ params(
219
+ signature_name: String,
220
+ configuration: T::Hash[Symbol, T.untyped],
221
+ metadata: T::Hash[Symbol, T.untyped],
222
+ program_id: T.nilable(String),
223
+ version: T.nilable(String)
224
+ ).returns(SignatureVersion)
225
+ end
226
+ def register_version(signature_name, configuration, metadata: {}, program_id: nil, version: nil)
227
+ emit_register_start_event(signature_name, version)
228
+
229
+ begin
230
+ version ||= generate_version_name if @config.auto_version
231
+
232
+ signature_version = SignatureVersion.new(
233
+ signature_name: signature_name,
234
+ version: version,
235
+ configuration: configuration,
236
+ metadata: metadata,
237
+ program_id: program_id
238
+ )
239
+
240
+ # Load existing versions
241
+ versions = load_signature_versions(signature_name)
242
+
243
+ # Check if version already exists
244
+ if versions.any? { |v| v.version == version }
245
+ raise ArgumentError, "Version #{version} already exists for signature #{signature_name}"
246
+ end
247
+
248
+ # Add new version
249
+ versions << signature_version
250
+
251
+ # Cleanup old versions if needed
252
+ if @config.max_versions_per_signature > 0 && versions.size > @config.max_versions_per_signature
253
+ versions = versions.sort_by(&:created_at).last(@config.max_versions_per_signature)
254
+ end
255
+
256
+ # Save versions
257
+ save_signature_versions(signature_name, versions)
258
+
259
+ emit_register_complete_event(signature_version)
260
+ signature_version
261
+
262
+ rescue => error
263
+ emit_register_error_event(signature_name, version, error)
264
+ raise
265
+ end
266
+ end
267
+
268
+ # Deploy a specific version
269
+ sig { params(signature_name: String, version: String).returns(T.nilable(SignatureVersion)) }
270
+ def deploy_version(signature_name, version)
271
+ emit_deploy_start_event(signature_name, version)
272
+
273
+ begin
274
+ versions = load_signature_versions(signature_name)
275
+ target_version = versions.find { |v| v.version == version }
276
+
277
+ return nil unless target_version
278
+
279
+ # Backup current deployment if configured
280
+ if @config.backup_on_deploy
281
+ current_deployed = get_deployed_version(signature_name)
282
+ if current_deployed
283
+ create_deployment_backup(current_deployed)
284
+ end
285
+ end
286
+
287
+ # Mark currently deployed version as previously deployed and undeploy all
288
+ versions = versions.map do |v|
289
+ if v.is_deployed
290
+ # Add deployment history metadata
291
+ updated_metadata = v.metadata.merge(was_deployed: true, last_deployed_at: Time.now.iso8601)
292
+ SignatureVersion.new(
293
+ signature_name: v.signature_name,
294
+ version: v.version,
295
+ configuration: v.configuration,
296
+ metadata: updated_metadata,
297
+ program_id: v.program_id,
298
+ is_deployed: false,
299
+ performance_score: v.performance_score
300
+ )
301
+ else
302
+ v.undeploy
303
+ end
304
+ end
305
+
306
+ # Deploy target version
307
+ target_index = versions.index { |v| v.version == version }
308
+ versions[target_index] = target_version.deploy
309
+
310
+ save_signature_versions(signature_name, versions)
311
+
312
+ deployed_version = versions[target_index]
313
+ emit_deploy_complete_event(deployed_version)
314
+ deployed_version
315
+
316
+ rescue => error
317
+ emit_deploy_error_event(signature_name, version, error)
318
+ nil
319
+ end
320
+ end
321
+
322
+ # Rollback to previous deployed version
323
+ sig { params(signature_name: String).returns(T.nilable(SignatureVersion)) }
324
+ def rollback(signature_name)
325
+ emit_rollback_start_event(signature_name)
326
+
327
+ begin
328
+ versions = load_signature_versions(signature_name)
329
+
330
+ # Find versions that have deployment history (previously deployed)
331
+ # Look for versions with deployment metadata or that were deployed
332
+ deployed_history = versions.select do |v|
333
+ v.metadata[:was_deployed] || v.is_deployed
334
+ end.sort_by(&:created_at)
335
+
336
+ # If we don't have deployment history, check if any versions exist
337
+ if deployed_history.empty?
338
+ # Look for the second newest version as fallback
339
+ all_versions = versions.sort_by(&:created_at)
340
+ if all_versions.size >= 2
341
+ previous_version = all_versions[-2]
342
+ result = deploy_version(signature_name, previous_version.version)
343
+ if result
344
+ emit_rollback_complete_event(result)
345
+ end
346
+ return result
347
+ end
348
+ elsif deployed_history.size >= 2
349
+ # Get the previous deployed version (excluding currently deployed)
350
+ current_deployed = versions.find(&:is_deployed)
351
+ previous_versions = deployed_history.reject { |v| v.version == current_deployed&.version }
352
+
353
+ if previous_versions.any?
354
+ previous_version = previous_versions.last
355
+ result = deploy_version(signature_name, previous_version.version)
356
+ if result
357
+ emit_rollback_complete_event(result)
358
+ end
359
+ return result
360
+ end
361
+ end
362
+
363
+ emit_rollback_error_event(signature_name, "No previous version to rollback to")
364
+ nil
365
+
366
+ rescue => error
367
+ emit_rollback_error_event(signature_name, error.message)
368
+ nil
369
+ end
370
+ end
371
+
372
+ # Get currently deployed version
373
+ sig { params(signature_name: String).returns(T.nilable(SignatureVersion)) }
374
+ def get_deployed_version(signature_name)
375
+ versions = load_signature_versions(signature_name)
376
+ versions.find(&:is_deployed)
377
+ end
378
+
379
+ # List all versions for a signature
380
+ sig { params(signature_name: String).returns(T::Array[SignatureVersion]) }
381
+ def list_versions(signature_name)
382
+ load_signature_versions(signature_name)
383
+ end
384
+
385
+ # List all signatures in registry
386
+ sig { returns(T::Array[String]) }
387
+ def list_signatures
388
+ Dir.glob(File.join(@config.registry_path, "signatures", "*.yml")).map do |file|
389
+ File.basename(file, ".yml")
390
+ end
391
+ end
392
+
393
+ # Update performance score for a version
394
+ sig { params(signature_name: String, version: String, score: Float).returns(T.nilable(SignatureVersion)) }
395
+ def update_performance_score(signature_name, version, score)
396
+ versions = load_signature_versions(signature_name)
397
+ target_index = versions.index { |v| v.version == version }
398
+
399
+ return nil unless target_index
400
+
401
+ versions[target_index] = versions[target_index].with_performance_score(score)
402
+ save_signature_versions(signature_name, versions)
403
+
404
+ emit_performance_update_event(versions[target_index])
405
+ versions[target_index]
406
+ end
407
+
408
+ # Get performance history for a signature
409
+ sig { params(signature_name: String).returns(T::Hash[Symbol, T.untyped]) }
410
+ def get_performance_history(signature_name)
411
+ versions = load_signature_versions(signature_name)
412
+ versions_with_scores = versions.select { |v| v.performance_score }
413
+
414
+ return { versions: [], trends: {} } if versions_with_scores.empty?
415
+
416
+ sorted_versions = versions_with_scores.sort_by(&:created_at)
417
+
418
+ {
419
+ versions: sorted_versions.map do |v|
420
+ {
421
+ version: v.version,
422
+ score: v.performance_score,
423
+ created_at: v.created_at.iso8601,
424
+ is_deployed: v.is_deployed
425
+ }
426
+ end,
427
+ trends: {
428
+ latest_score: sorted_versions.last.performance_score,
429
+ best_score: versions_with_scores.map(&:performance_score).compact.max,
430
+ worst_score: versions_with_scores.map(&:performance_score).compact.min,
431
+ improvement_trend: calculate_improvement_trend(sorted_versions)
432
+ }
433
+ }
434
+ end
435
+
436
+ # Compare two versions
437
+ sig do
438
+ params(
439
+ signature_name: String,
440
+ version1: String,
441
+ version2: String
442
+ ).returns(T.nilable(T::Hash[Symbol, T.untyped]))
443
+ end
444
+ def compare_versions(signature_name, version1, version2)
445
+ versions = load_signature_versions(signature_name)
446
+ v1 = versions.find { |v| v.version == version1 }
447
+ v2 = versions.find { |v| v.version == version2 }
448
+
449
+ return nil unless v1 && v2
450
+
451
+ {
452
+ version_1: {
453
+ version: v1.version,
454
+ created_at: v1.created_at.iso8601,
455
+ performance_score: v1.performance_score,
456
+ is_deployed: v1.is_deployed,
457
+ configuration: v1.configuration
458
+ },
459
+ version_2: {
460
+ version: v2.version,
461
+ created_at: v2.created_at.iso8601,
462
+ performance_score: v2.performance_score,
463
+ is_deployed: v2.is_deployed,
464
+ configuration: v2.configuration
465
+ },
466
+ comparison: {
467
+ age_difference_hours: ((v1.created_at - v2.created_at) / 3600).round(2),
468
+ performance_difference: (v1.performance_score || 0) - (v2.performance_score || 0),
469
+ configuration_changes: compare_configurations(v1.configuration, v2.configuration)
470
+ }
471
+ }
472
+ end
473
+
474
+ # Export registry state
475
+ sig { params(export_path: String).void }
476
+ def export_registry(export_path)
477
+ registry_data = {
478
+ exported_at: Time.now.iso8601,
479
+ config: @config.to_h,
480
+ signatures: {}
481
+ }
482
+
483
+ list_signatures.each do |signature_name|
484
+ registry_data[:signatures][signature_name] = load_signature_versions(signature_name).map(&:to_h)
485
+ end
486
+
487
+ File.write(export_path, YAML.dump(registry_data))
488
+ emit_export_event(export_path, list_signatures.size)
489
+ end
490
+
491
+ # Import registry state
492
+ sig { params(import_path: String).void }
493
+ def import_registry(import_path)
494
+ data = YAML.load_file(import_path, symbolize_names: true)
495
+ imported_count = 0
496
+
497
+ data[:signatures].each do |signature_name, versions_data|
498
+ versions = versions_data.map { |v| SignatureVersion.from_h(v) }
499
+ save_signature_versions(signature_name.to_s, versions)
500
+ imported_count += 1
501
+ end
502
+
503
+ emit_import_event(import_path, imported_count)
504
+ end
505
+
506
+ private
507
+
508
+ sig { void }
509
+ def setup_registry_directory
510
+ FileUtils.mkdir_p(@config.registry_path) unless Dir.exist?(@config.registry_path)
511
+
512
+ signatures_dir = File.join(@config.registry_path, "signatures")
513
+ FileUtils.mkdir_p(signatures_dir) unless Dir.exist?(signatures_dir)
514
+
515
+ backups_dir = File.join(@config.registry_path, "backups")
516
+ FileUtils.mkdir_p(backups_dir) unless Dir.exist?(backups_dir)
517
+ end
518
+
519
+ sig { void }
520
+ def load_or_create_config
521
+ config_path = File.join(@config.registry_path, @config.config_file)
522
+
523
+ if File.exist?(config_path)
524
+ config_data = YAML.load_file(config_path, symbolize_names: true)
525
+ @config.from_h(config_data)
526
+ else
527
+ save_config
528
+ end
529
+ end
530
+
531
+ sig { void }
532
+ def save_config
533
+ config_path = File.join(@config.registry_path, @config.config_file)
534
+ File.write(config_path, YAML.dump(@config.to_h))
535
+ end
536
+
537
+ sig { returns(String) }
538
+ def generate_version_name
539
+ Time.now.strftime(@config.version_format)
540
+ end
541
+
542
+ sig { params(signature_name: String).returns(T::Array[SignatureVersion]) }
543
+ def load_signature_versions(signature_name)
544
+ file_path = signature_file_path(signature_name)
545
+
546
+ return [] unless File.exist?(file_path)
547
+
548
+ versions_data = YAML.load_file(file_path, symbolize_names: true)
549
+ versions_data.map { |data| SignatureVersion.from_h(data) }
550
+ end
551
+
552
+ sig { params(signature_name: String, versions: T::Array[SignatureVersion]).void }
553
+ def save_signature_versions(signature_name, versions)
554
+ file_path = signature_file_path(signature_name)
555
+ versions_data = versions.map(&:to_h)
556
+ File.write(file_path, YAML.dump(versions_data))
557
+ end
558
+
559
+ sig { params(signature_name: String).returns(String) }
560
+ def signature_file_path(signature_name)
561
+ File.join(@config.registry_path, "signatures", "#{signature_name}.yml")
562
+ end
563
+
564
+ sig { params(version: SignatureVersion).void }
565
+ def create_deployment_backup(version)
566
+ backup_dir = File.join(@config.registry_path, "backups", version.signature_name)
567
+ FileUtils.mkdir_p(backup_dir) unless Dir.exist?(backup_dir)
568
+
569
+ backup_file = File.join(backup_dir, "#{version.version}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.yml")
570
+ File.write(backup_file, YAML.dump(version.to_h))
571
+ end
572
+
573
+ sig { params(versions: T::Array[SignatureVersion]).returns(Float) }
574
+ def calculate_improvement_trend(versions)
575
+ return 0.0 if versions.size < 2
576
+
577
+ scores = versions.map(&:performance_score).compact
578
+ return 0.0 if scores.size < 2
579
+
580
+ # Simple linear trend calculation
581
+ recent_scores = scores.last([scores.size / 2, 2].max)
582
+ older_scores = scores.first([scores.size / 2, 2].max)
583
+
584
+ recent_avg = recent_scores.sum.to_f / recent_scores.size
585
+ older_avg = older_scores.sum.to_f / older_scores.size
586
+
587
+ return 0.0 if older_avg == 0.0
588
+
589
+ ((recent_avg - older_avg) / older_avg * 100).round(2)
590
+ end
591
+
592
+ sig { params(config1: T::Hash[Symbol, T.untyped], config2: T::Hash[Symbol, T.untyped]).returns(T::Array[String]) }
593
+ def compare_configurations(config1, config2)
594
+ changes = []
595
+
596
+ all_keys = (config1.keys + config2.keys).uniq
597
+
598
+ all_keys.each do |key|
599
+ if !config1.key?(key)
600
+ changes << "Added #{key}: #{config2[key]}"
601
+ elsif !config2.key?(key)
602
+ changes << "Removed #{key}: #{config1[key]}"
603
+ elsif config1[key] != config2[key]
604
+ changes << "Changed #{key}: #{config1[key]} → #{config2[key]}"
605
+ end
606
+ end
607
+
608
+ changes
609
+ end
610
+
611
+ # Event emission methods
612
+ sig { params(signature_name: String, version: T.nilable(String)).void }
613
+ def emit_register_start_event(signature_name, version)
614
+ DSPy::Instrumentation.emit('dspy.registry.register_start', {
615
+ signature_name: signature_name,
616
+ version: version,
617
+ timestamp: Time.now.iso8601
618
+ })
619
+ end
620
+
621
+ sig { params(version: SignatureVersion).void }
622
+ def emit_register_complete_event(version)
623
+ DSPy::Instrumentation.emit('dspy.registry.register_complete', {
624
+ signature_name: version.signature_name,
625
+ version: version.version,
626
+ version_hash: version.version_hash,
627
+ timestamp: Time.now.iso8601
628
+ })
629
+ end
630
+
631
+ sig { params(signature_name: String, version: T.nilable(String), error: Exception).void }
632
+ def emit_register_error_event(signature_name, version, error)
633
+ DSPy::Instrumentation.emit('dspy.registry.register_error', {
634
+ signature_name: signature_name,
635
+ version: version,
636
+ error: error.message,
637
+ timestamp: Time.now.iso8601
638
+ })
639
+ end
640
+
641
+ sig { params(signature_name: String, version: String).void }
642
+ def emit_deploy_start_event(signature_name, version)
643
+ DSPy::Instrumentation.emit('dspy.registry.deploy_start', {
644
+ signature_name: signature_name,
645
+ version: version,
646
+ timestamp: Time.now.iso8601
647
+ })
648
+ end
649
+
650
+ sig { params(version: SignatureVersion).void }
651
+ def emit_deploy_complete_event(version)
652
+ DSPy::Instrumentation.emit('dspy.registry.deploy_complete', {
653
+ signature_name: version.signature_name,
654
+ version: version.version,
655
+ performance_score: version.performance_score,
656
+ timestamp: Time.now.iso8601
657
+ })
658
+ end
659
+
660
+ sig { params(signature_name: String, version: String, error: Exception).void }
661
+ def emit_deploy_error_event(signature_name, version, error)
662
+ DSPy::Instrumentation.emit('dspy.registry.deploy_error', {
663
+ signature_name: signature_name,
664
+ version: version,
665
+ error: error.message,
666
+ timestamp: Time.now.iso8601
667
+ })
668
+ end
669
+
670
+ sig { params(signature_name: String).void }
671
+ def emit_rollback_start_event(signature_name)
672
+ DSPy::Instrumentation.emit('dspy.registry.rollback_start', {
673
+ signature_name: signature_name,
674
+ timestamp: Time.now.iso8601
675
+ })
676
+ end
677
+
678
+ sig { params(version: SignatureVersion).void }
679
+ def emit_rollback_complete_event(version)
680
+ DSPy::Instrumentation.emit('dspy.registry.rollback_complete', {
681
+ signature_name: version.signature_name,
682
+ version: version.version,
683
+ timestamp: Time.now.iso8601
684
+ })
685
+ end
686
+
687
+ sig { params(signature_name: String, error_message: String).void }
688
+ def emit_rollback_error_event(signature_name, error_message)
689
+ DSPy::Instrumentation.emit('dspy.registry.rollback_error', {
690
+ signature_name: signature_name,
691
+ error: error_message,
692
+ timestamp: Time.now.iso8601
693
+ })
694
+ end
695
+
696
+ sig { params(version: SignatureVersion).void }
697
+ def emit_performance_update_event(version)
698
+ DSPy::Instrumentation.emit('dspy.registry.performance_update', {
699
+ signature_name: version.signature_name,
700
+ version: version.version,
701
+ performance_score: version.performance_score,
702
+ timestamp: Time.now.iso8601
703
+ })
704
+ end
705
+
706
+ sig { params(export_path: String, signature_count: Integer).void }
707
+ def emit_export_event(export_path, signature_count)
708
+ DSPy::Instrumentation.emit('dspy.registry.export', {
709
+ export_path: export_path,
710
+ signature_count: signature_count,
711
+ timestamp: Time.now.iso8601
712
+ })
713
+ end
714
+
715
+ sig { params(import_path: String, signature_count: Integer).void }
716
+ def emit_import_event(import_path, signature_count)
717
+ DSPy::Instrumentation.emit('dspy.registry.import', {
718
+ import_path: import_path,
719
+ signature_count: signature_count,
720
+ timestamp: Time.now.iso8601
721
+ })
722
+ end
723
+ end
724
+ end
725
+ end