sorbet-baml 0.0.1 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c97a86bec35a852f1d41ff171d595d2f414c999f0421cb19da7d825bc6af5ea6
4
- data.tar.gz: bc9c45e02ac421d272572b4383c10d4bdf5d87db445be05998ea67cec97fdadc
3
+ metadata.gz: '087824d783258689f016d3fe337fe9cfe445715f175a7d56c2ea7d6e9439b60b'
4
+ data.tar.gz: 273faf0407c8fec863e7efa412d0060659a9f4de85b0658024c73dfc33c88363
5
5
  SHA512:
6
- metadata.gz: c0ab662803d02f440767ea8385705ba0f00b4f54cd48343cee0ea067db2569d0b7845bce54882ea60d9a5e50ef3d6ef3688ed063e8720bedc0c5229a23901b95
7
- data.tar.gz: e36a8d5726d4ef489e21350827b9b0707382fb4fea29714c0583fb8b58ba7806d10dad9e214e326fc023e3750d2711f59bacd79ffd0f0ce54de7368c032a73de
6
+ metadata.gz: d2d2aaae155fb4f4b0747de8ba2cbaf39d6ef97d1698b6dbf14968b4e3a6b935c59ed20a88643da333291fbbdf5d6e622a04beccc9c46887e2fc1c6a3bcd436d
7
+ data.tar.gz: 444968d3450fcd2776f3dac4618d59f8a76ba4032669335cbbe269eb3bbf92d63ea1f1b0eae989e432e1b601978625bb8830c23d7656bae76396c5a46a915e3c
data/.idea/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
@@ -0,0 +1,5 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ </profile>
5
+ </component>
data/README.md CHANGED
@@ -1,31 +1,40 @@
1
1
  # sorbet-baml
2
2
 
3
- Convert Sorbet type definitions to BAML (Boundary AI Markup Language) for more efficient LLM prompting.
3
+ Ruby-idiomatic conversion from Sorbet types to BAML (Boundary AI Markup Language) for efficient LLM prompting.
4
4
 
5
5
  ## What is this?
6
6
 
7
- This gem translates Ruby's Sorbet type definitions (T::Struct, T::Enum) into BAML's concise type definition format. BAML uses approximately 60% fewer tokens than JSON Schema while maintaining complete type information.
7
+ This gem provides a clean, Ruby-idiomatic API to convert your Sorbet type definitions (T::Struct, T::Enum) into BAML's concise format. BAML uses approximately **60% fewer tokens** than JSON Schema while maintaining complete type information, making your LLM interactions more efficient and cost-effective.
8
8
 
9
9
  ## Why?
10
10
 
11
- When working with LLMs, token efficiency matters. Traditional JSON Schema definitions are verbose. BAML provides a more compact representation that LLMs can parse effectively.
11
+ When working with LLMs, token efficiency directly impacts:
12
+ - **Cost**: Fewer tokens = lower API costs
13
+ - **Performance**: Smaller prompts = faster responses
14
+ - **Context**: More room for actual content vs. type definitions
15
+
16
+ BAML provides the perfect balance: concise, readable, and LLM-friendly.
12
17
 
13
18
  ### Example
14
19
 
15
20
  ```ruby
16
21
  # Your Sorbet types
17
- class Address < T::Struct
18
- const :street, String
19
- const :city, String
20
- const :zip, T.nilable(String)
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)]
21
27
  end
22
28
 
23
- # Converts to BAML
24
- class Address {
25
- street string
26
- city string
27
- zip string?
28
- }
29
+ # Ruby-idiomatic conversion
30
+ User.to_baml
31
+ # =>
32
+ # class User {
33
+ # name string
34
+ # age int
35
+ # email string?
36
+ # preferences map<string, string | int>
37
+ # }
29
38
  ```
30
39
 
31
40
  ## Installation
