dspy 0.5.1 → 0.6.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,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sorbet-runtime'
4
+ require 'json'
5
+
6
+ module DSPy
7
+ module Tools
8
+ # Base class for multi-method tool classes where each method can be exposed as an individual tool
9
+ # Similar to Rails controllers where each action is exposed as an endpoint
10
+ class Toolset
11
+ extend T::Sig
12
+ extend T::Helpers
13
+
14
+ class << self
15
+ extend T::Sig
16
+
17
+ sig { returns(T::Hash[Symbol, T::Hash[Symbol, String]]) }
18
+ attr_reader :exposed_tools
19
+
20
+ # DSL method to expose a method as a tool
21
+ sig { params(method_name: Symbol, tool_name: T.nilable(String), description: T.nilable(String)).void }
22
+ def tool(method_name, tool_name: nil, description: nil)
23
+ @exposed_tools ||= {}
24
+ @exposed_tools[method_name] = {
25
+ tool_name: tool_name || "#{toolset_name}_#{method_name}",
26
+ description: description || "#{method_name.to_s.tr('_', ' ').capitalize} operation"
27
+ }
28
+ end
29
+
30
+ # DSL method to set the toolset name prefix
31
+ sig { params(name: T.nilable(String)).returns(String) }
32
+ def toolset_name(name = nil)
33
+ if name
34
+ @toolset_name = name
35
+ else
36
+ @toolset_name || self.name.split('::').last.gsub(/Toolset$/, '').downcase
37
+ end
38
+ end
39
+
40
+ # Get all exposed tools as individual tool instances
41
+ sig { returns(T::Array[ToolProxy]) }
42
+ def to_tools
43
+ instance = new
44
+ (@exposed_tools || {}).map do |method_name, config|
45
+ ToolProxy.new(instance, method_name, config[:tool_name], config[:description])
46
+ end
47
+ end
48
+
49
+ # Generate schema for a specific method using Sorbet signatures
50
+ sig { params(method_name: Symbol).returns(T::Hash[Symbol, T.untyped]) }
51
+ def schema_for_method(method_name)
52
+ method_obj = instance_method(method_name)
53
+ sig_info = T::Utils.signature_for_method(method_obj)
54
+
55
+ if sig_info.nil?
56
+ # Fallback for methods without signatures
57
+ return {
58
+ type: :object,
59
+ properties: {},
60
+ required: []
61
+ }
62
+ end
63
+
64
+ # Reuse the schema generation logic from Base
65
+ properties = {}
66
+ required = []
67
+
68
+ # Handle keyword arguments (most common in Ruby)
69
+ sig_info.kwarg_types.each do |param_name, param_type|
70
+ next if param_name == :block
71
+
72
+ properties[param_name] = {
73
+ type: sorbet_type_to_json_schema(param_type)[:type],
74
+ description: "Parameter #{param_name}"
75
+ }
76
+
77
+ # Check if parameter is required
78
+ if sig_info.req_kwarg_names.include?(param_name)
79
+ required << param_name.to_s
80
+ else
81
+ properties[param_name][:description] += " (optional)"
82
+ end
83
+ end
84
+
85
+ {
86
+ type: :object,
87
+ properties: properties,
88
+ required: required
89
+ }
90
+ end
91
+
92
+ private
93
+
94
+ # Convert Sorbet types to JSON Schema types (extracted from Base)
95
+ sig { params(sorbet_type: T.untyped).returns(T::Hash[Symbol, T.untyped]) }
96
+ def sorbet_type_to_json_schema(sorbet_type)
97
+ # Check for boolean types first (SimplePairUnion of TrueClass | FalseClass)
98
+ if sorbet_type.respond_to?(:types) && sorbet_type.types.length == 2
99
+ raw_types = sorbet_type.types.map do |t|
100
+ t.is_a?(T::Types::Simple) ? t.raw_type : t
101
+ end
102
+
103
+ if raw_types.include?(TrueClass) && raw_types.include?(FalseClass)
104
+ return { type: :boolean }
105
+ end
106
+ end
107
+
108
+ if sorbet_type.is_a?(T::Types::Simple)
109
+ raw_type = sorbet_type.raw_type
110
+
111
+ case raw_type
112
+ when String
113
+ { type: :string }
114
+ when Integer
115
+ { type: :integer }
116
+ when Float, Numeric
117
+ { type: :number }
118
+ when TrueClass, FalseClass, T::Boolean
119
+ { type: :boolean }
120
+ else
121
+ { type: :string, description: "#{raw_type} (converted to string)" }
122
+ end
123
+ elsif sorbet_type.is_a?(T::Types::Union)
124
+ # Handle nilable types
125
+ non_nil_types = sorbet_type.types.reject { |t| t == T::Utils.coerce(NilClass) }
126
+ if non_nil_types.length == 1
127
+ result = sorbet_type_to_json_schema(non_nil_types.first)
128
+ result[:description] = "#{result[:description] || ''} (optional)".strip
129
+ result
130
+ else
131
+ { type: :string, description: "Union type (converted to string)" }
132
+ end
133
+ elsif sorbet_type.is_a?(T::Types::TypedArray)
134
+ {
135
+ type: :array,
136
+ items: sorbet_type_to_json_schema(sorbet_type.type)
137
+ }
138
+ else
139
+ { type: :string, description: "#{sorbet_type} (converted to string)" }
140
+ end
141
+ end
142
+ end
143
+
144
+ # Inner class that wraps a method as a tool, compatible with DSPy::Tools::Base interface
145
+ class ToolProxy < Base
146
+ extend T::Sig
147
+
148
+ sig { params(instance: Toolset, method_name: Symbol, tool_name: String, description: String).void }
149
+ def initialize(instance, method_name, tool_name, description)
150
+ @instance = instance
151
+ @method_name = method_name
152
+ @tool_name_override = tool_name
153
+ @description_override = description
154
+ end
155
+
156
+ sig { override.returns(String) }
157
+ def name
158
+ @tool_name_override
159
+ end
160
+
161
+ sig { override.returns(String) }
162
+ def description
163
+ @description_override
164
+ end
165
+
166
+ sig { override.returns(String) }
167
+ def schema
168
+ schema_obj = @instance.class.schema_for_method(@method_name)
169
+ tool_info = {
170
+ name: name,
171
+ description: description,
172
+ parameters: schema_obj
173
+ }
174
+ JSON.generate(tool_info)
175
+ end
176
+
177
+ # The main call method that tools must implement
178
+ sig { params(kwargs: T.untyped).returns(T.untyped) }
179
+ def call(**kwargs)
180
+ @instance.send(@method_name, **kwargs)
181
+ end
182
+
183
+ sig { override.params(args_json: T.untyped).returns(T.untyped) }
184
+ def dynamic_call(args_json)
185
+ schema = @instance.class.schema_for_method(@method_name)
186
+
187
+ if schema[:properties].empty?
188
+ @instance.send(@method_name)
189
+ else
190
+ # Parse arguments
191
+ args = case args_json
192
+ when Hash
193
+ args_json
194
+ when String
195
+ begin
196
+ JSON.parse(args_json)
197
+ rescue JSON::ParserError
198
+ return "Error: Invalid JSON input"
199
+ end
200
+ else
201
+ return "Error: Expected Hash or JSON string"
202
+ end
203
+
204
+ # Convert string keys to symbols and validate types
205
+ kwargs = {}
206
+ schema[:properties].each do |param_name, param_schema|
207
+ key = param_name.to_s
208
+ if args.key?(key)
209
+ kwargs[param_name] = convert_argument_type(args[key], param_schema)
210
+ elsif schema[:required].include?(key)
211
+ return "Error: Missing required parameter: #{key}"
212
+ end
213
+ end
214
+
215
+ @instance.send(@method_name, **kwargs)
216
+ end
217
+ rescue => e
218
+ "Error: #{e.message}"
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
data/lib/dspy/tools.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'tools/base'
4
+ require_relative 'tools/toolset'
4
5
 
