sorbet-baml 0.1.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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +94 -0
  3. data/README.md +315 -122
  4. data/Rakefile +2 -2
  5. data/docs-site/.gitignore +48 -0
  6. data/docs-site/Gemfile +5 -0
  7. data/docs-site/Gemfile.lock +140 -0
  8. data/docs-site/Rakefile +3 -0
  9. data/docs-site/bridgetown.config.yml +15 -0
  10. data/docs-site/config/initializers.rb +9 -0
  11. data/docs-site/config/puma.rb +9 -0
  12. data/docs-site/config.ru +5 -0
  13. data/docs-site/esbuild.config.js +11 -0
  14. data/docs-site/frontend/javascript/index.js +22 -0
  15. data/docs-site/frontend/styles/index.css +61 -0
  16. data/docs-site/package.json +18 -0
  17. data/docs-site/postcss.config.js +6 -0
  18. data/docs-site/server/roda_app.rb +9 -0
  19. data/docs-site/src/_components/head.liquid +26 -0
  20. data/docs-site/src/_components/nav.liquid +68 -0
  21. data/docs-site/src/_layouts/default.liquid +27 -0
  22. data/docs-site/src/_layouts/doc.liquid +39 -0
  23. data/docs-site/src/advanced-usage.md +598 -0
  24. data/docs-site/src/getting-started.md +170 -0
  25. data/docs-site/src/index.md +183 -0
  26. data/docs-site/src/troubleshooting.md +317 -0
  27. data/docs-site/src/type-mapping.md +236 -0
  28. data/docs-site/tailwind.config.js +85 -0
  29. data/examples/description_parameters.rb +49 -0
  30. data/lib/sorbet_baml/comment_extractor.rb +51 -54
  31. data/lib/sorbet_baml/converter.rb +69 -35
  32. data/lib/sorbet_baml/dependency_resolver.rb +11 -11
  33. data/lib/sorbet_baml/description_extension.rb +34 -0
  34. data/lib/sorbet_baml/description_extractor.rb +34 -0
  35. data/lib/sorbet_baml/dspy_tool_converter.rb +97 -0
  36. data/lib/sorbet_baml/dspy_tool_extensions.rb +23 -0
  37. data/lib/sorbet_baml/enum_extensions.rb +2 -2
  38. data/lib/sorbet_baml/struct_extensions.rb +2 -2
  39. data/lib/sorbet_baml/tool_extensions.rb +23 -0
  40. data/lib/sorbet_baml/type_mapper.rb +35 -37
  41. data/lib/sorbet_baml/version.rb +1 -1
  42. data/lib/sorbet_baml.rb +41 -10
  43. data/sorbet/config +2 -0
  44. data/sorbet/rbi/gems/anthropic@1.5.0.rbi +21252 -0
  45. data/sorbet/rbi/gems/async@2.27.3.rbi +9 -0
  46. data/sorbet/rbi/gems/bigdecimal@3.2.2.rbi +9 -0
  47. data/sorbet/rbi/gems/concurrent-ruby@1.3.5.rbi +424 -0
  48. data/sorbet/rbi/gems/connection_pool@2.5.3.rbi +9 -0
  49. data/sorbet/rbi/gems/console@1.33.0.rbi +9 -0
  50. data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +672 -0
  51. data/sorbet/rbi/gems/dry-core@1.1.0.rbi +1729 -0
  52. data/sorbet/rbi/gems/dry-logger@1.1.0.rbi +1317 -0
  53. data/sorbet/rbi/gems/dspy@0.19.1.rbi +6677 -0
  54. data/sorbet/rbi/gems/ffi@1.17.2.rbi +2174 -0
  55. data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +9 -0
  56. data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +9 -0
  57. data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +9 -0
  58. data/sorbet/rbi/gems/google-protobuf@4.32.0.rbi +9 -0
  59. data/sorbet/rbi/gems/googleapis-common-protos-types@1.20.0.rbi +9 -0
  60. data/sorbet/rbi/gems/informers@1.2.1.rbi +1875 -0
  61. data/sorbet/rbi/gems/io-event@1.12.1.rbi +9 -0
  62. data/sorbet/rbi/gems/metrics@0.13.0.rbi +9 -0
  63. data/sorbet/rbi/gems/onnxruntime@0.10.0.rbi +304 -0
  64. data/sorbet/rbi/gems/openai@0.16.0.rbi +68055 -0
  65. data/sorbet/rbi/gems/opentelemetry-api@1.6.0.rbi +9 -0
  66. data/sorbet/rbi/gems/opentelemetry-common@0.22.0.rbi +9 -0
  67. data/sorbet/rbi/gems/opentelemetry-exporter-otlp@0.30.0.rbi +9 -0
  68. data/sorbet/rbi/gems/opentelemetry-registry@0.4.0.rbi +9 -0
  69. data/sorbet/rbi/gems/opentelemetry-sdk@1.8.1.rbi +9 -0
  70. data/sorbet/rbi/gems/opentelemetry-semantic_conventions@1.11.0.rbi +9 -0
  71. data/sorbet/rbi/gems/polars-df@0.20.0.rbi +9 -0
  72. data/sorbet/rbi/gems/sorbet-result@1.4.0.rbi +242 -0
  73. data/sorbet/rbi/gems/sorbet-schema@0.9.2.rbi +743 -0
  74. data/sorbet/rbi/gems/sorbet-struct-comparable@1.3.0.rbi +48 -0
  75. data/sorbet/rbi/gems/tokenizers@0.5.5.rbi +754 -0
  76. data/sorbet/rbi/gems/traces@0.17.0.rbi +9 -0
  77. data/sorbet/rbi/gems/zeitwerk@2.7.3.rbi +1429 -0
  78. metadata +67 -7
  79. data/docs/README.md +0 -117
  80. data/docs/advanced-usage.md +0 -427
  81. data/docs/getting-started.md +0 -91
  82. data/docs/troubleshooting.md +0 -291
  83. data/docs/type-mapping.md +0 -192
