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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fceccc2292efb7c17ce5457a08d6a18ddc3debd920052a1d8e368d74e450547e
4
- data.tar.gz: b16fafc541af3b9f1a1e3a96f81be21175a380606f2e40325afa29d76108daea
3
+ metadata.gz: 6bfabbb94f9550f5bae72fc6ad6046ba1fa6f9e99723c4fea969822bd76f33f9
4
+ data.tar.gz: 5b97078d6f40a912a366c4d8f26333ead3504126e9a2abd3d8d658f81d168283
5
5
  SHA512:
6
- metadata.gz: 984b11c9fcfcaf68f0892a9a7b9c10064436aa59279f0a0992ebae5c6d20fbcfd1e6afbef8376bf7a9239aaaea246dd9f8c7f6a3d9521509789c8e334e3e0363
7
- data.tar.gz: e9658cff5e3e1f0912eab47fe8438a5dfd5bf52928dd0198ff39131ecf1aeb5d3b2a8e08344feab0b363ed6a9a27747c1489fcfadefd8f30af4d03c77fb601d8
6
+ metadata.gz: adc5528fd26d989854bbf0d6255bda4123fa6df18d96722c38b47a7414ff8c53006212f8c987932d190913553bad3764870038589ebb20a070ebc38e034de87c
7
+ data.tar.gz: 85d522c3b21f5949ce3ed5dba3aef643836d16b5daaca44c2d6a8beeec67e6cfae65ca126e4dbd49edf434f0134aeecb715216c6f3da793d0445a38baa2442fb
data/README.md CHANGED
@@ -1,468 +1,28 @@
1
- # RubyLLM::Schema
1
+ # ruby_llm-schema (deprecated)
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/ruby_llm-schema.svg)](https://rubygems.org/gems/ruby_llm-schema)
4
- [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/crmne/ruby_llm-schema/blob/main/LICENSE)
5
- [![CI](https://github.com/crmne/ruby_llm-schema/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/crmne/ruby_llm-schema/actions/workflows/main.yml)
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
- A Ruby DSL for creating JSON schemas with a clean, Rails-inspired API.
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
- Originally created by [Daniel Friis](https://github.com/danielfriis).
10
+ ## Building and releasing
10
11
 
11
- ## Use Cases
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
- gem install ruby_llm-schema
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
- ## Usage
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
- ```ruby
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
- ## License
24
+ ## What the alias cannot forward
467
25
 
468
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
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.
@@ -1,99 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "schema/version"
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
- module RubyLLM
12
- class Schema
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
- def additional_properties(value = nil)
50
- return @additional_properties ||= false if value.nil?
8
+ gem 'schematist' # was: gem 'ruby_llm-schema'
9
+ class Person < Schematist::Schema; end # was: RubyLLM::Schema
51
10
 
52
- @additional_properties = value
53
- end
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
- def strict(*args)
56
- if args.empty?
57
- instance_variable_defined?(:@strict) ? @strict : true
58
- else
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
- def respond_to_missing?(method_name, include_private = false)
96
- %i[string number integer boolean array object any_of one_of null].include?(method_name) || super
97
- end
98
- end
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.3.1
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
- description: A compact Ruby DSL for building standards-oriented JSON Schema documents
13
- from Ruby.
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
- - lib/ruby_llm/schema/dsl.rb
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#readme
39
- source_code_uri: https://github.com/crmne/ruby_llm-schema
40
- changelog_uri: https://github.com/crmne/ruby_llm-schema/releases
41
- bug_tracker_uri: https://github.com/crmne/ruby_llm-schema/issues
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.10
64
+ rubygems_version: 4.0.16
58
65
  specification_version: 4
59
- summary: A simple Ruby DSL for creating JSON schemas.
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
@@ -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,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Helpers
5
- def schema(name = nil, description: nil, &block)
6
- schema_class = Schema.create(&block)
7
- schema_class.new(name, description: description)
8
- end
9
- end
10
- 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
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- class Schema
5
- VERSION = '0.3.1'
6
- end
7
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- namespace :release do
4
- desc 'Prepare for release'
5
- task :prepare do
6
- sh 'overcommit --run'
7
- end
8
- end