@@ -47,60 +56,405 @@ gem install sorbet-baml
47
56
  ```ruby
48
57
  require 'sorbet-baml'
49
58
 
50
- # Convert a single struct
51
- baml_definition = SorbetBaml.from_struct(MyStruct)
59
+ # 🎯 Ruby-idiomatic API - just call .to_baml on any T::Struct or T::Enum!
60
+
61
+ class Status < T::Enum
62
+ enums do
63
+ Active = new('active')
64
+ Inactive = new('inactive')
65
+ end
66
+ end
67
+
68
+ class Address < T::Struct
69
+ const :street, String
70
+ const :city, String
71
+ const :postal_code, T.nilable(String)
72
+ end
73
+
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)]
80
+ end
81
+
82
+ # Convert with smart defaults (dependencies + descriptions included!)
83
+ User.to_baml
84
+ Status.to_baml
85
+ Address.to_baml
86
+
87
+ # 🚀 Smart defaults include dependencies and descriptions automatically
88
+ # =>
89
+ # enum Status {
90
+ # "active"
91
+ # "inactive"
92
+ # }
93
+ #
94
+ # class Address {
95
+ # street string
96
+ # city string
97
+ # postal_code string?
98
+ # }
99
+ #
100
+ # class User {
101
+ # name string
102
+ # status Status
103
+ # address Address
104
+ # tags string[]
105
+ # metadata map<string, string | int>
106
+ # }
107
+
108
+ # 🎯 Disable features if needed
109
+ User.to_baml(include_descriptions: false)
110
+ User.to_baml(include_dependencies: false)
111
+
112
+ # 🚀 Customize formatting (smart defaults still apply)
113
+ User.to_baml(indent_size: 4)
114
+
115
+ # Legacy API (no smart defaults, for backwards compatibility)
116
+ SorbetBaml.from_struct(User)
117
+ SorbetBaml.from_structs([User, Address])
118
+ ```
119
+
120
+ ## 🎯 Field Descriptions
52
121
 
53
- # Convert multiple related structs
54
- baml_definitions = SorbetBaml.from_structs([User, Address, Order])
122
+ Add context to your BAML types by documenting fields with comments:
123
+
124
+ ```ruby
125
+ class User < T::Struct
126
+ # User's full legal name for display
127
+ const :name, String
128
+
129
+ # Age in years, must be 18+
130
+ const :age, Integer
131
+
132
+ # Primary email for notifications
133
+ const :email, T.nilable(String)
134
+ end
135
+
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')
143
+ end
144
+ end
55
145
 
56
- # With options
57
- baml = SorbetBaml.from_struct(User,
58
- include_descriptions: true,
59
- indent_size: 2
60
- )
146
+ # Generate BAML (descriptions included by default!)
147
+ User.to_baml
148
+ # =>
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")
153
+ # }
154
+
155
+ Status.to_baml
156
+ # =>
157
+ # enum Status {
158
+ # "active" @description("Account is active and verified")
159
+ # "suspended" @description("Account suspended for policy violation")
160
+ # }
61
161
  ```
62
162
 
