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.
- checksums.yaml +4 -4
- data/CLAUDE.md +94 -0
- data/README.md +315 -122
- data/Rakefile +2 -2
- data/docs-site/.gitignore +48 -0
- data/docs-site/Gemfile +5 -0
- data/docs-site/Gemfile.lock +140 -0
- data/docs-site/Rakefile +3 -0
- data/docs-site/bridgetown.config.yml +15 -0
- data/docs-site/config/initializers.rb +9 -0
- data/docs-site/config/puma.rb +9 -0
- data/docs-site/config.ru +5 -0
- data/docs-site/esbuild.config.js +11 -0
- data/docs-site/frontend/javascript/index.js +22 -0
- data/docs-site/frontend/styles/index.css +61 -0
- data/docs-site/package.json +18 -0
- data/docs-site/postcss.config.js +6 -0
- data/docs-site/server/roda_app.rb +9 -0
- data/docs-site/src/_components/head.liquid +26 -0
- data/docs-site/src/_components/nav.liquid +68 -0
- data/docs-site/src/_layouts/default.liquid +27 -0
- data/docs-site/src/_layouts/doc.liquid +39 -0
- data/docs-site/src/advanced-usage.md +598 -0
- data/docs-site/src/getting-started.md +170 -0
- data/docs-site/src/index.md +183 -0
- data/docs-site/src/troubleshooting.md +317 -0
- data/docs-site/src/type-mapping.md +236 -0
- data/docs-site/tailwind.config.js +85 -0
- data/examples/description_parameters.rb +49 -0
- data/lib/sorbet_baml/comment_extractor.rb +51 -54
- data/lib/sorbet_baml/converter.rb +69 -35
- data/lib/sorbet_baml/dependency_resolver.rb +11 -11
- data/lib/sorbet_baml/description_extension.rb +34 -0
- data/lib/sorbet_baml/description_extractor.rb +34 -0
- 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 +41 -10
- data/sorbet/config +2 -0
- data/sorbet/rbi/gems/anthropic@1.5.0.rbi +21252 -0
- data/sorbet/rbi/gems/async@2.27.3.rbi +9 -0
- data/sorbet/rbi/gems/bigdecimal@3.2.2.rbi +9 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.3.5.rbi +424 -0
- data/sorbet/rbi/gems/connection_pool@2.5.3.rbi +9 -0
- data/sorbet/rbi/gems/console@1.33.0.rbi +9 -0
- data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +672 -0
- data/sorbet/rbi/gems/dry-core@1.1.0.rbi +1729 -0
- data/sorbet/rbi/gems/dry-logger@1.1.0.rbi +1317 -0
- data/sorbet/rbi/gems/dspy@0.19.1.rbi +6677 -0
- data/sorbet/rbi/gems/ffi@1.17.2.rbi +2174 -0
- data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +9 -0
- data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +9 -0
- data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +9 -0
- data/sorbet/rbi/gems/google-protobuf@4.32.0.rbi +9 -0
- data/sorbet/rbi/gems/googleapis-common-protos-types@1.20.0.rbi +9 -0
- data/sorbet/rbi/gems/informers@1.2.1.rbi +1875 -0
- data/sorbet/rbi/gems/io-event@1.12.1.rbi +9 -0
- data/sorbet/rbi/gems/metrics@0.13.0.rbi +9 -0
- data/sorbet/rbi/gems/onnxruntime@0.10.0.rbi +304 -0
- data/sorbet/rbi/gems/openai@0.16.0.rbi +68055 -0
- data/sorbet/rbi/gems/opentelemetry-api@1.6.0.rbi +9 -0
- data/sorbet/rbi/gems/opentelemetry-common@0.22.0.rbi +9 -0
- data/sorbet/rbi/gems/opentelemetry-exporter-otlp@0.30.0.rbi +9 -0
- data/sorbet/rbi/gems/opentelemetry-registry@0.4.0.rbi +9 -0
- data/sorbet/rbi/gems/opentelemetry-sdk@1.8.1.rbi +9 -0
- data/sorbet/rbi/gems/opentelemetry-semantic_conventions@1.11.0.rbi +9 -0
- data/sorbet/rbi/gems/polars-df@0.20.0.rbi +9 -0
- data/sorbet/rbi/gems/sorbet-result@1.4.0.rbi +242 -0
- data/sorbet/rbi/gems/sorbet-schema@0.9.2.rbi +743 -0
- data/sorbet/rbi/gems/sorbet-struct-comparable@1.3.0.rbi +48 -0
- data/sorbet/rbi/gems/tokenizers@0.5.5.rbi +754 -0
- data/sorbet/rbi/gems/traces@0.17.0.rbi +9 -0
- data/sorbet/rbi/gems/zeitwerk@2.7.3.rbi +1429 -0
- metadata +67 -7
- 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
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: doc
|
|
3
|
+
title: "Type Mapping Reference"
|
|
4
|
+
description: "Complete mapping between Sorbet types and BAML output. Learn how every Sorbet type converts to efficient BAML format."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Type Mapping Reference
|
|
8
|
+
|
|
9
|
+
Complete mapping between Sorbet types and BAML output for autonomous LLM workflows. All listed types are **fully supported** with automatic field descriptions.
|
|
10
|
+
|
|
11
|
+
## Basic Types
|
|
12
|
+
|
|
13
|
+
| Sorbet Type | BAML Output | Example |
|
|
14
|
+
|-------------|-------------|---------|
|
|
15
|
+
| `String` | `string` | `name string` |
|
|
16
|
+
| `Integer` | `int` | `age int` |
|
|
17
|
+
| `Float` | `float` | `price float` |
|
|
18
|
+
| `T::Boolean` | `bool` | `active bool` |
|
|
19
|
+
| `NilClass` | `null` | `null` |
|
|
20
|
+
| `Symbol` | `string` | `status string` |
|
|
21
|
+
| `Date/DateTime/Time` | `string` | `created_at string` |
|
|
22
|
+
|
|
23
|
+
## Optional Types (T.nilable)
|
|
24
|
+
|
|
25
|
+
| Sorbet Type | BAML Output | Example |
|
|
26
|
+
|-------------|-------------|---------|
|
|
27
|
+
| `T.nilable(String)` | `string?` | `email string?` |
|
|
28
|
+
| `T.nilable(Integer)` | `int?` | `age int?` |
|
|
29
|
+
| `T.nilable(MyStruct)` | `MyStruct?` | `address Address?` |
|
|
30
|
+
|
|
31
|
+
## Collection Types
|
|
32
|
+
|
|
33
|
+
| Sorbet Type | BAML Output | Example |
|
|
34
|
+
|-------------|-------------|---------|
|
|
35
|
+
| `T::Array[String]` | `string[]` | `tags string[]` |
|
|
36
|
+
| `T::Array[Integer]` | `int[]` | `scores int[]` |
|
|
37
|
+
| `T::Array[MyStruct]` | `MyStruct[]` | `addresses Address[]` |
|
|
38
|
+
|
|
39
|
+
## Hash/Map Types
|
|
40
|
+
|
|
41
|
+
| Sorbet Type | BAML Output | Example |
|
|
42
|
+
|-------------|-------------|---------|
|
|
43
|
+
| `T::Hash[String, String]` | `map<string, string>` | `metadata map<string, string>` |
|
|
44
|
+
| `T::Hash[String, Integer]` | `map<string, int>` | `counts map<string, int>` |
|
|
45
|
+
| `T::Hash[Symbol, String]` | `map<string, string>` | `config map<string, string>` |
|
|
46
|
+
|
|
47
|
+
## Union Types (T.any)
|
|
48
|
+
|
|
49
|
+
| Sorbet Type | BAML Output | Example |
|
|
50
|
+
|-------------|-------------|---------|
|
|
51
|
+
| `T.any(String, Integer)` | `string \| int` | `value string \| int` |
|
|
52
|
+
| `T.any(String, Integer, Float)` | `string \| int \| float` | `mixed string \| int \| float` |
|
|
53
|
+
| `T.nilable(T.any(String, Integer))` | `(string \| int)?` | `optional (string \| int)?` |
|
|
54
|
+
|
|
55
|
+
## Complex Collection Types
|
|
56
|
+
|
|
57
|
+
| Sorbet Type | BAML Output | Example |
|
|
58
|
+
|-------------|-------------|---------|
|
|
59
|
+
| `T::Array[T.any(String, Integer)]` | `(string \| int)[]` | `mixed_array (string \| int)[]` |
|
|
60
|
+
| `T::Hash[String, T.any(String, Integer)]` | `map<string, string \| int>` | `settings map<string, string \| int>` |
|
|
61
|
+
| `T::Hash[String, T::Array[String]]` | `map<string, string[]>` | `labels map<string, string[]>` |
|
|
62
|
+
|
|
63
|
+
## Structured Types
|
|
64
|
+
|
|
65
|
+
### T::Struct to BAML Classes (Research Workflow Example)
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
class ConfidenceLevel < T::Enum
|
|
69
|
+
enums do
|
|
70
|
+
# Low confidence, requires further verification
|
|
71
|
+
Low = new('low')
|
|
72
|
+
# High confidence, strongly supported by evidence
|
|
73
|
+
High = new('high')
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class ResearchFindings < T::Struct
|
|
78
|
+
# Detailed research findings and analysis
|
|
79
|
+
const :findings, String
|
|
80
|
+
# Key actionable insights extracted
|
|
81
|
+
const :key_insights, T::Array[String]
|
|
82
|
+
# Assessment of evidence quality
|
|
83
|
+
const :evidence_quality, ConfidenceLevel
|
|
84
|
+
# Confidence score (1-10 scale)
|
|
85
|
+
const :confidence_score, Integer
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
ResearchFindings.to_baml
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Generated BAML:**
|
|
94
|
+
```baml
|
|
95
|
+
enum ConfidenceLevel {
|
|
96
|
+
"low" @description("Low confidence, requires further verification")
|
|
97
|
+
"high" @description("High confidence, strongly supported by evidence")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class ResearchFindings {
|
|
101
|
+
findings string @description("Detailed research findings and analysis")
|
|
102
|
+
key_insights string[] @description("Key actionable insights extracted")
|
|
103
|
+
evidence_quality ConfidenceLevel @description("Assessment of evidence quality")
|
|
104
|
+
confidence_score int @description("Confidence score (1-10 scale)")
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### T::Enum to BAML Enums (Task Classification)
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
class TaskType < T::Enum
|
|
112
|
+
enums do
|
|
113
|
+
# Literature review and information gathering
|
|
114
|
+
Research = new('research')
|
|
115
|
+
# Data analysis and statistical interpretation
|
|
116
|
+
Analysis = new('analysis')
|
|
117
|
+
# Combining multiple sources into coherent insights
|
|
118
|
+
Synthesis = new('synthesis')
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class ResearchTask < T::Struct
|
|
123
|
+
# Clear description of the research objective
|
|
124
|
+
const :objective, String
|
|
125
|
+
# Type of research task to be performed
|
|
126
|
+
const :task_type, TaskType
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
[TaskType, ResearchTask].map(&:to_baml).join("\n\n")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Generated BAML:**
|
|
135
|
+
```baml
|
|
136
|
+
enum TaskType {
|
|
137
|
+
"research" @description("Literature review and information gathering")
|
|
138
|
+
"analysis" @description("Data analysis and statistical interpretation")
|
|
139
|
+
"synthesis" @description("Combining multiple sources into coherent insights")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
class ResearchTask {
|
|
143
|
+
objective string @description("Clear description of the research objective")
|
|
144
|
+
task_type TaskType @description("Type of research task to be performed")
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Complex Real-World Example (Autonomous Research Agent)
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
class ComplexityLevel < T::Enum
|
|
152
|
+
enums do
|
|
153
|
+
# Basic analysis requiring straightforward research
|
|
154
|
+
Basic = new('basic')
|
|
155
|
+
# Advanced analysis requiring deep domain expertise
|
|
156
|
+
Advanced = new('advanced')
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
class TaskDecomposition < T::Struct
|
|
161
|
+
# The main research topic being investigated
|
|
162
|
+
const :research_topic, String
|
|
163
|
+
# Target complexity level for the decomposition
|
|
164
|
+
const :complexity_level, ComplexityLevel
|
|
165
|
+
# Autonomously generated list of research subtasks
|
|
166
|
+
const :subtasks, T::Array[String]
|
|
167
|
+
# Strategic priority rankings (1-5 scale) for each subtask
|
|
168
|
+
const :priority_order, T::Array[Integer]
|
|
169
|
+
# Task dependency relationships for optimal sequencing
|
|
170
|
+
const :dependencies, T::Array[String]
|
|
171
|
+
# Key-value metadata for agent coordination
|
|
172
|
+
const :agent_metadata, T::Hash[String, T.any(String, Integer)]
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
[ComplexityLevel, TaskDecomposition].map(&:to_baml).join("\n\n")
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Generated BAML:**
|
|
181
|
+
```baml
|
|
182
|
+
enum ComplexityLevel {
|
|
183
|
+
"basic" @description("Basic analysis requiring straightforward research")
|
|
184
|
+
"advanced" @description("Advanced analysis requiring deep domain expertise")
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
class TaskDecomposition {
|
|
188
|
+
research_topic string @description("The main research topic being investigated")
|
|
189
|
+
complexity_level ComplexityLevel @description("Target complexity level for the decomposition")
|
|
190
|
+
subtasks string[] @description("Autonomously generated list of research subtasks")
|
|
191
|
+
priority_order int[] @description("Strategic priority rankings (1-5 scale) for each subtask")
|
|
192
|
+
dependencies string[] @description("Task dependency relationships for optimal sequencing")
|
|
193
|
+
agent_metadata map<string, string | int> @description("Key-value metadata for agent coordination")
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Advanced Features
|
|
198
|
+
|
|
199
|
+
### Dependency Management
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
# Dependencies automatically included with smart defaults
|
|
203
|
+
TaskDecomposition.to_baml
|
|
204
|
+
# Outputs ComplexityLevel enum, then TaskDecomposition class in correct order
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Field Descriptions (Included by Default)
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
# Smart defaults extract field descriptions from comments
|
|
211
|
+
TaskDecomposition.to_baml
|
|
212
|
+
# Outputs BAML with @description() annotations for LLM context
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Custom Formatting
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
# Smart defaults include dependencies and descriptions automatically
|
|
219
|
+
TaskDecomposition.to_baml(indent_size: 4)
|
|
220
|
+
# Disable features if needed:
|
|
221
|
+
TaskDecomposition.to_baml(include_descriptions: false)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## ✅ Completed Features
|
|
225
|
+
|
|
226
|
+
- ✅ `T::Struct` → `class Name { ... }` with field descriptions
|
|
227
|
+
- ✅ `T::Enum` → `enum Name { ... }` with value descriptions
|
|
228
|
+
- ✅ Automatic dependency resolution and ordering
|
|
229
|
+
- ✅ Smart defaults (descriptions and dependencies enabled)
|
|
230
|
+
- ✅ Full type safety with Sorbet type checking
|
|
231
|
+
|
|
232
|
+
## Future Enhancements (Optional)
|
|
233
|
+
|
|
234
|
+
- `T.type_alias` → `type Name = ...`
|
|
235
|
+
- Custom naming strategies (snake_case ↔ camelCase)
|
|
236
|
+
- Self-referential type handling
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
content: [
|
|
4
|
+
"./src/**/*.{html,md,liquid,erb}",
|
|
5
|
+
"./frontend/**/*.js",
|
|
6
|
+
],
|
|
7
|
+
darkMode: 'media',
|
|
8
|
+
theme: {
|
|
9
|
+
extend: {
|
|
10
|
+
typography: {
|
|
11
|
+
DEFAULT: {
|
|
12
|
+
css: {
|
|
13
|
+
maxWidth: 'none',
|
|
14
|
+
color: 'inherit',
|
|
15
|
+
a: {
|
|
16
|
+
color: '#3b82f6',
|
|
17
|
+
textDecoration: 'underline',
|
|
18
|
+
fontWeight: '500',
|
|
19
|
+
},
|
|
20
|
+
'[class~="lead"]': {
|
|
21
|
+
color: 'inherit',
|
|
22
|
+
},
|
|
23
|
+
strong: {
|
|
24
|
+
color: 'inherit',
|
|
25
|
+
},
|
|
26
|
+
'ol > li::before': {
|
|
27
|
+
color: 'inherit',
|
|
28
|
+
},
|
|
29
|
+
'ul > li::before': {
|
|
30
|
+
backgroundColor: 'currentColor',
|
|
31
|
+
},
|
|
32
|
+
hr: {
|
|
33
|
+
borderColor: 'currentColor',
|
|
34
|
+
opacity: 0.3,
|
|
35
|
+
},
|
|
36
|
+
blockquote: {
|
|
37
|
+
color: 'inherit',
|
|
38
|
+
borderLeftColor: 'currentColor',
|
|
39
|
+
opacity: 0.8,
|
|
40
|
+
},
|
|
41
|
+
h1: {
|
|
42
|
+
color: 'inherit',
|
|
43
|
+
},
|
|
44
|
+
h2: {
|
|
45
|
+
color: 'inherit',
|
|
46
|
+
},
|
|
47
|
+
h3: {
|
|
48
|
+
color: 'inherit',
|
|
49
|
+
},
|
|
50
|
+
h4: {
|
|
51
|
+
color: 'inherit',
|
|
52
|
+
},
|
|
53
|
+
'figure figcaption': {
|
|
54
|
+
color: 'inherit',
|
|
55
|
+
},
|
|
56
|
+
code: {
|
|
57
|
+
color: 'inherit',
|
|
58
|
+
backgroundColor: 'rgba(156, 163, 175, 0.2)',
|
|
59
|
+
padding: '0.125rem 0.25rem',
|
|
60
|
+
borderRadius: '0.25rem',
|
|
61
|
+
fontWeight: '400',
|
|
62
|
+
},
|
|
63
|
+
'code::before': {
|
|
64
|
+
content: '""',
|
|
65
|
+
},
|
|
66
|
+
'code::after': {
|
|
67
|
+
content: '""',
|
|
68
|
+
},
|
|
69
|
+
pre: {
|
|
70
|
+
backgroundColor: '#1f2937',
|
|
71
|
+
color: '#f9fafb',
|
|
72
|
+
},
|
|
73
|
+
'pre code': {
|
|
74
|
+
backgroundColor: 'transparent',
|
|
75
|
+
color: 'inherit',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
plugins: [
|
|
83
|
+
require('@tailwindcss/typography'),
|
|
84
|
+
],
|
|
85
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# typed: false
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/sorbet_baml'
|
|
5
|
+
|
|
6
|
+
puts '🎯 Description Parameter Support Demo'
|
|
7
|
+
puts '=' * 50
|
|
8
|
+
|
|
9
|
+
# Example 1: Basic description parameters
|
|
10
|
+
class User < T::Struct
|
|
11
|
+
const :name, String, description: "User's full legal name"
|
|
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
|
+
end
|
|
16
|
+
|
|
17
|
+
puts "\n1. Basic T::Struct with description parameters:"
|
|
18
|
+
puts User.to_baml
|
|
19
|
+
|
|
20
|
+
# Example 2: Mixed description sources (parameters + comments)
|
|
21
|
+
class Product < T::Struct
|
|
22
|
+
# This comment will be used as fallback
|
|
23
|
+
const :id, String
|
|
24
|
+
|
|
25
|
+
const :name, String, description: 'Product name for display'
|
|
26
|
+
|
|
27
|
+
# Price in USD cents
|
|
28
|
+
prop :price_cents, Integer
|
|
29
|
+
|
|
30
|
+
const :category, String, description: 'Product category classification'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
puts "\n2. Mixed description sources (parameters take priority):"
|
|
34
|
+
puts Product.to_baml
|
|
35
|
+
|
|
36
|
+
# Example 3: Complex nested types with descriptions
|
|
37
|
+
class Order < T::Struct
|
|
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
|
+
end
|
|
44
|
+
|
|
45
|
+
puts "\n3. Complex nested types with dependencies:"
|
|
46
|
+
puts Order.to_baml
|
|
47
|
+
|
|
48
|
+
puts "\n✨ Beautiful, readable, and LLM-friendly!"
|
|
49
|
+
puts '🚀 Perfect for DSPy.rb, autonomous agents, and structured LLM outputs'
|
|
@@ -8,101 +8,100 @@ module SorbetBaml
|
|
|
8
8
|
|
|
9
9
|
sig { params(klass: T.class_of(T::Struct)).returns(T::Hash[String, T.nilable(String)]) }
|
|
10
10
|
def self.extract_field_comments(klass)
|
|
11
|
+
# First try to get descriptions from the description extractor (extra field)
|
|
12
|
+
descriptions = DescriptionExtractor.extract_prop_descriptions(klass)
|
|
13
|
+
|
|
14
|
+
# Then fall back to comment-based extraction for any missing descriptions
|
|
11
15
|
comments = {}
|
|
12
16
|
source_file = find_source_file(klass)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
comments
|
|
17
|
+
|
|
18
|
+
if source_file && File.exist?(source_file)
|
|
19
|
+
lines = File.readlines(source_file)
|
|
20
|
+
extract_comments_from_lines(lines, T.must(T.must(klass.name).split('::').last), comments)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Merge with priority: description parameters > comments
|
|
24
|
+
descriptions.merge(comments) { |_key, desc_param, _comment| desc_param }
|
|
20
25
|
end
|
|
21
26
|
|
|
22
27
|
sig { params(klass: T.class_of(T::Enum)).returns(T::Hash[String, T.nilable(String)]) }
|
|
23
28
|
def self.extract_enum_comments(klass)
|
|
24
29
|
comments = {}
|
|
25
30
|
source_file = find_source_file(klass)
|
|
26
|
-
|
|
31
|
+
|
|
27
32
|
return comments unless source_file && File.exist?(source_file)
|
|
28
|
-
|
|
33
|
+
|
|
29
34
|
lines = File.readlines(source_file)
|
|
30
|
-
extract_enum_comments_from_lines(lines, klass.name.split('::').last, comments)
|
|
31
|
-
|
|
35
|
+
extract_enum_comments_from_lines(lines, T.must(T.must(klass.name).split('::').last), comments)
|
|
36
|
+
|
|
32
37
|
comments
|
|
33
38
|
end
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
sig { params(klass: Class).returns(T.nilable(String)) }
|
|
40
|
+
sig { params(klass: T::Class[T.anything]).returns(T.nilable(String)) }
|
|
38
41
|
def self.find_source_file(klass)
|
|
39
42
|
# Try to find where the class was defined
|
|
40
43
|
# This is a heuristic approach since Ruby doesn't provide reliable source location for classes
|
|
41
|
-
|
|
44
|
+
|
|
42
45
|
# Method 1: Check if any methods have source location
|
|
43
46
|
begin
|
|
44
47
|
if klass.respond_to?(:new) && klass.method(:new).respond_to?(:source_location)
|
|
45
48
|
location = klass.method(:new).source_location
|
|
46
49
|
return location[0] if location
|
|
47
50
|
end
|
|
48
|
-
rescue
|
|
51
|
+
rescue StandardError
|
|
49
52
|
# Ignore errors
|
|
50
53
|
end
|
|
51
|
-
|
|
54
|
+
|
|
52
55
|
# Method 2: Look at the current call stack for files that might contain the class
|
|
53
56
|
caller_locations.each do |location|
|
|
54
57
|
file_path = location.absolute_path || location.path
|
|
55
58
|
next unless file_path && File.exist?(file_path)
|
|
56
|
-
|
|
59
|
+
|
|
57
60
|
# Read the file and check if it contains the class definition
|
|
58
61
|
begin
|
|
59
62
|
content = File.read(file_path)
|
|
60
|
-
class_name = klass.name.split('::').last
|
|
61
|
-
if content.match(/class\s+#{Regexp.escape(class_name)}\s*</)
|
|
62
|
-
|
|
63
|
-
end
|
|
64
|
-
rescue
|
|
63
|
+
class_name = T.must(klass.name).split('::').last
|
|
64
|
+
return file_path if content.match(/class\s+#{Regexp.escape(T.must(class_name))}\s*</)
|
|
65
|
+
rescue StandardError
|
|
65
66
|
# Ignore file read errors
|
|
66
67
|
end
|
|
67
68
|
end
|
|
68
|
-
|
|
69
|
+
|
|
69
70
|
nil
|
|
70
71
|
end
|
|
71
72
|
|
|
72
73
|
sig { params(lines: T::Array[String], class_name: String, comments: T::Hash[String, T.nilable(String)]).void }
|
|
73
74
|
def self.extract_comments_from_lines(lines, class_name, comments)
|
|
74
|
-
in_target_class = false
|
|
75
|
+
in_target_class = T.let(false, T::Boolean)
|
|
75
76
|
current_comment = T.let(nil, T.nilable(String))
|
|
76
77
|
brace_depth = 0
|
|
77
|
-
|
|
78
|
+
|
|
78
79
|
lines.each do |line|
|
|
79
80
|
stripped = line.strip
|
|
80
|
-
|
|
81
|
+
|
|
81
82
|
# Check if we're entering the target class
|
|
82
83
|
if stripped.match(/^class\s+#{Regexp.escape(class_name)}\s*<\s*T::Struct/)
|
|
83
84
|
in_target_class = true
|
|
84
85
|
brace_depth = 0
|
|
85
86
|
next
|
|
86
87
|
end
|
|
87
|
-
|
|
88
|
+
|
|
88
89
|
next unless in_target_class
|
|
89
|
-
|
|
90
|
+
|
|
90
91
|
# Track brace depth to handle nested classes
|
|
91
92
|
brace_depth += stripped.count('{')
|
|
92
93
|
brace_depth -= stripped.count('}')
|
|
93
|
-
|
|
94
|
+
|
|
94
95
|
# Exit when we reach the end of the class
|
|
95
|
-
if stripped == 'end' && brace_depth == 0
|
|
96
|
-
|
|
97
|
-
end
|
|
98
|
-
|
|
96
|
+
break if stripped == 'end' && brace_depth == 0
|
|
97
|
+
|
|
99
98
|
# Extract comment
|
|
100
99
|
if stripped.start_with?('#')
|
|
101
|
-
comment_text = stripped[1..-1].strip
|
|
100
|
+
comment_text = T.must(stripped[1..-1]).strip
|
|
102
101
|
current_comment = current_comment ? "#{current_comment} #{comment_text}" : comment_text
|
|
103
102
|
elsif stripped.match(/^const\s+:(\w+)/) && current_comment
|
|
104
|
-
field_name = stripped.match(/^const\s+:(\w+)/)[1]
|
|
105
|
-
comments[field_name] = current_comment
|
|
103
|
+
field_name = T.must(stripped.match(/^const\s+:(\w+)/))[1]
|
|
104
|
+
comments[T.must(field_name)] = current_comment
|
|
106
105
|
current_comment = nil
|
|
107
106
|
elsif !stripped.empty? && !stripped.start_with?('#')
|
|
108
107
|
# Reset comment if we hit non-comment, non-const line
|
|
@@ -113,47 +112,45 @@ module SorbetBaml
|
|
|
113
112
|
|
|
114
113
|
sig { params(lines: T::Array[String], class_name: String, comments: T::Hash[String, T.nilable(String)]).void }
|
|
115
114
|
def self.extract_enum_comments_from_lines(lines, class_name, comments)
|
|
116
|
-
in_target_class = false
|
|
117
|
-
in_enums_block = false
|
|
115
|
+
in_target_class = T.let(false, T::Boolean)
|
|
116
|
+
in_enums_block = T.let(false, T::Boolean)
|
|
118
117
|
current_comment = T.let(nil, T.nilable(String))
|
|
119
|
-
|
|
118
|
+
|
|
120
119
|
lines.each do |line|
|
|
121
120
|
stripped = line.strip
|
|
122
|
-
|
|
121
|
+
|
|
123
122
|
# Check if we're entering the target enum class
|
|
124
123
|
if stripped.match(/^class\s+#{Regexp.escape(class_name)}\s*<\s*T::Enum/)
|
|
125
124
|
in_target_class = true
|
|
126
125
|
next
|
|
127
126
|
end
|
|
128
|
-
|
|
127
|
+
|
|
129
128
|
next unless in_target_class
|
|
130
|
-
|
|
129
|
+
|
|
131
130
|
# Check if we're in the enums block
|
|
132
131
|
if stripped == 'enums do'
|
|
133
132
|
in_enums_block = true
|
|
134
133
|
next
|
|
135
134
|
end
|
|
136
|
-
|
|
135
|
+
|
|
137
136
|
# Exit enums block
|
|
138
137
|
if in_enums_block && stripped == 'end'
|
|
139
138
|
in_enums_block = false
|
|
140
139
|
next
|
|
141
140
|
end
|
|
142
|
-
|
|
141
|
+
|
|
143
142
|
# Exit class
|
|
144
|
-
if stripped == 'end' && !in_enums_block
|
|
145
|
-
|
|
146
|
-
end
|
|
147
|
-
|
|
143
|
+
break if stripped == 'end' && !in_enums_block
|
|
144
|
+
|
|
148
145
|
next unless in_enums_block
|
|
149
|
-
|
|
146
|
+
|
|
150
147
|
# Extract comment
|
|
151
148
|
if stripped.start_with?('#')
|
|
152
|
-
comment_text = stripped[1..-1].strip
|
|
149
|
+
comment_text = T.must(stripped[1..-1]).strip
|
|
153
150
|
current_comment = current_comment ? "#{current_comment} #{comment_text}" : comment_text
|
|
154
151
|
elsif stripped.match(/^(\w+)\s*=\s*new/) && current_comment
|
|
155
|
-
enum_name = stripped.match(/^(\w+)\s*=\s*new/)[1]
|
|
156
|
-
comments[enum_name] = current_comment
|
|
152
|
+
enum_name = T.must(stripped.match(/^(\w+)\s*=\s*new/))[1]
|
|
153
|
+
comments[T.must(enum_name)] = current_comment
|
|
157
154
|
current_comment = nil
|
|
158
155
|
elsif !stripped.empty? && !stripped.start_with?('#')
|
|
159
156
|
# Reset comment if we hit non-comment, non-enum line
|
|
@@ -162,4 +159,4 @@ module SorbetBaml
|
|
|
162
159
|
end
|
|
163
160
|
end
|
|
164
161
|
end
|
|
165
|
-
end
|
|
162
|
+
end
|