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.
@@ -1,18 +1,33 @@
1
1
  # Advanced Usage
2
2
 
3
- ## Converting Multiple Structs
3
+ ## Ruby-Idiomatic API
4
4
 
5
- When you have related structs, convert them together to maintain relationships:
5
+ The gem automatically extends all T::Struct and T::Enum classes with conversion methods:
6
6
 
7
7
  ```ruby
8
- class Address < T::Struct
9
- const :street, String
10
- const :city, String
8
+ class User < T::Struct
9
+ const :name, String
10
+ const :age, Integer
11
+ end
12
+
13
+ # Ruby-idiomatic - just call the method!
14
+ User.to_baml
15
+ User.baml_type_definition # Same as to_baml
16
+ ```
17
+
18
+ ## Automatic Dependency Management
19
+
20
+ The most powerful feature is automatic dependency resolution:
21
+
22
+ ```ruby
23
+ class ContactInfo < T::Struct
24
+ const :email, String
25
+ const :phone, T.nilable(String)
11
26
  end
12
27
 
13
28
  class Company < T::Struct
14
29
  const :name, String
15
- const :address, Address
30
+ const :contact, ContactInfo
16
31
  end
17
32
 
18
33
  class User < T::Struct
@@ -20,66 +35,393 @@ class User < T::Struct
20
35
  const :company, Company
21
36
  end
22
37
 
23
- # Convert all at once
24
- baml = SorbetBaml.from_structs([Address, Company, User])
38
+ # Dependencies included automatically with smart defaults!
39
+ User.to_baml
25
40
  ```
26
41
 
27
- ## Options
42
+ **Generated BAML (with correct ordering):**
43
+ ```baml
44
+ class ContactInfo {
45
+ email string
46
+ phone string?
47
+ }
48
+
49
+ class Company {
50
+ name string
51
+ contact ContactInfo
52
+ }
53
+
54
+ class User {
55
+ name string
56
+ company Company
57
+ }
58
+ ```
28
59
 
29
- ### Include Descriptions
60
+ ## Converting Multiple Types
61
+
62
+ ### Manual Collection
30
63
 
31
64
  ```ruby
32
- # Future feature - not yet implemented
33
- baml = SorbetBaml.from_struct(User,
34
- include_descriptions: true # Will add @description annotations
35
- )
65
+ # Convert multiple types manually
66
+ types = [ContactInfo, Company, User]
67
+ baml_output = types.map(&:to_baml).join("\n\n")
68
+ ```
69
+
70
+ ### Legacy API (still supported)
71
+
72
+ ```ruby
73
+ # Legacy API for multiple structs
74
+ SorbetBaml.from_structs([ContactInfo, Company, User])
75
+
76
+ # Legacy API for single struct
77
+ SorbetBaml.from_struct(User)
78
+ ```
79
+
80
+ ## Advanced Type Examples
81
+
82
+ ### Complex Enums with Structs
83
+
84
+ ```ruby
85
+ class OrderStatus < T::Enum
86
+ enums do
87
+ Pending = new('pending')
88
+ Processing = new('processing')
89
+ Shipped = new('shipped')
90
+ Delivered = new('delivered')
91
+ Cancelled = new('cancelled')
92
+ end
93
+ end
94
+
95
+ class OrderItem < T::Struct
96
+ const :product_id, String
97
+ const :quantity, Integer
98
+ const :price, Float
99
+ end
100
+
101
+ class Order < T::Struct
102
+ const :id, String
103
+ const :status, OrderStatus
104
+ const :items, T::Array[OrderItem]
105
+ const :metadata, T::Hash[String, T.any(String, Integer, Float)]
106
+ const :shipping_address, T.nilable(Address)
107
+ end
108
+
109
+ # Generate complete type definitions
110
+ [OrderStatus, OrderItem, Order].map(&:to_baml).join("\n\n")
36
111
  ```
37
112
 