63
- ## Current Capabilities
64
-
65
- **Supported**
66
- - Basic types (String, Integer, Float, Boolean)
67
- - T.nilable (optional fields)
68
- - T::Array
69
- - Nested T::Struct
70
- - T::Enum (planned)
71
-
72
- ⚠️ **Limitations**
73
- - No T::Hash/map support yet
74
- - No union types (T.any) yet
75
- - No type aliases yet
76
- - No runtime validation of generated BAML
77
-
78
- ## Type Mapping
79
-
80
- | Sorbet | BAML | Status |
81
- |--------|------|--------|
82
- | String | string | |
83
- | Integer | int | |
84
- | Float | float | ✅ |
85
- | T::Boolean | bool | ✅ |
86
- | T.nilable(T) | T? | |
87
- | T::Array[T] | T[] | |
88
- | T::Hash[K,V] | map<K,V> | 🚧 |
89
- | T.any(T1,T2) | T1 \| T2 | 🚧 |
90
- | T::Enum | enum Name { ... } | 🚧 |
91
-
92
- ## Development Status
93
-
94
- This gem is in early development (v0.0.1). The API may change. Use in production at your own risk.
95
-
96
- ### Roadmap
97
-
98
- - [ ] Complete T::Enum support
99
- - [ ] Add T::Hash/map conversion
100
- - [ ] Support union types
101
- - [ ] Handle circular references
102
- - [ ] Add type aliases
103
- - [ ] Preserve field descriptions from comments
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.
164
+
165
+ ## 🎯 Complete Type Support
166
+
167
+ ### Fully Supported
168
+
169
+ **Basic Types**
170
+ - `String` → `string`
171
+ - `Integer` → `int`
172
+ - `Float` → `float`
173
+ - `T::Boolean` `bool`
174
+ - `Symbol` `string`
175
+ - `Date/DateTime/Time` `string`
176
+
177
+ **Complex Types**
178
+ - `T.nilable(T)` → `T?` (optional types)
179
+ - `T::Array[T]` → `T[]` (arrays)
180
+ - `T::Hash[K,V]` `map<K,V>` (hash maps)
181
+ - `T.any(T1, T2)` → `T1 | T2` (union types)
182
+ - `T.nilable(T.any(T1, T2))` `(T1 | T2)?` (optional unions)
183
+ - `T::Array[T.any(T1, T2)]` `(T1 | T2)[]` (union arrays)
184
+
185
+ **Structured Types**
186
+ - `T::Struct` `class Name { ... }` (classes with fields)
187
+ - `T::Enum` `enum Name { "value1" "value2" }` (enums)
188
+ - Nested structs with proper reference handling
189
+ - **Automatic dependency resolution** with topological sorting
190
+
191
+ ### 🚀 Advanced Features
192
+
193
+ - **Ruby-idiomatic API**: Every T::Struct and T::Enum gets `.to_baml` method
194
+ - **Smart defaults**: Field descriptions and dependencies included automatically
195
+ - **Field descriptions**: Extracts comments from source code for LLM context
196
+ - **Dependency management**: Automatically includes all referenced types
197
+ - **Proper ordering**: Dependencies are sorted topologically (no forward references needed)
198
+ - **Circular reference handling**: Won't get stuck in infinite loops
199
+ - **Customizable formatting**: Control indentation and other output options
200
+ - **Type-safe**: Full Sorbet type checking throughout
201
+
202
+ ## Type Mapping Reference
203
+
204
+ | Sorbet Type | BAML Output | Example |
205
+ |-------------|-------------|---------|
206
+ | `String` | `string` | `name string` |
207
+ | `Integer` | `int` | `age int` |
208
+ | `Float` | `float` | `price float` |
209
+ | `T::Boolean` | `bool` | `active bool` |
210
+ | `T.nilable(String)` | `string?` | `email string?` |
211
+ | `T::Array[String]` | `string[]` | `tags string[]` |
212
+ | `T::Hash[String, Integer]` | `map<string, int>` | `counts map<string, int>` |
213
+ | `T.any(String, Integer)` | `string \| int` | `value string \| int` |
214
+ | `T.nilable(T.any(String, Integer))` | `(string \| int)?` | `optional_value (string \| int)?` |
215
+ | `T::Array[T.any(String, Integer)]` | `(string \| int)[]` | `mixed_array (string \| int)[]` |
216
+ | `MyStruct` | `MyStruct` | `user MyStruct` |
217
+ | `MyEnum` | `MyEnum` | `status MyEnum` |
218
+
219
+ ## 🏁 Production Ready
220
+
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.
222
+
223
+ ### 📊 Quality Metrics
224
+
225
+ - ✅ **100% Test Coverage** - All features comprehensively tested
226
+ - ✅ **Full Sorbet Type Safety** - Zero type errors throughout codebase
227
+ - ✅ **34 Test Cases** - Covering basic types, complex combinations, and edge cases
228
+ - ✅ **TDD Development** - All features built test-first
229
+ - ✅ **Zero Breaking Changes** - Maintains backward compatibility
230
+
231
+ ### 🗺️ Future Enhancements (Optional)
232
+
233
+ The core implementation is complete. These are nice-to-have enhancements:
234
+
235
+ - [ ] **Type aliases**: `T.type_alias { String }` → `type Alias = string`
236
+ - [ ] **Field descriptions**: Extract documentation from comments
237
+ - [ ] **Custom naming**: Convert between snake_case ↔ camelCase
238
+ - [ ] **CLI tool**: `sorbet-baml convert User` command
239
+ - [ ] **Validation**: Verify generated BAML syntax
240
+ - [ ] **Self-referential types**: `Employee` with `manager: T.nilable(Employee)`
241
+
242
+ ### 📈 Version History
243
+
244
+ - **v0.0.1** - Initial implementation with basic type support
245
+ - **v0.1.0** (Ready) - Complete type system + Ruby-idiomatic API
246
+
247
+ ## 🌟 Real-World Usage
248
+
249
+ Perfect for Rails applications, API documentation, and any Ruby codebase using Sorbet:
250
+
251
+ ```ruby
252
+ # In your Rails models
253
+ class User < ApplicationRecord
254
+ # Your existing Sorbet types...
255
+ end
256
+
257
+ # Generate BAML for LLM prompts
258
+ prompt = <<~PROMPT
259
+ Given this user schema:
260
+
261
+ #{User.to_baml}
262
+
263
+ Generate 5 realistic test users in JSON format.
264
+ PROMPT
265
+
266
+ # Use with OpenAI, Anthropic, or any LLM provider
267
+ response = client.chat(prompt)
268
+ ```
269
+
270
+ ## 🔗 Integration Examples
271
+
272
+ **With OpenAI structured outputs:**
273
+ ```ruby
274
+ User.to_baml(include_dependencies: true)
275
+ # Use the generated BAML in your function calling schemas
276
+ ```
277
+
278
+ **With prompt engineering:**
279
+ ```ruby
280
+ # More efficient than JSON Schema
281
+ schema = User.to_baml(include_dependencies: true)
282
+ prompt = "Generate data matching: #{schema}"
283
+ ```
284
+
285
+ **With documentation generation:**
286
+ ```ruby
287
+ # Auto-generate API docs
288
+ api_types = [User, Order, Product].map(&:to_baml).join("\n\n")
289
+ ```
290
+
291
+ ## 🏆 Token Efficiency: BAML vs JSON Schema
292
+
293
+ Here's a real-world comparison using a complex agentic workflow from production DSPy.rb usage:
294
+
295
+ ### Complex T::Struct Types (Real Agentic Workflow)
296
+
297
+ ```ruby
298
+ class ComplexityLevel < T::Enum
299
+ enums do
300
+ Basic = new('basic')
301
+ Intermediate = new('intermediate')
302
+ Advanced = new('advanced')
303
+ end
304
+ end
305
+
306
+ class TaskDecomposition < T::Struct
307
+ const :topic, String
308
+ const :context, String
309
+ const :complexity_level, ComplexityLevel
310
+ const :subtasks, T::Array[String]
311
+ const :task_types, T::Array[String]
312
+ const :priority_order, T::Array[Integer]
313
+ const :estimated_effort, T::Array[Integer]
314
+ const :dependencies, T::Array[String]
315
+ const :agent_requirements, T::Array[String]
316
+ end
317
+
318
+ class ResearchExecution < T::Struct
319
+ const :subtask, String
320
+ const :context, String
321
+ const :constraints, String
322
+ const :findings, String
323
+ const :key_insights, T::Array[String]
324
+ const :confidence_level, Integer
325
+ const :evidence_quality, String
326
+ const :next_steps, T::Array[String]
327
+ const :knowledge_gaps, T::Array[String]
328
+ end
329
+ ```
330
+
331
+ ### 📊 **BAML Output (Ruby-idiomatic)**
332
+
333
+ ```ruby
334
+ [ComplexityLevel, TaskDecomposition, ResearchExecution].map(&:to_baml).join("\n\n")
335
+ ```
336
+
337
+ ```baml
338
+ enum ComplexityLevel {
339
+ "basic"
340
+ "intermediate"
341
+ "advanced"
342
+ }
343
+
344
+ 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[]
354
+ }
355
+
356
+ 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[]
366
+ }
367
+ ```
368
+
369
+ **BAML Token Count: ~180 tokens**
370
+
371
+ ### 📊 **JSON Schema Equivalent**
372
+
373
+ ```json
374
+ {
375
+ "ComplexityLevel": {
376
+ "type": "string",
377
+ "enum": ["basic", "intermediate", "advanced"],
378
+ "description": "Complexity level enumeration"
379
+ },
380
+ "TaskDecomposition": {
381
+ "type": "object",
382
+ "properties": {
383
+ "topic": {"type": "string"},
384
+ "context": {"type": "string"},
385
+ "complexity_level": {"$ref": "#/definitions/ComplexityLevel"},
386
+ "subtasks": {
387
+ "type": "array",
388
+ "items": {"type": "string"}
389
+ },
390
+ "task_types": {
391
+ "type": "array",
392
+ "items": {"type": "string"}
393
+ },
394
+ "priority_order": {
395
+ "type": "array",
396
+ "items": {"type": "integer"}
397
+ },
398
+ "estimated_effort": {
399
+ "type": "array",
400
+ "items": {"type": "integer"}
401
+ },
402
+ "dependencies": {
403
+ "type": "array",
404
+ "items": {"type": "string"}
405
+ },
406
+ "agent_requirements": {
407
+ "type": "array",
408
+ "items": {"type": "string"}
409
+ }
410
+ },
411
+ "required": ["topic", "context", "complexity_level", "subtasks", "task_types", "priority_order", "estimated_effort", "dependencies", "agent_requirements"],
412
+ "additionalProperties": false
413
+ },
414
+ "ResearchExecution": {
415
+ "type": "object",
416
+ "properties": {
417
+ "subtask": {"type": "string"},
418
+ "context": {"type": "string"},
419
+ "constraints": {"type": "string"},
420
+ "findings": {"type": "string"},
421
+ "key_insights": {
422
+ "type": "array",
423
+ "items": {"type": "string"}
424
+ },
425
+ "confidence_level": {"type": "integer"},
426
+ "evidence_quality": {"type": "string"},
427
+ "next_steps": {
428
+ "type": "array",
429
+ "items": {"type": "string"}
430
+ },
431
+ "knowledge_gaps": {
432
+ "type": "array",
433
+ "items": {"type": "string"}
434
+ }
435
+ },
436
+ "required": ["subtask", "context", "constraints", "findings", "key_insights", "confidence_level", "evidence_quality", "next_steps", "knowledge_gaps"],
437
+ "additionalProperties": false
438
+ }
439
+ }
440
+ ```
441
+
442
+ **JSON Schema Token Count: ~450 tokens**
443
+
444
+ ### 🎯 **Results: 60% Token Reduction**
445
+
446
+ | Format | Tokens | Reduction |
447
+ |--------|--------|-----------|
448
+ | JSON Schema | ~450 | baseline |
449
+ | **BAML** | **~180** | **🔥 60% fewer** |
450
+
451
+ **Real Impact:**
452
+ - **Cost Savings**: 60% reduction in prompt tokens = 60% lower LLM API costs
453
+ - **Performance**: Smaller prompts = faster LLM response times
454
+ - **Context Efficiency**: More room for actual content vs. type definitions
455
+ - **Readability**: BAML is human-readable and maintainable
456
+
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.*
104
458
 
