sorbet-baml 0.1.0 → 0.2.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/CLAUDE.md +94 -0
- data/README.md +197 -125
- data/examples/description_parameters.rb +49 -0
- data/lib/sorbet_baml/comment_extractor.rb +23 -18
- data/lib/sorbet_baml/converter.rb +3 -3
- data/lib/sorbet_baml/description_extension.rb +34 -0
- data/lib/sorbet_baml/description_extractor.rb +36 -0
- data/lib/sorbet_baml/version.rb +1 -1
- data/lib/sorbet_baml.rb +3 -0
- metadata +5 -6
- data/docs/README.md +0 -117
- data/docs/advanced-usage.md +0 -427
- data/docs/getting-started.md +0 -91
- data/docs/troubleshooting.md +0 -291
- data/docs/type-mapping.md +0 -192
data/docs/advanced-usage.md
DELETED
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
# Advanced Usage
|
|
2
|
-
|
|
3
|
-
## Ruby-Idiomatic API
|
|
4
|
-
|
|
5
|
-
The gem automatically extends all T::Struct and T::Enum classes with conversion methods:
|
|
6
|
-
|
|
7
|
-
```ruby
|
|
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)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
class Company < T::Struct
|
|
29
|
-
const :name, String
|
|
30
|
-
const :contact, ContactInfo
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
class User < T::Struct
|
|
34
|
-
const :name, String
|
|
35
|
-
const :company, Company
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Dependencies included automatically with smart defaults!
|
|
39
|
-
User.to_baml
|
|
40
|
-
```
|
|
41
|
-
|
|
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
|
-
```
|
|
59
|
-
|
|
60
|
-
## Converting Multiple Types
|
|
61
|
-
|
|
62
|
-
### Manual Collection
|
|
63
|
-
|
|
64
|
-
```ruby
|
|
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")
|
|
111
|
-
```
|
|
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
|
-
|
|
161
|
-
### Custom Indentation
|
|
162
|
-
|
|
163
|
-
```ruby
|
|
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
|
|
214
|
-
)
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
## File Generation
|
|
218
|
-
|
|
219
|
-
### Single File Output
|
|
220
|
-
|
|
221
|
-
```ruby
|
|
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
|
-
```
|
|
226
|
-
|
|
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
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### Build Process Integration
|
|
238
|
-
|
|
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
|
|
257
|
-
|
|
258
|
-
```ruby
|
|
259
|
-
require 'openai'
|
|
260
|
-
require 'sorbet-baml'
|
|
261
|
-
|
|
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
|
|
272
|
-
|
|
273
|
-
client = OpenAI::Client.new
|
|
274
|
-
response = client.chat(
|
|
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
|
-
}
|
|
288
|
-
)
|
|
289
|
-
```
|
|
290
|
-
|
|
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
|
|
357
|
-
|
|
358
|
-
```ruby
|
|
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
|
|
427
|
-
```
|
data/docs/getting-started.md
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# Getting Started
|
|
2
|
-
|
|
3
|
-
## Prerequisites
|
|
4
|
-
|
|
5
|
-
- Ruby 3.2+
|
|
6
|
-
- Sorbet installed in your project
|
|
7
|
-
- Basic familiarity with T::Struct
|
|
8
|
-
|
|
9
|
-
## Quick Start
|
|
10
|
-
|
|
11
|
-
### 1. Define your Sorbet types
|
|
12
|
-
|
|
13
|
-
```ruby
|
|
14
|
-
class User < T::Struct
|
|
15
|
-
const :id, Integer
|
|
16
|
-
const :name, String
|
|
17
|
-
const :email, T.nilable(String)
|
|
18
|
-
end
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### 2. Convert to BAML
|
|
22
|
-
|
|
23
|
-
```ruby
|
|
24
|
-
require 'sorbet-baml'
|
|
25
|
-
|
|
26
|
-
# Ruby-idiomatic API (recommended)
|
|
27
|
-
User.to_baml
|
|
28
|
-
|
|
29
|
-
# Legacy API (still supported)
|
|
30
|
-
baml = SorbetBaml.from_struct(User)
|
|
31
|
-
puts baml
|
|
32
|
-
```
|
|
33
|
-
|
|
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
|
|
73
|
-
|
|
74
|
-
Include the BAML definition in your prompt:
|
|
75
|
-
|
|
76
|
-
```ruby
|
|
77
|
-
baml = User.to_baml
|
|
78
|
-
prompt = <<~PROMPT
|
|
79
|
-
Generate sample data matching this schema:
|
|
80
|
-
|
|
81
|
-
#{baml}
|
|
82
|
-
|
|
83
|
-
Return 3 realistic examples.
|
|
84
|
-
PROMPT
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## Next Steps
|
|
88
|
-
|
|
89
|
-
- [Type Mapping Reference](./type-mapping.md)
|
|
90
|
-
- [Advanced Usage](./advanced-usage.md)
|
|
91
|
-
- [Troubleshooting](./troubleshooting.md)
|