dspy 0.2.0 → 0.3.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.
@@ -6,70 +6,70 @@ require 'json'
6
6
  module DSPy
7
7
  module Tools
8
8
  # Base class for all Sorbet-based tools with DSL support
9
- class SorbetTool
9
+ class Base
10
10
  extend T::Sig
11
11
  extend T::Helpers
12
-
12
+
13
13
  class << self
14
14
  extend T::Sig
15
-
15
+
16
16
  sig { returns(T.nilable(String)) }
17
17
  attr_reader :tool_name_value, :tool_description_value
18
-
18
+
19
19
  # DSL method to set tool name
20
20
  sig { params(name: String).void }
21
21
  def tool_name(name)
22
22
  @tool_name_value = name
23
23
  end
24
-
24
+
25
25
  # DSL method to set tool description
26
26
  sig { params(description: String).void }
27
27
  def tool_description(description)
28
28
  @tool_description_value = description
29
29
  end
30
-
30
+
31
31
  # Get the JSON schema for the call method based on its Sorbet signature
32
32
  sig { returns(T::Hash[Symbol, T.untyped]) }
33
33
  def call_schema
34
34
  method_obj = instance_method(:call)
35
35
  sig_info = T::Utils.signature_for_method(method_obj)
36
-
36
+
37
37
  if sig_info.nil?
38
38
  # Fallback for methods without signatures
39
- return {
40
- type: :object,
41
- properties: {},
42
- required: []
39
+ return {
40
+ type: :object,
41
+ properties: {},
42
+ required: []
43
43
  }
44
44
  end
45
-
45
+
46
46
  properties = {}
47
47
  required = []
48
-
48
+
49
49
  # Handle positional arguments
50
50
  sig_info.arg_types.each do |param_name, param_type|
51
51
  next if param_name == :block # Skip block parameters
52
-
52
+
53
53
  properties[param_name] = {
54
54
  type: sorbet_type_to_json_schema(param_type)[:type],
55
55
  description: "Parameter #{param_name}"
56
56
  }
57
-
57
+
58
58
  # Check if parameter is required (not nilable)
59
59
  unless param_type.class.name.include?('Union') && param_type.name.include?('NilClass')
60
60
  required << param_name.to_s
61
61
  end
62
62
  end
63
-
63
+
64
64
  # Handle keyword arguments (more common in Ruby)
65
65
  sig_info.kwarg_types.each do |param_name, param_type|
66
66
  next if param_name == :block # Skip block parameters
67
-
67
+
68
68
  properties[param_name] = {
69
69
  type: sorbet_type_to_json_schema(param_type)[:type],
70
70
  description: "Parameter #{param_name}"
71
71
  }
72
-
72
+
73
73
  # Check if parameter is required by looking at required kwarg names
74
74
  if sig_info.req_kwarg_names.include?(param_name)
75
75
  required << param_name.to_s
@@ -77,22 +77,22 @@ module DSPy
77
77
  properties[param_name][:description] += " (optional)"
78
78
  end
79
79
  end
80
-
80
+
81
81
  {
82
82
  type: :object,
83
83
  properties: properties,
84
84
  required: required
85
85
  }
86
86
  end
87
-
87
+
88
88
  private
89
-
89
+
90
90
  # Convert Sorbet types to JSON Schema types
91
91
  sig { params(sorbet_type: T.untyped).returns(T::Hash[Symbol, T.untyped]) }
92
92
  def sorbet_type_to_json_schema(sorbet_type)
93
93
  if sorbet_type.is_a?(T::Types::Simple)
94
94
  raw_type = sorbet_type.raw_type
95
-
95
+
96
96
  if raw_type == String
97
97
  { type: :string }
98
98
  elsif raw_type == Integer
@@ -128,18 +128,18 @@ module DSPy
128
128
  end
129
129
  end
130
130
  end
131
-
131
+
132
132
  # Instance methods that tools can use
133
133
  sig { returns(String) }
134
134
  def name
135
135
  self.class.tool_name_value || self.class.name&.split('::')&.last&.downcase || 'unknown_tool'
136
136
  end
137
-
137
+
138
138
  sig { returns(String) }
139
139
  def description
140
140
  self.class.tool_description_value || "Tool: #{name}"
141
141
  end
142
-
142
+
143
143
  # Get the JSON schema string for the tool, formatted for LLM consumption
144
144
  sig { returns(String) }
145
145
  def schema
@@ -149,15 +149,15 @@ module DSPy
149
149
  description: description,
150
150
  parameters: schema_obj
151
151
  }