data/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # sorbet-baml
2
2
 
3
+ [![Gem Version](https://img.shields.io/gem/v/sorbet-baml)](https://rubygems.org/gems/sorbet-baml)
4
+ [![Total Downloads](https://img.shields.io/gem/dt/sorbet-baml)](https://rubygems.org/gems/sorbet-baml)
5
+ [![License](https://img.shields.io/github/license/vicentereig/sorbet-baml)](https://github.com/vicentereig/sorbet-baml/blob/main/LICENSE.txt)
6
+ [![Sorbet Compatible](https://img.shields.io/badge/Sorbet-compatible-blue)](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?
@@ -15,25 +20,43 @@ When working with LLMs, token efficiency directly impacts:
15
20
 
16
21
  BAML provides the perfect balance: concise, readable, and LLM-friendly.
17
22
 
18
- ### Example
23
+ ### Example: Autonomous Research Workflow
19
24
 
20
25
  ```ruby
21
- # Your Sorbet types
22
- class User < T::Struct
23
- const :name, String
24
- const :age, Integer
25
- const :email, T.nilable(String)
26
- const :preferences, T::Hash[String, T.any(String, Integer)]
26
+ # Complex LLM workflow types for autonomous research
27
+ class ComplexityLevel < T::Enum
28
+ enums do
29
+ # Basic analysis requiring straightforward research
30
+ Basic = new('basic')
31
+ # Advanced analysis requiring deep domain expertise
32
+ Advanced = new('advanced')
33
+ end
27
34
  end
28
35
 
29
- # Ruby-idiomatic conversion
30
- User.to_baml
36
+ class TaskDecomposition < T::Struct
37
+ # The main research topic being investigated
38
+ const :research_topic, String
39
+ # Target complexity level for the decomposition
40
+ const :complexity_level, ComplexityLevel
41
+ # Autonomously generated list of research subtasks
42
+ const :subtasks, T::Array[String]
43
+ # Strategic priority rankings for each subtask
44
+ const :priority_order, T::Array[Integer]
45
+ end
46
+
47
+ # Ruby-idiomatic conversion with dependencies
48
+ TaskDecomposition.to_baml
31
49
  # =>
32
- # class User {
33
- # name string
34
- # age int
35
- # email string?
36
- # preferences map<string, string | int>
50
+ # enum ComplexityLevel {
51
+ # "basic" @description("Basic analysis requiring straightforward research")
52
+ # "advanced" @description("Advanced analysis requiring deep domain expertise")
53
+ # }
54
+ #
55
+ # class TaskDecomposition {
56
+ # research_topic string @description("The main research topic being investigated")
57
+ # complexity_level ComplexityLevel @description("Target complexity level for the decomposition")
58
+ # subtasks string[] @description("Autonomously generated list of research subtasks")
59
+ # priority_order int[] @description("Strategic priority rankings for each subtask")
37
60
  # }
38
61
  ```
39
62
 
@@ -56,111 +79,231 @@ gem install sorbet-baml
56
79
  ```ruby
57
80
  require 'sorbet-baml'
58
81
 
59
- # 🎯 Ruby-idiomatic API - just call .to_baml on any T::Struct or T::Enum!
82
+ # 🎯 Ruby-idiomatic API for complex LLM workflows
60
83
 
61
- class Status < T::Enum
84
+ class ConfidenceLevel < T::Enum
62
85
  enums do
63
- Active = new('active')
64
- Inactive = new('inactive')
86
+ # Low confidence, requires further verification
87
+ Low = new('low')
88
+ # High confidence, strongly supported by multiple sources
89
+ High = new('high')
65
90
  end
66
91
  end
67
92
 
68
- class Address < T::Struct
69
- const :street, String
70
- const :city, String
71
- const :postal_code, T.nilable(String)
93
+ class ResearchFindings < T::Struct
94
+ # Detailed findings and analysis results
95
+ const :findings, String
96
+ # Key actionable insights extracted
97
+ const :key_insights, T::Array[String]
98
+ # Assessment of evidence quality and reliability
99
+ const :evidence_quality, ConfidenceLevel
100
+ # Confidence score for the findings (1-10 scale)
101
+ const :confidence_score, Integer
72
102
  end
73
103
 
74
- class User < T::Struct
75
- const :name, String
76
- const :status, Status
77
- const :address, Address
78
- const :tags, T::Array[String]
79
- const :metadata, T::Hash[String, T.any(String, Integer)]
104
+ class ResearchSynthesis < T::Struct
105
+ # High-level executive summary of all findings
106
+ const :executive_summary, String
107
+ # Primary conclusions drawn from the research
108
+ const :key_conclusions, T::Array[String]
109
+ # Collection of research findings
110
+ const :findings_collection, T::Array[ResearchFindings]
80
111
  end
81
112
 
82
113
  # Convert with smart defaults (dependencies + descriptions included!)
83
- User.to_baml
84
- Status.to_baml
85
- Address.to_baml
114
+ ResearchSynthesis.to_baml
86
115
 
87
116
  # 🚀 Smart defaults include dependencies and descriptions automatically
88
117
  # =>
89
- # enum Status {
90
- # "active"
91
- # "inactive"
118
+ # enum ConfidenceLevel {
119
+ # "low" @description("Low confidence, requires further verification")
120
+ # "high" @description("High confidence, strongly supported by multiple sources")
92
121
  # }
93
122
  #
94
- # class Address {
95
- # street string
96
- # city string
97
- # postal_code string?
123
+ # class ResearchFindings {
124
+ # findings string @description("Detailed findings and analysis results")
125
+ # key_insights string[] @description("Key actionable insights extracted")
126
+ # evidence_quality ConfidenceLevel @description("Assessment of evidence quality and reliability")
127
+ # confidence_score int @description("Confidence score for the findings (1-10 scale)")
98
128
  # }
99
129
  #
100
- # class User {
101
- # name string
102
- # status Status
103
- # address Address
104
- # tags string[]
105
- # metadata map<string, string | int>
130
+ # class ResearchSynthesis {
131
+ # executive_summary string @description("High-level executive summary of all findings")
132
+ # key_conclusions string[] @description("Primary conclusions drawn from the research")
133
+ # findings_collection ResearchFindings[] @description("Collection of research findings")
106
134
  # }
107
135
 
108
136
  # 🎯 Disable features if needed
109
- User.to_baml(include_descriptions: false)
110
- User.to_baml(include_dependencies: false)
137
+ ResearchSynthesis.to_baml(include_descriptions: false)
138
+ ResearchSynthesis.to_baml(include_dependencies: false)
111
139
 
112
140
  # 🚀 Customize formatting (smart defaults still apply)
113
- User.to_baml(indent_size: 4)
141
+ ResearchSynthesis.to_baml(indent_size: 4)
114
142
 
115
143
  # Legacy API (no smart defaults, for backwards compatibility)
116
- SorbetBaml.from_struct(User)
117
- SorbetBaml.from_structs([User, Address])
144
+ SorbetBaml.from_struct(ResearchSynthesis)
145
+ SorbetBaml.from_structs([ResearchSynthesis, ResearchFindings])
118
146
  ```
119
147
 
120
- ## 🎯 Field Descriptions
148
+ ## 🎯 Field Descriptions for LLM Context
121
149
 
122
- Add context to your BAML types by documenting fields with comments:
150
+ Add crucial context to your BAML types by documenting fields with comments - essential for autonomous agents and complex workflows:
123
151
 
124
152
  ```ruby
125
- class User < T::Struct
126
- # User's full legal name for display
127
- const :name, String
153
+ class TaskType < T::Enum
154
+ enums do
155
+ # Literature review and information gathering
156
+ Research = new('research')
157
+ # Combining multiple sources into coherent insights
158
+ Synthesis = new('synthesis')
159
+ # Evaluating options or making recommendations
160
+ Evaluation = new('evaluation')
161
+ end
162
+ end
163
+
164
+ class ResearchSubtask < T::Struct
165
+ # Clear description of the specific research objective
166
+ const :objective, String
167
+
168
+ # Type of research task to be performed
169
+ const :task_type, TaskType
170
+
171
+ # Strategic priority ranking for task sequencing (1-5 scale)
172
+ const :priority, Integer
128
173
 
129
- # Age in years, must be 18+
130
- const :age, Integer
174
+ # Estimated effort required in hours
175
+ const :estimated_hours, Integer
131
176
 
132
- # Primary email for notifications
133
- const :email, T.nilable(String)
177
+ # Suggested agent capabilities needed for optimal execution
178
+ const :required_capabilities, T::Array[String]
134
179
  end
135
180
 
136
- class Status < T::Enum
137
- enums do
138
- # Account is active and verified
139
- Active = new('active')
140
-
141
- # Account suspended for policy violation
142
- Suspended = new('suspended')
181
+ # Generate BAML (descriptions included by default!)
182
+ ResearchSubtask.to_baml
183
+ # =>
184
+ # enum TaskType {
185
+ # "research" @description("Literature review and information gathering")
186
+ # "synthesis" @description("Combining multiple sources into coherent insights")
187
+ # "evaluation" @description("Evaluating options or making recommendations")
188
+ # }
189
+ #
190
+ # class ResearchSubtask {
191
+ # objective string @description("Clear description of the specific research objective")
192
+ # task_type TaskType @description("Type of research task to be performed")
193
+ # priority int @description("Strategic priority ranking for task sequencing (1-5 scale)")
194
+ # estimated_hours int @description("Estimated effort required in hours")
195
+ # required_capabilities string[] @description("Suggested agent capabilities needed for optimal execution")
196
+ # }
197
+ ```
198
+
199
+ **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.
200
+
201
+ ## 🛠️ Tool Type Definitions
202
+
203
+ Generate BAML tool specifications for agentic workflows, function calling, and structured LLM interactions:
204
+
205
+ ### T::Struct-based Tools
206
+
207
+ ```ruby
208
+ class ReplyTool < T::Struct
209
+ # The response message to send back to the user
210
+ const :response, String
211
+ end
212
+
213
+ class SearchTool < T::Struct
214
+ # The search query to execute
215
+ const :query, String
216
+ # Maximum number of results to return
217
+ const :limit, T.nilable(Integer)
218
+ end
219
+
220
+ # Generate BAML tool definitions
221
+ ReplyTool.to_baml_tool
222
+ # =>
223
+ # class ReplyTool {
224
+ # response string @description("The response message to send back to the user")
225
+ # }
226
+
227
+ SearchTool.to_baml_tool
228
+ # =>
229
+ # class SearchTool {
230
+ # query string @description("The search query to execute")
231
+ # limit int? @description("Maximum number of results to return")
232
+ # }
233
+
234
+ # Module API also available
235
+ SorbetBaml.from_tool(ReplyTool)
236
+ ```
237
+
238
+ ### DSPy-style Tools (Optional)
239
+
240
+ When `dspy.rb` is available, automatically convert DSPy tools with rich metadata:
241
+
242
+ ```ruby
243
+ class CalculatorTool < DSPy::Tools::Base
244
+ extend T::Sig
245
+
246
+ tool_name 'calculator'
247
+ tool_description 'Performs basic arithmetic operations'
248
+
249
+ sig { params(operation: String, num1: Float, num2: Float).returns(T.any(Float, String)) }
250
+ def call(operation:, num1:, num2:)
251
+ case operation.downcase
252
+ when 'add' then num1 + num2
253
+ when 'subtract' then num1 - num2
254
+ when 'multiply' then num1 * num2
255
+ when 'divide'
256
+ return "Error: Cannot divide by zero" if num2 == 0
257
+ num1 / num2
258
+ else
259
+ "Error: Unknown operation '#{operation}'. Use add, subtract, multiply, or divide"
260
+ end
143
261
  end
144
262
  end
145
263
 
146
- # Generate BAML (descriptions included by default!)
147
- User.to_baml
264
+ # Automatic extraction of tool metadata and parameter types
265
+ CalculatorTool.to_baml
148
266
  # =>
149
- # class User {
150
- # name string @description("User's full legal name for display")
151
- # age int @description("Age in years, must be 18+")
152
- # email string? @description("Primary email for notifications")
267
+ # // Performs basic arithmetic operations
268
+ # class calculator {
269
+ # operation string @description("Parameter operation")
270
+ # num1 float @description("Parameter num1")
271
+ # num2 float @description("Parameter num2")
153
272
  # }
154
273
 
155
- Status.to_baml
274
+ # Optional parameters handled correctly
275
+ class SearchTool < DSPy::Tools::Base
276
+ extend T::Sig
277
+
278
+ tool_name 'search'
279
+ tool_description 'Search for information'
280
+
281
+ sig { params(query: String, limit: T.nilable(Integer)).returns(T::Array[String]) }
282
+ def call(query:, limit: nil)
283
+ # Implementation...
284
+ end
285
+ end
286
+
287
+ SearchTool.to_baml
156
288
  # =>
157
- # enum Status {
158
- # "active" @description("Account is active and verified")
159
- # "suspended" @description("Account suspended for policy violation")
289
+ # // Search for information
290
+ # class search {
291
+ # query string @description("Parameter query")
292
+ # limit int? @description("Parameter limit (optional)")
160
293
  # }
294
+
295
+ # Module API also available
296
+ SorbetBaml.from_dspy_tool(CalculatorTool)
161
297
  ```
162
298
 
163
- **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.
299
+ **Tool Features:**
300
+ - ✅ **T::Struct tools**: Convert any struct to BAML tool definition
301
+ - ✅ **DSPy integration**: Automatic extraction from DSPy::Tools::Base classes
302
+ - ✅ **Parameter types**: Full Sorbet type support (string, int, float, arrays, maps, etc.)
303
+ - ✅ **Optional parameters**: Automatically detect and mark with `?`
304
+ - ✅ **Descriptions**: Extract from comments (T::Struct) or automatic generation (DSPy)
305
+ - ✅ **Tool metadata**: Names, descriptions, and parameter documentation
306
+ - ✅ **Ruby-idiomatic**: `.to_baml_tool()` and `.to_baml()` methods
164
307
 
165
308
  ## 🎯 Complete Type Support
166
309
 
@@ -191,6 +334,8 @@ Status.to_baml
191
334
  ### 🚀 Advanced Features
192
335
 
193
336
  - **Ruby-idiomatic API**: Every T::Struct and T::Enum gets `.to_baml` method
337
+ - **Tool definitions**: Generate BAML tool specs for function calling and agentic workflows
338
+ - **DSPy integration**: Automatic tool conversion from DSPy::Tools::Base classes
194
339
  - **Smart defaults**: Field descriptions and dependencies included automatically
195
340
  - **Field descriptions**: Extracts comments from source code for LLM context
196
341
  - **Dependency management**: Automatically includes all referenced types
@@ -218,53 +363,72 @@ Status.to_baml
218
363
 
219
364
  ## 🏁 Production Ready
220
365
 
221
- This gem has reached **feature completeness** for core BAML conversion needs. The Ruby-idiomatic API is stable and thoroughly tested with **34 test cases** covering all type combinations and edge cases.
366
+ 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.
222
367
 
223
368
  ### 📊 Quality Metrics
224
369
 
225
370
  - ✅ **100% Test Coverage** - All features comprehensively tested
226
371
  - ✅ **Full Sorbet Type Safety** - Zero type errors throughout codebase
227
- - ✅ **34 Test Cases** - Covering basic types, complex combinations, and edge cases
372
+ - ✅ **80+ Test Cases** - Covering basic types, complex combinations, tool definitions, and edge cases
228
373
  - ✅ **TDD Development** - All features built test-first
374
+ - ✅ **Field Descriptions** - Automatic comment extraction for LLM context
375
+ - ✅ **Tool Definitions** - BAML tool specifications for function calling and agentic workflows
376
+ - ✅ **DSPy Integration** - Automatic tool conversion from DSPy::Tools::Base classes
377
+ - ✅ **Smart Defaults** - Dependencies and descriptions included by default
229
378
  - ✅ **Zero Breaking Changes** - Maintains backward compatibility
230
379
 
231
- ### 🗺️ Future Enhancements (Optional)
380
+ ### Complete Feature Set
381
+
382
+ - ✅ **Ruby-idiomatic API**: Every T::Struct and T::Enum gets `.to_baml` method
383
+ - ✅ **Tool definitions**: Generate BAML tool specifications from T::Struct classes
384
+ - ✅ **DSPy integration**: Automatic tool conversion from DSPy::Tools::Base classes
385
+ - ✅ **Smart defaults**: Field descriptions and dependencies included automatically
386
+ - ✅ **Field descriptions**: Extract documentation from comments for LLM context
387
+ - ✅ **Dependency management**: Automatically includes all referenced types
388
+ - ✅ **Proper ordering**: Dependencies are sorted topologically
389
+ - ✅ **Type safety**: Full Sorbet type checking throughout
232
390
 
233
- The core implementation is complete. These are nice-to-have enhancements:
391
+ ### 🗺️ Future Enhancements (Optional)
234
392
 
393
+ - [ ] **DSPy-independent tool API**: Tools shouldn't require DSPy, just follow the same API pattern
235
394
  - [ ] **Type aliases**: `T.type_alias { String }` → `type Alias = string`
236
- - [ ] **Field descriptions**: Extract documentation from comments
237
395
  - [ ] **Custom naming**: Convert between snake_case ↔ camelCase
238
- - [ ] **CLI tool**: `sorbet-baml convert User` command
396
+ - [ ] **CLI tool**: `sorbet-baml convert MyStruct` command
239
397
  - [ ] **Validation**: Verify generated BAML syntax
240
398
  - [ ] **Self-referential types**: `Employee` with `manager: T.nilable(Employee)`
241
399
 
242
400
  ### 📈 Version History
243
401
 
244
402
  - **v0.0.1** - Initial implementation with basic type support
245
- - **v0.1.0** (Ready) - Complete type system + Ruby-idiomatic API
403
+ - **v0.1.0** - Complete type system + Ruby-idiomatic API + field descriptions + smart defaults
404
+ - **v0.2.0** - Description parameter support and enhanced field extraction
405
+ - **v0.3.0** - Tool type definitions + DSPy integration + 80+ test cases + comprehensive documentation
246
406
 
247
- ## 🌟 Real-World Usage
407
+ ## 🌟 Real-World Usage: Autonomous Research Agents
248
408
 
249
- Perfect for Rails applications, API documentation, and any Ruby codebase using Sorbet:
409
+ Perfect for agentic workflows, deep research systems, and complex LLM applications:
250
410
 
251
411
  ```ruby
252
- # In your Rails models
253
- class User < ApplicationRecord
254
- # Your existing Sorbet types...
412
+ # Define your autonomous research workflow types
413
+ class TaskDecomposition < T::Struct
414
+ # Your complex research schema...
255
415
  end
256
416
 
257
- # Generate BAML for LLM prompts
417
+ # Generate BAML for LLM agents
258
418
  prompt = <<~PROMPT
259
- Given this user schema:
419
+ You are an autonomous research agent. Analyze this topic and decompose it into strategic subtasks.
260
420
 
261
- #{User.to_baml}
421
+ Schema for your output:
422
+ #{TaskDecomposition.to_baml}
262
423
 
263
- Generate 5 realistic test users in JSON format.
424
+ Topic: "Impact of AI on healthcare delivery systems"
425
+
426
+ Provide a comprehensive task decomposition in JSON format.
264
427
  PROMPT
265
428
 
266
429
  # Use with OpenAI, Anthropic, or any LLM provider
267
- response = client.chat(prompt)
430
+ response = llm_client.chat(prompt)
431
+ result = TaskDecomposition.from_json(response.content)
268
432
  ```
269
433
 
270
434
  ## 🔗 Integration Examples
@@ -292,43 +456,65 @@ api_types = [User, Order, Product].map(&:to_baml).join("\n\n")
292
456
 
293
457
  Here's a real-world comparison using a complex agentic workflow from production DSPy.rb usage:
294
458
 
295
- ### Complex T::Struct Types (Real Agentic Workflow)
459
+ ### Complex T::Struct Types (Production Agentic Workflow)
296
460
 
297
461
  ```ruby
462
+ # Real autonomous research workflow from production DSPy.rb usage
298
463
  class ComplexityLevel < T::Enum
299
464
  enums do
465
+ # Basic analysis requiring straightforward research
300
466
  Basic = new('basic')
467
+ # Intermediate analysis requiring synthesis of multiple sources
301
468
  Intermediate = new('intermediate')
469
+ # Advanced analysis requiring deep domain expertise and complex reasoning
302
470
  Advanced = new('advanced')
303
471
  end
304
472
  end
305
473
 
306
474
  class TaskDecomposition < T::Struct
307
- const :topic, String
475
+ # The main research topic being investigated
476
+ const :research_topic, String
477
+ # Additional context or constraints for the research
308
478
  const :context, String
479
+ # Target complexity level for the decomposition
309
480
  const :complexity_level, ComplexityLevel
481
+ # Autonomously generated list of research subtasks
310
482
  const :subtasks, T::Array[String]
483
+ # Type classification for each task (analysis, synthesis, investigation, etc.)
311
484
  const :task_types, T::Array[String]
485
+ # Strategic priority rankings (1-5 scale) for each subtask
312
486
  const :priority_order, T::Array[Integer]
487
+ # Effort estimates in hours for each subtask
313
488
  const :estimated_effort, T::Array[Integer]
489
+ # Task dependency relationships for optimal sequencing
314
490
  const :dependencies, T::Array[String]
491
+ # Suggested agent types/skills needed for each task
315
492
  const :agent_requirements, T::Array[String]
316
493
  end
317
494
 
318
495
  class ResearchExecution < T::Struct
496
+ # The specific research subtask to execute
319
497
  const :subtask, String
498
+ # Accumulated context from previous research steps
320
499
  const :context, String
500
+ # Any specific constraints or focus areas for this research
321
501
  const :constraints, String
502
+ # Detailed research findings and analysis
322
503
  const :findings, String
504
+ # Key actionable insights extracted from the research
323
505
  const :key_insights, T::Array[String]
506
+ # Confidence in findings quality (1-10 scale)
324
507
  const :confidence_level, Integer
508
+ # Assessment of evidence quality and reliability
325
509
  const :evidence_quality, String
510
+ # Recommended next steps based on these findings
326
511
  const :next_steps, T::Array[String]
512
+ # Identified gaps in knowledge or areas needing further research
327
513
  const :knowledge_gaps, T::Array[String]
328
514
  end
329
515
  ```
330
516
 
331
- ### 📊 **BAML Output (Ruby-idiomatic)**
517
+ ### 📊 **BAML Output (Ruby-idiomatic with descriptions)**
332
518
 
333
519
  ```ruby
334
520
  [ComplexityLevel, TaskDecomposition, ResearchExecution].map(&:to_baml).join("\n\n")
@@ -336,37 +522,37 @@ end
336
522
 
337
523
  ```baml
338
524
  enum ComplexityLevel {
339
- "basic"
340
- "intermediate"
341
- "advanced"
525
+ "basic" @description("Basic analysis requiring straightforward research")
526
+ "intermediate" @description("Intermediate analysis requiring synthesis of multiple sources")
527
+ "advanced" @description("Advanced analysis requiring deep domain expertise and complex reasoning")
342
528
  }
343
529
 
344
530
  class TaskDecomposition {
345
- topic string
346
- context string
347
- complexity_level ComplexityLevel
348
- subtasks string[]
349
- task_types string[]
350
- priority_order int[]
351
- estimated_effort int[]
352
- dependencies string[]
353
- agent_requirements string[]
531
+ research_topic string @description("The main research topic being investigated")
532
+ context string @description("Additional context or constraints for the research")
533
+ complexity_level ComplexityLevel @description("Target complexity level for the decomposition")
534
+ subtasks string[] @description("Autonomously generated list of research subtasks")
535
+ task_types string[] @description("Type classification for each task (analysis, synthesis, investigation, etc.)")
536
+ priority_order int[] @description("Strategic priority rankings (1-5 scale) for each subtask")
537
+ estimated_effort int[] @description("Effort estimates in hours for each subtask")
538
+ dependencies string[] @description("Task dependency relationships for optimal sequencing")
539
+ agent_requirements string[] @description("Suggested agent types/skills needed for each task")
354
540
  }
355
541
 
356
542
  class ResearchExecution {
357
- subtask string
358
- context string
359
- constraints string
360
- findings string
361
- key_insights string[]
362
- confidence_level int
363
- evidence_quality string
364
- next_steps string[]
365
- knowledge_gaps string[]
543
+ subtask string @description("The specific research subtask to execute")
544
+ context string @description("Accumulated context from previous research steps")
545
+ constraints string @description("Any specific constraints or focus areas for this research")
546
+ findings string @description("Detailed research findings and analysis")
547
+ key_insights string[] @description("Key actionable insights extracted from the research")
548
+ confidence_level int @description("Confidence in findings quality (1-10 scale)")
549
+ evidence_quality string @description("Assessment of evidence quality and reliability")
550
+ next_steps string[] @description("Recommended next steps based on these findings")
551
+ knowledge_gaps string[] @description("Identified gaps in knowledge or areas needing further research")
366
552
  }
367
553
  ```
368
554
 
369
- **BAML Token Count: ~180 tokens**
555
+ **BAML Token Count: ~320 tokens**
370
556
 
371
557
  ### 📊 **JSON Schema Equivalent**
372
558
 
@@ -439,22 +625,29 @@ class ResearchExecution {
439
625
  }
440
626
  ```
441
627
 
442
- **JSON Schema Token Count: ~450 tokens**
628
+ **JSON Schema Token Count: ~680 tokens**
629
+
630
+ ### 🎯 **Results: 53% Token Reduction (with descriptions)**
443
631
 
444
- ### 🎯 **Results: 60% Token Reduction**
632
+ | Format | Tokens | Reduction |
633
+ |--------|--------|-----------|
634
+ | JSON Schema | ~680 | baseline |
635
+ | **BAML** | **~320** | **🔥 53% fewer** |
445
636
 
637
+ **Without descriptions:**
446
638
  | Format | Tokens | Reduction |
447
639
  |--------|--------|-----------|
448
640
  | JSON Schema | ~450 | baseline |
449
641
  | **BAML** | **~180** | **🔥 60% fewer** |
450
642
 
451
643
  **Real Impact:**
452
- - **Cost Savings**: 60% reduction in prompt tokens = 60% lower LLM API costs
644
+ - **Cost Savings**: 53-60% reduction in prompt tokens = significant LLM API cost savings
453
645
  - **Performance**: Smaller prompts = faster LLM response times
454
646
  - **Context Efficiency**: More room for actual content vs. type definitions
647
+ - **LLM Understanding**: Descriptions provide crucial context for autonomous agents
455
648
  - **Readability**: BAML is human-readable and maintainable
456
649
 
457
- *This example represents actual agentic workflows from production DSPy.rb applications using complex nested types, enums, and arrays - exactly the scenarios where token efficiency matters most.*
650
+ *This example represents actual agentic workflows from production DSPy.rb applications using complex nested types, enums, and arrays - exactly the scenarios where token efficiency and LLM understanding matter most.*
458
651
 
459
652
  ## Credits
460
653
 
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7