5
6
  module DSPy
6
7
  # Define the tools available to the agent
data/lib/dspy/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DSPy
4
- VERSION = "0.5.1"
4
+ VERSION = "0.6.0"
5
5
  end
data/lib/dspy.rb CHANGED
@@ -112,6 +112,7 @@ require_relative 'dspy/lm'
112
112
  require_relative 'dspy/predict'
113
113
  require_relative 'dspy/chain_of_thought'
114
114
  require_relative 'dspy/re_act'
115
+ require_relative 'dspy/code_act'
115
116
  require_relative 'dspy/evaluate'
116
117
  require_relative 'dspy/teleprompt/teleprompter'
117
118
  require_relative 'dspy/teleprompt/utils'
@@ -121,6 +122,7 @@ require_relative 'dspy/teleprompt/simple_optimizer'
121
122
  require_relative 'dspy/teleprompt/mipro_v2'
122
123
  require_relative 'dspy/subscribers/logger_subscriber'
123
124
  require_relative 'dspy/tools'
125
+ require_relative 'dspy/memory'
124
126
  require_relative 'dspy/instrumentation'
125
127
  require_relative 'dspy/storage/program_storage'
126
128
  require_relative 'dspy/storage/storage_manager'
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.5.1
4
+ version: 0.6.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-07-04 00:00:00.000000000 Z
10
+ date: 2025-07-05 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dry-configurable
@@ -135,6 +135,20 @@ dependencies:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
137
  version: 0.20.0
