dspy 0.28.1 → 0.28.2
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/lib/dspy/callbacks.rb +222 -0
- data/lib/dspy/chain_of_thought.rb +2 -1
- data/lib/dspy/lm.rb +5 -4
- data/lib/dspy/module.rb +33 -0
- data/lib/dspy/predict.rb +7 -0
- data/lib/dspy/prompt.rb +90 -20
- data/lib/dspy/propose/dataset_summary_generator.rb +177 -0
- data/lib/dspy/propose/grounded_proposer.rb +208 -61
- data/lib/dspy/teleprompt/bootstrap_strategy.rb +26 -0
- data/lib/dspy/teleprompt/mipro_v2.rb +81 -56
- data/lib/dspy/teleprompt/simple_optimizer.rb +40 -34
- data/lib/dspy/teleprompt/utils.rb +343 -41
- data/lib/dspy/version.rb +1 -1
- data/lib/dspy.rb +1 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1cc0ac1e2e1dc27f6255b11ca70ff0ad0eb37b5ae50ff38b97ae7d35b7b69b8
|
4
|
+
data.tar.gz: 2399987757b4f037080632e646714e785328fbe3c1fd39138fe750336cdd7710
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea48762186d3de89a005e8eac27f57ca90b294c4ab096ec1c7985d06396216d719ca3a2144406d1f43af472a560670e502329a785d33b8923b5c9c4c0f83dfd1
|
7
|
+
data.tar.gz: 98fee1468f2692a0f7cc87622722f945dbc344fb299e71935ef592bc1d59e68e48e1390208981b6263998627dacb38dc325727cc191f81e1db79fff57c1537a4
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
|
5
|
+
module DSPy
|
6
|
+
# Provides Rails-style callback hooks for DSPy modules
|
7
|
+
#
|
8
|
+
# @example Define callbacks in base class
|
9
|
+
# class DSPy::Module
|
10
|
+
# include DSPy::Callbacks
|
11
|
+
#
|
12
|
+
# create_before_callback :forward
|
13
|
+
# create_after_callback :forward
|
14
|
+
# create_around_callback :forward
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @example Use callbacks in subclasses
|
18
|
+
# class MyAgent < DSPy::Module
|
19
|
+
# before :setup_context
|
20
|
+
# after :log_metrics
|
21
|
+
# around :manage_memory
|
22
|
+
#
|
23
|
+
# private
|
24
|
+
#
|
25
|
+
# def setup_context
|
26
|
+
# @start_time = Time.now
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# def log_metrics
|
30
|
+
# puts "Duration: #{Time.now - @start_time}"
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def manage_memory
|
34
|
+
# load_context
|
35
|
+
# yield
|
36
|
+
# save_context
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
module Callbacks
|
40
|
+
def self.included(base)
|
41
|
+
base.extend(ClassMethods)
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
# Creates a before callback hook for the specified method
|
46
|
+
#
|
47
|
+
# @param method_name [Symbol] the method to add callback support to
|
48
|
+
def create_before_callback(method_name)
|
49
|
+
mark_method_has_callbacks(method_name)
|
50
|
+
ensure_callback_method_defined(:before, method_name)
|
51
|
+
wrap_method_with_callbacks(method_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Creates an after callback hook for the specified method
|
55
|
+
#
|
56
|
+
# @param method_name [Symbol] the method to add callback support to
|
57
|
+
def create_after_callback(method_name)
|
58
|
+
mark_method_has_callbacks(method_name)
|
59
|
+
ensure_callback_method_defined(:after, method_name)
|
60
|
+
wrap_method_with_callbacks(method_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Creates an around callback hook for the specified method
|
64
|
+
#
|
65
|
+
# @param method_name [Symbol] the method to add callback support to
|
66
|
+
def create_around_callback(method_name)
|
67
|
+
mark_method_has_callbacks(method_name)
|
68
|
+
ensure_callback_method_defined(:around, method_name)
|
69
|
+
wrap_method_with_callbacks(method_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Ensures the callback registration method exists
|
75
|
+
def ensure_callback_method_defined(type, target_method_name)
|
76
|
+
return if singleton_class.method_defined?(type)
|
77
|
+
|
78
|
+
define_singleton_method(type) do |callback_method|
|
79
|
+
register_callback(type, target_method_name, callback_method)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Registers a callback for execution
|
84
|
+
def register_callback(type, method_name, callback_method)
|
85
|
+
own_callbacks_for(method_name)[type] ||= []
|
86
|
+
own_callbacks_for(method_name)[type] << callback_method
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns own callbacks (not including parent)
|
90
|
+
def own_callbacks_for(method_name)
|
91
|
+
@callbacks ||= {}
|
92
|
+
@callbacks[method_name] ||= {}
|
93
|
+
end
|
94
|
+
|
95
|
+
# Marks that a method has callback support (even if no callbacks registered yet)
|
96
|
+
def mark_method_has_callbacks(method_name)
|
97
|
+
own_callbacks_for(method_name)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the callback registry for a method
|
101
|
+
# Includes callbacks from parent classes
|
102
|
+
def callbacks_for(method_name)
|
103
|
+
own_callbacks = own_callbacks_for(method_name)
|
104
|
+
|
105
|
+
# Merge parent callbacks if this is a subclass
|
106
|
+
if superclass.respond_to?(:callbacks_for, true)
|
107
|
+
parent_callbacks = superclass.send(:callbacks_for, method_name)
|
108
|
+
|
109
|
+
# Merge each callback type, with own callbacks coming after parent callbacks
|
110
|
+
merged_callbacks = {}
|
111
|
+
[:before, :after, :around].each do |type|
|
112
|
+
parent_list = parent_callbacks[type] || []
|
113
|
+
own_list = own_callbacks[type] || []
|
114
|
+
merged_callbacks[type] = parent_list + own_list if parent_list.any? || own_list.any?
|
115
|
+
end
|
116
|
+
|
117
|
+
merged_callbacks
|
118
|
+
else
|
119
|
+
own_callbacks
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Wraps a method with callback execution logic
|
124
|
+
def wrap_method_with_callbacks(method_name)
|
125
|
+
return if method_wrapped?(method_name)
|
126
|
+
|
127
|
+
# Defer wrapping if method doesn't exist yet
|
128
|
+
return unless method_defined?(method_name)
|
129
|
+
|
130
|
+
# Mark as wrapped BEFORE define_method to prevent infinite recursion
|
131
|
+
mark_method_wrapped(method_name)
|
132
|
+
|
133
|
+
original_method = instance_method(method_name)
|
134
|
+
|
135
|
+
define_method(method_name) do |*args, **kwargs, &block|
|
136
|
+
# Execute before callbacks
|
137
|
+
run_callbacks(:before, method_name)
|
138
|
+
|
139
|
+
# Execute around callbacks or original method
|
140
|
+
result = if self.class.send(:has_around_callbacks?, method_name)
|
141
|
+
execute_with_around_callbacks(method_name, original_method, *args, **kwargs, &block)
|
142
|
+
else
|
143
|
+
original_method.bind(self).call(*args, **kwargs, &block)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Execute after callbacks
|
147
|
+
run_callbacks(:after, method_name)
|
148
|
+
|
149
|
+
result
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Checks if method has around callbacks
|
154
|
+
def has_around_callbacks?(method_name)
|
155
|
+
callbacks_for(method_name)[:around]&.any?
|
156
|
+
end
|
157
|
+
|
158
|
+
# Hook into method_added to wrap methods when they're defined
|
159
|
+
def method_added(method_name)
|
160
|
+
super
|
161
|
+
|
162
|
+
# Check if this method or any parent has callback support (even if no callbacks registered yet)
|
163
|
+
has_callback_support = method_has_callback_support?(method_name)
|
164
|
+
|
165
|
+
return unless has_callback_support
|
166
|
+
return if method_wrapped?(method_name)
|
167
|
+
|
168
|
+
wrap_method_with_callbacks(method_name)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Checks if a method has callback support in this class or parents
|
172
|
+
def method_has_callback_support?(method_name)
|
173
|
+
# Check own callbacks registry
|
174
|
+
return true if @callbacks&.key?(method_name)
|
175
|
+
|
176
|
+
# Check parent class
|
177
|
+
if superclass.respond_to?(:method_has_callback_support?, true)
|
178
|
+
superclass.send(:method_has_callback_support?, method_name)
|
179
|
+
else
|
180
|
+
false
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Marks a method as wrapped
|
185
|
+
def mark_method_wrapped(method_name)
|
186
|
+
@wrapped_methods ||= []
|
187
|
+
@wrapped_methods << method_name
|
188
|
+
end
|
189
|
+
|
190
|
+
# Checks if method is already wrapped
|
191
|
+
def method_wrapped?(method_name)
|
192
|
+
@wrapped_methods&.include?(method_name)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
# Executes callbacks of a specific type
|
199
|
+
def run_callbacks(type, method_name)
|
200
|
+
callbacks = self.class.send(:callbacks_for, method_name)[type]
|
201
|
+
return unless callbacks
|
202
|
+
|
203
|
+
callbacks.each do |callback_method|
|
204
|
+
send(callback_method)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Executes method with around callbacks
|
209
|
+
def execute_with_around_callbacks(method_name, original_method, *args, **kwargs, &block)
|
210
|
+
callbacks = self.class.send(:callbacks_for, method_name)[:around]
|
211
|
+
|
212
|
+
# Build callback chain from innermost (original method) to outermost
|
213
|
+
chain = callbacks.reverse.inject(
|
214
|
+
-> { original_method.bind(self).call(*args, **kwargs, &block) }
|
215
|
+
) do |inner, callback_method|
|
216
|
+
-> { send(callback_method) { inner.call } }
|
217
|
+
end
|
218
|
+
|
219
|
+
chain.call
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -46,7 +46,8 @@ module DSPy
|
|
46
46
|
input_schema: @signature_class.input_json_schema,
|
47
47
|
output_schema: @signature_class.output_json_schema,
|
48
48
|
few_shot_examples: new_prompt.few_shot_examples,
|
49
|
-
signature_class_name: @signature_class.name
|
49
|
+
signature_class_name: @signature_class.name,
|
50
|
+
schema_format: new_prompt.schema_format
|
50
51
|
)
|
51
52
|
|
52
53
|
instance.instance_variable_set(:@prompt, enhanced_prompt)
|
data/lib/dspy/lm.rb
CHANGED
@@ -31,15 +31,16 @@ require_relative 'structured_outputs_prompt'
|
|
31
31
|
module DSPy
|
32
32
|
class LM
|
33
33
|
extend T::Sig
|
34
|
-
attr_reader :model_id, :api_key, :model, :provider, :adapter
|
34
|
+
attr_reader :model_id, :api_key, :model, :provider, :adapter, :schema_format
|
35
35
|
|
36
|
-
def initialize(model_id, api_key: nil, **options)
|
36
|
+
def initialize(model_id, api_key: nil, schema_format: :json, **options)
|
37
37
|
@model_id = model_id
|
38
38
|
@api_key = api_key
|
39
|
-
|
39
|
+
@schema_format = schema_format
|
40
|
+
|
40
41
|
# Parse provider and model from model_id
|
41
42
|
@provider, @model = parse_model_id(model_id)
|
42
|
-
|
43
|
+
|
43
44
|
# Create appropriate adapter with options
|
44
45
|
@adapter = AdapterFactory.create(model_id, api_key: api_key, **options)
|
45
46
|
end
|
data/lib/dspy/module.rb
CHANGED
@@ -3,16 +3,23 @@
|
|
3
3
|
require 'sorbet-runtime'
|
4
4
|
require 'dry-configurable'
|
5
5
|
require_relative 'context'
|
6
|
+
require_relative 'callbacks'
|
6
7
|
|
7
8
|
module DSPy
|
8
9
|
class Module
|
9
10
|
extend T::Sig
|
10
11
|
extend T::Generic
|
11
12
|
include Dry::Configurable
|
13
|
+
include DSPy::Callbacks
|
12
14
|
|
13
15
|
# Per-instance LM configuration
|
14
16
|
setting :lm, default: nil
|
15
17
|
|
18
|
+
# Define callback hooks for forward method
|
19
|
+
create_before_callback :forward
|
20
|
+
create_after_callback :forward
|
21
|
+
create_around_callback :forward
|
22
|
+
|
16
23
|
# The main forward method that users will call is generic and type parameterized
|
17
24
|
sig do
|
18
25
|
type_parameters(:I, :O)
|
@@ -72,5 +79,31 @@ module DSPy
|
|
72
79
|
def lm
|
73
80
|
config.lm || DSPy.current_lm
|
74
81
|
end
|
82
|
+
|
83
|
+
# Save the module state to a JSON file
|
84
|
+
# Lightweight serialization for intermediate optimization trials
|
85
|
+
#
|
86
|
+
# @param path [String] Path to save the module state (JSON format)
|
87
|
+
sig { params(path: String).void }
|
88
|
+
def save(path)
|
89
|
+
require 'json'
|
90
|
+
require 'fileutils'
|
91
|
+
|
92
|
+
# Ensure parent directory exists
|
93
|
+
dir = File.dirname(path)
|
94
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
95
|
+
|
96
|
+
# Serialize module to JSON
|
97
|
+
File.write(path, JSON.pretty_generate(to_h))
|
98
|
+
end
|
99
|
+
|
100
|
+
# Default serialization method - subclasses can override
|
101
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
102
|
+
def to_h
|
103
|
+
{
|
104
|
+
class_name: self.class.name,
|
105
|
+
state: {}
|
106
|
+
}
|
107
|
+
end
|
75
108
|
end
|
76
109
|
end
|
data/lib/dspy/predict.rb
CHANGED
@@ -53,11 +53,18 @@ module DSPy
|
|
53
53
|
sig { returns(Prompt) }
|
54
54
|
attr_reader :prompt
|
55
55
|
|
56
|
+
# Mutable demos attribute for MIPROv2 compatibility
|
57
|
+
sig { returns(T.nilable(T::Array[FewShotExample])) }
|
58
|
+
attr_accessor :demos
|
59
|
+
|
56
60
|
sig { params(signature_class: T.class_of(Signature)).void }
|
57
61
|
def initialize(signature_class)
|
58
62
|
super()
|
59
63
|
@signature_class = signature_class
|
64
|
+
|
65
|
+
# Prompt will read schema_format from config automatically
|
60
66
|
@prompt = Prompt.from_signature(signature_class)
|
67
|
+
@demos = nil
|
61
68
|
end
|
62
69
|
|
63
70
|
# Reconstruct program from serialized hash
|
data/lib/dspy/prompt.rb
CHANGED
@@ -22,21 +22,39 @@ module DSPy
|
|
22
22
|
sig { returns(T.nilable(String)) }
|
23
23
|
attr_reader :signature_class_name
|
24
24
|
|
25
|
+
# Returns the effective schema format
|
26
|
+
# Precedence: instance variable (if not :json default) > config.lm > :json
|
27
|
+
sig { returns(Symbol) }
|
28
|
+
def schema_format
|
29
|
+
# If @schema_format was explicitly set to something other than :json, respect it
|
30
|
+
return @schema_format if @schema_format && @schema_format != :json
|
31
|
+
|
32
|
+
# Otherwise, read from config if available
|
33
|
+
DSPy.config.lm&.schema_format || @schema_format || :json
|
34
|
+
end
|
35
|
+
|
36
|
+
sig { returns(T.nilable(T.class_of(Signature))) }
|
37
|
+
attr_reader :signature_class
|
38
|
+
|
25
39
|
sig do
|
26
40
|
params(
|
27
41
|
instruction: String,
|
28
42
|
input_schema: T::Hash[Symbol, T.untyped],
|
29
43
|
output_schema: T::Hash[Symbol, T.untyped],
|
30
44
|
few_shot_examples: T::Array[FewShotExample],
|
31
|
-
signature_class_name: T.nilable(String)
|
45
|
+
signature_class_name: T.nilable(String),
|
46
|
+
schema_format: Symbol,
|
47
|
+
signature_class: T.nilable(T.class_of(Signature))
|
32
48
|
).void
|
33
49
|
end
|
34
|
-
def initialize(instruction:, input_schema:, output_schema:, few_shot_examples: [], signature_class_name: nil)
|
50
|
+
def initialize(instruction:, input_schema:, output_schema:, few_shot_examples: [], signature_class_name: nil, schema_format: :json, signature_class: nil)
|
35
51
|
@instruction = instruction
|
36
52
|
@few_shot_examples = few_shot_examples.freeze
|
37
53
|
@input_schema = input_schema.freeze
|
38
54
|
@output_schema = output_schema.freeze
|
39
55
|
@signature_class_name = signature_class_name
|
56
|
+
@schema_format = schema_format
|
57
|
+
@signature_class = signature_class
|
40
58
|
end
|
41
59
|
|
42
60
|
# Immutable update methods for optimization
|
@@ -47,7 +65,9 @@ module DSPy
|
|
47
65
|
input_schema: @input_schema,
|
48
66
|
output_schema: @output_schema,
|
49
67
|
few_shot_examples: @few_shot_examples,
|
50
|
-
signature_class_name: @signature_class_name
|
68
|
+
signature_class_name: @signature_class_name,
|
69
|
+
schema_format: @schema_format,
|
70
|
+
signature_class: @signature_class
|
51
71
|
)
|
52
72
|
end
|
53
73
|
|
@@ -58,7 +78,9 @@ module DSPy
|
|
58
78
|
input_schema: @input_schema,
|
59
79
|
output_schema: @output_schema,
|
60
80
|
few_shot_examples: new_examples,
|
61
|
-
signature_class_name: @signature_class_name
|
81
|
+
signature_class_name: @signature_class_name,
|
82
|
+
schema_format: @schema_format,
|
83
|
+
signature_class: @signature_class
|
62
84
|
)
|
63
85
|
end
|
64
86
|
|
@@ -72,16 +94,29 @@ module DSPy
|
|
72
94
|
sig { returns(String) }
|
73
95
|
def render_system_prompt
|
74
96
|
sections = []
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
97
|
+
|
98
|
+
case schema_format
|
99
|
+
when :baml
|
100
|
+
sections << "Your input schema fields are:"
|
101
|
+
sections << "```baml"
|
102
|
+
sections << render_baml_schema(@input_schema, :input)
|
103
|
+
sections << "```"
|
104
|
+
|
105
|
+
sections << "Your output schema fields are:"
|
106
|
+
sections << "```baml"
|
107
|
+
sections << render_baml_schema(@output_schema, :output)
|
108
|
+
sections << "```"
|
109
|
+
else # :json (default)
|
110
|
+
sections << "Your input schema fields are:"
|
111
|
+
sections << "```json"
|
112
|
+
sections << JSON.pretty_generate(@input_schema)
|
113
|
+
sections << "```"
|
114
|
+
|
115
|
+
sections << "Your output schema fields are:"
|
116
|
+
sections << "```json"
|
117
|
+
sections << JSON.pretty_generate(@output_schema)
|
118
|
+
sections << "```"
|
119
|
+
end
|
85
120
|
|
86
121
|
sections << ""
|
87
122
|
sections << "All interactions will be structured in the following way, with the appropriate values filled in."
|
@@ -148,32 +183,36 @@ module DSPy
|
|
148
183
|
few_shot_examples: @few_shot_examples.map(&:to_h),
|
149
184
|
input_schema: @input_schema,
|
150
185
|
output_schema: @output_schema,
|
151
|
-
signature_class_name: @signature_class_name
|
186
|
+
signature_class_name: @signature_class_name,
|
187
|
+
schema_format: @schema_format
|
152
188
|
}
|
153
189
|
end
|
154
190
|
|
155
191
|
sig { params(hash: T::Hash[Symbol, T.untyped]).returns(Prompt) }
|
156
192
|
def self.from_h(hash)
|
157
193
|
examples = (hash[:few_shot_examples] || []).map { |ex| FewShotExample.from_h(ex) }
|
158
|
-
|
194
|
+
|
159
195
|
new(
|
160
196
|
instruction: hash[:instruction] || "",
|
161
197
|
input_schema: hash[:input_schema] || {},
|
162
198
|
output_schema: hash[:output_schema] || {},
|
163
199
|
few_shot_examples: examples,
|
164
|
-
signature_class_name: hash[:signature_class_name]
|
200
|
+
signature_class_name: hash[:signature_class_name],
|
201
|
+
schema_format: hash[:schema_format] || :json
|
165
202
|
)
|
166
203
|
end
|
167
204
|
|
168
205
|
# Create prompt from signature class
|
169
|
-
sig { params(signature_class: T.class_of(Signature)).returns(Prompt) }
|
170
|
-
def self.from_signature(signature_class)
|
206
|
+
sig { params(signature_class: T.class_of(Signature), schema_format: Symbol).returns(Prompt) }
|
207
|
+
def self.from_signature(signature_class, schema_format: :json)
|
171
208
|
new(
|
172
209
|
instruction: signature_class.description || "Complete this task.",
|
173
210
|
input_schema: signature_class.input_json_schema,
|
174
211
|
output_schema: signature_class.output_json_schema,
|
175
212
|
few_shot_examples: [],
|
176
|
-
signature_class_name: signature_class.name
|
213
|
+
signature_class_name: signature_class.name,
|
214
|
+
schema_format: schema_format,
|
215
|
+
signature_class: signature_class
|
177
216
|
)
|
178
217
|
end
|
179
218
|
|
@@ -221,6 +260,37 @@ module DSPy
|
|
221
260
|
|
222
261
|
private
|
223
262
|
|
263
|
+
# Render BAML schema for input or output
|
264
|
+
sig { params(schema: T::Hash[Symbol, T.untyped], type: Symbol).returns(String) }
|
265
|
+
def render_baml_schema(schema, type)
|
266
|
+
# If we have a signature_class, use sorbet-baml's to_baml method with custom name
|
267
|
+
if @signature_class
|
268
|
+
begin
|
269
|
+
require 'sorbet_baml'
|
270
|
+
|
271
|
+
struct_class = type == :input ? @signature_class.input_struct_class : @signature_class.output_struct_class
|
272
|
+
if struct_class
|
273
|
+
# Generate a proper class name from signature class name
|
274
|
+
base_name = @signature_class_name || @signature_class.name || "Schema"
|
275
|
+
class_name = type == :input ? "#{base_name}Input" : "#{base_name}Output"
|
276
|
+
|
277
|
+
# Get raw BAML and replace the ugly class name
|
278
|
+
raw_baml = struct_class.to_baml
|
279
|
+
# Replace the class definition line with a proper name
|
280
|
+
return raw_baml.sub(/^class #<Class:0x[0-9a-f]+>/, "class #{class_name}")
|
281
|
+
end
|
282
|
+
rescue LoadError
|
283
|
+
# Fall back to manual BAML generation if sorbet_baml is not available
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# Fallback: generate BAML manually from schema
|
288
|
+
# This is a simple implementation that handles basic types
|
289
|
+
# For production use, sorbet-baml should be available
|
290
|
+
"# BAML schema generation requires sorbet-baml gem\n" \
|
291
|
+
"# Please install: gem install sorbet-baml"
|
292
|
+
end
|
293
|
+
|
224
294
|
# Recursively serialize complex objects for JSON representation
|
225
295
|
sig { params(obj: T.untyped).returns(T.untyped) }
|
226
296
|
def serialize_for_json(obj)
|