dspy 0.10.1 → 0.11.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 +2 -2
- data/lib/dspy/errors.rb +4 -0
- data/lib/dspy/example.rb +11 -9
- data/lib/dspy/prediction.rb +48 -2
- data/lib/dspy/re_act.rb +3 -3
- data/lib/dspy/signature.rb +13 -0
- data/lib/dspy/type_serializer.rb +55 -0
- data/lib/dspy/version.rb +1 -1
- data/lib/dspy.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de6126089636bd0d5fdaf9cd2bba791157186683a7984174bac980be8e32a483
|
4
|
+
data.tar.gz: 1e78c20f7e1a37cf025158be0f4014ee2abe9ba95b84682361ff9ea544fecd2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b3dbde7e5040dc1b0562142bdc560f9fc393bbb7cf20bef4d767541be079408dc1c6e196257f07193266713585bdfb21ac77bd08df3c6ab69607354393987c2
|
7
|
+
data.tar.gz: 412cf071635f85bb3fb08f339a1dce23d4a9f00144104fc03fc25cc706d87c86c707221bc8a98f6847a7e157e2fa86de8bee4ee7c44d2bd1b9b0ab41934dd411
|
data/README.md
CHANGED
@@ -43,7 +43,7 @@ The result? LLM applications that actually scale and don't break when you sneeze
|
|
43
43
|
|
44
44
|
## Development Status
|
45
45
|
|
46
|
-
DSPy.rb is actively developed and approaching stability at **v0.
|
46
|
+
DSPy.rb is actively developed and approaching stability at **v0.10.1**. The core framework is production-ready with comprehensive documentation, but I'm battle-testing features through the 0.x series before committing to a stable v1.0 API.
|
47
47
|
|
48
48
|
Real-world usage feedback is invaluable - if you encounter issues or have suggestions, please open a GitHub issue!
|
49
49
|
|
@@ -174,7 +174,7 @@ DSPy.rb has rapidly evolved from experimental to production-ready:
|
|
174
174
|
|
175
175
|
## Roadmap - Battle-Testing Toward v1.0
|
176
176
|
|
177
|
-
DSPy.rb is currently at **v0.
|
177
|
+
DSPy.rb is currently at **v0.10.1** and approaching stability. I'm focusing on real-world usage and refinement through the 0.11, 0.12+ series before committing to a stable v1.0 API.
|
178
178
|
|
179
179
|
**Current Focus Areas:**
|
180
180
|
- 🚧 **Ollama Support** - Local model integration
|
data/lib/dspy/errors.rb
CHANGED
data/lib/dspy/example.rb
CHANGED
@@ -77,6 +77,17 @@ module DSPy
|
|
77
77
|
expected_hash
|
78
78
|
end
|
79
79
|
|
80
|
+
# Custom equality comparison
|
81
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
82
|
+
def ==(other)
|
83
|
+
return false unless other.is_a?(Example)
|
84
|
+
|
85
|
+
@signature_class == other.signature_class &&
|
86
|
+
input_values == other.input_values &&
|
87
|
+
expected_values == other.expected_values
|
88
|
+
end
|
89
|
+
|
90
|
+
|
80
91
|
# Check if prediction matches expected output using struct comparison
|
81
92
|
sig { params(prediction: T.untyped).returns(T::Boolean) }
|
82
93
|
def matches_prediction?(prediction)
|
@@ -179,15 +190,6 @@ module DSPy
|
|
179
190
|
examples
|
180
191
|
end
|
181
192
|
|
182
|
-
# Equality comparison
|
183
|
-
sig { params(other: T.untyped).returns(T::Boolean) }
|
184
|
-
def ==(other)
|
185
|
-
return false unless other.is_a?(Example)
|
186
|
-
|
187
|
-
@signature_class == other.signature_class &&
|
188
|
-
input_values == other.input_values &&
|
189
|
-
expected_values == other.expected_values
|
190
|
-
end
|
191
193
|
|
192
194
|
# String representation for debugging
|
193
195
|
sig { returns(String) }
|
data/lib/dspy/prediction.rb
CHANGED
@@ -319,6 +319,9 @@ module DSPy
|
|
319
319
|
end
|
320
320
|
|
321
321
|
value.each do |k, v|
|
322
|
+
# Skip _type field from being added to the struct (it's not a real field)
|
323
|
+
next if k == :_type || k == "_type"
|
324
|
+
|
322
325
|
prop_info = struct_class.props[k]
|
323
326
|
if prop_info
|
324
327
|
prop_type = prop_info[:type_object] || prop_info[:type]
|
@@ -343,7 +346,27 @@ module DSPy
|
|
343
346
|
value
|
344
347
|
end
|
345
348
|
when T::Types::Union
|
346
|
-
#
|
349
|
+
# Check if value has a _type field for automatic type detection
|
350
|
+
type_name = value[:_type] || value["_type"]
|
351
|
+
|
352
|
+
if type_name
|
353
|
+
# Use _type field to determine which struct to instantiate
|
354
|
+
type.types.each do |t|
|
355
|
+
next if t == T::Utils.coerce(NilClass)
|
356
|
+
|
357
|
+
if t.is_a?(T::Types::Simple) && t.raw_type < T::Struct
|
358
|
+
struct_name = t.raw_type.name.split('::').last
|
359
|
+
if struct_name == type_name
|
360
|
+
return convert_to_struct(value, t)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
# If no matching type found, raise an error
|
366
|
+
raise DSPy::DeserializationError, "Unknown type: #{type_name}. Expected one of: #{type.types.map { |t| t.is_a?(T::Types::Simple) && t.raw_type < T::Struct ? t.raw_type.name.split('::').last : nil }.compact.join(', ')}"
|
367
|
+
end
|
368
|
+
|
369
|
+
# Fallback to trying each type if no _type field
|
347
370
|
type.types.each do |t|
|
348
371
|
next if t == T::Utils.coerce(NilClass)
|
349
372
|
|
@@ -398,7 +421,27 @@ module DSPy
|
|
398
421
|
|
399
422
|
sig { params(hash: T::Hash[Symbol, T.untyped], union_type: T::Types::Union).returns(T.untyped) }
|
400
423
|
def convert_hash_to_union_struct(hash, union_type)
|
401
|
-
#
|
424
|
+
# First check if hash has a _type field for automatic type detection
|
425
|
+
type_name = hash[:_type] || hash["_type"]
|
426
|
+
|
427
|
+
if type_name
|
428
|
+
# Use _type field to determine which struct to instantiate
|
429
|
+
union_type.types.each do |type|
|
430
|
+
next if type == T::Utils.coerce(NilClass)
|
431
|
+
|
432
|
+
if type.is_a?(T::Types::Simple) && type.raw_type < T::Struct
|
433
|
+
struct_name = type.raw_type.name.split('::').last
|
434
|
+
if struct_name == type_name
|
435
|
+
return convert_to_struct(hash, type)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
# If no matching type found, raise an error
|
441
|
+
raise DSPy::DeserializationError, "Unknown type: #{type_name}. Expected one of: #{union_type.types.map { |t| t.is_a?(T::Types::Simple) && t.raw_type < T::Struct ? t.raw_type.name.split('::').last : nil }.compact.join(', ')}"
|
442
|
+
end
|
443
|
+
|
444
|
+
# Fallback: Try to match the hash structure to one of the union types
|
402
445
|
union_type.types.each do |type|
|
403
446
|
next if type == T::Utils.coerce(NilClass)
|
404
447
|
|
@@ -412,6 +455,9 @@ module DSPy
|
|
412
455
|
# Need to convert nested values too
|
413
456
|
converted_hash = {}
|
414
457
|
hash.each do |k, v|
|
458
|
+
# Skip _type field
|
459
|
+
next if k == :_type || k == "_type"
|
460
|
+
|
415
461
|
prop_info = struct_class.props[k]
|
416
462
|
if prop_info
|
417
463
|
prop_type = prop_info[:type_object] || prop_info[:type]
|
data/lib/dspy/re_act.rb
CHANGED
@@ -256,7 +256,7 @@ module DSPy
|
|
256
256
|
}) do
|
257
257
|
# Generate thought and action
|
258
258
|
thought_obj = @thought_generator.forward(
|
259
|
-
input_context:
|
259
|
+
input_context: DSPy::TypeSerializer.serialize(input_struct).to_json,
|
260
260
|
history: history,
|
261
261
|
available_tools: available_tools_desc
|
262
262
|
)
|
@@ -385,7 +385,7 @@ module DSPy
|
|
385
385
|
return { should_finish: false } if observation.include?("Unknown action")
|
386
386
|
|
387
387
|
observation_result = @observation_processor.forward(
|
388
|
-
input_context:
|
388
|
+
input_context: DSPy::TypeSerializer.serialize(input_struct).to_json,
|
389
389
|
history: history,
|
390
390
|
observation: observation
|
391
391
|
)
|
@@ -402,7 +402,7 @@ module DSPy
|
|
402
402
|
sig { params(input_struct: T.untyped, history: T::Array[HistoryEntry], available_tools_desc: T::Array[T::Hash[String, T.untyped]], observation_result: T.untyped, iteration: Integer).returns(String) }
|
403
403
|
def generate_forced_final_answer(input_struct, history, available_tools_desc, observation_result, iteration)
|
404
404
|
final_thought = @thought_generator.forward(
|
405
|
-
input_context:
|
405
|
+
input_context: DSPy::TypeSerializer.serialize(input_struct).to_json,
|
406
406
|
history: history,
|
407
407
|
available_tools: available_tools_desc
|
408
408
|
)
|
data/lib/dspy/signature.rb
CHANGED
@@ -292,6 +292,19 @@ module DSPy
|
|
292
292
|
properties = {}
|
293
293
|
required = []
|
294
294
|
|
295
|
+
# Check if struct already has a _type field
|
296
|
+
if struct_class.props.key?(:_type)
|
297
|
+
raise DSPy::ValidationError, "_type field conflict: #{struct_class.name} already has a _type field defined. " \
|
298
|
+
"DSPy uses _type for automatic type detection in union types."
|
299
|
+
end
|
300
|
+
|
301
|
+
# Add automatic _type field for type detection
|
302
|
+
properties[:_type] = {
|
303
|
+
type: "string",
|
304
|
+
const: struct_class.name.split('::').last # Use the simple class name
|
305
|
+
}
|
306
|
+
required << "_type"
|
307
|
+
|
295
308
|
struct_class.props.each do |prop_name, prop_info|
|
296
309
|
prop_type = prop_info[:type_object] || prop_info[:type]
|
297
310
|
properties[prop_name] = type_to_json_schema(prop_type)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sorbet-runtime"
|
4
|
+
|
5
|
+
module DSPy
|
6
|
+
class TypeSerializer
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
# Serialize a value, injecting _type fields for T::Struct instances
|
10
|
+
sig { params(value: T.untyped).returns(T.untyped) }
|
11
|
+
def self.serialize(value)
|
12
|
+
case value
|
13
|
+
when T::Struct
|
14
|
+
serialize_struct(value)
|
15
|
+
when Array
|
16
|
+
value.map { |item| serialize(item) }
|
17
|
+
when Hash
|
18
|
+
value.transform_values { |v| serialize(v) }
|
19
|
+
else
|
20
|
+
value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
sig { params(struct: T::Struct).returns(T::Hash[String, T.untyped]) }
|
27
|
+
def self.serialize_struct(struct)
|
28
|
+
# Handle anonymous structs that don't have a name
|
29
|
+
class_name = struct.class.name
|
30
|
+
type_name = if class_name.nil? || class_name.empty?
|
31
|
+
# For anonymous structs, use a generic identifier
|
32
|
+
"AnonymousStruct"
|
33
|
+
else
|
34
|
+
class_name.split('::').last
|
35
|
+
end
|
36
|
+
|
37
|
+
result = {
|
38
|
+
"_type" => type_name
|
39
|
+
}
|
40
|
+
|
41
|
+
# Get all props and serialize their values
|
42
|
+
struct.class.props.each do |prop_name, prop_info|
|
43
|
+
prop_value = struct.send(prop_name)
|
44
|
+
|
45
|
+
# Skip nil values for optional fields
|
46
|
+
next if prop_value.nil? && prop_info[:fully_optional]
|
47
|
+
|
48
|
+
# Recursively serialize nested values
|
49
|
+
result[prop_name.to_s] = serialize(prop_value)
|
50
|
+
end
|
51
|
+
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/dspy/version.rb
CHANGED
data/lib/dspy.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dspy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vicente Reig Rincón de Arellano
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-07-
|
11
|
+
date: 2025-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-configurable
|
@@ -223,6 +223,7 @@ files:
|
|
223
223
|
- lib/dspy/tools/memory_toolset.rb
|
224
224
|
- lib/dspy/tools/text_processing_toolset.rb
|
225
225
|
- lib/dspy/tools/toolset.rb
|
226
|
+
- lib/dspy/type_serializer.rb
|
226
227
|
- lib/dspy/version.rb
|
227
228
|
homepage: https://github.com/vicentereig/dspy.rb
|
228
229
|
licenses:
|