138
+ - !ruby/object:Gem::Dependency
139
+ name: informers
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '1.2'
145
+ type: :runtime
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '1.2'
138
152
  description: A Ruby implementation of DSPy, a framework for programming with large
139
153
  language models
140
154
  email:
@@ -146,6 +160,7 @@ files:
146
160
  - README.md
147
161
  - lib/dspy.rb
148
162
  - lib/dspy/chain_of_thought.rb
163
+ - lib/dspy/code_act.rb
149
164
  - lib/dspy/evaluate.rb
150
165
  - lib/dspy/example.rb
151
166
  - lib/dspy/few_shot_example.rb
@@ -159,6 +174,14 @@ files:
159
174
  - lib/dspy/lm/adapters/openai_adapter.rb
160
175
  - lib/dspy/lm/errors.rb
161
176
  - lib/dspy/lm/response.rb
177
+ - lib/dspy/memory.rb
178
+ - lib/dspy/memory/embedding_engine.rb
179
+ - lib/dspy/memory/in_memory_store.rb
180
+ - lib/dspy/memory/local_embedding_engine.rb
181
+ - lib/dspy/memory/memory_compactor.rb
182
+ - lib/dspy/memory/memory_manager.rb
183
+ - lib/dspy/memory/memory_record.rb
184
+ - lib/dspy/memory/memory_store.rb
162
185
  - lib/dspy/mixins/instrumentation_helpers.rb
163
186
  - lib/dspy/mixins/struct_builder.rb
164
187
  - lib/dspy/mixins/type_coercion.rb
@@ -184,6 +207,9 @@ files:
184
207
  - lib/dspy/teleprompt/utils.rb
185
208
  - lib/dspy/tools.rb
186
209
  - lib/dspy/tools/base.rb
210
+ - lib/dspy/tools/memory_toolset.rb
211
+ - lib/dspy/tools/text_processing_toolset.rb
212
+ - lib/dspy/tools/toolset.rb
187
213
  - lib/dspy/version.rb
188
214
  homepage: https://github.com/vicentereig/dspy.rb
189
215
  licenses: