sorbet-baml 0.2.0 → 0.3.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 +123 -2
- data/Rakefile +2 -2
- data/docs-site/.gitignore +48 -0
- data/docs-site/Gemfile +5 -0
- data/docs-site/Gemfile.lock +140 -0
- data/docs-site/Rakefile +3 -0
- data/docs-site/bridgetown.config.yml +15 -0
- data/docs-site/config/initializers.rb +9 -0
- data/docs-site/config/puma.rb +9 -0
- data/docs-site/config.ru +5 -0
- data/docs-site/esbuild.config.js +11 -0
- data/docs-site/frontend/javascript/index.js +22 -0
- data/docs-site/frontend/styles/index.css +61 -0
- data/docs-site/package.json +18 -0
- data/docs-site/postcss.config.js +6 -0
- data/docs-site/server/roda_app.rb +9 -0
- data/docs-site/src/_components/head.liquid +26 -0
- data/docs-site/src/_components/nav.liquid +68 -0
- data/docs-site/src/_layouts/default.liquid +27 -0
- data/docs-site/src/_layouts/doc.liquid +39 -0
- data/docs-site/src/advanced-usage.md +598 -0
- data/docs-site/src/getting-started.md +170 -0
- data/docs-site/src/index.md +183 -0
- data/docs-site/src/troubleshooting.md +317 -0
- data/docs-site/src/type-mapping.md +236 -0
- data/docs-site/tailwind.config.js +85 -0
- data/examples/description_parameters.rb +16 -16
- data/lib/sorbet_baml/comment_extractor.rb +31 -39
- data/lib/sorbet_baml/converter.rb +66 -32
- data/lib/sorbet_baml/dependency_resolver.rb +11 -11
- data/lib/sorbet_baml/description_extension.rb +5 -5
- data/lib/sorbet_baml/description_extractor.rb +8 -10
- data/lib/sorbet_baml/dspy_tool_converter.rb +97 -0
- data/lib/sorbet_baml/dspy_tool_extensions.rb +23 -0
- data/lib/sorbet_baml/enum_extensions.rb +2 -2
- data/lib/sorbet_baml/struct_extensions.rb +2 -2
- data/lib/sorbet_baml/tool_extensions.rb +23 -0
- data/lib/sorbet_baml/type_mapper.rb +35 -37
- data/lib/sorbet_baml/version.rb +1 -1
- data/lib/sorbet_baml.rb +41 -13
- data/sorbet/config +2 -0
- data/sorbet/rbi/gems/anthropic@1.5.0.rbi +21252 -0
- data/sorbet/rbi/gems/async@2.27.3.rbi +9 -0
- data/sorbet/rbi/gems/bigdecimal@3.2.2.rbi +9 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.3.5.rbi +424 -0
- data/sorbet/rbi/gems/connection_pool@2.5.3.rbi +9 -0
- data/sorbet/rbi/gems/console@1.33.0.rbi +9 -0
- data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +672 -0
- data/sorbet/rbi/gems/dry-core@1.1.0.rbi +1729 -0
- data/sorbet/rbi/gems/dry-logger@1.1.0.rbi +1317 -0
- data/sorbet/rbi/gems/dspy@0.19.1.rbi +6677 -0
- data/sorbet/rbi/gems/ffi@1.17.2.rbi +2174 -0
- data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +9 -0
- data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +9 -0
- data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +9 -0
- data/sorbet/rbi/gems/google-protobuf@4.32.0.rbi +9 -0
- data/sorbet/rbi/gems/googleapis-common-protos-types@1.20.0.rbi +9 -0
- data/sorbet/rbi/gems/informers@1.2.1.rbi +1875 -0
- data/sorbet/rbi/gems/io-event@1.12.1.rbi +9 -0
- data/sorbet/rbi/gems/metrics@0.13.0.rbi +9 -0
- data/sorbet/rbi/gems/onnxruntime@0.10.0.rbi +304 -0
- data/sorbet/rbi/gems/openai@0.16.0.rbi +68055 -0
- data/sorbet/rbi/gems/opentelemetry-api@1.6.0.rbi +9 -0
- data/sorbet/rbi/gems/opentelemetry-common@0.22.0.rbi +9 -0
- data/sorbet/rbi/gems/opentelemetry-exporter-otlp@0.30.0.rbi +9 -0
- data/sorbet/rbi/gems/opentelemetry-registry@0.4.0.rbi +9 -0
- data/sorbet/rbi/gems/opentelemetry-sdk@1.8.1.rbi +9 -0
- data/sorbet/rbi/gems/opentelemetry-semantic_conventions@1.11.0.rbi +9 -0
- data/sorbet/rbi/gems/polars-df@0.20.0.rbi +9 -0
- data/sorbet/rbi/gems/sorbet-result@1.4.0.rbi +242 -0
- data/sorbet/rbi/gems/sorbet-schema@0.9.2.rbi +743 -0
- data/sorbet/rbi/gems/sorbet-struct-comparable@1.3.0.rbi +48 -0
- data/sorbet/rbi/gems/tokenizers@0.5.5.rbi +754 -0
- data/sorbet/rbi/gems/traces@0.17.0.rbi +9 -0
- data/sorbet/rbi/gems/zeitwerk@2.7.3.rbi +1429 -0
- metadata +63 -2
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require
|
|
5
|
-
|
|
6
|
-
require_relative "comment_extractor"
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
|
+
require_relative 'comment_extractor'
|
|
7
6
|
|
|
8
7
|
module SorbetBaml
|
|
9
8
|
# Main converter class for transforming Sorbet types to BAML
|
|
@@ -18,24 +17,24 @@ module SorbetBaml
|
|
|
18
17
|
sig { params(klasses: T::Array[T.class_of(T::Struct)], options: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
19
18
|
def self.from_structs(klasses, options = {})
|
|
20
19
|
converter = new(options)
|
|
21
|
-
|
|
20
|
+
|
|
22
21
|
if converter.instance_variable_get(:@include_dependencies)
|
|
23
22
|
# When dependencies are enabled, collect all unique dependencies and convert once
|
|
24
23
|
all_dependencies = Set.new
|
|
25
24
|
enum_dependencies = Set.new
|
|
26
|
-
|
|
25
|
+
|
|
27
26
|
klasses.each do |klass|
|
|
28
27
|
deps = DependencyResolver.resolve_dependencies(klass)
|
|
29
28
|
all_dependencies.merge(deps)
|
|
30
29
|
enum_deps = converter.send(:find_enum_dependencies, deps)
|
|
31
30
|
enum_dependencies.merge(enum_deps)
|
|
32
31
|
end
|
|
33
|
-
|
|
32
|
+
|
|
34
33
|
# Convert all unique types
|
|
35
34
|
converted_types = []
|
|
36
35
|
enum_dependencies.each { |enum_klass| converted_types << converter.convert_enum(enum_klass) }
|
|
37
36
|
all_dependencies.each { |struct_klass| converted_types << converter.send(:convert_single_struct, struct_klass) }
|
|
38
|
-
|
|
37
|
+
|
|
39
38
|
converted_types.join("\n\n")
|
|
40
39
|
else
|
|
41
40
|
# When dependencies are disabled, convert each struct individually
|
|
@@ -43,6 +42,11 @@ module SorbetBaml
|
|
|
43
42
|
end
|
|
44
43
|
end
|
|
45
44
|
|
|
45
|
+
sig { params(klass: T.class_of(T::Struct), options: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
46
|
+
def self.from_tool(klass, options = {})
|
|
47
|
+
new(options).convert_tool(klass)
|
|
48
|
+
end
|
|
49
|
+
|
|
46
50
|
sig { params(klass: T.class_of(T::Enum), options: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
47
51
|
def self.from_enum(klass, options = {})
|
|
48
52
|
new(options).convert_enum(klass)
|
|
@@ -61,15 +65,15 @@ module SorbetBaml
|
|
|
61
65
|
if @include_dependencies
|
|
62
66
|
# Get all dependencies in correct order and convert them all
|
|
63
67
|
dependencies = DependencyResolver.resolve_dependencies(klass)
|
|
64
|
-
|
|
68
|
+
|
|
65
69
|
# Also find all enum dependencies
|
|
66
70
|
enum_dependencies = find_enum_dependencies(dependencies)
|
|
67
|
-
|
|
71
|
+
|
|
68
72
|
# Convert enums first, then structs
|
|
69
73
|
converted_types = []
|
|
70
74
|
enum_dependencies.each { |enum_klass| converted_types << convert_enum(enum_klass) }
|
|
71
75
|
dependencies.each { |dep_klass| converted_types << convert_single_struct(dep_klass) }
|
|
72
|
-
|
|
76
|
+
|
|
73
77
|
converted_types.join("\n\n")
|
|
74
78
|
else
|
|
75
79
|
# Just convert the single struct
|
|
@@ -82,15 +86,15 @@ module SorbetBaml
|
|
|
82
86
|
class_name = klass.name || klass.to_s
|
|
83
87
|
simple_name = class_name.split('::').last
|
|
84
88
|
lines = ["enum #{simple_name} {"]
|
|
85
|
-
|
|
89
|
+
|
|
86
90
|
# Extract comments if requested
|
|
87
91
|
comments = @include_descriptions ? CommentExtractor.extract_enum_comments(klass) : {}
|
|
88
|
-
|
|
92
|
+
|
|
89
93
|
# Get all enum values by calling values method
|
|
90
94
|
enum_values = klass.values
|
|
91
95
|
enum_values.each do |enum_instance|
|
|
92
96
|
value = enum_instance.serialize
|
|
93
|
-
|
|
97
|
+
|
|
94
98
|
# Find the constant name for this value
|
|
95
99
|
constant_name = T.let(nil, T.nilable(String))
|
|
96
100
|
klass.constants.each do |const_name|
|
|
@@ -100,18 +104,48 @@ module SorbetBaml
|
|
|
100
104
|
break
|
|
101
105
|
end
|
|
102
106
|
end
|
|
103
|
-
|
|
107
|
+
|
|
104
108
|
line = "#{' ' * @indent_size}\"#{value}\""
|
|
105
|
-
|
|
109
|
+
|
|
106
110
|
# Add description if available (BAML uses @description annotations, not comments)
|
|
107
111
|
if @include_descriptions && constant_name && comments[constant_name]
|
|
108
112
|
line += " @description(\"#{T.must(comments[constant_name]).gsub('"', '\\"')}\")"
|
|
109
113
|
end
|
|
110
|
-
|
|
114
|
+
|
|
111
115
|
lines << line
|
|
112
116
|
end
|
|
113
|
-
|
|
114
|
-
lines <<
|
|
117
|
+
|
|
118
|
+
lines << '}'
|
|
119
|
+
lines.join("\n")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
sig { params(klass: T.class_of(T::Struct)).returns(String) }
|
|
123
|
+
def convert_tool(klass)
|
|
124
|
+
# Tools generate BAML class definitions similar to structs but for tool specifications
|
|
125
|
+
# They can include action fields with @description annotations
|
|
126
|
+
props = klass.props
|
|
127
|
+
|
|
128
|
+
class_name = klass.name || klass.to_s
|
|
129
|
+
simple_name = class_name.split('::').last
|
|
130
|
+
lines = ["class #{simple_name} {"]
|
|
131
|
+
|
|
132
|
+
# Extract comments if requested
|
|
133
|
+
comments = @include_descriptions ? CommentExtractor.extract_field_comments(klass) : {}
|
|
134
|
+
|
|
135
|
+
props.each do |name, prop_info|
|
|
136
|
+
baml_type = TypeMapper.map_type(prop_info[:type_object])
|
|
137
|
+
line = "#{' ' * @indent_size}#{name} #{baml_type}"
|
|
138
|
+
|
|
139
|
+
# Add description if available (BAML uses @description annotations)
|
|
140
|
+
if @include_descriptions && comments[name.to_s]
|
|
141
|
+
escaped_comment = T.must(comments[name.to_s]).gsub('"', '\\"')
|
|
142
|
+
line += " @description(\"#{escaped_comment}\")"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
lines << line
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
lines << '}'
|
|
115
149
|
lines.join("\n")
|
|
116
150
|
end
|
|
117
151
|
|
|
@@ -120,49 +154,49 @@ module SorbetBaml
|
|
|
120
154
|
sig { params(klass: T.class_of(T::Struct)).returns(String) }
|
|
121
155
|
def convert_single_struct(klass)
|
|
122
156
|
props = klass.props
|
|
123
|
-
|
|
157
|
+
|
|
124
158
|
class_name = klass.name || klass.to_s
|
|
125
159
|
simple_name = class_name.split('::').last
|
|
126
160
|
lines = ["class #{simple_name} {"]
|
|
127
|
-
|
|
161
|
+
|
|
128
162
|
# Extract comments if requested
|
|
129
163
|
comments = @include_descriptions ? CommentExtractor.extract_field_comments(klass) : {}
|
|
130
|
-
|
|
164
|
+
|
|
131
165
|
props.each do |name, prop_info|
|
|
132
166
|
baml_type = TypeMapper.map_type(prop_info[:type_object])
|
|
133
167
|
line = "#{' ' * @indent_size}#{name} #{baml_type}"
|
|
134
|
-
|
|
168
|
+
|
|
135
169
|
# Add description if available (BAML uses @description annotations)
|
|
136
170
|
if @include_descriptions && comments[name.to_s]
|
|
137
171
|
escaped_comment = T.must(comments[name.to_s]).gsub('"', '\\"')
|
|
138
172
|
line += " @description(\"#{escaped_comment}\")"
|
|
139
173
|
end
|
|
140
|
-
|
|
174
|
+
|
|
141
175
|
lines << line
|
|
142
176
|
end
|
|
143
|
-
|
|
144
|
-
lines <<
|
|
177
|
+
|
|
178
|
+
lines << '}'
|
|
145
179
|
lines.join("\n")
|
|
146
180
|
end
|
|
147
|
-
|
|
181
|
+
|
|
148
182
|
sig { params(struct_classes: T::Array[T.class_of(T::Struct)]).returns(T::Array[T.class_of(T::Enum)]) }
|
|
149
183
|
def find_enum_dependencies(struct_classes)
|
|
150
184
|
enum_deps = Set.new
|
|
151
|
-
|
|
185
|
+
|
|
152
186
|
struct_classes.each do |struct_klass|
|
|
153
187
|
struct_klass.props.each do |_name, prop_info|
|
|
154
188
|
type_object = prop_info[:type_object]
|
|
155
189
|
enum_deps.merge(extract_enum_types(type_object))
|
|
156
190
|
end
|
|
157
191
|
end
|
|
158
|
-
|
|
192
|
+
|
|
159
193
|
enum_deps.to_a
|
|
160
194
|
end
|
|
161
|
-
|
|
195
|
+
|
|
162
196
|
sig { params(type_object: T.untyped).returns(T::Array[T.class_of(T::Enum)]) }
|
|
163
197
|
def extract_enum_types(type_object)
|
|
164
198
|
return [] if type_object.nil?
|
|
165
|
-
|
|
199
|
+
|
|
166
200
|
case type_object
|
|
167
201
|
when T::Types::Simple
|
|
168
202
|
extract_enum_from_simple_type(type_object.raw_type)
|
|
@@ -182,7 +216,7 @@ module SorbetBaml
|
|
|
182
216
|
end
|
|
183
217
|
end
|
|
184
218
|
end
|
|
185
|
-
|
|
219
|
+
|
|
186
220
|
sig { params(raw_type: T.untyped).returns(T::Array[T.class_of(T::Enum)]) }
|
|
187
221
|
def extract_enum_from_simple_type(raw_type)
|
|
188
222
|
# Check if this raw_type is a T::Enum subclass
|
|
@@ -193,4 +227,4 @@ module SorbetBaml
|
|
|
193
227
|
end
|
|
194
228
|
end
|
|
195
229
|
end
|
|
196
|
-
end
|
|
230
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
5
|
|
|
6
6
|
module SorbetBaml
|
|
7
7
|
# Resolves dependencies between T::Struct types and orders them topologically
|
|
@@ -23,10 +23,10 @@ module SorbetBaml
|
|
|
23
23
|
def resolve_dependencies(klass)
|
|
24
24
|
@visited.clear
|
|
25
25
|
@dependencies.clear
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
# Perform depth-first search to find all dependencies
|
|
28
28
|
visit(klass)
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
# Dependencies are already in correct topological order
|
|
31
31
|
# (dependencies first, then the types that depend on them)
|
|
32
32
|
@dependencies
|
|
@@ -37,15 +37,15 @@ module SorbetBaml
|
|
|
37
37
|
sig { params(klass: T.class_of(T::Struct)).void }
|
|
38
38
|
def visit(klass)
|
|
39
39
|
return if @visited.include?(klass)
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
@visited.add(klass)
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
# Find all T::Struct dependencies in this class
|
|
44
44
|
struct_dependencies = find_struct_dependencies(klass)
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
# Visit dependencies first (depth-first)
|
|
47
47
|
struct_dependencies.each { |dep| visit(dep) }
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
# Add this class after its dependencies
|
|
50
50
|
@dependencies << klass
|
|
51
51
|
end
|
|
@@ -53,19 +53,19 @@ module SorbetBaml
|
|
|
53
53
|
sig { params(klass: T.class_of(T::Struct)).returns(T::Array[T.class_of(T::Struct)]) }
|
|
54
54
|
def find_struct_dependencies(klass)
|
|
55
55
|
dependencies = []
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
klass.props.each do |_name, prop_info|
|
|
58
58
|
type_object = prop_info[:type_object]
|
|
59
59
|
dependencies.concat(extract_struct_types(type_object))
|
|
60
60
|
end
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
dependencies.uniq
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
sig { params(type_object: T.untyped).returns(T::Array[T.class_of(T::Struct)]) }
|
|
66
66
|
def extract_struct_types(type_object)
|
|
67
67
|
return [] if type_object.nil?
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
case type_object
|
|
70
70
|
when T::Types::Simple
|
|
71
71
|
extract_from_simple_type(type_object.raw_type)
|
|
@@ -96,4 +96,4 @@ module SorbetBaml
|
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
end
|
|
99
|
-
end
|
|
99
|
+
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
5
|
|
|
6
6
|
module SorbetBaml
|
|
7
7
|
# Extension module to add description: parameter support to T::Struct
|
|
8
8
|
module DescriptionExtension
|
|
9
9
|
extend T::Sig
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
# Override const to support description parameter
|
|
12
12
|
sig { params(name: Symbol, type: T.untyped, description: T.nilable(String), kwargs: T.untyped).void }
|
|
13
13
|
def const(name, type, description: nil, **kwargs)
|
|
@@ -17,8 +17,8 @@ module SorbetBaml
|
|
|
17
17
|
super(name, type, **kwargs)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
|
-
|
|
21
|
-
# Override prop to support description parameter
|
|
20
|
+
|
|
21
|
+
# Override prop to support description parameter
|
|
22
22
|
sig { params(name: Symbol, type: T.untyped, description: T.nilable(String), kwargs: T.untyped).void }
|
|
23
23
|
def prop(name, type, description: nil, **kwargs)
|
|
24
24
|
if description
|
|
@@ -31,4 +31,4 @@ module SorbetBaml
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Automatically extend T::Struct with description support
|
|
34
|
-
T::Struct.extend(SorbetBaml::DescriptionExtension)
|
|
34
|
+
T::Struct.extend(SorbetBaml::DescriptionExtension)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
5
|
|
|
6
6
|
module SorbetBaml
|
|
7
7
|
# Extracts description parameters from T::Struct prop and const declarations
|
|
@@ -11,26 +11,24 @@ module SorbetBaml
|
|
|
11
11
|
sig { params(klass: T::Class[T.anything]).returns(T::Hash[String, T.nilable(String)]) }
|
|
12
12
|
def self.extract_prop_descriptions(klass)
|
|
13
13
|
descriptions = {}
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
# Check if this is a T::Struct with props
|
|
16
16
|
return descriptions unless klass.respond_to?(:props)
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
begin
|
|
19
19
|
T.unsafe(klass).props.each do |field_name, prop_info|
|
|
20
20
|
next unless prop_info.is_a?(Hash)
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
# Check if the prop has a description in the :extra field
|
|
23
23
|
extra = prop_info[:extra]
|
|
24
|
-
if extra.is_a?(Hash) && extra[:description].is_a?(String)
|
|
25
|
-
descriptions[field_name.to_s] = extra[:description]
|
|
26
|
-
end
|
|
24
|
+
descriptions[field_name.to_s] = extra[:description] if extra.is_a?(Hash) && extra[:description].is_a?(String)
|
|
27
25
|
end
|
|
28
|
-
rescue
|
|
26
|
+
rescue StandardError
|
|
29
27
|
# Handle any errors gracefully and return empty hash
|
|
30
28
|
return {}
|
|
31
29
|
end
|
|
32
|
-
|
|
30
|
+
|
|
33
31
|
descriptions
|
|
34
32
|
end
|
|
35
33
|
end
|
|
36
|
-
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
|
+
|
|
6
|
+
module SorbetBaml
|
|
7
|
+
# Converter for DSPy tools to BAML format
|
|
8
|
+
class DSPyToolConverter
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
sig { params(klass: T.class_of(DSPy::Tools::Base), options: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
12
|
+
def self.from_dspy_tool(klass, options = {})
|
|
13
|
+
new(options).convert_dspy_tool(klass)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).void }
|
|
17
|
+
def initialize(options = {})
|
|
18
|
+
@options = options
|
|
19
|
+
@indent_size = T.let(options.fetch(:indent_size, 2), Integer)
|
|
20
|
+
@include_descriptions = T.let(options.fetch(:include_descriptions, true), T::Boolean)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
sig { params(klass: T.class_of(DSPy::Tools::Base)).returns(String) }
|
|
24
|
+
def convert_dspy_tool(klass)
|
|
25
|
+
# Extract tool metadata from DSPy tool
|
|
26
|
+
tool_name = klass.tool_name_value || klass.name&.split('::')&.last
|
|
27
|
+
tool_description = klass.tool_description_value
|
|
28
|
+
|
|
29
|
+
# Get parameters from DSPy's call_schema which extracts from Sorbet signatures
|
|
30
|
+
call_schema = klass.call_schema
|
|
31
|
+
parameters = call_schema[:properties] || {}
|
|
32
|
+
required_params = call_schema[:required] || []
|
|
33
|
+
|
|
34
|
+
lines = []
|
|
35
|
+
|
|
36
|
+
# Add tool description as class-level comment if available
|
|
37
|
+
lines << "// #{tool_description}" if @include_descriptions && tool_description
|
|
38
|
+
|
|
39
|
+
# Generate BAML class definition
|
|
40
|
+
lines << "class #{tool_name} {"
|
|
41
|
+
|
|
42
|
+
parameters.each do |param_name, param_info|
|
|
43
|
+
# Convert JSON schema type to BAML type
|
|
44
|
+
baml_type = json_schema_type_to_baml(param_info)
|
|
45
|
+
|
|
46
|
+
# Add optional marker if parameter is not required
|
|
47
|
+
baml_type += '?' unless required_params.include?(param_name.to_s)
|
|
48
|
+
|
|
49
|
+
line = "#{' ' * @indent_size}#{param_name} #{baml_type}"
|
|
50
|
+
|
|
51
|
+
# Add description from schema if available
|
|
52
|
+
if @include_descriptions && param_info[:description]
|
|
53
|
+
escaped_description = param_info[:description].gsub('"', '\\"')
|
|
54
|
+
line += " @description(\"#{escaped_description}\")"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
lines << line
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
lines << '}'
|
|
61
|
+
lines.join("\n")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
sig { params(json_schema_info: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
67
|
+
def json_schema_type_to_baml(json_schema_info)
|
|
68
|
+
type = json_schema_info[:type]
|
|
69
|
+
|
|
70
|
+
case type
|
|
71
|
+
when :string, 'string'
|
|
72
|
+
'string'
|
|
73
|
+
when :integer, 'integer'
|
|
74
|
+
'int'
|
|
75
|
+
when :number, 'number'
|
|
76
|
+
'float'
|
|
77
|
+
when :boolean, 'boolean'
|
|
78
|
+
'bool'
|
|
79
|
+
when :array, 'array'
|
|
80
|
+
item_type = json_schema_info.dig(:items, :type)
|
|
81
|
+
case item_type
|
|
82
|
+
when :string, 'string' then 'string[]'
|
|
83
|
+
when :integer, 'integer' then 'int[]'
|
|
84
|
+
when :number, 'number' then 'float[]'
|
|
85
|
+
when :boolean, 'boolean' then 'bool[]'
|
|
86
|
+
else 'string[]' # fallback
|
|
87
|
+
end
|
|
88
|
+
when :object, 'object'
|
|
89
|
+
# For object types, we'll default to a map<string, string>
|
|
90
|
+
# In a more sophisticated implementation, we'd handle nested objects
|
|
91
|
+
'map<string, string>'
|
|
92
|
+
else
|
|
93
|
+
'string' # fallback for unknown types
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
|
+
|
|
6
|
+
module SorbetBaml
|
|
7
|
+
# Extensions to add BAML conversion methods to DSPy::Tools::Base
|
|
8
|
+
module DSPyToolExtensions
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
# Convert this DSPy tool to BAML tool definition
|
|
12
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
13
|
+
def to_baml(options = {})
|
|
14
|
+
baml_tool_definition(options)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Convert this DSPy tool to BAML tool definition with options
|
|
18
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
19
|
+
def baml_tool_definition(options = {})
|
|
20
|
+
SorbetBaml::DSPyToolConverter.from_dspy_tool(T.cast(self, T.class_of(DSPy::Tools::Base)), options)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
5
|
|
|
6
6
|
module SorbetBaml
|
|
7
7
|
# Extensions to add BAML conversion methods to T::Enum
|
|
@@ -20,4 +20,4 @@ module SorbetBaml
|
|
|
20
20
|
SorbetBaml::Converter.from_enum(T.cast(self, T.class_of(T::Enum)), options)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
|
-
end
|
|
23
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
5
|
|
|
6
6
|
module SorbetBaml
|
|
7
7
|
# Extensions to add BAML conversion methods to T::Struct
|
|
@@ -20,4 +20,4 @@ module SorbetBaml
|
|
|
20
20
|
SorbetBaml::Converter.from_struct(T.cast(self, T.class_of(T::Struct)), options)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
|
-
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
|
+
|
|
6
|
+
module SorbetBaml
|
|
7
|
+
# Extensions to add BAML tool conversion methods to T::Struct
|
|
8
|
+
module ToolExtensions
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
# Convert this struct to BAML tool definition
|
|
12
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
13
|
+
def to_baml_tool(options = {})
|
|
14
|
+
baml_tool_definition(options)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Convert this struct to BAML tool definition with options
|
|
18
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
19
|
+
def baml_tool_definition(options = {})
|
|
20
|
+
SorbetBaml::Converter.from_tool(T.cast(self, T.class_of(T::Struct)), options)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
5
|
|
|
6
6
|
module SorbetBaml
|
|
7
7
|
# Maps Sorbet type objects to BAML type strings
|
|
@@ -10,8 +10,8 @@ module SorbetBaml
|
|
|
10
10
|
|
|
11
11
|
sig { params(type_object: T.untyped).returns(String) }
|
|
12
12
|
def self.map_type(type_object)
|
|
13
|
-
return
|
|
14
|
-
|
|
13
|
+
return 'string' if type_object.nil?
|
|
14
|
+
|
|
15
15
|
case type_object
|
|
16
16
|
when T::Types::Simple
|
|
17
17
|
map_simple_type(type_object.raw_type)
|
|
@@ -25,7 +25,7 @@ module SorbetBaml
|
|
|
25
25
|
map_union_type(type_object)
|
|
26
26
|
else
|
|
27
27
|
# Fallback for unknown types
|
|
28
|
-
|
|
28
|
+
'unknown'
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -33,27 +33,27 @@ module SorbetBaml
|
|
|
33
33
|
sig { params(raw_type: T.untyped).returns(String) }
|
|
34
34
|
def self.map_simple_type(raw_type)
|
|
35
35
|
case raw_type.name
|
|
36
|
-
when
|
|
37
|
-
|
|
38
|
-
when
|
|
39
|
-
|
|
40
|
-
when
|
|
41
|
-
|
|
42
|
-
when
|
|
43
|
-
|
|
44
|
-
when
|
|
45
|
-
|
|
46
|
-
when
|
|
47
|
-
|
|
48
|
-
when
|
|
49
|
-
|
|
36
|
+
when 'String'
|
|
37
|
+
'string'
|
|
38
|
+
when 'Integer'
|
|
39
|
+
'int'
|
|
40
|
+
when 'Float'
|
|
41
|
+
'float'
|
|
42
|
+
when 'TrueClass', 'FalseClass'
|
|
43
|
+
'bool'
|
|
44
|
+
when 'NilClass'
|
|
45
|
+
'null'
|
|
46
|
+
when 'Symbol'
|
|
47
|
+
'string'
|
|
48
|
+
when 'Date', 'DateTime', 'Time'
|
|
49
|
+
'string'
|
|
50
50
|
else
|
|
51
51
|
# Check if it's a T::Struct or T::Enum
|
|
52
52
|
if raw_type < T::Struct || raw_type < T::Enum
|
|
53
53
|
type_name = raw_type.name || raw_type.to_s
|
|
54
|
-
type_name.split('::').last ||
|
|
54
|
+
type_name.split('::').last || 'unknown'
|
|
55
55
|
else
|
|
56
|
-
|
|
56
|
+
'unknown'
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
end
|
|
@@ -63,28 +63,26 @@ module SorbetBaml
|
|
|
63
63
|
types = type_object.types
|
|
64
64
|
nil_type = types.find { |t| t.raw_type == NilClass }
|
|
65
65
|
non_nil_types = types.reject { |t| t.raw_type == NilClass }
|
|
66
|
-
|
|
67
|
-
if non_nil_types.empty?
|
|
68
|
-
|
|
69
|
-
end
|
|
70
|
-
|
|
66
|
+
|
|
67
|
+
return 'null' if non_nil_types.empty?
|
|
68
|
+
|
|
71
69
|
if non_nil_types.size == 1
|
|
72
70
|
# This is T.nilable(T) - single type with nil
|
|
73
71
|
base_type = map_type(non_nil_types.first)
|
|
74
72
|
return nil_type ? "#{base_type}?" : base_type
|
|
75
73
|
end
|
|
76
|
-
|
|
74
|
+
|
|
77
75
|
# This is T.any with multiple types
|
|
78
76
|
mapped_types = non_nil_types.map { |t| map_type(t) }
|
|
79
|
-
|
|
77
|
+
|
|
80
78
|
# Special case: TrueClass + FalseClass = bool
|
|
81
|
-
if mapped_types.sort == [
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
union_string = if mapped_types.sort == %w[bool bool]
|
|
80
|
+
'bool'
|
|
81
|
+
else
|
|
82
|
+
# Remove duplicates and join
|
|
83
|
+
mapped_types.uniq.join(' | ')
|
|
84
|
+
end
|
|
85
|
+
|
|
88
86
|
# If nil is present, wrap in parentheses and add ?
|
|
89
87
|
if nil_type
|
|
90
88
|
"(#{union_string})?"
|
|
@@ -96,9 +94,9 @@ module SorbetBaml
|
|
|
96
94
|
sig { params(type_object: T.untyped).returns(String) }
|
|
97
95
|
def self.map_array_type(type_object)
|
|
98
96
|
element_type = map_type(type_object.type)
|
|
99
|
-
|
|
97
|
+
|
|
100
98
|
# If element type contains union (|), wrap in parentheses for correct precedence
|
|
101
|
-
if element_type.include?(
|
|
99
|
+
if element_type.include?('|')
|
|
102
100
|
"(#{element_type})[]"
|
|
103
101
|
else
|
|
104
102
|
"#{element_type}[]"
|
|
@@ -113,4 +111,4 @@ module SorbetBaml
|
|
|
113
111
|
"map<#{key_type}, #{value_type}>"
|
|
114
112
|
end
|
|
115
113
|
end
|
|
116
|
-
end
|
|
114
|
+
end
|
data/lib/sorbet_baml/version.rb
CHANGED