ruby_llm-schema 0.3.1 → 1.0.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/README.md +17 -457
- data/lib/ruby_llm/schema.rb +23 -91
- metadata +30 -23
- data/LICENSE +0 -21
- data/lib/ruby_llm/schema/dsl/complex_types.rb +0 -32
- data/lib/ruby_llm/schema/dsl/primitive_types.rb +0 -29
- data/lib/ruby_llm/schema/dsl/schema_builders.rb +0 -189
- data/lib/ruby_llm/schema/dsl/utilities.rb +0 -53
- data/lib/ruby_llm/schema/dsl.rb +0 -17
- data/lib/ruby_llm/schema/errors.rb +0 -30
- data/lib/ruby_llm/schema/helpers.rb +0 -10
- data/lib/ruby_llm/schema/json_output.rb +0 -34
- data/lib/ruby_llm/schema/validator.rb +0 -93
- data/lib/ruby_llm/schema/version.rb +0 -7
- data/lib/tasks/release.rake +0 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6bfabbb94f9550f5bae72fc6ad6046ba1fa6f9e99723c4fea969822bd76f33f9
|
|
4
|
+
data.tar.gz: 5b97078d6f40a912a366c4d8f26333ead3504126e9a2abd3d8d658f81d168283
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: adc5528fd26d989854bbf0d6255bda4123fa6df18d96722c38b47a7414ff8c53006212f8c987932d190913553bad3764870038589ebb20a070ebc38e034de87c
|
|
7
|
+
data.tar.gz: 85d522c3b21f5949ce3ed5dba3aef643836d16b5daaca44c2d6a8beeec67e6cfae65ca126e4dbd49edf434f0134aeecb715216c6f3da793d0445a38baa2442fb
|
data/README.md
CHANGED
|
@@ -1,468 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ruby_llm-schema (deprecated)
|
|
2
2
|
|
|
3
|
-
[
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
This gem is now [schematist](https://github.com/crmne/schematist). This directory holds the
|
|
4
|
+
final `ruby_llm-schema` release, which does nothing but depend on schematist and alias
|
|
5
|
+
`RubyLLM::Schema` to `Schematist::Schema`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
It lives in the schematist repo rather than its own, so the alias cannot drift from the gem
|
|
8
|
+
it forwards to. The spec suite loads it and checks the aliases still resolve.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## Building and releasing
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
JSON Schema is useful wherever Ruby code needs to describe structured data in a portable format.
|
|
14
|
-
|
|
15
|
-
Some ideal use cases:
|
|
16
|
-
|
|
17
|
-
- Defining API request and response shapes
|
|
18
|
-
- Describing configuration files or structured payloads
|
|
19
|
-
- Sharing validation contracts across systems
|
|
20
|
-
- Generating structured output schemas for LLM workflows
|
|
21
|
-
- Defining structured parameters for RubyLLM tools
|
|
22
|
-
|
|
23
|
-
### Simple Example
|
|
24
|
-
|
|
25
|
-
```ruby
|
|
26
|
-
class PersonSchema < RubyLLM::Schema
|
|
27
|
-
string :name, description: "Person's full name"
|
|
28
|
-
number :age, description: "Age in years", minimum: 0, maximum: 120
|
|
29
|
-
boolean :active, required: false
|
|
30
|
-
|
|
31
|
-
object :address do
|
|
32
|
-
string :street
|
|
33
|
-
string :city
|
|
34
|
-
string :country, required: false
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
array :tags, of: :string, description: "User tags"
|
|
38
|
-
|
|
39
|
-
array :contacts do
|
|
40
|
-
object do
|
|
41
|
-
string :email, format: "email"
|
|
42
|
-
string :phone, required: false
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
any_of :status do
|
|
47
|
-
string enum: ["active", "pending", "inactive"]
|
|
48
|
-
null
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Usage
|
|
53
|
-
schema = PersonSchema.new
|
|
54
|
-
puts schema.to_json
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### RubyLLM structured output
|
|
58
|
-
|
|
59
|
-
```ruby
|
|
60
|
-
class PersonSchema < RubyLLM::Schema
|
|
61
|
-
string :name, description: "Person's full name"
|
|
62
|
-
integer :age, description: "Person's age in years"
|
|
63
|
-
string :city, required: false, description: "City where they live"
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Use it natively with RubyLLM
|
|
67
|
-
chat = RubyLLM.chat
|
|
68
|
-
response = chat.with_schema(PersonSchema)
|
|
69
|
-
.ask("Generate a person named Alice who is 30 years old and lives in New York")
|
|
70
|
-
|
|
71
|
-
# The response is automatically parsed from JSON
|
|
72
|
-
puts response.content # => {"name" => "Alice", "age" => 30}
|
|
73
|
-
puts response.content.class # => Hash
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### RubyLLM tools
|
|
77
|
-
|
|
78
|
-
RubyLLM tools can use schema classes for structured parameters. This is useful when the same argument shape is shared across tools or elsewhere in your app.
|
|
79
|
-
|
|
80
|
-
```ruby
|
|
81
|
-
class SearchParams < RubyLLM::Schema
|
|
82
|
-
string :query, description: "Search query"
|
|
83
|
-
integer :limit, required: false, description: "Maximum results"
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
class SearchDocuments < RubyLLM::Tool
|
|
87
|
-
desc "Searches internal documents"
|
|
88
|
-
params SearchParams
|
|
89
|
-
|
|
90
|
-
def execute(query:, limit: 10)
|
|
91
|
-
DocumentSearch.call(query:, limit:)
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
For tool-specific parameters, define the schema inline with `params do ... end`.
|
|
97
|
-
|
|
98
|
-
```ruby
|
|
99
|
-
class Weather < RubyLLM::Tool
|
|
100
|
-
desc "Gets current weather"
|
|
101
|
-
|
|
102
|
-
params do
|
|
103
|
-
string :city, description: "City name"
|
|
104
|
-
string :units, enum: %w[celsius fahrenheit], required: false
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def execute(city:, units: "celsius")
|
|
108
|
-
WeatherAPI.current(city:, units:)
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## Installation
|
|
114
|
-
|
|
115
|
-
Add this line to your application's Gemfile:
|
|
116
|
-
|
|
117
|
-
```ruby
|
|
118
|
-
gem 'ruby_llm-schema'
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
And then execute:
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
bundle install
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Or install it yourself as:
|
|
12
|
+
The root gemspec builds schematist. This one has to be built from this directory:
|
|
128
13
|
|
|
129
14
|
```bash
|
|
130
|
-
|
|
15
|
+
cd compat/ruby_llm-schema
|
|
16
|
+
gem build ruby_llm-schema.gemspec
|
|
17
|
+
gem push ruby_llm-schema-1.0.0.gem
|
|
131
18
|
```
|
|
132
19
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
Three approaches for creating schemas:
|
|
136
|
-
|
|
137
|
-
### Class Inheritance
|
|
138
|
-
|
|
139
|
-
```ruby
|
|
140
|
-
class PersonSchema < RubyLLM::Schema
|
|
141
|
-
string :name, description: "Person's full name"
|
|
142
|
-
number :age
|
|
143
|
-
boolean :active, required: false
|
|
144
|
-
|
|
145
|
-
object :address do
|
|
146
|
-
string :street
|
|
147
|
-
string :city
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
array :tags, of: :string
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
schema = PersonSchema.new
|
|
154
|
-
puts schema.to_json
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Factory Method
|
|
158
|
-
|
|
159
|
-
```ruby
|
|
160
|
-
PersonSchema = RubyLLM::Schema.create do
|
|
161
|
-
string :name, description: "Person's full name"
|
|
162
|
-
number :age
|
|
163
|
-
boolean :active, required: false
|
|
164
|
-
|
|
165
|
-
object :address do
|
|
166
|
-
string :street
|
|
167
|
-
string :city
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
array :tags, of: :string
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
schema = PersonSchema.new
|
|
174
|
-
puts schema.to_json
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Global Helper
|
|
178
|
-
|
|
179
|
-
```ruby
|
|
180
|
-
require 'ruby_llm/schema'
|
|
181
|
-
include RubyLLM::Helpers
|
|
182
|
-
|
|
183
|
-
person_schema = schema "PersonData", description: "A person object" do
|
|
184
|
-
string :name, description: "Person's full name"
|
|
185
|
-
number :age
|
|
186
|
-
boolean :active, required: false
|
|
187
|
-
|
|
188
|
-
object :address do
|
|
189
|
-
string :street
|
|
190
|
-
string :city
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
array :tags, of: :string
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
puts person_schema.to_json
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
## Schema Property Types
|
|
200
|
-
|
|
201
|
-
A schema is a collection of properties, which can be of different types. Each type has its own set of properties you can set.
|
|
202
|
-
|
|
203
|
-
All property types can (along with the required `name` key) be set with a `description` and a `required` flag (default is `true`).
|
|
20
|
+
schematist 1.0.0 must be on RubyGems first, since this gem depends on it.
|
|
204
21
|
|
|
205
|
-
|
|
206
|
-
string :name, description: "Person's full name"
|
|
207
|
-
number :age, description: "Person's age", required: false
|
|
208
|
-
boolean :is_active, description: "Whether the person is active"
|
|
209
|
-
null :placeholder, description: "A placeholder property"
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
⚠️ Please consult the LLM provider documentation for any limitations or restrictions. For example, as of now, OpenAI requires all properties to be required. In that case, you can use the `any_of` method to make a property optional.
|
|
213
|
-
|
|
214
|
-
```ruby
|
|
215
|
-
any_of :name, description: "Person's full name" do
|
|
216
|
-
string
|
|
217
|
-
null
|
|
218
|
-
end
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### Strings
|
|
222
|
-
|
|
223
|
-
String types support the following properties:
|
|
224
|
-
|
|
225
|
-
- `enum`: an array of allowed values (e.g. `enum: ["on", "off"]`)
|
|
226
|
-
- `pattern`: a regex pattern (e.g. `pattern: "\\d+"`)
|
|
227
|
-
- `format`: a format string (e.g. `format: "email"`)
|
|
228
|
-
- `min_length`: the minimum length of the string (e.g. `min_length: 3`)
|
|
229
|
-
- `max_length`: the maximum length of the string (e.g. `max_length: 10`)
|
|
230
|
-
|
|
231
|
-
Please consult the LLM provider documentation for the available formats and patterns.
|
|
232
|
-
|
|
233
|
-
```ruby
|
|
234
|
-
string :name, description: "Person's full name"
|
|
235
|
-
string :email, format: "email"
|
|
236
|
-
string :phone, pattern: "\\d+"
|
|
237
|
-
string :status, enum: ["on", "off"]
|
|
238
|
-
string :code, min_length: 3, max_length: 10
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### Numbers
|
|
242
|
-
|
|
243
|
-
Number types support the following properties:
|
|
244
|
-
|
|
245
|
-
- `multiple_of`: a multiple of the number (e.g. `multiple_of: 0.01`)
|
|
246
|
-
- `minimum`: the minimum value of the number (e.g. `minimum: 0`)
|
|
247
|
-
- `maximum`: the maximum value of the number (e.g. `maximum: 100`)
|
|
248
|
-
|
|
249
|
-
```ruby
|
|
250
|
-
number :price, minimum: 0, maximum: 100
|
|
251
|
-
number :amount, multiple_of: 0.01
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### Booleans
|
|
255
|
-
|
|
256
|
-
```ruby
|
|
257
|
-
boolean :is_active
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
Boolean types doesn't support any additional properties.
|
|
261
|
-
|
|
262
|
-
### Null
|
|
263
|
-
|
|
264
|
-
```ruby
|
|
265
|
-
null :placeholder
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
Null types doesn't support any additional properties.
|
|
269
|
-
|
|
270
|
-
### Arrays
|
|
271
|
-
|
|
272
|
-
An array is a list of items. You can set the type of the items in the array with the `of` option or by passing a block with the `object` method.
|
|
273
|
-
|
|
274
|
-
An array can have a `min_items` and `max_items` option to set the minimum and maximum number of items in the array.
|
|
275
|
-
|
|
276
|
-
```ruby
|
|
277
|
-
array :tags, of: :string # Array of strings
|
|
278
|
-
array :scores, of: :number # Array of numbers
|
|
279
|
-
array :items, min_items: 1, max_items: 10 # Array with size constraints
|
|
280
|
-
|
|
281
|
-
array :items do # Array of objects
|
|
282
|
-
object do
|
|
283
|
-
string :name
|
|
284
|
-
number :price
|
|
285
|
-
end
|
|
286
|
-
end
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### Objects
|
|
290
|
-
|
|
291
|
-
Objects types expect a block with the properties of the object.
|
|
292
|
-
|
|
293
|
-
```ruby
|
|
294
|
-
object :user do
|
|
295
|
-
string :name
|
|
296
|
-
number :age
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
object :settings, description: "User preferences" do
|
|
300
|
-
boolean :notifications
|
|
301
|
-
string :theme, enum: ["light", "dark"]
|
|
302
|
-
end
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
### Union Types (anyOf)
|
|
306
|
-
|
|
307
|
-
Union types are a way to specify that a property can be one of several types.
|
|
308
|
-
|
|
309
|
-
```ruby
|
|
310
|
-
any_of :value do
|
|
311
|
-
string
|
|
312
|
-
number
|
|
313
|
-
null
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
any_of :identifier do
|
|
317
|
-
string description: "Username"
|
|
318
|
-
number description: "User ID"
|
|
319
|
-
end
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
### Schema Definitions and References
|
|
323
|
-
|
|
324
|
-
You can define sub-schemas and reference them in other schemas, or reference the root schema to generate recursive schemas.
|
|
325
|
-
|
|
326
|
-
```ruby
|
|
327
|
-
class MySchema < RubyLLM::Schema
|
|
328
|
-
define :location do
|
|
329
|
-
string :latitude
|
|
330
|
-
string :longitude
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
# Using a reference in an array
|
|
334
|
-
array :coordinates, of: :location
|
|
335
|
-
|
|
336
|
-
# Using a reference in an object via the `reference` option
|
|
337
|
-
object :home_location, reference: :location
|
|
338
|
-
|
|
339
|
-
# Using a reference in an object via block
|
|
340
|
-
object :user do
|
|
341
|
-
reference :location
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
# Using a reference to the root schema
|
|
345
|
-
object :ui_schema do
|
|
346
|
-
string :element, enum: ["input", "button"]
|
|
347
|
-
string :label
|
|
348
|
-
object :sub_schema, reference: :root
|
|
349
|
-
end
|
|
350
|
-
end
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
### Nested Schemas
|
|
354
|
-
|
|
355
|
-
You can embed existing schema classes directly within objects or arrays for reusable schema composition.
|
|
356
|
-
|
|
357
|
-
```ruby
|
|
358
|
-
class PersonSchema < RubyLLM::Schema
|
|
359
|
-
string :name
|
|
360
|
-
integer :age
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
class CompanySchema < RubyLLM::Schema
|
|
364
|
-
# Using 'of' parameter
|
|
365
|
-
object :ceo, of: PersonSchema
|
|
366
|
-
array :employees, of: PersonSchema
|
|
367
|
-
|
|
368
|
-
# Using Schema.new in block
|
|
369
|
-
object :founder do
|
|
370
|
-
PersonSchema.new
|
|
371
|
-
end
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
schema = CompanySchema.new
|
|
375
|
-
schema.to_json_schema
|
|
376
|
-
# =>
|
|
377
|
-
# {
|
|
378
|
-
# "name":"CompanySchema",
|
|
379
|
-
# "description":"nil",
|
|
380
|
-
# "schema":{
|
|
381
|
-
# "type":"object",
|
|
382
|
-
# "properties":{
|
|
383
|
-
# "ceo":{
|
|
384
|
-
# "type":"object",
|
|
385
|
-
# "properties":{
|
|
386
|
-
# "name":{
|
|
387
|
-
# "type":"string"
|
|
388
|
-
# },
|
|
389
|
-
# "age":{
|
|
390
|
-
# "type":"integer"
|
|
391
|
-
# }
|
|
392
|
-
# },
|
|
393
|
-
# "required":[
|
|
394
|
-
# :"name",
|
|
395
|
-
# :"age"
|
|
396
|
-
# ],
|
|
397
|
-
# "additionalProperties":false
|
|
398
|
-
# },
|
|
399
|
-
# "employees":{
|
|
400
|
-
# "type":"array",
|
|
401
|
-
# "items":{
|
|
402
|
-
# "type":"object",
|
|
403
|
-
# "properties":{
|
|
404
|
-
# "name":{
|
|
405
|
-
# "type":"string"
|
|
406
|
-
# },
|
|
407
|
-
# "age":{
|
|
408
|
-
# "type":"integer"
|
|
409
|
-
# }
|
|
410
|
-
# },
|
|
411
|
-
# "required":[
|
|
412
|
-
# :"name",
|
|
413
|
-
# :"age"
|
|
414
|
-
# ],
|
|
415
|
-
# "additionalProperties":false
|
|
416
|
-
# }
|
|
417
|
-
# },
|
|
418
|
-
# "founder":{
|
|
419
|
-
# "type":"object",
|
|
420
|
-
# "properties":{
|
|
421
|
-
# "name":{
|
|
422
|
-
# "type":"string"
|
|
423
|
-
# },
|
|
424
|
-
# "age":{
|
|
425
|
-
# "type":"integer"
|
|
426
|
-
# }
|
|
427
|
-
# },
|
|
428
|
-
# "required":[
|
|
429
|
-
# :"name",
|
|
430
|
-
# :"age"
|
|
431
|
-
# ],
|
|
432
|
-
# "additionalProperties":false
|
|
433
|
-
# }
|
|
434
|
-
# },
|
|
435
|
-
# "required":[
|
|
436
|
-
# :"ceo",
|
|
437
|
-
# :"employees",
|
|
438
|
-
# :"founder"
|
|
439
|
-
# ],
|
|
440
|
-
# "additionalProperties":false,
|
|
441
|
-
# "strict":true
|
|
442
|
-
# }
|
|
443
|
-
# }
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
## JSON Output
|
|
447
|
-
|
|
448
|
-
```ruby
|
|
449
|
-
schema = PersonSchema.new
|
|
450
|
-
schema.to_json_schema
|
|
451
|
-
# => {
|
|
452
|
-
# name: "PersonSchema",
|
|
453
|
-
# description: nil,
|
|
454
|
-
# schema: {
|
|
455
|
-
# type: "object",
|
|
456
|
-
# properties: { ... },
|
|
457
|
-
# required: [...],
|
|
458
|
-
# additionalProperties: false,
|
|
459
|
-
# strict: true
|
|
460
|
-
# }
|
|
461
|
-
# }
|
|
462
|
-
|
|
463
|
-
puts schema.to_json # Pretty JSON string
|
|
464
|
-
```
|
|
22
|
+
The version tracks the schematist release it forwards to.
|
|
465
23
|
|
|
466
|
-
##
|
|
24
|
+
## What the alias cannot forward
|
|
467
25
|
|
|
468
|
-
|
|
26
|
+
- `strict` was removed. It is an OpenAI `response_format` flag, not a JSON Schema keyword.
|
|
27
|
+
- `to_json_schema` returns a Draft 2020-12 document with string keys, not the
|
|
28
|
+
`{name:, description:, schema:}` provider envelope. Build that envelope where you send it.
|
data/lib/ruby_llm/schema.rb
CHANGED
|
@@ -1,99 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require_relative "schema/errors"
|
|
5
|
-
require_relative "schema/helpers"
|
|
6
|
-
require_relative "schema/validator"
|
|
7
|
-
require_relative "schema/dsl"
|
|
8
|
-
require_relative "schema/json_output"
|
|
9
|
-
require "json"
|
|
3
|
+
require "schematist"
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
extend DSL
|
|
14
|
-
include JsonOutput
|
|
15
|
-
|
|
16
|
-
PRIMITIVE_TYPES = %i[string number integer boolean null].freeze
|
|
17
|
-
|
|
18
|
-
class << self
|
|
19
|
-
def create(&block)
|
|
20
|
-
schema_class = Class.new(Schema)
|
|
21
|
-
schema_class.class_eval(&block)
|
|
22
|
-
schema_class
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def properties
|
|
26
|
-
@properties ||= {}
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def required_properties
|
|
30
|
-
@required_properties ||= []
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def definitions
|
|
34
|
-
@definitions ||= {}
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def name(name = nil)
|
|
38
|
-
@schema_name = name if name
|
|
39
|
-
return @schema_name if defined?(@schema_name)
|
|
40
|
-
|
|
41
|
-
super()
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def description(description = nil)
|
|
45
|
-
@description = description if description
|
|
46
|
-
@description
|
|
47
|
-
end
|
|
5
|
+
warn <<~DEPRECATION
|
|
6
|
+
[DEPRECATION] ruby_llm-schema is now schematist, and this gem only forwards to it.
|
|
48
7
|
|
|
49
|
-
|
|
50
|
-
|
|
8
|
+
gem 'schematist' # was: gem 'ruby_llm-schema'
|
|
9
|
+
class Person < Schematist::Schema; end # was: RubyLLM::Schema
|
|
51
10
|
|
|
52
|
-
|
|
53
|
-
|
|
11
|
+
Two things the aliases below cannot forward: `strict` is gone, since it is an OpenAI
|
|
12
|
+
response_format flag rather than a JSON Schema keyword, and `to_json_schema` now returns
|
|
13
|
+
a Draft 2020-12 document instead of the provider envelope. See
|
|
14
|
+
https://github.com/crmne/schematist#migrating-from-ruby_llm-schema
|
|
15
|
+
DEPRECATION
|
|
54
16
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@strict = args.first
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def validate!
|
|
64
|
-
validator = Validator.new(self)
|
|
65
|
-
validator.validate!
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def valid?
|
|
69
|
-
validator = Validator.new(self)
|
|
70
|
-
validator.valid?
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def initialize(name = nil, description: nil)
|
|
75
|
-
@name = name || self.class.name || "Schema"
|
|
76
|
-
@description = description
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def validate!
|
|
80
|
-
self.class.validate!
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def valid?
|
|
84
|
-
self.class.valid?
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def method_missing(method_name, ...)
|
|
88
|
-
if respond_to_missing?(method_name)
|
|
89
|
-
self.class.send(method_name, ...)
|
|
90
|
-
else
|
|
91
|
-
super
|
|
92
|
-
end
|
|
93
|
-
end
|
|
17
|
+
module RubyLLM
|
|
18
|
+
Schema = Schematist::Schema
|
|
19
|
+
Helpers = Schematist::Helpers
|
|
20
|
+
end
|
|
94
21
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
22
|
+
# The error hierarchy moved from RubyLLM::Schema::X to Schematist::X. RubyLLM::Schema and
|
|
23
|
+
# Schematist::Schema are the same object, so these also become reachable as
|
|
24
|
+
# Schematist::Schema::X. Harmless — they are aliases to the very same classes — and it is
|
|
25
|
+
# the only way to keep `rescue RubyLLM::Schema::ValidationError` working.
|
|
26
|
+
%i[
|
|
27
|
+
Error InvalidSchemaTypeError InvalidArrayTypeError InvalidObjectTypeError
|
|
28
|
+
InvalidSchemaError ValidationError LimitExceededError
|
|
29
|
+
].each do |name|
|
|
30
|
+
RubyLLM::Schema.const_set(name, Schematist.const_get(name))
|
|
99
31
|
end
|
metadata
CHANGED
|
@@ -1,45 +1,52 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_llm-schema
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Friis
|
|
8
|
+
- Carmine Paolino
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
11
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: schematist
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.0'
|
|
27
|
+
description: Forwards RubyLLM::Schema to Schematist::Schema. Depend on schematist
|
|
28
|
+
instead.
|
|
14
29
|
email:
|
|
15
30
|
- d@friis.me
|
|
31
|
+
- carmine@paolino.me
|
|
16
32
|
executables: []
|
|
17
33
|
extensions: []
|
|
18
34
|
extra_rdoc_files: []
|
|
19
35
|
files:
|
|
20
|
-
- LICENSE
|
|
21
36
|
- README.md
|
|
22
37
|
- lib/ruby_llm/schema.rb
|
|
23
|
-
|
|
24
|
-
- lib/ruby_llm/schema/dsl/complex_types.rb
|
|
25
|
-
- lib/ruby_llm/schema/dsl/primitive_types.rb
|
|
26
|
-
- lib/ruby_llm/schema/dsl/schema_builders.rb
|
|
27
|
-
- lib/ruby_llm/schema/dsl/utilities.rb
|
|
28
|
-
- lib/ruby_llm/schema/errors.rb
|
|
29
|
-
- lib/ruby_llm/schema/helpers.rb
|
|
30
|
-
- lib/ruby_llm/schema/json_output.rb
|
|
31
|
-
- lib/ruby_llm/schema/validator.rb
|
|
32
|
-
- lib/ruby_llm/schema/version.rb
|
|
33
|
-
- lib/tasks/release.rake
|
|
34
|
-
homepage: https://github.com/crmne/ruby_llm-schema#readme
|
|
38
|
+
homepage: https://github.com/crmne/schematist#migrating-from-ruby_llm-schema
|
|
35
39
|
licenses:
|
|
36
40
|
- MIT
|
|
37
41
|
metadata:
|
|
38
|
-
homepage_uri: https://github.com/crmne/ruby_llm-schema
|
|
39
|
-
source_code_uri: https://github.com/crmne/
|
|
40
|
-
changelog_uri: https://github.com/crmne/
|
|
41
|
-
bug_tracker_uri: https://github.com/crmne/
|
|
42
|
+
homepage_uri: https://github.com/crmne/schematist#migrating-from-ruby_llm-schema
|
|
43
|
+
source_code_uri: https://github.com/crmne/schematist
|
|
44
|
+
changelog_uri: https://github.com/crmne/schematist/releases
|
|
45
|
+
bug_tracker_uri: https://github.com/crmne/schematist/issues
|
|
42
46
|
rubygems_mfa_required: 'true'
|
|
47
|
+
post_install_message: |
|
|
48
|
+
ruby_llm-schema is now schematist. This release only forwards to it.
|
|
49
|
+
Switch your Gemfile to `gem 'schematist'` and RubyLLM::Schema to Schematist::Schema.
|
|
43
50
|
rdoc_options: []
|
|
44
51
|
require_paths:
|
|
45
52
|
- lib
|
|
@@ -54,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
54
61
|
- !ruby/object:Gem::Version
|
|
55
62
|
version: '0'
|
|
56
63
|
requirements: []
|
|
57
|
-
rubygems_version: 4.0.
|
|
64
|
+
rubygems_version: 4.0.16
|
|
58
65
|
specification_version: 4
|
|
59
|
-
summary:
|
|
66
|
+
summary: Deprecated. ruby_llm-schema is now schematist.
|
|
60
67
|
test_files: []
|
data/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
The MIT License (MIT)
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Daniel Friis
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
|
13
|
-
all copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
THE SOFTWARE.
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
class Schema
|
|
5
|
-
module DSL
|
|
6
|
-
module ComplexTypes
|
|
7
|
-
def object(name, description: nil, required: true, **options, &block)
|
|
8
|
-
add_property(name, object_schema(description: description, **options, &block), required: required)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def array(name, description: nil, required: true, **options, &block)
|
|
12
|
-
add_property(name, array_schema(description: description, **options, &block), required: required)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def any_of(name, description: nil, required: true, **options, &block)
|
|
16
|
-
add_property(name, any_of_schema(description: description, **options, &block), required: required)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def one_of(name, description: nil, required: true, **options, &block)
|
|
20
|
-
add_property(name, one_of_schema(description: description, **options, &block), required: required)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def optional(name, description: nil, &block)
|
|
24
|
-
any_of(name, description: description) do
|
|
25
|
-
instance_eval(&block)
|
|
26
|
-
null
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
class Schema
|
|
5
|
-
module DSL
|
|
6
|
-
module PrimitiveTypes
|
|
7
|
-
def string(name, description: nil, required: true, **options)
|
|
8
|
-
add_property(name, string_schema(description: description, **options), required: required)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def number(name, description: nil, required: true, **options)
|
|
12
|
-
add_property(name, number_schema(description: description, **options), required: required)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def integer(name, description: nil, required: true, **options)
|
|
16
|
-
add_property(name, integer_schema(description: description, **options), required: required)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def boolean(name, description: nil, required: true, **options)
|
|
20
|
-
add_property(name, boolean_schema(description: description, **options), required: required)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def null(name, description: nil, required: true, **options)
|
|
24
|
-
add_property(name, null_schema(description: description, **options), required: required)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
class Schema
|
|
5
|
-
module DSL
|
|
6
|
-
module SchemaBuilders
|
|
7
|
-
def string_schema(description: nil, enum: nil, min_length: nil, max_length: nil, pattern: nil, format: nil)
|
|
8
|
-
{
|
|
9
|
-
type: "string",
|
|
10
|
-
enum: enum,
|
|
11
|
-
description: description,
|
|
12
|
-
minLength: min_length,
|
|
13
|
-
maxLength: max_length,
|
|
14
|
-
pattern: pattern,
|
|
15
|
-
format: format
|
|
16
|
-
}.compact
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def number_schema(description: nil, minimum: nil, maximum: nil, multiple_of: nil)
|
|
20
|
-
{
|
|
21
|
-
type: "number",
|
|
22
|
-
description: description,
|
|
23
|
-
minimum: minimum,
|
|
24
|
-
maximum: maximum,
|
|
25
|
-
multipleOf: multiple_of
|
|
26
|
-
}.compact
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def integer_schema(description: nil, minimum: nil, maximum: nil, multiple_of: nil)
|
|
30
|
-
{
|
|
31
|
-
type: "integer",
|
|
32
|
-
description: description,
|
|
33
|
-
minimum: minimum,
|
|
34
|
-
maximum: maximum,
|
|
35
|
-
multipleOf: multiple_of
|
|
36
|
-
}.compact
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def boolean_schema(description: nil)
|
|
40
|
-
{type: "boolean", description: description}.compact
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def null_schema(description: nil)
|
|
44
|
-
{type: "null", description: description}.compact
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def object_schema(description: nil, of: nil, reference: nil, &block)
|
|
48
|
-
if reference
|
|
49
|
-
warn "[DEPRECATION] The `reference` option will be deprecated. Please use `of` instead."
|
|
50
|
-
of = reference
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
if of
|
|
54
|
-
determine_object_reference(of, description)
|
|
55
|
-
else
|
|
56
|
-
sub_schema = Class.new(Schema)
|
|
57
|
-
result = sub_schema.class_eval(&block)
|
|
58
|
-
|
|
59
|
-
# If the block returned a reference and no properties were added, use the reference
|
|
60
|
-
if result.is_a?(Hash) && result["$ref"] && sub_schema.properties.empty?
|
|
61
|
-
result.merge(description ? {description: description} : {})
|
|
62
|
-
# If the block returned a Schema class or instance, convert it to inline schema
|
|
63
|
-
elsif schema_class?(result) && sub_schema.properties.empty?
|
|
64
|
-
schema_class_to_inline_schema(result).merge(description ? {description: description} : {})
|
|
65
|
-
# Block didn't return reference or schema, so we build an inline object schema
|
|
66
|
-
else
|
|
67
|
-
{
|
|
68
|
-
type: "object",
|
|
69
|
-
properties: sub_schema.properties,
|
|
70
|
-
required: sub_schema.required_properties,
|
|
71
|
-
additionalProperties: sub_schema.additional_properties,
|
|
72
|
-
description: description
|
|
73
|
-
}.compact
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def array_schema(description: nil, of: nil, min_items: nil, max_items: nil, &block)
|
|
79
|
-
items = determine_array_items(of, &block)
|
|
80
|
-
|
|
81
|
-
{
|
|
82
|
-
type: "array",
|
|
83
|
-
description: description,
|
|
84
|
-
items: items,
|
|
85
|
-
minItems: min_items,
|
|
86
|
-
maxItems: max_items
|
|
87
|
-
}.compact
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def any_of_schema(description: nil, &block)
|
|
91
|
-
schemas = collect_schemas_from_block(&block)
|
|
92
|
-
|
|
93
|
-
{
|
|
94
|
-
description: description,
|
|
95
|
-
anyOf: schemas
|
|
96
|
-
}.compact
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def one_of_schema(description: nil, &block)
|
|
100
|
-
schemas = collect_schemas_from_block(&block)
|
|
101
|
-
|
|
102
|
-
{
|
|
103
|
-
description: description,
|
|
104
|
-
oneOf: schemas
|
|
105
|
-
}.compact
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
private
|
|
109
|
-
|
|
110
|
-
def determine_array_items(of, &)
|
|
111
|
-
return collect_schemas_from_block(&).first if block_given?
|
|
112
|
-
return send("#{of}_schema") if primitive_type?(of)
|
|
113
|
-
return reference(of) if of.is_a?(Symbol)
|
|
114
|
-
return schema_class_to_inline_schema(of) if schema_class?(of)
|
|
115
|
-
|
|
116
|
-
raise InvalidArrayTypeError, "Invalid array type: #{of.inspect}. Must be a primitive type (:string, :number, etc.), a symbol reference, a Schema class, or a Schema instance."
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def determine_object_reference(of, description = nil)
|
|
120
|
-
result = case of
|
|
121
|
-
when Symbol
|
|
122
|
-
reference(of)
|
|
123
|
-
when Class
|
|
124
|
-
raise InvalidObjectTypeError, "Invalid object type: #{of.inspect}. Class must inherit from RubyLLM::Schema." unless schema_class?(of)
|
|
125
|
-
|
|
126
|
-
schema_class_to_inline_schema(of)
|
|
127
|
-
|
|
128
|
-
else
|
|
129
|
-
raise InvalidObjectTypeError, "Invalid object type: #{of.inspect}. Must be a symbol reference, a Schema class, or a Schema instance." unless schema_class?(of)
|
|
130
|
-
|
|
131
|
-
schema_class_to_inline_schema(of)
|
|
132
|
-
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
description ? result.merge(description: description) : result
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def collect_schemas_from_block(&block)
|
|
139
|
-
schemas = []
|
|
140
|
-
schema_builder = self
|
|
141
|
-
|
|
142
|
-
context = Object.new
|
|
143
|
-
|
|
144
|
-
# Dynamically create methods for all schema builders
|
|
145
|
-
schema_builder.methods.grep(/_schema$/).each do |schema_method|
|
|
146
|
-
type_name = schema_method.to_s.sub(/_schema$/, "")
|
|
147
|
-
|
|
148
|
-
context.define_singleton_method(type_name) do |_name = nil, **options, &blk|
|
|
149
|
-
schemas << schema_builder.send(schema_method, **options, &blk)
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
# Allow Schema classes to be accessed in the context
|
|
154
|
-
context.define_singleton_method(:const_missing) do |name|
|
|
155
|
-
const_get(name) if const_defined?(name)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
context.instance_eval(&block)
|
|
159
|
-
schemas
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def schema_class_to_inline_schema(schema_class_or_instance)
|
|
163
|
-
# Handle both Schema classes and Schema instances
|
|
164
|
-
schema_class = if schema_class_or_instance.is_a?(Class)
|
|
165
|
-
schema_class_or_instance
|
|
166
|
-
else
|
|
167
|
-
schema_class_or_instance.class
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
# Directly convert schema class to inline object schema
|
|
171
|
-
{
|
|
172
|
-
type: "object",
|
|
173
|
-
properties: schema_class.properties,
|
|
174
|
-
required: schema_class.required_properties,
|
|
175
|
-
additionalProperties: schema_class.additional_properties
|
|
176
|
-
}.tap do |schema|
|
|
177
|
-
# For instances, prefer instance description over class description
|
|
178
|
-
description = if schema_class_or_instance.is_a?(Class)
|
|
179
|
-
schema_class.description
|
|
180
|
-
else
|
|
181
|
-
schema_class_or_instance.instance_variable_get(:@description) || schema_class.description
|
|
182
|
-
end
|
|
183
|
-
schema[:description] = description if description
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
end
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
class Schema
|
|
5
|
-
module DSL
|
|
6
|
-
module Utilities
|
|
7
|
-
# Schema definition and reference methods
|
|
8
|
-
def define(name, &)
|
|
9
|
-
sub_schema = Class.new(Schema)
|
|
10
|
-
sub_schema.class_eval(&)
|
|
11
|
-
|
|
12
|
-
definitions[name] = {
|
|
13
|
-
type: "object",
|
|
14
|
-
properties: sub_schema.properties,
|
|
15
|
-
required: sub_schema.required_properties,
|
|
16
|
-
additionalProperties: sub_schema.additional_properties
|
|
17
|
-
}
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def reference(schema_name)
|
|
21
|
-
if schema_name == :root
|
|
22
|
-
{"$ref" => "#"}
|
|
23
|
-
else
|
|
24
|
-
{"$ref" => "#/$defs/#{schema_name}"}
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def add_property(name, definition, required:)
|
|
31
|
-
property_name = name.to_sym
|
|
32
|
-
|
|
33
|
-
properties[property_name] = definition
|
|
34
|
-
if required
|
|
35
|
-
required_properties << property_name unless required_properties.include?(property_name)
|
|
36
|
-
else
|
|
37
|
-
required_properties.delete(property_name)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
nil
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def primitive_type?(type)
|
|
44
|
-
type.is_a?(Symbol) && PRIMITIVE_TYPES.include?(type)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def schema_class?(type)
|
|
48
|
-
(type.is_a?(Class) && type < Schema) || type.is_a?(Schema)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
data/lib/ruby_llm/schema/dsl.rb
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "dsl/schema_builders"
|
|
4
|
-
require_relative "dsl/primitive_types"
|
|
5
|
-
require_relative "dsl/complex_types"
|
|
6
|
-
require_relative "dsl/utilities"
|
|
7
|
-
|
|
8
|
-
module RubyLLM
|
|
9
|
-
class Schema
|
|
10
|
-
module DSL
|
|
11
|
-
include SchemaBuilders
|
|
12
|
-
include PrimitiveTypes
|
|
13
|
-
include ComplexTypes
|
|
14
|
-
include Utilities
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
class Schema
|
|
5
|
-
# Base error class for all schema-related errors
|
|
6
|
-
class Error < StandardError; end
|
|
7
|
-
|
|
8
|
-
# Raised when an invalid schema type is specified
|
|
9
|
-
class InvalidSchemaTypeError < Error
|
|
10
|
-
def initialize(type)
|
|
11
|
-
super("Unknown schema type: #{type}")
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
# Raised when an invalid array type is specified
|
|
16
|
-
class InvalidArrayTypeError < Error; end
|
|
17
|
-
|
|
18
|
-
# Raised when an invalid object type is specified
|
|
19
|
-
class InvalidObjectTypeError < Error; end
|
|
20
|
-
|
|
21
|
-
# Raised when schema definition is invalid
|
|
22
|
-
class InvalidSchemaError < Error; end
|
|
23
|
-
|
|
24
|
-
# Raised when schema validation fails
|
|
25
|
-
class ValidationError < Error; end
|
|
26
|
-
|
|
27
|
-
# Raised when maximum limits are exceeded
|
|
28
|
-
class LimitExceededError < Error; end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
class Schema
|
|
5
|
-
module JsonOutput
|
|
6
|
-
def to_json_schema
|
|
7
|
-
validate! # Validate schema before generating JSON
|
|
8
|
-
|
|
9
|
-
schema_hash = {
|
|
10
|
-
type: "object",
|
|
11
|
-
properties: self.class.properties,
|
|
12
|
-
required: self.class.required_properties,
|
|
13
|
-
additionalProperties: self.class.additional_properties
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
schema_hash[:strict] = self.class.strict unless self.class.strict.nil?
|
|
17
|
-
|
|
18
|
-
# Only include $defs if there are definitions
|
|
19
|
-
schema_hash["$defs"] = self.class.definitions unless self.class.definitions.empty?
|
|
20
|
-
|
|
21
|
-
{
|
|
22
|
-
name: @name,
|
|
23
|
-
description: @description || self.class.description,
|
|
24
|
-
schema: schema_hash
|
|
25
|
-
}
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def to_json(*_args)
|
|
29
|
-
validate! # Validate schema before generating JSON string
|
|
30
|
-
JSON.pretty_generate(to_json_schema)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
class Schema
|
|
5
|
-
class Validator
|
|
6
|
-
# Node states for DFS-based topological sort
|
|
7
|
-
WHITE = :white # No mark (unvisited)
|
|
8
|
-
GRAY = :gray # Temporary mark (currently being processed)
|
|
9
|
-
BLACK = :black # Permanent mark (completely processed)
|
|
10
|
-
|
|
11
|
-
def initialize(schema_class)
|
|
12
|
-
@schema_class = schema_class
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def validate!
|
|
16
|
-
validate_circular_references!
|
|
17
|
-
# Future validations can be added here
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def valid?
|
|
21
|
-
validate!
|
|
22
|
-
true
|
|
23
|
-
rescue ValidationError
|
|
24
|
-
false
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
def validate_circular_references!
|
|
30
|
-
definitions = @schema_class.definitions
|
|
31
|
-
return if definitions.empty?
|
|
32
|
-
|
|
33
|
-
# Initialize all nodes as WHITE (no mark)
|
|
34
|
-
marks = Hash.new { WHITE }
|
|
35
|
-
|
|
36
|
-
# Visit each unmarked node
|
|
37
|
-
definitions.each_key do |node|
|
|
38
|
-
visit(node, definitions, marks) if marks[node] == WHITE
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# DFS visit function
|
|
43
|
-
def visit(node, definitions, marks)
|
|
44
|
-
# If node has a permanent mark, return
|
|
45
|
-
return if marks[node] == BLACK
|
|
46
|
-
|
|
47
|
-
# If node has a temporary mark, we found a cycle
|
|
48
|
-
raise ValidationError, "Circular reference detected involving '#{node}'" if marks[node] == GRAY
|
|
49
|
-
|
|
50
|
-
# Mark node with temporary mark
|
|
51
|
-
marks[node] = GRAY
|
|
52
|
-
|
|
53
|
-
# Visit all adjacent nodes (dependencies)
|
|
54
|
-
definition = definitions[node]
|
|
55
|
-
if definition && definition[:properties]
|
|
56
|
-
definition[:properties].each_value do |property|
|
|
57
|
-
references = extract_references(property)
|
|
58
|
-
references.each do |adjacent_node|
|
|
59
|
-
visit(adjacent_node, definitions, marks)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Mark node with permanent mark
|
|
65
|
-
marks[node] = BLACK
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def extract_references(property)
|
|
69
|
-
references = []
|
|
70
|
-
|
|
71
|
-
case property
|
|
72
|
-
when Hash
|
|
73
|
-
if property["$ref"]
|
|
74
|
-
# Extract definition name from reference like "#/$defs/user"
|
|
75
|
-
ref_name = property["$ref"].split("/").last&.to_sym
|
|
76
|
-
references << ref_name if ref_name
|
|
77
|
-
else
|
|
78
|
-
# Recursively check nested properties
|
|
79
|
-
property.each_value do |value|
|
|
80
|
-
references.concat(extract_references(value))
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
when Array
|
|
84
|
-
property.each do |item|
|
|
85
|
-
references.concat(extract_references(item))
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
references
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|