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.
- checksums.yaml +4 -4
- data/README.md +374 -3
- data/lib/dspy/chain_of_thought.rb +22 -0
- data/lib/dspy/ext/dry_schema.rb +94 -0
- data/lib/dspy/field.rb +23 -0
- data/lib/dspy/lm.rb +76 -0
- data/lib/dspy/module.rb +13 -0
- data/lib/dspy/predict.rb +72 -0
- data/lib/dspy/re_act.rb +253 -0
- data/lib/dspy/signature.rb +26 -0
- data/lib/dspy/sorbet_chain_of_thought.rb +91 -0
- data/lib/dspy/sorbet_module.rb +47 -0
- data/lib/dspy/sorbet_predict.rb +180 -0
- data/lib/dspy/sorbet_re_act.rb +332 -0
- data/lib/dspy/sorbet_signature.rb +218 -0
- data/lib/dspy/tools/sorbet_tool.rb +226 -0
- data/lib/dspy/tools.rb +21 -0
- data/lib/dspy/types.rb +3 -0
- data/lib/dspy.rb +29 -2
- metadata +117 -3
@@ -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
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
|
-
|
5
|
-
|
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.
|
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-
|
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
|