113
+ **Generated BAML:**
114
+ ```baml
115
+ enum OrderStatus {
116
+ "pending"
117
+ "processing"
118
+ "shipped"
119
+ "delivered"
120
+ "cancelled"
121
+ }
122
+
123
+ class OrderItem {
124
+ product_id string
125
+ quantity int
126
+ price float
127
+ }
128
+
129
+ class Order {
130
+ id string
131
+ status OrderStatus
132
+ items OrderItem[]
133
+ metadata map<string, string | int | float>
134
+ shipping_address Address?
135
+ }
136
+ ```
137
+
138
+ ### Self-Referential Types
139
+
140
+ ```ruby
141
+ class Category < T::Struct
142
+ const :name, String
143
+ const :parent, T.nilable(Category)
144
+ const :children, T::Array[Category]
145
+ end
146
+
147
+ Category.to_baml
148
+ ```
149
+
150
+ **Generated BAML:**
151
+ ```baml
152
+ class Category {
153
+ name string
154
+ parent Category?
155
+ children Category[]
156
+ }
157
+ ```
158
+
159
+ ## Configuration Options
160
+
38
161
  ### Custom Indentation
39
162
 
40
163
  ```ruby
41
- # Future feature - not yet implemented
42
- baml = SorbetBaml.from_struct(User,
43
- indent_size: 4 # Use 4 spaces instead of 2
164
+ User.to_baml(indent_size: 4)
165
+ ```
166
+
167
+ **Generated BAML:**
168
+ ```baml
169
+ class User {
170
+ name string
171
+ age int
172
+ }
173
+ ```
174
+
175
+ ### Field Descriptions
176
+
177
+ Extract documentation from Ruby comments to provide LLM context:
178
+
179
+ ```ruby
180
+ class DocumentedUser < T::Struct
181
+ # User's full legal name for official records
182
+ const :full_name, String
183
+
184
+ # Age in years, must be 18 or older for account creation
185
+ const :age, Integer
186
+
187
+ # Primary email address for account notifications
188
+ const :email, String
189
+ end
190
+
191
+ DocumentedUser.to_baml
192
+ ```
193
+
194
+ **Generated BAML with descriptions:**
195
+ ```baml
196
+ class DocumentedUser {
197
+ full_name string @description("User's full legal name for official records")
198
+ age int @description("Age in years, must be 18 or older for account creation")
199
+ email string @description("Primary email address for account notifications")
200
+ }
201
+ ```
202
+
203
+ ### Combining Options
204
+
205
+ ```ruby
206
+ # Smart defaults: dependencies and descriptions already included!
207
+ User.to_baml(indent_size: 4)
208
+
209
+ # Or disable features if needed
210
+ User.to_baml(
211
+ include_dependencies: false,
212
+ include_descriptions: false,
213
+ indent_size: 4
44
214
  )
45
215
  ```
46
216
 
47
- ## Working with Existing BAML Projects
217
+ ## File Generation
48
218
 
49
- If you're already using BAML, you can generate type definitions to include in your `.baml` files:
219
+ ### Single File Output
50
220
 
51
221
  ```ruby
52
- # Generate just the class definition
53
- definition = SorbetBaml.from_struct(MyStruct)
222
+ # Generate and write to file (dependencies included by default)
223
+ baml_content = User.to_baml
224
+ File.write("types/user.baml", baml_content)
225
+ ```
54
226
 
55
- # Write to a BAML file
56
- File.write("types/my_struct.baml", definition)
227
+ ### Multiple Files
228
+
229
+ ```ruby
230
+ # Generate separate files for each type
231
+ [User, Company, ContactInfo].each do |type|
232
+ filename = type.name.downcase.gsub('::', '_')
233
+ File.write("types/#{filename}.baml", type.to_baml)
234
+ end
57
235
  ```
58
236
 
59
- ## Integration with LLM Libraries
237
+ ### Build Process Integration
60
238
 
