sorbet-baml 0.2.0 → 0.4.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/CHANGELOG.md +59 -0
- data/README.md +148 -6
- data/Rakefile +2 -2
- 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 +43 -13
- metadata +6 -57
- data/.idea/.gitignore +0 -8
- data/.idea/inspectionProfiles/Project_Default.xml +0 -5
- data/.rspec +0 -3
- data/sorbet/config +0 -4
- data/sorbet/rbi/annotations/.gitattributes +0 -1
- data/sorbet/rbi/annotations/rainbow.rbi +0 -269
- data/sorbet/rbi/gems/.gitattributes +0 -1
- data/sorbet/rbi/gems/ast@2.4.3.rbi +0 -585
- data/sorbet/rbi/gems/benchmark@0.4.1.rbi +0 -619
- data/sorbet/rbi/gems/byebug@11.1.3.rbi +0 -37
- data/sorbet/rbi/gems/date@3.4.1.rbi +0 -75
- data/sorbet/rbi/gems/diff-lcs@1.6.2.rbi +0 -1134
- data/sorbet/rbi/gems/erb@5.0.2.rbi +0 -878
- data/sorbet/rbi/gems/erubi@1.13.1.rbi +0 -155
- data/sorbet/rbi/gems/io-console@0.8.1.rbi +0 -9
- data/sorbet/rbi/gems/json@2.13.2.rbi +0 -2087
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.5.rbi +0 -9
- data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +0 -240
- data/sorbet/rbi/gems/logger@1.7.0.rbi +0 -963
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +0 -159
- data/sorbet/rbi/gems/parallel@1.27.0.rbi +0 -291
- data/sorbet/rbi/gems/parser@3.3.9.0.rbi +0 -5535
- data/sorbet/rbi/gems/pp@0.6.2.rbi +0 -368
- data/sorbet/rbi/gems/prettyprint@0.2.0.rbi +0 -477
- data/sorbet/rbi/gems/prism@1.4.0.rbi +0 -41732
- data/sorbet/rbi/gems/psych@5.2.6.rbi +0 -2469
- data/sorbet/rbi/gems/racc@1.8.1.rbi +0 -164
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +0 -403
- data/sorbet/rbi/gems/rake@13.3.0.rbi +0 -3031
- data/sorbet/rbi/gems/rbi@0.3.6.rbi +0 -6893
- data/sorbet/rbi/gems/rbs@3.9.4.rbi +0 -6976
- data/sorbet/rbi/gems/rdoc@6.14.2.rbi +0 -12688
- data/sorbet/rbi/gems/regexp_parser@2.11.2.rbi +0 -3845
- data/sorbet/rbi/gems/reline@0.6.2.rbi +0 -2441
- data/sorbet/rbi/gems/rexml@3.4.1.rbi +0 -5240
- data/sorbet/rbi/gems/rspec-core@3.13.5.rbi +0 -11250
- data/sorbet/rbi/gems/rspec-expectations@3.13.5.rbi +0 -8189
- data/sorbet/rbi/gems/rspec-mocks@3.13.5.rbi +0 -5350
- data/sorbet/rbi/gems/rspec-support@3.13.4.rbi +0 -1630
- data/sorbet/rbi/gems/rspec@3.13.1.rbi +0 -83
- data/sorbet/rbi/gems/rubocop-ast@1.46.0.rbi +0 -7764
- data/sorbet/rbi/gems/rubocop-sorbet@0.10.5.rbi +0 -2386
- data/sorbet/rbi/gems/rubocop@1.79.2.rbi +0 -63321
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +0 -1318
- data/sorbet/rbi/gems/spoom@1.6.3.rbi +0 -6985
- data/sorbet/rbi/gems/stringio@3.1.7.rbi +0 -9
- data/sorbet/rbi/gems/tapioca@0.16.11.rbi +0 -3628
- data/sorbet/rbi/gems/thor@1.4.0.rbi +0 -4399
- data/sorbet/rbi/gems/unicode-display_width@3.1.5.rbi +0 -132
- data/sorbet/rbi/gems/unicode-emoji@4.0.4.rbi +0 -251
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
- data/sorbet/rbi/gems/yard@0.9.37.rbi +0 -18379
- data/sorbet/tapioca/config.yml +0 -13
- data/sorbet/tapioca/require.rb +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4de7b80da4ee748d5c22196c22f78ca92e855f9b4983437d4ea6ad02a5201fba
|
|
4
|
+
data.tar.gz: 22a81719e5d2021b52945d913e3134482ff999e917d655f67ec894a35fa2a40b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d64e1cef217666030c2f34cacd48333d6ce46d5fac40e2a2855c0f73fe43dadf39a620ad01b37df904ca5aa792a7d46cd3a1bdaf45ce797b34f3010ce4a2be12
|
|
7
|
+
data.tar.gz: 6131b255163a544f2ef7591f58e173ab0436c44426003513888b51ea4d3a35a5ff90d186b491d867035b9d0c86929b176c6ff8bfeb6de7106b87662546ea0c3e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.4.0] - 2025-10-14
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Dramatically reduced gem size from 1.46MB to 20KB (98.6% reduction)**
|
|
8
|
+
- Excluded development-only Sorbet RBI files (`sorbet/` directory - 11MB)
|
|
9
|
+
- Excluded documentation site source (`docs-site/` directory - 43MB)
|
|
10
|
+
- Excluded IDE configuration files (`.idea/` directory)
|
|
11
|
+
- Excluded documentation output (`docs-output/` directory)
|
|
12
|
+
- Excluded RSpec configuration files (`.rspec`)
|
|
13
|
+
- **File count reduced from 134 to 22 essential files**
|
|
14
|
+
|
|
15
|
+
### Technical Details
|
|
16
|
+
|
|
17
|
+
The gem now only includes runtime-required files:
|
|
18
|
+
- Core library code (`lib/` directory)
|
|
19
|
+
- Documentation (README.md, LICENSE.txt, CHANGELOG.md)
|
|
20
|
+
- Examples (`examples/` directory)
|
|
21
|
+
|
|
22
|
+
All Sorbet type checking infrastructure and development tooling remains available in the repository for contributors but is no longer shipped to end users.
|
|
23
|
+
|
|
24
|
+
### Impact
|
|
25
|
+
|
|
26
|
+
- **Faster gem installation** - 92% less data to download
|
|
27
|
+
- **Smaller disk footprint** - More efficient for deployed applications
|
|
28
|
+
- **No functionality changes** - All features work exactly as before
|
|
29
|
+
- **Zero breaking changes** - Full backward compatibility maintained
|
|
30
|
+
|
|
31
|
+
## [0.3.0] - 2025-08-17
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- Comprehensive BAML tool type definitions for agentic workflows
|
|
36
|
+
- `.to_baml_tool()` method for T::Struct classes
|
|
37
|
+
- Automatic DSPy tool conversion when `dspy.rb` is available
|
|
38
|
+
- Tool metadata extraction (`tool_name`, `tool_description`)
|
|
39
|
+
- Support for function calling APIs (OpenAI, Anthropic, etc.)
|
|
40
|
+
|
|
41
|
+
### Improved
|
|
42
|
+
|
|
43
|
+
- 80+ test cases with comprehensive coverage
|
|
44
|
+
- Full Sorbet type safety throughout
|
|
45
|
+
- Enhanced documentation with tool examples
|
|
46
|
+
|
|
47
|
+
## [0.2.0] - 2025-08-16
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- Clean `description:` parameter for `const` and `prop` declarations
|
|
52
|
+
- Smart fallback chain: description parameter → Ruby comments → nil
|
|
53
|
+
- `DescriptionExtension` for T::Struct
|
|
54
|
+
- `DescriptionExtractor` for programmatic access
|
|
55
|
+
|
|
56
|
+
### Improved
|
|
57
|
+
|
|
58
|
+
- Better LLM context with rich field descriptions
|
|
59
|
+
- Zero breaking changes to existing APIs
|
|
60
|
+
- Full backward compatibility
|
|
61
|
+
|
|
3
62
|
## [0.1.0] - 2025-08-16
|
|
4
63
|
|
|
5
64
|
- Initial release
|
data/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# sorbet-baml
|
|
2
2
|
|
|
3
|
+
[](https://rubygems.org/gems/sorbet-baml)
|
|
4
|
+
[](https://rubygems.org/gems/sorbet-baml)
|
|
5
|
+
[](https://github.com/vicentereig/sorbet-baml/blob/main/LICENSE.txt)
|
|
6
|
+
[](https://sorbet.org)
|
|
7
|
+
|
|
3
8
|
Ruby-idiomatic conversion from Sorbet types to BAML (Boundary AI Markup Language) for efficient LLM prompting.
|
|
4
9
|
|
|
5
10
|
## What is this?
|
|
@@ -142,7 +147,29 @@ SorbetBaml.from_structs([ResearchSynthesis, ResearchFindings])
|
|
|
142
147
|
|
|
143
148
|
## 🎯 Field Descriptions for LLM Context
|
|
144
149
|
|
|
145
|
-
Add crucial context to your BAML types by documenting fields
|
|
150
|
+
Add crucial context to your BAML types by documenting fields - essential for autonomous agents and complex workflows. The gem supports **two ways** to provide field descriptions:
|
|
151
|
+
|
|
152
|
+
### Method 1: Comments (Traditional)
|
|
153
|
+
```ruby
|
|
154
|
+
class ResearchTask < T::Struct
|
|
155
|
+
# Clear description of the research objective
|
|
156
|
+
const :objective, String
|
|
157
|
+
# Strategic priority ranking (1-5 scale)
|
|
158
|
+
const :priority, Integer
|
|
159
|
+
end
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Method 2: Description Parameter (New in v0.2.0+)
|
|
163
|
+
```ruby
|
|
164
|
+
class ResearchTask < T::Struct
|
|
165
|
+
const :objective, String, description: "Clear description of the research objective"
|
|
166
|
+
const :priority, Integer, description: "Strategic priority ranking (1-5 scale)"
|
|
167
|
+
end
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Both methods produce identical BAML output with `@description` annotations. The `description:` parameter provides a more explicit, Sorbet-native way to document fields.
|
|
171
|
+
|
|
172
|
+
### Complete Example
|
|
146
173
|
|
|
147
174
|
```ruby
|
|
148
175
|
class TaskType < T::Enum
|
|
@@ -193,6 +220,112 @@ ResearchSubtask.to_baml
|
|
|
193
220
|
|
|
194
221
|
**Why descriptions matter**: LLMs use field descriptions to understand context and generate more accurate, meaningful data. This is crucial for complex domains where field names alone aren't sufficient.
|
|
195
222
|
|
|
223
|
+
## 🛠️ Tool Type Definitions
|
|
224
|
+
|
|
225
|
+
Generate BAML tool specifications for agentic workflows, function calling, and structured LLM interactions:
|
|
226
|
+
|
|
227
|
+
### T::Struct-based Tools
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
class ReplyTool < T::Struct
|
|
231
|
+
# The response message to send back to the user
|
|
232
|
+
const :response, String
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
class SearchTool < T::Struct
|
|
236
|
+
# Using description: parameter (both methods work)
|
|
237
|
+
const :query, String, description: "The search query to execute"
|
|
238
|
+
const :limit, T.nilable(Integer), description: "Maximum number of results to return"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Generate BAML tool definitions
|
|
242
|
+
ReplyTool.to_baml_tool
|
|
243
|
+
# =>
|
|
244
|
+
# class ReplyTool {
|
|
245
|
+
# response string @description("The response message to send back to the user")
|
|
246
|
+
# }
|
|
247
|
+
|
|
248
|
+
SearchTool.to_baml_tool
|
|
249
|
+
# =>
|
|
250
|
+
# class SearchTool {
|
|
251
|
+
# query string @description("The search query to execute")
|
|
252
|
+
# limit int? @description("Maximum number of results to return")
|
|
253
|
+
# }
|
|
254
|
+
|
|
255
|
+
# Module API also available
|
|
256
|
+
SorbetBaml.from_tool(ReplyTool)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### DSPy-style Tools (Optional)
|
|
260
|
+
|
|
261
|
+
When `dspy.rb` is available, automatically convert DSPy tools with rich metadata:
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
class CalculatorTool < DSPy::Tools::Base
|
|
265
|
+
extend T::Sig
|
|
266
|
+
|
|
267
|
+
tool_name 'calculator'
|
|
268
|
+
tool_description 'Performs basic arithmetic operations'
|
|
269
|
+
|
|
270
|
+
sig { params(operation: String, num1: Float, num2: Float).returns(T.any(Float, String)) }
|
|
271
|
+
def call(operation:, num1:, num2:)
|
|
272
|
+
case operation.downcase
|
|
273
|
+
when 'add' then num1 + num2
|
|
274
|
+
when 'subtract' then num1 - num2
|
|
275
|
+
when 'multiply' then num1 * num2
|
|
276
|
+
when 'divide'
|
|
277
|
+
return "Error: Cannot divide by zero" if num2 == 0
|
|
278
|
+
num1 / num2
|
|
279
|
+
else
|
|
280
|
+
"Error: Unknown operation '#{operation}'. Use add, subtract, multiply, or divide"
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Automatic extraction of tool metadata and parameter types
|
|
286
|
+
CalculatorTool.to_baml
|
|
287
|
+
# =>
|
|
288
|
+
# // Performs basic arithmetic operations
|
|
289
|
+
# class calculator {
|
|
290
|
+
# operation string @description("Parameter operation")
|
|
291
|
+
# num1 float @description("Parameter num1")
|
|
292
|
+
# num2 float @description("Parameter num2")
|
|
293
|
+
# }
|
|
294
|
+
|
|
295
|
+
# Optional parameters handled correctly
|
|
296
|
+
class SearchTool < DSPy::Tools::Base
|
|
297
|
+
extend T::Sig
|
|
298
|
+
|
|
299
|
+
tool_name 'search'
|
|
300
|
+
tool_description 'Search for information'
|
|
301
|
+
|
|
302
|
+
sig { params(query: String, limit: T.nilable(Integer)).returns(T::Array[String]) }
|
|
303
|
+
def call(query:, limit: nil)
|
|
304
|
+
# Implementation...
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
SearchTool.to_baml
|
|
309
|
+
# =>
|
|
310
|
+
# // Search for information
|
|
311
|
+
# class search {
|
|
312
|
+
# query string @description("Parameter query")
|
|
313
|
+
# limit int? @description("Parameter limit (optional)")
|
|
314
|
+
# }
|
|
315
|
+
|
|
316
|
+
# Module API also available
|
|
317
|
+
SorbetBaml.from_dspy_tool(CalculatorTool)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Tool Features:**
|
|
321
|
+
- ✅ **T::Struct tools**: Convert any struct to BAML tool definition
|
|
322
|
+
- ✅ **DSPy integration**: Automatic extraction from DSPy::Tools::Base classes
|
|
323
|
+
- ✅ **Parameter types**: Full Sorbet type support (string, int, float, arrays, maps, etc.)
|
|
324
|
+
- ✅ **Optional parameters**: Automatically detect and mark with `?`
|
|
325
|
+
- ✅ **Descriptions**: Extract from comments or `description:` parameter (T::Struct) or automatic generation (DSPy)
|
|
326
|
+
- ✅ **Tool metadata**: Names, descriptions, and parameter documentation
|
|
327
|
+
- ✅ **Ruby-idiomatic**: `.to_baml_tool()` and `.to_baml()` methods
|
|
328
|
+
|
|
196
329
|
## 🎯 Complete Type Support
|
|
197
330
|
|
|
198
331
|
### ✅ Fully Supported
|
|
@@ -222,8 +355,10 @@ ResearchSubtask.to_baml
|
|
|
222
355
|
### 🚀 Advanced Features
|
|
223
356
|
|
|
224
357
|
- **Ruby-idiomatic API**: Every T::Struct and T::Enum gets `.to_baml` method
|
|
358
|
+
- **Tool definitions**: Generate BAML tool specs for function calling and agentic workflows
|
|
359
|
+
- **DSPy integration**: Automatic tool conversion from DSPy::Tools::Base classes
|
|
225
360
|
- **Smart defaults**: Field descriptions and dependencies included automatically
|
|
226
|
-
- **Field descriptions**: Extracts comments
|
|
361
|
+
- **Field descriptions**: Extracts from comments or `description:` parameter for LLM context
|
|
227
362
|
- **Dependency management**: Automatically includes all referenced types
|
|
228
363
|
- **Proper ordering**: Dependencies are sorted topologically (no forward references needed)
|
|
229
364
|
- **Circular reference handling**: Won't get stuck in infinite loops
|
|
@@ -249,29 +384,34 @@ ResearchSubtask.to_baml
|
|
|
249
384
|
|
|
250
385
|
## 🏁 Production Ready
|
|
251
386
|
|
|
252
|
-
This gem has reached **feature completeness** for core BAML conversion needs. The Ruby-idiomatic API is stable and thoroughly tested with **
|
|
387
|
+
This gem has reached **feature completeness** for core BAML conversion needs. The Ruby-idiomatic API is stable and thoroughly tested with **80 test cases** covering all type combinations, tool definitions, and edge cases.
|
|
253
388
|
|
|
254
389
|
### 📊 Quality Metrics
|
|
255
390
|
|
|
256
|
-
- ✅ **
|
|
391
|
+
- ✅ **Comprehensive Test Coverage** - All features thoroughly tested
|
|
257
392
|
- ✅ **Full Sorbet Type Safety** - Zero type errors throughout codebase
|
|
258
|
-
- ✅ **
|
|
393
|
+
- ✅ **80 Test Cases** - Covering basic types, complex combinations, tool definitions, and edge cases
|
|
259
394
|
- ✅ **TDD Development** - All features built test-first
|
|
260
395
|
- ✅ **Field Descriptions** - Automatic comment extraction for LLM context
|
|
396
|
+
- ✅ **Tool Definitions** - BAML tool specifications for function calling and agentic workflows
|
|
397
|
+
- ✅ **DSPy Integration** - Automatic tool conversion from DSPy::Tools::Base classes
|
|
261
398
|
- ✅ **Smart Defaults** - Dependencies and descriptions included by default
|
|
262
399
|
- ✅ **Zero Breaking Changes** - Maintains backward compatibility
|
|
263
400
|
|
|
264
401
|
### ✅ Complete Feature Set
|
|
265
402
|
|
|
266
403
|
- ✅ **Ruby-idiomatic API**: Every T::Struct and T::Enum gets `.to_baml` method
|
|
404
|
+
- ✅ **Tool definitions**: Generate BAML tool specifications from T::Struct classes
|
|
405
|
+
- ✅ **DSPy integration**: Automatic tool conversion from DSPy::Tools::Base classes
|
|
267
406
|
- ✅ **Smart defaults**: Field descriptions and dependencies included automatically
|
|
268
|
-
- ✅ **Field descriptions**: Extract documentation from comments for LLM context
|
|
407
|
+
- ✅ **Field descriptions**: Extract documentation from comments or `description:` parameter for LLM context
|
|
269
408
|
- ✅ **Dependency management**: Automatically includes all referenced types
|
|
270
409
|
- ✅ **Proper ordering**: Dependencies are sorted topologically
|
|
271
410
|
- ✅ **Type safety**: Full Sorbet type checking throughout
|
|
272
411
|
|
|
273
412
|
### 🗺️ Future Enhancements (Optional)
|
|
274
413
|
|
|
414
|
+
- [ ] **DSPy-independent tool API**: Tools shouldn't require DSPy, just follow the same API pattern
|
|
275
415
|
- [ ] **Type aliases**: `T.type_alias { String }` → `type Alias = string`
|
|
276
416
|
- [ ] **Custom naming**: Convert between snake_case ↔ camelCase
|
|
277
417
|
- [ ] **CLI tool**: `sorbet-baml convert MyStruct` command
|
|
@@ -282,6 +422,8 @@ This gem has reached **feature completeness** for core BAML conversion needs. Th
|
|
|
282
422
|
|
|
283
423
|
- **v0.0.1** - Initial implementation with basic type support
|
|
284
424
|
- **v0.1.0** - Complete type system + Ruby-idiomatic API + field descriptions + smart defaults
|
|
425
|
+
- **v0.2.0** - Description parameter support and enhanced field extraction
|
|
426
|
+
- **v0.3.0** - Tool type definitions + DSPy integration + 80+ test cases + comprehensive documentation
|
|
285
427
|
|
|
286
428
|
## 🌟 Real-World Usage: Autonomous Research Agents
|
|
287
429
|
|
data/Rakefile
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
require_relative '../lib/sorbet_baml'
|
|
5
5
|
|
|
6
|
-
puts
|
|
7
|
-
puts
|
|
6
|
+
puts '🎯 Description Parameter Support Demo'
|
|
7
|
+
puts '=' * 50
|
|
8
8
|
|
|
9
9
|
# Example 1: Basic description parameters
|
|
10
10
|
class User < T::Struct
|
|
11
11
|
const :name, String, description: "User's full legal name"
|
|
12
|
-
prop :age, Integer, description:
|
|
13
|
-
const :email, T.nilable(String), description:
|
|
14
|
-
const :interests, T::Array[String], description:
|
|
12
|
+
prop :age, Integer, description: 'Age in years'
|
|
13
|
+
const :email, T.nilable(String), description: 'Optional email address for notifications'
|
|
14
|
+
const :interests, T::Array[String], description: 'List of user hobbies and interests'
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
puts "\n1. Basic T::Struct with description parameters:"
|
|
@@ -21,13 +21,13 @@ puts User.to_baml
|
|
|
21
21
|
class Product < T::Struct
|
|
22
22
|
# This comment will be used as fallback
|
|
23
23
|
const :id, String
|
|
24
|
-
|
|
25
|
-
const :name, String, description:
|
|
26
|
-
|
|
24
|
+
|
|
25
|
+
const :name, String, description: 'Product name for display'
|
|
26
|
+
|
|
27
27
|
# Price in USD cents
|
|
28
28
|
prop :price_cents, Integer
|
|
29
|
-
|
|
30
|
-
const :category, String, description:
|
|
29
|
+
|
|
30
|
+
const :category, String, description: 'Product category classification'
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
puts "\n2. Mixed description sources (parameters take priority):"
|
|
@@ -35,15 +35,15 @@ puts Product.to_baml
|
|
|
35
35
|
|
|
36
36
|
# Example 3: Complex nested types with descriptions
|
|
37
37
|
class Order < T::Struct
|
|
38
|
-
const :id, String, description:
|
|
39
|
-
const :customer, User, description:
|
|
40
|
-
const :items, T::Array[Product], description:
|
|
41
|
-
const :total_cents, Integer, description:
|
|
42
|
-
const :status, String, description:
|
|
38
|
+
const :id, String, description: 'Unique order identifier'
|
|
39
|
+
const :customer, User, description: 'Customer who placed the order'
|
|
40
|
+
const :items, T::Array[Product], description: 'List of ordered products'
|
|
41
|
+
const :total_cents, Integer, description: 'Total order value in USD cents'
|
|
42
|
+
const :status, String, description: 'Current order processing status'
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
puts "\n3. Complex nested types with dependencies:"
|
|
46
46
|
puts Order.to_baml
|
|
47
47
|
|
|
48
48
|
puts "\n✨ Beautiful, readable, and LLM-friendly!"
|
|
49
|
-
puts
|
|
49
|
+
puts '🚀 Perfect for DSPy.rb, autonomous agents, and structured LLM outputs'
|
|
@@ -10,67 +10,63 @@ module SorbetBaml
|
|
|
10
10
|
def self.extract_field_comments(klass)
|
|
11
11
|
# First try to get descriptions from the description extractor (extra field)
|
|
12
12
|
descriptions = DescriptionExtractor.extract_prop_descriptions(klass)
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
# Then fall back to comment-based extraction for any missing descriptions
|
|
15
15
|
comments = {}
|
|
16
16
|
source_file = find_source_file(klass)
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
if source_file && File.exist?(source_file)
|
|
19
19
|
lines = File.readlines(source_file)
|
|
20
20
|
extract_comments_from_lines(lines, T.must(T.must(klass.name).split('::').last), comments)
|
|
21
21
|
end
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
# Merge with priority: description parameters > comments
|
|
24
|
-
descriptions.merge(comments) { |
|
|
24
|
+
descriptions.merge(comments) { |_key, desc_param, _comment| desc_param }
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
sig { params(klass: T.class_of(T::Enum)).returns(T::Hash[String, T.nilable(String)]) }
|
|
28
28
|
def self.extract_enum_comments(klass)
|
|
29
29
|
comments = {}
|
|
30
30
|
source_file = find_source_file(klass)
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
return comments unless source_file && File.exist?(source_file)
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
lines = File.readlines(source_file)
|
|
35
35
|
extract_enum_comments_from_lines(lines, T.must(T.must(klass.name).split('::').last), comments)
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
comments
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
private
|
|
41
|
-
|
|
42
40
|
sig { params(klass: T::Class[T.anything]).returns(T.nilable(String)) }
|
|
43
41
|
def self.find_source_file(klass)
|
|
44
42
|
# Try to find where the class was defined
|
|
45
43
|
# This is a heuristic approach since Ruby doesn't provide reliable source location for classes
|
|
46
|
-
|
|
44
|
+
|
|
47
45
|
# Method 1: Check if any methods have source location
|
|
48
46
|
begin
|
|
49
47
|
if klass.respond_to?(:new) && klass.method(:new).respond_to?(:source_location)
|
|
50
48
|
location = klass.method(:new).source_location
|
|
51
49
|
return location[0] if location
|
|
52
50
|
end
|
|
53
|
-
rescue
|
|
51
|
+
rescue StandardError
|
|
54
52
|
# Ignore errors
|
|
55
53
|
end
|
|
56
|
-
|
|
54
|
+
|
|
57
55
|
# Method 2: Look at the current call stack for files that might contain the class
|
|
58
56
|
caller_locations.each do |location|
|
|
59
57
|
file_path = location.absolute_path || location.path
|
|
60
58
|
next unless file_path && File.exist?(file_path)
|
|
61
|
-
|
|
59
|
+
|
|
62
60
|
# Read the file and check if it contains the class definition
|
|
63
61
|
begin
|
|
64
62
|
content = File.read(file_path)
|
|
65
63
|
class_name = T.must(klass.name).split('::').last
|
|
66
|
-
if content.match(/class\s+#{Regexp.escape(T.must(class_name))}\s*</)
|
|
67
|
-
|
|
68
|
-
end
|
|
69
|
-
rescue
|
|
64
|
+
return file_path if content.match(/class\s+#{Regexp.escape(T.must(class_name))}\s*</)
|
|
65
|
+
rescue StandardError
|
|
70
66
|
# Ignore file read errors
|
|
71
67
|
end
|
|
72
68
|
end
|
|
73
|
-
|
|
69
|
+
|
|
74
70
|
nil
|
|
75
71
|
end
|
|
76
72
|
|
|
@@ -79,28 +75,26 @@ module SorbetBaml
|
|
|
79
75
|
in_target_class = T.let(false, T::Boolean)
|
|
80
76
|
current_comment = T.let(nil, T.nilable(String))
|
|
81
77
|
brace_depth = 0
|
|
82
|
-
|
|
78
|
+
|
|
83
79
|
lines.each do |line|
|
|
84
80
|
stripped = line.strip
|
|
85
|
-
|
|
81
|
+
|
|
86
82
|
# Check if we're entering the target class
|
|
87
83
|
if stripped.match(/^class\s+#{Regexp.escape(class_name)}\s*<\s*T::Struct/)
|
|
88
84
|
in_target_class = true
|
|
89
85
|
brace_depth = 0
|
|
90
86
|
next
|
|
91
87
|
end
|
|
92
|
-
|
|
88
|
+
|
|
93
89
|
next unless in_target_class
|
|
94
|
-
|
|
90
|
+
|
|
95
91
|
# Track brace depth to handle nested classes
|
|
96
92
|
brace_depth += stripped.count('{')
|
|
97
93
|
brace_depth -= stripped.count('}')
|
|
98
|
-
|
|
94
|
+
|
|
99
95
|
# Exit when we reach the end of the class
|
|
100
|
-
if stripped == 'end' && brace_depth == 0
|
|
101
|
-
|
|
102
|
-
end
|
|
103
|
-
|
|
96
|
+
break if stripped == 'end' && brace_depth == 0
|
|
97
|
+
|
|
104
98
|
# Extract comment
|
|
105
99
|
if stripped.start_with?('#')
|
|
106
100
|
comment_text = T.must(stripped[1..-1]).strip
|
|
@@ -121,37 +115,35 @@ module SorbetBaml
|
|
|
121
115
|
in_target_class = T.let(false, T::Boolean)
|
|
122
116
|
in_enums_block = T.let(false, T::Boolean)
|
|
123
117
|
current_comment = T.let(nil, T.nilable(String))
|
|
124
|
-
|
|
118
|
+
|
|
125
119
|
lines.each do |line|
|
|
126
120
|
stripped = line.strip
|
|
127
|
-
|
|
121
|
+
|
|
128
122
|
# Check if we're entering the target enum class
|
|
129
123
|
if stripped.match(/^class\s+#{Regexp.escape(class_name)}\s*<\s*T::Enum/)
|
|
130
124
|
in_target_class = true
|
|
131
125
|
next
|
|
132
126
|
end
|
|
133
|
-
|
|
127
|
+
|
|
134
128
|
next unless in_target_class
|
|
135
|
-
|
|
129
|
+
|
|
136
130
|
# Check if we're in the enums block
|
|
137
131
|
if stripped == 'enums do'
|
|
138
132
|
in_enums_block = true
|
|
139
133
|
next
|
|
140
134
|
end
|
|
141
|
-
|
|
135
|
+
|
|
142
136
|
# Exit enums block
|
|
143
137
|
if in_enums_block && stripped == 'end'
|
|
144
138
|
in_enums_block = false
|
|
145
139
|
next
|
|
146
140
|
end
|
|
147
|
-
|
|
141
|
+
|
|
148
142
|
# Exit class
|
|
149
|
-
if stripped == 'end' && !in_enums_block
|
|
150
|
-
|
|
151
|
-
end
|
|
152
|
-
|
|
143
|
+
break if stripped == 'end' && !in_enums_block
|
|
144
|
+
|
|
153
145
|
next unless in_enums_block
|
|
154
|
-
|
|
146
|
+
|
|
155
147
|
# Extract comment
|
|
156
148
|
if stripped.start_with?('#')
|
|
157
149
|
comment_text = T.must(stripped[1..-1]).strip
|
|
@@ -167,4 +159,4 @@ module SorbetBaml
|
|
|
167
159
|
end
|
|
168
160
|
end
|
|
169
161
|
end
|
|
170
|
-
end
|
|
162
|
+
end
|