dspy 0.29.1 → 0.30.1
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/LICENSE +45 -0
- data/README.md +159 -95
- data/lib/dspy/callbacks.rb +93 -19
- data/lib/dspy/context.rb +101 -5
- data/lib/dspy/errors.rb +19 -1
- data/lib/dspy/{datasets.rb → evals/version.rb} +2 -3
- data/lib/dspy/{evaluate.rb → evals.rb} +373 -110
- data/lib/dspy/mixins/instruction_updatable.rb +22 -0
- data/lib/dspy/module.rb +213 -17
- data/lib/dspy/observability.rb +40 -182
- data/lib/dspy/predict.rb +10 -2
- data/lib/dspy/propose/dataset_summary_generator.rb +28 -18
- data/lib/dspy/re_act.rb +21 -0
- data/lib/dspy/schema/sorbet_json_schema.rb +302 -0
- data/lib/dspy/schema/version.rb +7 -0
- data/lib/dspy/schema.rb +4 -0
- data/lib/dspy/structured_outputs_prompt.rb +48 -0
- data/lib/dspy/support/warning_filters.rb +27 -0
- data/lib/dspy/teleprompt/gepa.rb +9 -588
- data/lib/dspy/teleprompt/instruction_updates.rb +94 -0
- data/lib/dspy/teleprompt/teleprompter.rb +6 -6
- data/lib/dspy/teleprompt/utils.rb +5 -65
- data/lib/dspy/type_system/sorbet_json_schema.rb +2 -299
- data/lib/dspy/version.rb +1 -1
- data/lib/dspy.rb +39 -7
- metadata +18 -61
- data/lib/dspy/code_act.rb +0 -477
- data/lib/dspy/datasets/ade.rb +0 -90
- data/lib/dspy/observability/async_span_processor.rb +0 -250
- data/lib/dspy/observability/observation_type.rb +0 -65
- data/lib/dspy/optimizers/gaussian_process.rb +0 -141
- data/lib/dspy/teleprompt/mipro_v2.rb +0 -1672
- data/lib/gepa/api.rb +0 -61
- data/lib/gepa/core/engine.rb +0 -226
- data/lib/gepa/core/evaluation_batch.rb +0 -26
- data/lib/gepa/core/result.rb +0 -92
- data/lib/gepa/core/state.rb +0 -231
- data/lib/gepa/logging/experiment_tracker.rb +0 -54
- data/lib/gepa/logging/logger.rb +0 -57
- data/lib/gepa/logging.rb +0 -9
- data/lib/gepa/proposer/base.rb +0 -27
- data/lib/gepa/proposer/merge_proposer.rb +0 -424
- data/lib/gepa/proposer/reflective_mutation/base.rb +0 -48
- data/lib/gepa/proposer/reflective_mutation/reflective_mutation.rb +0 -188
- data/lib/gepa/strategies/batch_sampler.rb +0 -91
- data/lib/gepa/strategies/candidate_selector.rb +0 -97
- data/lib/gepa/strategies/component_selector.rb +0 -57
- data/lib/gepa/strategies/instruction_proposal.rb +0 -120
- data/lib/gepa/telemetry.rb +0 -122
- data/lib/gepa/utils/pareto.rb +0 -119
- data/lib/gepa.rb +0 -21
data/lib/dspy/context.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'securerandom'
|
|
4
|
+
require 'json'
|
|
4
5
|
|
|
5
6
|
module DSPy
|
|
6
7
|
class Context
|
|
@@ -32,7 +33,8 @@ module DSPy
|
|
|
32
33
|
context = {
|
|
33
34
|
trace_id: SecureRandom.uuid,
|
|
34
35
|
span_stack: [],
|
|
35
|
-
otel_span_stack: []
|
|
36
|
+
otel_span_stack: [],
|
|
37
|
+
module_stack: []
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
# Set in both Thread and Fiber storage
|
|
@@ -47,14 +49,15 @@ module DSPy
|
|
|
47
49
|
span_id = SecureRandom.uuid
|
|
48
50
|
parent_span_id = current[:span_stack].last
|
|
49
51
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
50
|
-
|
|
52
|
+
sanitized_attributes = sanitize_span_attributes(attributes)
|
|
53
|
+
|
|
51
54
|
# Prepare attributes with context information
|
|
52
55
|
span_attributes = {
|
|
53
56
|
trace_id: current[:trace_id],
|
|
54
57
|
span_id: span_id,
|
|
55
58
|
parent_span_id: parent_span_id,
|
|
56
59
|
operation: operation,
|
|
57
|
-
**
|
|
60
|
+
**sanitized_attributes
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
# Log span start with proper hierarchy (internal logging only)
|
|
@@ -67,7 +70,7 @@ module DSPy
|
|
|
67
70
|
# Use OpenTelemetry's proper context management for nesting
|
|
68
71
|
if DSPy::Observability.enabled? && DSPy::Observability.tracer
|
|
69
72
|
# Prepare attributes and add trace name for root spans
|
|
70
|
-
span_attributes =
|
|
73
|
+
span_attributes = sanitized_attributes.transform_keys(&:to_s).reject { |k, v| v.nil? }
|
|
71
74
|
|
|
72
75
|
# Set trace name if this is likely a root span (no parent in our stack)
|
|
73
76
|
if current[:span_stack].length == 1 # This will be the first span
|
|
@@ -156,6 +159,48 @@ module DSPy
|
|
|
156
159
|
end
|
|
157
160
|
end
|
|
158
161
|
end
|
|
162
|
+
|
|
163
|
+
def with_module(module_instance, label: nil)
|
|
164
|
+
stack = module_stack
|
|
165
|
+
entry = build_module_entry(module_instance, label)
|
|
166
|
+
stack.push(entry)
|
|
167
|
+
yield
|
|
168
|
+
ensure
|
|
169
|
+
if stack.last.equal?(entry)
|
|
170
|
+
stack.pop
|
|
171
|
+
else
|
|
172
|
+
stack.delete(entry)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def module_stack
|
|
177
|
+
current[:module_stack] ||= []
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def module_context_attributes
|
|
181
|
+
stack = module_stack
|
|
182
|
+
return {} if stack.empty?
|
|
183
|
+
|
|
184
|
+
path = stack.map do |entry|
|
|
185
|
+
{
|
|
186
|
+
id: entry[:id],
|
|
187
|
+
class: entry[:class],
|
|
188
|
+
label: entry[:label]
|
|
189
|
+
}
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
ancestry_token = path.map { |node| node[:id] }.join('>')
|
|
193
|
+
|
|
194
|
+
{
|
|
195
|
+
module_path: path,
|
|
196
|
+
module_root: path.first,
|
|
197
|
+
module_leaf: path.last,
|
|
198
|
+
module_scope: {
|
|
199
|
+
ancestry_token: ancestry_token,
|
|
200
|
+
depth: path.length
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
end
|
|
159
204
|
|
|
160
205
|
def clear!
|
|
161
206
|
# Clear both the thread-specific key and the legacy key
|
|
@@ -173,6 +218,57 @@ module DSPy
|
|
|
173
218
|
rescue
|
|
174
219
|
false
|
|
175
220
|
end
|
|
221
|
+
|
|
222
|
+
def sanitize_span_attributes(attributes)
|
|
223
|
+
attributes.each_with_object({}) do |(key, value), acc|
|
|
224
|
+
sanitized_value = sanitize_attribute_value(value)
|
|
225
|
+
acc[key] = sanitized_value
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def sanitize_attribute_value(value)
|
|
230
|
+
case value
|
|
231
|
+
when nil, String, Integer, Float, TrueClass, FalseClass
|
|
232
|
+
value
|
|
233
|
+
when Time
|
|
234
|
+
value.iso8601(3)
|
|
235
|
+
when Array
|
|
236
|
+
begin
|
|
237
|
+
JSON.generate(value.map { |item| sanitize_attribute_value(item) })
|
|
238
|
+
rescue StandardError
|
|
239
|
+
value.map(&:to_s).to_s
|
|
240
|
+
end
|
|
241
|
+
when Hash
|
|
242
|
+
begin
|
|
243
|
+
sanitized_hash = value.each_with_object({}) do |(k, v), hash|
|
|
244
|
+
sanitized = sanitize_attribute_value(v)
|
|
245
|
+
hash[k.to_s] = sanitized unless sanitized.nil?
|
|
246
|
+
end
|
|
247
|
+
JSON.generate(sanitized_hash)
|
|
248
|
+
rescue StandardError
|
|
249
|
+
value.to_s
|
|
250
|
+
end
|
|
251
|
+
else
|
|
252
|
+
if defined?(T::Struct) && value.is_a?(T::Struct)
|
|
253
|
+
begin
|
|
254
|
+
struct_hash = value.to_h.transform_keys(&:to_s).transform_values { |v| sanitize_attribute_value(v) }
|
|
255
|
+
JSON.generate(struct_hash)
|
|
256
|
+
rescue StandardError
|
|
257
|
+
value.to_s
|
|
258
|
+
end
|
|
259
|
+
else
|
|
260
|
+
value.respond_to?(:to_json) ? value.to_json : value.to_s
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def build_module_entry(module_instance, explicit_label)
|
|
266
|
+
{
|
|
267
|
+
id: (module_instance.respond_to?(:module_scope_id) ? module_instance.module_scope_id : SecureRandom.uuid),
|
|
268
|
+
class: module_instance.class.name,
|
|
269
|
+
label: explicit_label || (module_instance.respond_to?(:module_scope_label) ? module_instance.module_scope_label : nil)
|
|
270
|
+
}
|
|
271
|
+
end
|
|
176
272
|
end
|
|
177
273
|
end
|
|
178
|
-
end
|
|
274
|
+
end
|
data/lib/dspy/errors.rb
CHANGED
|
@@ -28,4 +28,22 @@ module DSPy
|
|
|
28
28
|
MESSAGE
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
class InstructionUpdateError < Error
|
|
33
|
+
def self.missing_instruction_capability(module_class)
|
|
34
|
+
new(<<~MESSAGE)
|
|
35
|
+
#{module_class} must implement `with_instruction(new_instruction)` to support DSPy teleprompters.
|
|
36
|
+
|
|
37
|
+
Update the module to return a new instance with the provided instruction, or opt out of teleprompter optimizers.
|
|
38
|
+
MESSAGE
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.missing_examples_capability(module_class)
|
|
42
|
+
new(<<~MESSAGE)
|
|
43
|
+
#{module_class} must implement `with_examples(few_shot_examples)` to support DSPy teleprompters.
|
|
44
|
+
|
|
45
|
+
Update the module to return a new instance with the provided examples, or opt out of example optimization flows.
|
|
46
|
+
MESSAGE
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|