61
- ### With OpenAI
239
+ ```ruby
240
+ # Rakefile
241
+ desc "Generate BAML type definitions"
242
+ task :generate_baml do
243
+ require 'sorbet-baml'
244
+ require_relative 'app/models'
245
+
246
+ types = [User, Company, Order, Product] # Your app types
247
+ baml_content = types.map(&:to_baml).join("\n\n")
248
+
249
+ File.write("lib/types.baml", baml_content)
250
+ puts "Generated BAML types in lib/types.baml"
251
+ end
252
+ ```
253
+
254
+ ## LLM Integration Patterns
255
+
256
+ ### With OpenAI Structured Outputs
62
257
 
63
258
  ```ruby
64
259
  require 'openai'
65
260
  require 'sorbet-baml'
66
261
 
67
- schema = SorbetBaml.from_struct(ResponseFormat)
262
+ # Define your response format
263
+ class AnalysisResult < T::Struct
264
+ const :sentiment, String
265
+ const :confidence, Float
266
+ const :key_phrases, T::Array[String]
267
+ const :metadata, T::Hash[String, String]
268
+ end
269
+
270
+ # Generate schema for LLM
271
+ schema = AnalysisResult.to_baml
68
272
 
273
+ client = OpenAI::Client.new
69
274
  response = client.chat(
70
- model: "gpt-4",
71
- messages: [{
72
- role: "system",
73
- content: "You must respond with data matching this BAML schema:\n\n#{schema}"
74
- }, {
75
- role: "user",
76
- content: "Generate a sample user"
77
- }]
275
+ parameters: {
276
+ model: "gpt-4o",
277
+ messages: [
278
+ {
279
+ role: "system",
280
+ content: "Analyze text and respond with data matching this BAML schema:\n\n#{schema}"
281
+ },
282
+ {
283
+ role: "user",
284
+ content: "Analyze: 'I love this new product!'"
285
+ }
286
+ ]
287
+ }
78
288
  )
79
289
  ```
80
290
 
81
- ### With DSPy.rb
291
+ ### With Anthropic Claude
292
+
293
+ ```ruby
294
+ require 'anthropic'
295
+ require 'sorbet-baml'
296
+
297
+ schema = UserProfile.to_baml(include_dependencies: true)
298
+
299
+ client = Anthropic::Client.new
300
+ response = client.messages(
301
+ model: "claude-3-5-sonnet-20241022",
302
+ max_tokens: 1000,
303
+ messages: [
304
+ {
305
+ role: "user",
306
+ content: "Generate a realistic user profile matching this schema:\n\n#{schema}"
307
+ }
308
+ ]
309
+ )
310
+ ```
311
+
312
+ ### With DSPy.rb Integration
313
+
314
+ ```ruby
315
+ require 'dspy'
316
+ require 'sorbet-baml'
317
+
318
+ # Your T::Struct automatically works with DSPy signatures
319
+ class UserAnalysis < DSPy::Signature
320
+ input { const :user_data, String }
321
+ output { const :analysis, AnalysisResult } # Uses your T::Struct
322
+ end
323
+
324
+ # The BAML schema is automatically generated for LLM prompts
325
+ predictor = DSPy::Predict.new(UserAnalysis)
326
+ result = predictor.call(user_data: "John, 25, loves hiking")
327
+ ```
328
+
329
+ ### Prompt Engineering
330
+
331
+ ```ruby
332
+ # Template for complex prompts
333
+ def build_analysis_prompt(data, schema)
334
+ <<~PROMPT
335
+ You are a data analyst. Analyze the following data and return results
336
+ in the exact format specified by this BAML schema:
337
+
338
+ #{schema}
339
+
340
+ Data to analyze:
341
+ #{data}
342
+
343
+ Requirements:
344
+ - Follow the schema exactly
345
+ - Provide confidence scores between 0.0 and 1.0
346
+ - Extract meaningful insights
347
+ PROMPT
348
+ end
349
+
350
+ schema = AnalysisResult.to_baml
351
+ prompt = build_analysis_prompt(user_input, schema)
352
+ ```
353
+
354
+ ## Rails Integration
355
+
356
+ ### Model Integration
82
357
 
