dspy 0.24.1 → 0.25.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/README.md +3 -1
- data/lib/dspy/chain_of_thought.rb +5 -3
- data/lib/dspy/context.rb +56 -12
- data/lib/dspy/lm/adapters/openai/schema_converter.rb +63 -3
- data/lib/dspy/lm/retry_handler.rb +7 -3
- data/lib/dspy/lm.rb +16 -13
- data/lib/dspy/observability/async_span_processor.rb +274 -0
- data/lib/dspy/observability.rb +29 -11
- data/lib/dspy/predict.rb +2 -1
- data/lib/dspy/teleprompt/gepa.rb +329 -772
- data/lib/dspy/utils/serialization.rb +35 -0
- data/lib/dspy/version.rb +1 -1
- data/lib/dspy.rb +30 -25
- metadata +6 -4
@@ -0,0 +1,35 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module DSPy
|
5
|
+
module Utils
|
6
|
+
class Serialization
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
# Deep serializes any T::Struct objects to hashes for observability
|
10
|
+
sig { params(obj: T.untyped).returns(T.untyped) }
|
11
|
+
def self.deep_serialize(obj)
|
12
|
+
case obj
|
13
|
+
when T::Struct
|
14
|
+
# Use the serialize method to convert to a plain hash
|
15
|
+
deep_serialize(obj.serialize)
|
16
|
+
when Hash
|
17
|
+
# Recursively serialize hash values
|
18
|
+
obj.transform_values { |v| deep_serialize(v) }
|
19
|
+
when Array
|
20
|
+
# Recursively serialize array elements
|
21
|
+
obj.map { |v| deep_serialize(v) }
|
22
|
+
else
|
23
|
+
# Return primitive values as-is
|
24
|
+
obj
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Serializes an object to JSON with proper T::Struct handling
|
29
|
+
sig { params(obj: T.untyped).returns(String) }
|
30
|
+
def self.to_json(obj)
|
31
|
+
deep_serialize(obj).to_json
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/dspy/version.rb
CHANGED
data/lib/dspy.rb
CHANGED
@@ -14,10 +14,10 @@ require_relative 'dspy/events/types'
|
|
14
14
|
|
15
15
|
module DSPy
|
16
16
|
extend Dry::Configurable
|
17
|
-
|
17
|
+
|
18
18
|
setting :lm
|
19
19
|
setting :logger, default: Dry.Logger(:dspy, formatter: :string)
|
20
|
-
|
20
|
+
|
21
21
|
# Structured output configuration for LLM providers
|
22
22
|
setting :structured_outputs do
|
23
23
|
setting :openai, default: false
|
@@ -27,7 +27,7 @@ module DSPy
|
|
27
27
|
setting :max_retries, default: 3
|
28
28
|
setting :fallback_enabled, default: true
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
# Test mode disables sleeps in retry logic
|
32
32
|
setting :test_mode, default: false
|
33
33
|
|
@@ -35,14 +35,13 @@ module DSPy
|
|
35
35
|
@logger ||= create_logger
|
36
36
|
end
|
37
37
|
|
38
|
-
def self.log(
|
38
|
+
def self.log(event_name, **attributes)
|
39
39
|
# Return nil early if logger is not configured (backward compatibility)
|
40
40
|
return nil unless logger
|
41
|
-
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
|
42
|
+
# Direct logging - simple and straightforward
|
43
|
+
emit_log(event_name, attributes)
|
44
|
+
|
46
45
|
# Return nil to maintain backward compatibility
|
47
46
|
nil
|
48
47
|
end
|
@@ -53,7 +52,7 @@ module DSPy
|
|
53
52
|
event_obj = event_name_or_object
|
54
53
|
event_name = event_obj.name
|
55
54
|
attributes = event_obj.to_attributes
|
56
|
-
|
55
|
+
|
57
56
|
# For LLM events, use OpenTelemetry semantic conventions for spans
|
58
57
|
if event_obj.is_a?(DSPy::Events::LLMEvent)
|
59
58
|
otel_attributes = event_obj.to_otel_attributes
|
@@ -65,44 +64,50 @@ module DSPy
|
|
65
64
|
# Handle string event names (backward compatibility)
|
66
65
|
event_name = event_name_or_object
|
67
66
|
raise ArgumentError, "Event name cannot be nil" if event_name.nil?
|
68
|
-
|
67
|
+
|
69
68
|
# Handle nil attributes
|
70
69
|
attributes = {} if attributes.nil?
|
71
|
-
|
70
|
+
|
72
71
|
# Create OpenTelemetry span for the event if observability is enabled
|
73
72
|
create_event_span(event_name, attributes)
|
74
73
|
end
|
75
|
-
|
74
|
+
|
76
75
|
# Perform the actual logging (original DSPy.log behavior)
|
77
|
-
emit_log(event_name, attributes)
|
78
|
-
|
76
|
+
# emit_log(event_name, attributes)
|
77
|
+
|
79
78
|
# Notify event listeners
|
80
79
|
events.notify(event_name, attributes)
|
81
80
|
end
|
82
81
|
|
83
82
|
def self.events
|
84
|
-
@event_registry ||= DSPy::EventRegistry.new
|
83
|
+
@event_registry ||= DSPy::EventRegistry.new.tap do |registry|
|
84
|
+
# Subscribe logger to all events - use a proc that calls logger each time
|
85
|
+
# to support mocking in tests
|
86
|
+
registry.subscribe('*') { |event_name, attributes|
|
87
|
+
emit_log(event_name, attributes) if logger
|
88
|
+
}
|
89
|
+
end
|
85
90
|
end
|
86
91
|
|
87
92
|
private
|
88
93
|
|
89
94
|
def self.emit_log(event_name, attributes)
|
90
95
|
return unless logger
|
91
|
-
|
96
|
+
|
92
97
|
# Merge context automatically (but don't include span_stack)
|
93
98
|
context = Context.current.dup
|
94
99
|
context.delete(:span_stack)
|
95
100
|
attributes = context.merge(attributes)
|
96
101
|
attributes[:event] = event_name
|
97
|
-
|
102
|
+
|
98
103
|
# Use Dry::Logger's structured logging
|
99
104
|
logger.info(attributes)
|
100
105
|
end
|
101
106
|
|
102
107
|
# Internal events that should not create OpenTelemetry spans
|
103
108
|
INTERNAL_EVENTS = [
|
104
|
-
'span.start',
|
105
|
-
'span.end',
|
109
|
+
'span.start',
|
110
|
+
'span.end',
|
106
111
|
'span.attributes',
|
107
112
|
'observability.disabled',
|
108
113
|
'observability.error',
|
@@ -114,11 +119,11 @@ module DSPy
|
|
114
119
|
def self.create_event_span(event_name, attributes)
|
115
120
|
return unless DSPy::Observability.enabled?
|
116
121
|
return if INTERNAL_EVENTS.include?(event_name)
|
117
|
-
|
122
|
+
|
118
123
|
begin
|
119
124
|
# Flatten nested hashes for OpenTelemetry span attributes
|
120
125
|
flattened_attributes = flatten_attributes(attributes)
|
121
|
-
|
126
|
+
|
122
127
|
# Create and immediately finish a span for this event
|
123
128
|
# Events are instant moments in time, not ongoing operations
|
124
129
|
span = DSPy::Observability.start_span(event_name, flattened_attributes)
|
@@ -137,21 +142,21 @@ module DSPy
|
|
137
142
|
def self.flatten_attributes(attributes, parent_key = '', result = {})
|
138
143
|
attributes.each do |key, value|
|
139
144
|
new_key = parent_key.empty? ? key.to_s : "#{parent_key}.#{key}"
|
140
|
-
|
145
|
+
|
141
146
|
if value.is_a?(Hash)
|
142
147
|
flatten_attributes(value, new_key, result)
|
143
148
|
else
|
144
149
|
result[new_key] = value
|
145
150
|
end
|
146
151
|
end
|
147
|
-
|
152
|
+
|
148
153
|
result
|
149
154
|
end
|
150
155
|
|
151
156
|
def self.create_logger
|
152
157
|
env = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
153
158
|
log_output = ENV['DSPY_LOG'] # Allow override
|
154
|
-
|
159
|
+
|
155
160
|
case env
|
156
161
|
when 'test'
|
157
162
|
# Test: key=value format to log/test.log (or override)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dspy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.25.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vicente Reig Rincón de Arellano
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-09-
|
10
|
+
date: 2025-09-07 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: dry-configurable
|
@@ -43,14 +43,14 @@ dependencies:
|
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '2.
|
46
|
+
version: '2.29'
|
47
47
|
type: :runtime
|
48
48
|
prerelease: false
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: '2.
|
53
|
+
version: '2.29'
|
54
54
|
- !ruby/object:Gem::Dependency
|
55
55
|
name: openai
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -238,6 +238,7 @@ files:
|
|
238
238
|
- lib/dspy/mixins/type_coercion.rb
|
239
239
|
- lib/dspy/module.rb
|
240
240
|
- lib/dspy/observability.rb
|
241
|
+
- lib/dspy/observability/async_span_processor.rb
|
241
242
|
- lib/dspy/predict.rb
|
242
243
|
- lib/dspy/prediction.rb
|
243
244
|
- lib/dspy/prompt.rb
|
@@ -262,6 +263,7 @@ files:
|
|
262
263
|
- lib/dspy/tools/text_processing_toolset.rb
|
263
264
|
- lib/dspy/tools/toolset.rb
|
264
265
|
- lib/dspy/type_serializer.rb
|
266
|
+
- lib/dspy/utils/serialization.rb
|
265
267
|
- lib/dspy/version.rb
|
266
268
|
homepage: https://github.com/vicentereig/dspy.rb
|
267
269
|
licenses:
|