dspy 0.1.0 → 0.2.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,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sorbet-runtime'
4
+ require 'json'
5
+
6
+ module DSPy
7
+ module Tools
8
+ # Base class for all Sorbet-based tools with DSL support
9
+ class SorbetTool
10
+ extend T::Sig
11
+ extend T::Helpers
12
+
13
+ class << self
14
+ extend T::Sig
15
+
16
+ sig { returns(T.nilable(String)) }
17
+ attr_reader :tool_name_value, :tool_description_value
18
+
19
+ # DSL method to set tool name
20
+ sig { params(name: String).void }
21
+ def tool_name(name)
22
+ @tool_name_value = name
23
+ end
24
+
25
+ # DSL method to set tool description
26
+ sig { params(description: String).void }
27
+ def tool_description(description)
28
+ @tool_description_value = description
29
+ end
30
+
31
+ # Get the JSON schema for the call method based on its Sorbet signature
32
+ sig { returns(T::Hash[Symbol, T.untyped]) }
33
+ def call_schema
34
+ method_obj = instance_method(:call)
35
+ sig_info = T::Utils.signature_for_method(method_obj)
36
+
37
+ if sig_info.nil?
38
+ # Fallback for methods without signatures
39
+ return {
40
+ type: :object,
41
+ properties: {},
42
+ required: []
43
+ }
44
+ end
45
+
46
+ properties = {}
47
+ required = []
48
+
49
+ # Handle positional arguments
50
+ sig_info.arg_types.each do |param_name, param_type|
51
+ next if param_name == :block # Skip block parameters
52
+
53
+ properties[param_name] = {
54
+ type: sorbet_type_to_json_schema(param_type)[:type],
55
+ description: "Parameter #{param_name}"
56
+ }
57
+
58
+ # Check if parameter is required (not nilable)
59
+ unless param_type.class.name.include?('Union') && param_type.name.include?('NilClass')
60
+ required << param_name.to_s
61
+ end
62
+ end
63
+
64
+ # Handle keyword arguments (more common in Ruby)
65
+ sig_info.kwarg_types.each do |param_name, param_type|
66
+ next if param_name == :block # Skip block parameters
67
+
68
+ properties[param_name] = {
69
+ type: sorbet_type_to_json_schema(param_type)[:type],
70
+ description: "Parameter #{param_name}"
71
+ }
72
+
73
+ # Check if parameter is required by looking at required kwarg names
74
+ if sig_info.req_kwarg_names.include?(param_name)
75
+ required << param_name.to_s
76
+ else
77
+ properties[param_name][:description] += " (optional)"
78
+ end
79
+ end
80
+
81
+ {
82
+ type: :object,
83
+ properties: properties,
84
+ required: required
85
+ }
86
+ end
87
+
88
+ private
89
+
90
+ # Convert Sorbet types to JSON Schema types
91
+ sig { params(sorbet_type: T.untyped).returns(T::Hash[Symbol, T.untyped]) }
92
+ def sorbet_type_to_json_schema(sorbet_type)
93
+ if sorbet_type.is_a?(T::Types::Simple)
94
+ raw_type = sorbet_type.raw_type
95
+
96
+ if raw_type == String
97
+ { type: :string }
98
+ elsif raw_type == Integer
99
+ { type: :integer }
100
+ elsif raw_type == Float
101
+ { type: :number }
102
+ elsif raw_type == Numeric
103
+ { type: :number }
104
+ elsif raw_type == TrueClass || raw_type == FalseClass
105
+ { type: :boolean }
106
+ elsif raw_type == T::Boolean
107
+ { type: :boolean }
108
+ else
109
+ { type: :string, description: "#{raw_type} (converted to string)" }
110
+ end
111
+ elsif sorbet_type.is_a?(T::Types::Union)
112
+ # Handle nilable types
113
+ non_nil_types = sorbet_type.types.reject { |t| t == T::Utils.coerce(NilClass) }
114
+ if non_nil_types.length == 1
115
+ result = sorbet_type_to_json_schema(non_nil_types.first)
116
+ result[:description] = "#{result[:description] || ''} (optional)".strip
117
+ result
118
+ else
119
+ { type: :string, description: "Union type (converted to string)" }
120
+ end
121
+ elsif sorbet_type.is_a?(T::Types::TypedArray)
122
+ {
123
+ type: :array,
124
+ items: sorbet_type_to_json_schema(sorbet_type.type)
125
+ }
126
+ else
127
+ { type: :string, description: "#{sorbet_type} (converted to string)" }
128
+ end
129
+ end
130
+ end
131
+
132
+ # Instance methods that tools can use
133
+ sig { returns(String) }
134
+ def name
135
+ self.class.tool_name_value || self.class.name&.split('::')&.last&.downcase || 'unknown_tool'
136
+ end
137
+
138
+ sig { returns(String) }
139
+ def description
140
+ self.class.tool_description_value || "Tool: #{name}"
141
+ end
142
+
143
+ # Get the JSON schema string for the tool, formatted for LLM consumption
144
+ sig { returns(String) }
145
+ def schema
146
+ schema_obj = self.class.call_schema
147
+ tool_info = {
148
+ name: name,
149
+ description: description,
150
+ parameters: schema_obj
151
+ }
152
+ JSON.pretty_generate(tool_info)
153
+ end
154
+
155
+ # Dynamic call method for ReAct agent - parses JSON arguments and calls the typed method
156
+ sig { params(args_json: T.untyped).returns(T.untyped) }
157
+ def dynamic_call(args_json)
158
+ # Parse arguments based on the call schema
159
+ schema = self.class.call_schema
160
+
161
+ if schema[:properties].empty?
162
+ # No parameters - call without arguments
163
+ call
164
+ else
165
+ # Parse arguments and call with keyword arguments
166
+ args = case args_json
167
+ when Hash
168
+ args_json
169
+ when String
170
+ begin
171
+ JSON.parse(args_json)
172
+ rescue JSON::ParserError
173
+ return "Error: Invalid JSON input"
174
+ end
175
+ else
176
+ return "Error: Expected Hash or JSON string"
177
+ end
178
+
179
+ # Convert string keys to symbols and validate types
180
+ kwargs = {}
181
+ schema[:properties].each do |param_name, param_schema|
182
+ key = param_name.to_s
183
+ if args.key?(key)
184
+ kwargs[param_name] = convert_argument_type(args[key], param_schema)
185
+ elsif schema[:required].include?(key)
186
+ return "Error: Missing required parameter: #{key}"
187
+ end
188
+ end
189
+
190
+ call(**kwargs)
191
+ end
192
+ rescue => e
193
+ "Error: #{e.message}"
194
+ end
195
+
196
+ # Subclasses must implement their own call method with their own signature
197
+
198
+ private
199
+
200
+ # Convert argument to the expected type based on JSON schema
201
+ sig { params(value: T.untyped, schema: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
202
+ def convert_argument_type(value, schema)
203
+ case schema[:type]
204
+ when :integer
205
+ value.is_a?(Integer) ? value : value.to_i
206
+ when :number
207
+ # Always convert to Float for :number types to ensure compatibility with strict Float signatures
208
+ value.to_f
209
+ when :boolean
210
+ case value
211
+ when true, false
212
+ value
213
+ when "true", "1", 1
214
+ true
215
+ when "false", "0", 0
216
+ false
217
+ else
218
+ !!value
219
+ end
220
+ else
221
+ value.to_s
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
data/lib/dspy/tools.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'tools/sorbet_tool'
4
+
5
+ module DSPy
6
+ # Define the tools available to the agent
7
+ module Tools
8
+ class Tool
9
+ attr_reader :name, :description
10
+
11
+ def initialize(name, description)
12
+ @name = name
13
+ @description = description
14
+ end
15
+
16
+ def call(input)
17
+ raise NotImplementedError, "Subclasses must implement the call method"
18
+ end
19
+ end
20
+ end
21
+ end
data/lib/dspy/types.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Types
2
+ include Dry.Types()
3
+ end
data/lib/dspy.rb CHANGED
@@ -1,5 +1,32 @@
1
1
  # frozen_string_literal: true
2
+ require 'ruby_llm'
3
+ require 'dry-schema'
4
+ require 'dry-configurable'
5
+ require 'dry/logger'
6
+ require_relative 'dspy/ext/dry_schema'
2
7
 
3
8
  module DSPy
4
- # Your code will go here
5
- end
9
+ extend Dry::Configurable
10
+ setting :lm
11
+ setting :logger, default: Dry.Logger(:dspy, formatter: :string)
12
+
13
+ def self.logger
14
+ config.logger
15
+ end
16
+ end
17
+
18
+ require_relative 'dspy/types'
19
+ require_relative 'dspy/module'
20
+ require_relative 'dspy/field'
21
+ require_relative 'dspy/signature'
22
+ require_relative 'dspy/lm'
23
+ require_relative 'dspy/predict'
24
+ require_relative 'dspy/chain_of_thought'
25
+ require_relative 'dspy/re_act'
26
+ 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,14 +1,112 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dspy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.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-03-15 00:00:00.000000000 Z
11
- dependencies: []
10
+ date: 2025-06-01 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ruby_llm
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: dry-schema
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.14'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.14'
40
+ - !ruby/object:Gem::Dependency
41
+ name: dry-configurable
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: dry-logger
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: ruby-openai
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '8.0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '8.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: async
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '2.23'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '2.23'
96
+ - !ruby/object:Gem::Dependency
97
+ name: openai
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: 0.1.0.pre.alpha.4
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 0.1.0.pre.alpha.4
12
110
  description: A Ruby implementation of DSPy, a framework for programming with large
13
111
  language models
14
112
  email:
@@ -19,6 +117,22 @@ extra_rdoc_files: []
19
117
  files:
20
118
  - README.md
21
119
  - lib/dspy.rb
120
+ - lib/dspy/chain_of_thought.rb
121
+ - lib/dspy/ext/dry_schema.rb
122
+ - lib/dspy/field.rb
123
+ - lib/dspy/lm.rb
124
+ - lib/dspy/module.rb
125
+ - lib/dspy/predict.rb
126
+ - lib/dspy/re_act.rb
127
+ - 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
133
+ - lib/dspy/tools.rb
134
+ - lib/dspy/tools/sorbet_tool.rb
135
+ - lib/dspy/types.rb
22
136
  homepage: https://github.com/vicentereig/dspy.rb
23
137
  licenses:
24
138
  - MIT