152
- JSON.pretty_generate(tool_info)
152
+ JSON.generate(tool_info)
153
153
  end
154
-
154
+
155
155
  # Dynamic call method for ReAct agent - parses JSON arguments and calls the typed method
156
156
  sig { params(args_json: T.untyped).returns(T.untyped) }
157
157
  def dynamic_call(args_json)
158
158
  # Parse arguments based on the call schema
159
159
  schema = self.class.call_schema
160
-
160
+
161
161
  if schema[:properties].empty?
162
162
  # No parameters - call without arguments
163
163
  call
@@ -175,7 +175,7 @@ module DSPy
175
175
  else
176
176
  return "Error: Expected Hash or JSON string"
177
177
  end
178
-
178
+
179
179
  # Convert string keys to symbols and validate types
180
180
  kwargs = {}
181
181
  schema[:properties].each do |param_name, param_schema|
@@ -186,17 +186,17 @@ module DSPy
186
186
  return "Error: Missing required parameter: #{key}"
187
187
  end
188
188
  end
189
-
189
+
190
190
  call(**kwargs)
191
191
  end
192
192
  rescue => e
193
193
  "Error: #{e.message}"
194
194
  end
195
-
195
+
196
196
  # Subclasses must implement their own call method with their own signature
197
-
197
+
198
198
  private
199
-
199
+
200
200
  # Convert argument to the expected type based on JSON schema