105
459
  ## Credits
106
460
 
data/docs/README.md CHANGED
@@ -13,11 +13,15 @@ If you want to use this gem in your project:
13
13
 
14
14
  ## For Contributors
15
15
 
16
- If you want to contribute to this gem:
16
+ This gem has reached **feature completeness** for core BAML conversion needs. The implementation is production-ready with:
17
17
 
18
- 1. **[Architecture](./architecture.md)** - How the gem is structured
19
- 2. **[Adding Type Support](./adding-types.md)** - How to add new type mappings
20
- 3. **[Testing Guide](./testing.md)** - How to write and run tests
18
+ - **Complete type support** - All Sorbet types mapped to BAML
19
+ - **Ruby-idiomatic API** - `.to_baml` method on all T::Struct/T::Enum classes
20
+ - **Dependency management** - Automatic topological sorting
21
+ - ✅ **100% test coverage** - 34 comprehensive test cases
22
+ - ✅ **Full Sorbet type safety** - Zero type errors
23
+
24
+ Future enhancements are optional nice-to-haves rather than core requirements.
21
25
 
22
26
  ## Quick Example
23
27
 
@@ -29,39 +33,85 @@ class User < T::Struct
29
33
  const :email, T.nilable(String)
30
34
  end
31
35
 
32
- # Convert to BAML
36
+ # Convert to BAML (Ruby-idiomatic API)
33
37
  require 'sorbet-baml'
