sorbet-baml 0.4.0 → 0.5.1
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/CHANGELOG.md +11 -0
- data/lib/sorbet-baml.rb +3 -0
- data/lib/sorbet_baml/description_extractor.rb +14 -5
- data/lib/sorbet_baml/dspy_tool_converter.rb +83 -7
- data/lib/sorbet_baml/type_mapper.rb +14 -2
- data/lib/sorbet_baml/version.rb +1 -1
- 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: '0853732b922565aa837ede825e6c074a29975d1fb7eb16140b699ec5db495e92'
|
|
4
|
+
data.tar.gz: 55f2775ead79f10978a7c7858889f124cd5c3e01f430b6cc7ff32f6484dad955
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d519cf11e3cbdfebff145542b48a51fc2e90ebbbe841f047f3ba344de508d26efa27b248fcc3aa4ac9777870c3b94b208c53bd7d238fcbc291048a350b4275d
|
|
7
|
+
data.tar.gz: fc3b1ce2f6d829c49e0a0f2c7e16f6d46e211258e094c9f83deefbd0aeafd07f8797cb3e3366568b652b759722445eb45de84ecf95fa6aa40847279fc5748f7d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.1] - 2026-03-07
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Avoid emitting invalid double optional markers like `int??` when converting DSPy tool parameters backed by `T.nilable(...)`
|
|
8
|
+
- Add regression coverage for nilable scalar, enum, and enum-array DSPy tool parameters so malformed `??` output fails tests
|
|
9
|
+
|
|
10
|
+
### Improved
|
|
11
|
+
|
|
12
|
+
- Tighten DSPy tool conversion assertions to validate full rendered optional parameter lines instead of permissive substrings
|
|
13
|
+
|
|
3
14
|
## [0.4.0] - 2025-10-14
|
|
4
15
|
|
|
5
16
|
### Changed
|
data/lib/sorbet-baml.rb
ADDED
|
@@ -16,12 +16,21 @@ module SorbetBaml
|
|
|
16
16
|
return descriptions unless klass.respond_to?(:props)
|
|
17
17
|
|
|
18
18
|
begin
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
# DSPy >= 0.34 stores descriptions via DSPy::Ext::StructDescriptions#field_descriptions
|
|
20
|
+
if klass.respond_to?(:field_descriptions)
|
|
21
|
+
T.unsafe(klass).field_descriptions.each do |field_name, desc|
|
|
22
|
+
descriptions[field_name.to_s] = desc if desc.is_a?(String)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Fall back to the legacy :extra hash for structs that store descriptions there
|
|
27
|
+
if descriptions.empty?
|
|
28
|
+
T.unsafe(klass).props.each do |field_name, prop_info|
|
|
29
|
+
next unless prop_info.is_a?(Hash)
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
extra = prop_info[:extra]
|
|
32
|
+
descriptions[field_name.to_s] = extra[:description] if extra.is_a?(Hash) && extra[:description].is_a?(String)
|
|
33
|
+
end
|
|
25
34
|
end
|
|
26
35
|
rescue StandardError
|
|
27
36
|
# Handle any errors gracefully and return empty hash
|
|
@@ -26,25 +26,50 @@ module SorbetBaml
|
|
|
26
26
|
tool_name = klass.tool_name_value || klass.name&.split('::')&.last
|
|
27
27
|
tool_description = klass.tool_description_value
|
|
28
28
|
|
|
29
|
-
# Get parameters from DSPy's
|
|
30
|
-
call_schema = klass.
|
|
29
|
+
# Get parameters from DSPy's call_schema_object which extracts from Sorbet signatures.
|
|
30
|
+
call_schema = klass.call_schema_object
|
|
31
31
|
parameters = call_schema[:properties] || {}
|
|
32
32
|
required_params = call_schema[:required] || []
|
|
33
33
|
|
|
34
|
+
# Extract Sorbet types directly from the call method signature for richer type info.
|
|
35
|
+
# This gives us access to T::Enum classes, T::Struct references, etc. that are lost
|
|
36
|
+
# when converting to JSON schema.
|
|
37
|
+
sorbet_types = extract_sorbet_types(klass)
|
|
38
|
+
|
|
39
|
+
# Collect enum classes referenced by parameters for generating enum definitions.
|
|
40
|
+
enum_classes = collect_enum_classes(sorbet_types)
|
|
41
|
+
|
|
34
42
|
lines = []
|
|
35
43
|
|
|
36
44
|
# Add tool description as class-level comment if available
|
|
37
45
|
lines << "// #{tool_description}" if @include_descriptions && tool_description
|
|
38
46
|
|
|
47
|
+
# Generate enum definitions between description and class
|
|
48
|
+
if enum_classes.any?
|
|
49
|
+
lines << '' unless lines.empty?
|
|
50
|
+
enum_converter = Converter.new(@options)
|
|
51
|
+
enum_classes.each_with_index do |enum_klass, i|
|
|
52
|
+
lines << '' unless i.zero?
|
|
53
|
+
lines << enum_converter.convert_enum(enum_klass)
|
|
54
|
+
end
|
|
55
|
+
lines << ''
|
|
56
|
+
end
|
|
57
|
+
|
|
39
58
|
# Generate BAML class definition
|
|
40
59
|
lines << "class #{tool_name} {"
|
|
41
60
|
|
|
42
61
|
parameters.each do |param_name, param_info|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
# Use Sorbet type mapping when available (preserves enum/struct type names).
|
|
63
|
+
# Fall back to JSON schema conversion for tools without Sorbet signatures.
|
|
64
|
+
baml_type = if sorbet_types.key?(param_name)
|
|
65
|
+
TypeMapper.map_type(sorbet_types[param_name])
|
|
66
|
+
else
|
|
67
|
+
json_schema_type_to_baml(param_info)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# TypeMapper already encodes T.nilable(...) as a trailing `?`.
|
|
71
|
+
# Avoid emitting invalid `??` when DSPy also marks the kwarg optional.
|
|
72
|
+
baml_type += '?' if !required_params.include?(param_name.to_s) && !baml_type.end_with?('?')
|
|
48
73
|
|
|
49
74
|
line = "#{' ' * @indent_size}#{param_name} #{baml_type}"
|
|
50
75
|
|
|
@@ -63,10 +88,61 @@ module SorbetBaml
|
|
|
63
88
|
|
|
64
89
|
private
|
|
65
90
|
|
|
91
|
+
# Extract Sorbet types from the tool's call method signature.
|
|
92
|
+
sig { params(klass: T.untyped).returns(T::Hash[Symbol, T.untyped]) }
|
|
93
|
+
def extract_sorbet_types(klass)
|
|
94
|
+
method_obj = klass.instance_method(:call)
|
|
95
|
+
sig_info = T::Utils.signature_for_method(method_obj)
|
|
96
|
+
return {} if sig_info.nil?
|
|
97
|
+
|
|
98
|
+
types = {}
|
|
99
|
+
sig_info.arg_types.each { |name, type| types[name] = type }
|
|
100
|
+
sig_info.kwarg_types.each { |name, type| types[name] = type }
|
|
101
|
+
types
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Collect all T::Enum classes referenced by the given Sorbet types.
|
|
105
|
+
sig { params(sorbet_types: T::Hash[Symbol, T.untyped]).returns(T::Array[T.class_of(T::Enum)]) }
|
|
106
|
+
def collect_enum_classes(sorbet_types)
|
|
107
|
+
enum_classes = T.let([], T::Array[T.class_of(T::Enum)])
|
|
108
|
+
sorbet_types.each_value do |type|
|
|
109
|
+
enum_classes.concat(extract_enum_types(type))
|
|
110
|
+
end
|
|
111
|
+
enum_classes.uniq
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Recursively extract T::Enum classes from a Sorbet type object.
|
|
115
|
+
sig { params(type: T.untyped).returns(T::Array[T.class_of(T::Enum)]) }
|
|
116
|
+
def extract_enum_types(type)
|
|
117
|
+
case type
|
|
118
|
+
when T::Types::Simple
|
|
119
|
+
raw = type.raw_type
|
|
120
|
+
raw.is_a?(Class) && raw < T::Enum ? [raw] : []
|
|
121
|
+
when T::Types::TypedArray
|
|
122
|
+
extract_enum_types(type.type)
|
|
123
|
+
when T::Types::TypedHash
|
|
124
|
+
extract_enum_types(type.keys) + extract_enum_types(type.values)
|
|
125
|
+
else
|
|
126
|
+
if type.is_a?(Class) && type < T::Enum
|
|
127
|
+
[type]
|
|
128
|
+
elsif type.respond_to?(:types)
|
|
129
|
+
type.types.flat_map { |t| extract_enum_types(t) }
|
|
130
|
+
else
|
|
131
|
+
[]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Fallback: convert JSON schema type info to BAML type string.
|
|
137
|
+
# Used when Sorbet signature is not available.
|
|
66
138
|
sig { params(json_schema_info: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
67
139
|
def json_schema_type_to_baml(json_schema_info)
|
|
68
140
|
type = json_schema_info[:type]
|
|
69
141
|
|
|
142
|
+
# dspy >= 0.34 represents nilable types as ["integer", "null"] rather than just "integer".
|
|
143
|
+
# Extract the primary (non-null) type so the case below matches correctly.
|
|
144
|
+
type = type.reject { |t| t == 'null' }.first if type.is_a?(Array)
|
|
145
|
+
|
|
70
146
|
case type
|
|
71
147
|
when :string, 'string'
|
|
72
148
|
'string'
|
|
@@ -19,6 +19,9 @@ module SorbetBaml
|
|
|
19
19
|
map_array_type(type_object)
|
|
20
20
|
when T::Types::TypedHash
|
|
21
21
|
map_hash_type(type_object)
|
|
22
|
+
when T::Types::Untyped
|
|
23
|
+
# T.untyped maps to BAML's json type for dynamic values
|
|
24
|
+
'json'
|
|
22
25
|
else
|
|
23
26
|
# Check if it's a union type (T.nilable or T.any)
|
|
24
27
|
if type_object.respond_to?(:types)
|
|
@@ -61,8 +64,10 @@ module SorbetBaml
|
|
|
61
64
|
sig { params(type_object: T.untyped).returns(String) }
|
|
62
65
|
def self.map_union_type(type_object)
|
|
63
66
|
types = type_object.types
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
|
|
68
|
+
# Only T::Types::Simple has raw_type - check type class before accessing
|
|
69
|
+
nil_type = types.find { |t| nil_type?(t) }
|
|
70
|
+
non_nil_types = types.reject { |t| nil_type?(t) }
|
|
66
71
|
|
|
67
72
|
return 'null' if non_nil_types.empty?
|
|
68
73
|
|
|
@@ -91,6 +96,13 @@ module SorbetBaml
|
|
|
91
96
|
end
|
|
92
97
|
end
|
|
93
98
|
|
|
99
|
+
# Check if a type represents NilClass safely
|
|
100
|
+
# Only T::Types::Simple has raw_type; other type classes (TypedHash, TypedArray, Untyped) do not
|
|
101
|
+
sig { params(type_obj: T.untyped).returns(T::Boolean) }
|
|
102
|
+
def self.nil_type?(type_obj)
|
|
103
|
+
type_obj.is_a?(T::Types::Simple) && type_obj.raw_type == NilClass || false
|
|
104
|
+
end
|
|
105
|
+
|
|
94
106
|
sig { params(type_object: T.untyped).returns(String) }
|
|
95
107
|
def self.map_array_type(type_object)
|
|
96
108
|
element_type = map_type(type_object.type)
|
data/lib/sorbet_baml/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sorbet-baml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vicente Reig Rincon de Arellano
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-03-07 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: sorbet-runtime
|
|
@@ -38,6 +38,7 @@ files:
|
|
|
38
38
|
- README.md
|
|
39
39
|
- Rakefile
|
|
40
40
|
- examples/description_parameters.rb
|
|
41
|
+
- lib/sorbet-baml.rb
|
|
41
42
|
- lib/sorbet_baml.rb
|
|
42
43
|
- lib/sorbet_baml/comment_extractor.rb
|
|
43
44
|
- lib/sorbet_baml/converter.rb
|