83
358
  ```ruby
84
- # Coming soon - integration with DSPy.rb for automatic schema generation
359
+ # app/models/user.rb
360
+ class User < ApplicationRecord
361
+ # Your ActiveRecord model...
362
+
363
+ # Add Sorbet types for API schemas
364
+ class UserAPI < T::Struct
365
+ const :id, Integer
366
+ const :name, String
367
+ const :email, String
368
+ const :created_at, String
369
+ end
370
+
371
+ def to_api_schema
372
+ UserAPI.to_baml
373
+ end
374
+ end
375
+
376
+ # Usage in controllers
377
+ class UsersController < ApplicationController
378
+ def schema
379
+ render json: { schema: User::UserAPI.to_baml }
380
+ end
381
+ end
382
+ ```
383
+
384
+ ### API Documentation
385
+
386
+ ```ruby
387
+ # Generate API docs automatically
388
+ class ApiDocsGenerator
389
+ API_TYPES = [
390
+ User::UserAPI,
391
+ Order::OrderAPI,
392
+ Product::ProductAPI
393
+ ].freeze
394
+
395
+ def self.generate
396
+ schema = API_TYPES.map(&:to_baml).join("\n\n")
397
+ File.write("docs/api_schema.baml", schema)
398
+ end
399
+ end
400
+ ```
401
+
402
+ ## Performance Considerations
403
+
404
+ ### Caching Generated BAML
405
+
406
+ ```ruby
407
+ class CachedTypeConverter
408
+ def self.to_baml(type)
409
+ @cache ||= {}
410
+ @cache[type] ||= type.to_baml
411
+ end
412
+ end
413
+
414
+ # Use in production for frequently accessed types
415
+ schema = CachedTypeConverter.to_baml(User)
416
+ ```
417
+
418
+ ### Lazy Loading
419
+
420
+ ```ruby
421
+ # Only generate BAML when needed (smart defaults apply)
422
+ class ApiResponse
423
+ def schema
424
+ @schema ||= self.class.to_baml
425
+ end
426
+ end
85
427
  ```
@@ -23,27 +23,64 @@ end
23
23
  ```ruby
24
24
  require 'sorbet-baml'
25
25
 
26
+ # Ruby-idiomatic API (recommended)
27
+ User.to_baml
28
+
29
+ # Legacy API (still supported)
26
30
  baml = SorbetBaml.from_struct(User)
27
31
  puts baml
28
- # Output:
29
- # class User {
30
- # id int
31
- # name string
32
- # email string?
33
- # }
34
32
  ```
35
33
 
36
- ### 3. Use with your LLM
34
+ **Generated BAML:**
35
+ ```baml
36
+ class User {
37
+ id int
38
+ name string
39
+ email string?
40
+ }
41
+ ```
42
+
43
+ ### 3. Add field descriptions (optional)
44
+
45
+ Document your fields with comments for better LLM understanding:
46
+
47
+ ```ruby
48
+ class User < T::Struct
49
+ # Unique identifier for the user account
50
+ const :id, Integer
51
+
52
+ # User's display name, visible to other users
53
+ const :name, String
54
+
55
+ # Optional email for notifications and login
56
+ const :email, T.nilable(String)
57
+ end
58
+
59
+ # Generate BAML (descriptions included by default!)
60
+ User.to_baml
61
+ ```
62
+
63
+ **Generated BAML with descriptions:**
64
+ ```baml
65
+ class User {
66
+ id int @description("Unique identifier for the user account")
67
+ name string @description("User's display name, visible to other users")
68
+ email string? @description("Optional email for notifications and login")
69
+ }
70
+ ```
71
+
72
+ ### 4. Use with your LLM
37
73
 
38
74
  Include the BAML definition in your prompt:
39
75
 
40
76
  ```ruby
77
+ baml = User.to_baml
41
78
  prompt = <<~PROMPT
42
79
  Generate sample data matching this schema:
43
80
 
44
81
  #{baml}
45
82
 
46
- Return 3 examples.
83
+ Return 3 realistic examples.
47
84
  PROMPT
48
85
  ```
49
86