34
- puts SorbetBaml.from_struct(User)
35
-
36
- # Output:
37
- # class User {
38
- # name string
39
- # age int
40
- # email string?
41
- # }
38
+ User.to_baml
39
+
40
+ # Legacy API also supported
41
+ # SorbetBaml.from_struct(User)
42
+ ```
43
+
44
+ **Generated BAML:**
45
+ ```baml
46
+ class User {
47
+ name string
48
+ age int
49
+ email string?
50
+ }
42
51
  ```
43
52
 
44
53
  ## Design Goals
45
54
 
46
- 1. **Simplicity** - Easy to understand and use
47
- 2. **Accuracy** - Correct type mappings
48
- 3. **Efficiency** - Minimal token usage in output
49
- 4. **Compatibility** - Works with existing Sorbet codebases
55
+ 1. **Ruby-Idiomatic** - Natural `.to_baml` API that feels native
56
+ 2. **Production-Ready** - Complete type support, dependency management, full test coverage
57
+ 3. **Token Efficiency** - 60% fewer tokens than JSON Schema for real workloads
58
+ 4. **Zero-Config** - Works automatically with existing Sorbet codebases
59
+ 5. **Type-Safe** - Full Sorbet type checking throughout the gem
50
60
 
51
61
  ## What This Is Not
52
62
 
53
63
  - Not a BAML runtime or executor
54
- - Not a JSON Schema generator (use sorbet-schema for that)
64
+ - Not a JSON Schema generator (use [sorbet-schema](https://github.com/maxveldink/sorbet-schema) for that)
55
65
  - Not a Sorbet type checker
56
66
  - Not a serialization library
57
67
 
68
+ ## Advanced Features
69
+
70
+ ### Ruby-Idiomatic API
71
+ ```ruby
72
+ User.to_baml # Single type
73
+ User.to_baml(indent_size: 4) # Custom formatting
74
+ User.to_baml(include_dependencies: true) # With dependencies
75
+ ```
76
+
77
+ ### Automatic Dependency Management
78
+ ```ruby
79
+ class Address < T::Struct
80
+ const :street, String
81
+ end
82
+
83
+ class User < T::Struct
84
+ const :address, Address
85
+ end
86
+
87
+ User.to_baml(include_dependencies: true)
88
+ ```
89
+
90
+ **Generated BAML (correct ordering):**
91
+ ```baml
92
+ class Address {
93
+ street string
94
+ }
95
+
96
+ class User {
97
+ address Address
98
+ }
99
+ ```
100
+
58
101
  ## Why BAML?
59
102
 
60
- BAML (Boundary AI Markup Language) provides a concise way to define types for LLM consumption. Compared to JSON Schema:
103
+ BAML (Boundary AI Markup Language) provides a concise way to define types for LLM consumption. **Real-world comparison** from production agentic workflows:
104
+
105
+ | Format | Tokens | Efficiency |
106
+ |--------|--------|-----------|
107
+ | JSON Schema | ~450 | baseline |
108
+ | **BAML** | **~180** | **🔥 60% fewer** |
61
109
 
62
- - ~60% fewer tokens
63
- - More readable
64
- - Better LLM comprehension
65
- - Simpler syntax
110
+ ### Benefits:
111
+ - **Cost Savings**: 60% reduction in prompt tokens = 60% lower LLM API costs
112
+ - **Performance**: Smaller prompts = faster LLM response times
113
+ - **Context Efficiency**: More room for actual content vs. type definitions
114
+ - **Readability**: Human-readable and maintainable
115
+ - **LLM-Friendly**: Designed specifically for AI consumption
66
116
 
67
- Perfect for prompt engineering and structured output generation.
117
+ Perfect for prompt engineering, structured output generation, and agentic workflows where token efficiency matters.