201
201
  sig { params(value: T.untyped, schema: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
202
202
  def convert_argument_type(value, schema)
data/lib/dspy/tools.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'tools/sorbet_tool'
3
+ require_relative 'tools/base'
4
4
 
5
5
  module DSPy
6
6
  # Define the tools available to the agent
data/lib/dspy.rb CHANGED
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
- require 'ruby_llm'
3
- require 'dry-schema'
2
+ require 'sorbet-runtime'
4
3
  require 'dry-configurable'
5
4
  require 'dry/logger'
6
- require_relative 'dspy/ext/dry_schema'
7
5
 
8
6
  module DSPy
9
7
  extend Dry::Configurable
@@ -13,9 +11,25 @@ module DSPy
13
11
  def self.logger
14
12
  config.logger
15
13
  end
14
+
15
+ # Convenient instrumentation configuration
16
+ def self.configure_instrumentation(&block)
17
+ require_relative 'dspy/instrumentation'
18
+ require_relative 'dspy/instrumentation/dry_monitor_bridge'
19
+
20
+ Instrumentation.configure(&block)
21
+
22
+ # Setup dry-monitor bridge for HTTP instrumentation
23
+ if Instrumentation.config.enabled
24
+ Instrumentation::DryMonitorBridge.setup!
25
+
26
+ # Auto-enable logger subscriber for event-based logging
27
+ require_relative 'dspy/subscribers/logger_subscriber'
28
+ @logger_subscriber ||= Subscribers::LoggerSubscriber.new
29
+ end
30
+ end
16
31
  end
17
32
 
18
- require_relative 'dspy/types'
19
33
  require_relative 'dspy/module'
20
34
  require_relative 'dspy/field'
21
35
  require_relative 'dspy/signature'
@@ -23,10 +37,5 @@ require_relative 'dspy/lm'
23
37
  require_relative 'dspy/predict'
24
38
  require_relative 'dspy/chain_of_thought'
25
39
  require_relative 'dspy/re_act'
40
+ require_relative 'dspy/subscribers/logger_subscriber'
26
41
  require_relative 'dspy/tools'
27
-
28
- # Sorbet implementation
29
- require_relative 'dspy/sorbet_signature'
30
- require_relative 'dspy/sorbet_module'
31
- require_relative 'dspy/sorbet_predict'
32
- require_relative 'dspy/sorbet_chain_of_thought'
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dspy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.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-06-01 00:00:00.000000000 Z
10
+ date: 2025-06-27 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: ruby_llm
13
+ name: dry-configurable
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
@@ -24,21 +24,21 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '1.0'
26
26
  - !ruby/object:Gem::Dependency
27
- name: dry-schema
27
+ name: dry-logger
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '1.14'
32
+ version: '1.0'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '1.14'
39
+ version: '1.0'
40
40
  - !ruby/object:Gem::Dependency
41
- name: dry-configurable
41
+ name: dry-monitor
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
@@ -52,61 +52,89 @@ dependencies:
52
52
  - !ruby/object:Gem::Version
53
53
  version: '1.0'
54
54
  - !ruby/object:Gem::Dependency
55
- name: dry-logger
55
+ name: async
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '1.0'
60
+ version: '2.23'
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '1.0'
67
+ version: '2.23'
68
68
  - !ruby/object:Gem::Dependency
69
- name: ruby-openai
69
+ name: openai
70
70
  requirement: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '8.0'
74
+ version: 0.9.0
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '8.0'
81
+ version: 0.9.0
82
82
  - !ruby/object:Gem::Dependency
83
- name: async
83
+ name: anthropic
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '2.23'
88
+ version: 1.1.0
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '2.23'
95
+ version: 1.1.0
96
96
  - !ruby/object:Gem::Dependency
97
- name: openai
97
+ name: ruby_llm
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.0'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: sorbet-runtime
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.5'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '0.5'
124
+ - !ruby/object:Gem::Dependency
125
+ name: sorbet-schema
98
126
  requirement: !ruby/object:Gem::Requirement
99
127
  requirements:
100
128
  - - "~>"
101
129
  - !ruby/object:Gem::Version
102
- version: 0.1.0.pre.alpha.4
130
+ version: '0.3'
103
131
  type: :runtime
104
132
  prerelease: false
105
133
  version_requirements: !ruby/object:Gem::Requirement
106
134
  requirements:
107
135
  - - "~>"
108
136
  - !ruby/object:Gem::Version
109
- version: 0.1.0.pre.alpha.4
137
+ version: '0.3'
110
138
  description: A Ruby implementation of DSPy, a framework for programming with large
111
139
  language models
112
140
  email:
@@ -118,21 +146,25 @@ files:
118
146
  - README.md
119
147
  - lib/dspy.rb
120
148
  - lib/dspy/chain_of_thought.rb
121
- - lib/dspy/ext/dry_schema.rb
122
149
  - lib/dspy/field.rb
150
+ - lib/dspy/instrumentation.rb
151
+ - lib/dspy/instrumentation/token_tracker.rb
123
152
  - lib/dspy/lm.rb
153
+ - lib/dspy/lm/adapter.rb
154
+ - lib/dspy/lm/adapter_factory.rb
155
+ - lib/dspy/lm/adapters/anthropic_adapter.rb
156
+ - lib/dspy/lm/adapters/openai_adapter.rb
157
+ - lib/dspy/lm/adapters/ruby_llm_adapter.rb
158
+ - lib/dspy/lm/errors.rb
159
+ - lib/dspy/lm/response.rb
124
160
  - lib/dspy/module.rb
125
161
  - lib/dspy/predict.rb
126
162
  - lib/dspy/re_act.rb
163
+ - lib/dspy/schema_adapters.rb
127
164
  - lib/dspy/signature.rb
128
- - lib/dspy/sorbet_chain_of_thought.rb
129
- - lib/dspy/sorbet_module.rb
130
- - lib/dspy/sorbet_predict.rb
131
- - lib/dspy/sorbet_re_act.rb
132
- - lib/dspy/sorbet_signature.rb
165
+ - lib/dspy/subscribers/logger_subscriber.rb
133
166
  - lib/dspy/tools.rb
134
- - lib/dspy/tools/sorbet_tool.rb
135
- - lib/dspy/types.rb
167
+ - lib/dspy/tools/base.rb
136
168
  homepage: https://github.com/vicentereig/dspy.rb
137
169
  licenses:
138
170
  - MIT
@@ -144,7 +176,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
144
176
  requirements:
145
177
  - - ">="
146
178
  - !ruby/object:Gem::Version
147
- version: 2.6.0
179
+ version: 3.3.0
148
180
  required_rubygems_version: !ruby/object:Gem::Requirement
149
181
  requirements:
150
182
  - - ">="
@@ -1,94 +0,0 @@
1
- require 'dry/schema/version'
2
-
3
- if Dry::Schema::VERSION > Gem::Version.new('1.15')
4
- raise 'Double check this monkey path before upgrading drys-schema.'
5
- end
6
-
7
- Dry::Schema.load_extensions(:json_schema)
8
- # Monkey patch Macros::Core to add meta method
9
- module Dry
10
- module Schema
11
- module Macros
12
- class Core
13
- def meta(metadata)
14
- schema_dsl.meta(name, metadata)
15
- self
16
- end
17
- end
18
- end
19
- end
20
- end
21
-
22
- # Monkey patch DSL to store metadata
23
- module Dry
24
- module Schema
25
- class DSL
26
- def meta(name, metadata)
27
- @metas ||= {}
28
- @metas[name] = metadata
29
- self
30
- end
31
-
32
- def metas
33
- @metas ||= {}
34
- end
35
-
36
- # Ensure metas are included in new instances
37
- alias_method :original_new, :new
38
- def new(**options, &block)
39
- options[:metas] = metas
40
- original_new(**options, &block)
41
- end
42
-
43
- # Ensure processor has access to metas
44
- alias_method :original_call, :call
45
- def call
46
- processor = original_call
47
- processor.instance_variable_set(:@schema_metas, metas)
48
- processor
49
- end
50
- end
51
- end
52
- end
53
-
54
- # Monkey patch Processor to expose schema_metas
55
- module Dry
56
- module Schema
57
- class Processor
58
- attr_reader :schema_metas
59
-
60
- # Add schema_metas accessor
61
- def schema_metas
62
- @schema_metas ||= {}
63
- end
64
- end
65
- end
66
- end
67
-
68
- # Directly monkey patch the JSON Schema generation
69
- module Dry
70
- module Schema
71
- module JSONSchema
72
- module SchemaMethods
73
- # Override the original json_schema method
74
- def json_schema(loose: false)
75
- compiler = SchemaCompiler.new(root: true, loose: loose)
76
- compiler.call(to_ast)
77
- result = compiler.to_hash
78
-
79
- # Add descriptions to properties from schema_metas
80
- if respond_to?(:schema_metas) && !schema_metas.empty?
81
- schema_metas.each do |key, meta|
82
- if meta[:description] && result[:properties][key]
83
- result[:properties][key][:description] = meta[:description]
84
- end
85
- end
86
- end
87
-
88
- result
89
- end
90
- end
91
- end
92
- end
93
- end
94
-
@@ -1,91 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require 'sorbet-runtime'
5
- require_relative 'sorbet_predict'
6
- require_relative 'sorbet_signature'
7
-
8
- module DSPy
9
- # Enhances prediction by encouraging step-by-step reasoning
10
- # before providing a final answer using Sorbet signatures.
11
- class SorbetChainOfThought < SorbetPredict
12
- extend T::Sig
13
-
14
- FieldDescriptor = DSPy::SorbetSignature::FieldDescriptor
15
-
16
- sig { params(signature_class: T.class_of(DSPy::SorbetSignature)).void }
17
- def initialize(signature_class)
18
- @original_signature = signature_class
19
-
20
- # Create enhanced output struct with reasoning
21
- enhanced_output_struct = create_enhanced_output_struct(signature_class)
22
-
23
- # Create enhanced signature class
24
- enhanced_signature = Class.new(DSPy::SorbetSignature) do
25
- # Set the description
26
- description "#{signature_class.description} Think step by step."
27
-
28
- # Use the same input struct and copy field descriptors
29
- @input_struct_class = signature_class.input_struct_class
30
- @input_field_descriptors = signature_class.instance_variable_get(:@input_field_descriptors) || {}
31
-
32
- # Use the enhanced output struct and create field descriptors for it
33
- @output_struct_class = enhanced_output_struct
34
-
35
- # Create field descriptors for the enhanced output struct
36
- @output_field_descriptors = {}
37
-
38
- # Copy original output field descriptors
39
- original_output_descriptors = signature_class.instance_variable_get(:@output_field_descriptors) || {}
40
- @output_field_descriptors.merge!(original_output_descriptors)
41
-
42
- # Add reasoning field descriptor
43
- @output_field_descriptors[:reasoning] = FieldDescriptor.new(String, nil)
44
-
45
- class << self
46
- attr_reader :input_struct_class, :output_struct_class
47
- end
48
- end
49
-
50
- # Call parent constructor with enhanced signature
51
- super(enhanced_signature)
52
- end
53
-
54
- private
55
-
56
- sig { params(signature_class: T.class_of(DSPy::SorbetSignature)).returns(T.class_of(T::Struct)) }
57
- def create_enhanced_output_struct(signature_class)
58
- # Get original output props
59
- original_props = signature_class.output_struct_class.props
60
-
61
- # Create new struct class with reasoning added
62
- Class.new(T::Struct) do
63
- # Add all original fields
64
- original_props.each do |name, prop|
65
- # Extract the type and other options
66
- type = prop[:type]
67
- options = prop.except(:type, :type_object, :accessor_key, :sensitivity, :redaction)
68
-
69
- # Handle default values
70
- if options[:default]
71
- const name, type, default: options[:default]
72
- elsif options[:factory]
73
- const name, type, factory: options[:factory]
74
- else
75
- const name, type
76
- end
77
- end
78
-
79
- # Add reasoning field
80
- const :reasoning, String
81
- end
82
- end
83
-
84
- sig { override.returns(T::Hash[Symbol, T.untyped]) }
85
- def generate_example_output
86
- example = super
87
- example[:reasoning] = "Let me think through this step by step..."
88
- example
89
- end
90
- end
91
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'sorbet-runtime'
4
-
5
- module DSPy
6
- class SorbetModule
7
- extend T::Sig
8
- extend T::Generic
9
-
10
- # The main forward method that users will call is generic and type parameterized
11
- sig do
12
- type_parameters(:I, :O)
13
- .params(
14
- input_values: T.type_parameter(:I)
15
- )
16
- .returns(T.type_parameter(:O))
17
- end
18
- def forward(**input_values)
19
- # Cast the result of forward_untyped to the expected output type
20
- T.cast(forward_untyped(**input_values), T.type_parameter(:O))
21
- end
22
-
23
- # The implementation method that subclasses must override
24
- sig { params(input_values: T.untyped).returns(T.untyped) }
25
- def forward_untyped(**input_values)
26
- raise NotImplementedError, "Subclasses must implement forward_untyped method"
27
- end
28
-
29
- # The main call method that users will call is generic and type parameterized
30
- sig do
31
- type_parameters(:I, :O)
32
- .params(
33
- input_values: T.type_parameter(:I)
34
- )
35
- .returns(T.type_parameter(:O))
36
- end
37
- def call(**input_values)
38
- forward(**input_values)
39
- end
40
-
41
- # The implementation method for call
42
- sig { params(input_values: T.untyped).returns(T.untyped) }
43
- def call_untyped(**input_values)
44
- forward_untyped(**input_values)
45
- end
46
- end
47
- end