ruby_llm-schema 0.3.0 → 0.4.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: 228903cdd6e6080b7c687147ecc609349601d315da71e861b1d900f7c252ac00
4
- data.tar.gz: ecdc3e346f33710ceff0d68c8eb13266956bddc1a36852f02128e3d3f2d94ff5
3
+ metadata.gz: 21c3e0ee2e5a1034eb571749cc2f27af5db9e4abaf07e2df677325f9270db6f1
4
+ data.tar.gz: 57db5c67a751f4e31f1ed2d56bba8bac76ddb989fee7b49add969b1c50be844e
5
5
  SHA512:
6
- metadata.gz: ca4ee64989087583e4a15e3b608b7e867034f0682a5f67753efeedee2f98bdc60ce933ff9a0823c93a633d43a9258e550c51b4d3fdbcddc0b3b2462ff40c5586
7
- data.tar.gz: 03affcdc52ba81f9c7c5d62fcd23e58f800dad7be7580df6c73d6c00b7d1941db0eacd867c83e075aee51090d52e418159472fe72b456533d4cbf1c1141c5ecc
6
+ metadata.gz: cd93b24110859806ee43a5a794353849e51273797696d84469b998c69d9e93e761ef817ede4d89377e34e227f8b6115d2e9133d58496bfcb2399f7720e4087e3
7
+ data.tar.gz: 1153950f2917a95e00744cb832a2c3ce7b1bd61965314f5652dc4337333d3b9f258d52b01257e62739b997bbd6ede840c9a7adfcd339477dc996bca093bf2a0d
data/README.md CHANGED
@@ -1,21 +1,25 @@
1
1
  # RubyLLM::Schema
2
2
 
3
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/danielfriis/ruby_llm-schema/blob/main/LICENSE.txt)
5
- [![CI](https://github.com/danielfriis/ruby_llm-schema/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/danielfriis/ruby_llm-schema/actions/workflows/ci.yml)
4
+ [![Gem Downloads](https://img.shields.io/gem/dt/ruby_llm-schema)](https://rubygems.org/gems/ruby_llm-schema)
5
+ [![codecov](https://codecov.io/gh/crmne/ruby_llm-schema/branch/main/graph/badge.svg)](https://codecov.io/gh/crmne/ruby_llm-schema)
6
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop)
6
7
 
7
- A Ruby DSL for creating JSON schemas with a clean, Rails-inspired API. Perfect for defining structured data schemas for LLM function calling or structured outputs.
8
+ A Ruby DSL for creating JSON schemas with a clean, Rails-inspired API.
9
+
10
+ Originally created by [Daniel Friis](https://github.com/danielfriis).
8
11
 
9
12
  ## Use Cases
10
13
 
11
- Structured output is a powerful tool for LLMs to generate consistent and predictable responses.
14
+ JSON Schema is useful wherever Ruby code needs to describe structured data in a portable format.
12
15
 
13
16
  Some ideal use cases:
14
17
 
15
- - Extracting *metadata, topics, and summary* from articles or blog posts
16
- - Organizing unstructured feedback or reviews with *sentiment and summary*
17
- - Defining structured *actions* from user messages or emails
18
- - Extracting *entities and relationships* from documents
18
+ - Defining API request and response shapes
19
+ - Describing configuration files or structured payloads
20
+ - Sharing validation contracts across systems
21
+ - Generating structured output schemas for LLM workflows
22
+ - Defining structured parameters for RubyLLM tools
19
23
 
20
24
  ### Simple Example
21
25
 
@@ -24,22 +28,22 @@ class PersonSchema < RubyLLM::Schema
24
28
  string :name, description: "Person's full name"
25
29
  number :age, description: "Age in years", minimum: 0, maximum: 120
26
30
  boolean :active, required: false
27
-
31
+
28
32
  object :address do
29
33
  string :street
30
34
  string :city
31
35
  string :country, required: false
32
36
  end
33
-
37
+
34
38
  array :tags, of: :string, description: "User tags"
35
-
39
+
36
40
  array :contacts do
37
41
  object do
38
42
  string :email, format: "email"
39
43
  string :phone, required: false
40
44
  end
41
45
  end
42
-
46
+
43
47
  any_of :status do
44
48
  string enum: ["active", "pending", "inactive"]
45
49
  null
@@ -51,7 +55,7 @@ schema = PersonSchema.new
51
55
  puts schema.to_json
52
56
  ```
53
57
 
54
- ### Most common use case with RubyLLM
58
+ ### RubyLLM structured output
55
59
 
56
60
  ```ruby
57
61
  class PersonSchema < RubyLLM::Schema
@@ -70,6 +74,43 @@ puts response.content # => {"name" => "Alice", "age" => 30}
70
74
  puts response.content.class # => Hash
71
75
  ```
72
76
 
77
+ ### RubyLLM tools
78
+
79
+ 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.
80
+
81
+ ```ruby
82
+ class SearchParams < RubyLLM::Schema
83
+ string :query, description: "Search query"
84
+ integer :limit, required: false, description: "Maximum results"
85
+ end
86
+
87
+ class SearchDocuments < RubyLLM::Tool
88
+ desc "Searches internal documents"
89
+ params SearchParams
90
+
91
+ def execute(query:, limit: 10)
92
+ DocumentSearch.call(query:, limit:)
93
+ end
94
+ end
95
+ ```
96
+
97
+ For tool-specific parameters, define the schema inline with `params do ... end`.
98
+
99
+ ```ruby
100
+ class Weather < RubyLLM::Tool
101
+ desc "Gets current weather"
102
+
103
+ params do
104
+ string :city, description: "City name"
105
+ string :units, enum: %w[celsius fahrenheit], required: false
106
+ end
107
+
108
+ def execute(city:, units: "celsius")
109
+ WeatherAPI.current(city:, units:)
110
+ end
111
+ end
112
+ ```
113
+
73
114
  ## Installation
74
115
 
75
116
  Add this line to your application's Gemfile:
@@ -101,12 +142,12 @@ class PersonSchema < RubyLLM::Schema
101
142
  string :name, description: "Person's full name"
102
143
  number :age
103
144
  boolean :active, required: false
104
-
145
+
105
146
  object :address do
106
147
  string :street
107
148
  string :city
108
149
  end
109
-
150
+
110
151
  array :tags, of: :string
111
152
  end
112
153
 
@@ -121,12 +162,12 @@ PersonSchema = RubyLLM::Schema.create do
121
162
  string :name, description: "Person's full name"
122
163
  number :age
123
164
  boolean :active, required: false
124
-
165
+
125
166
  object :address do
126
167
  string :street
127
168
  string :city
128
169
  end
129
-
170
+
130
171
  array :tags, of: :string
131
172
  end
132
173
 
@@ -144,12 +185,12 @@ person_schema = schema "PersonData", description: "A person object" do
144
185
  string :name, description: "Person's full name"
145
186
  number :age
146
187
  boolean :active, required: false
147
-
188
+
148
189
  object :address do
149
190
  string :street
150
191
  string :city
151
192
  end
152
-
193
+
153
194
  array :tags, of: :string
154
195
  end
155
196
 
@@ -269,7 +310,7 @@ Union types are a way to specify that a property can be one of several types.
269
310
  ```ruby
270
311
  any_of :value do
271
312
  string
272
- number
313
+ number
273
314
  null
274
315
  end
275
316
 
@@ -289,7 +330,7 @@ class MySchema < RubyLLM::Schema
289
330
  string :latitude
290
331
  string :longitude
291
332
  end
292
-
333
+
293
334
  # Using a reference in an array
294
335
  array :coordinates, of: :location
295
336
 
@@ -324,7 +365,7 @@ class CompanySchema < RubyLLM::Schema
324
365
  # Using 'of' parameter
325
366
  object :ceo, of: PersonSchema
326
367
  array :employees, of: PersonSchema
327
-
368
+
328
369
  # Using Schema.new in block
329
370
  object :founder do
330
371
  PersonSchema.new
@@ -403,6 +444,65 @@ schema.to_json_schema
403
444
  # }
404
445
  ```
405
446
 
447
+ ### Dependencies
448
+
449
+ Use `requires:` inline or `dependent` block to express that the presence of one property requires others. Maps to [`dependentRequired`](https://json-schema.org/understanding-json-schema/reference/conditionals#dependentRequired) (Draft 2019-09) and [`dependentSchemas`](https://json-schema.org/understanding-json-schema/reference/conditionals#dependentSchemas) (Draft 2019-09). Check your provider's documentation for compatibility.
450
+
451
+ ```ruby
452
+ class PaymentSchema < RubyLLM::Schema
453
+ string :name
454
+ number :credit_card, required: false, requires: %i[billing_address cvv]
455
+ string :billing_address, required: false
456
+ string :cvv, required: false
457
+ end
458
+ ```
459
+
460
+ Use a `dependent` block when you also need validations — this upgrades the output to `dependentSchemas`:
461
+
462
+ ```ruby
463
+ dependent :credit_card do
464
+ requires :billing_address
465
+ validates :billing_address, type: :string, min_length: 1
466
+ end
467
+ ```
468
+
469
+ ### Conditionals
470
+
471
+ Use `given` to add [JSON Schema `if`/`then`/`else`](https://json-schema.org/understanding-json-schema/reference/conditionals#ifthenelse) (Draft 7) rules. Condition values are automatically coerced: strings → `const`, arrays → `enum`, regexps → `pattern`, hashes → raw schema.
472
+
473
+ ```ruby
474
+ class OrderSchema < RubyLLM::Schema
475
+ string :status, enum: ["pending", "shipped", "cancelled"]
476
+ string :tracking_number, required: false
477
+ string :cancellation_reason, required: false
478
+
479
+ given status: "shipped" do
480
+ requires :tracking_number
481
+ end
482
+
483
+ given status: "cancelled" do
484
+ requires :cancellation_reason
485
+ validates :cancellation_reason, type: :string, min_length: 1
486
+ end
487
+ end
488
+ ```
489
+
490
+ `validates` supports: `type:`, `not_value:`, `min_length:`, `max_length:`, `pattern:` (string or regexp), `enum:`, `const:`, `minimum:`, `maximum:`.
491
+
492
+ Use `otherwise` for an `else` branch:
493
+
494
+ ```ruby
495
+ given domestic: true do
496
+ requires :state
497
+
498
+ otherwise do
499
+ requires :country
500
+ end
501
+ end
502
+ ```
503
+
504
+ Conditions propagate through nested schemas via `of:`.
505
+
406
506
  ## JSON Output
407
507
 
408
508
  ```ruby
@@ -4,20 +4,20 @@ module RubyLLM
4
4
  class Schema
5
5
  module DSL
6
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)
7
+ def object(name, description: nil, required: true, requires: nil, **options, &block)
8
+ add_property(name, object_schema(description: description, **options, &block), required: required, requires: requires)
9
9
  end
10
10
 
11
- def array(name, description: nil, required: true, **options, &block)
12
- add_property(name, array_schema(description: description, **options, &block), required: required)
11
+ def array(name, description: nil, required: true, requires: nil, **options, &block)
12
+ add_property(name, array_schema(description: description, **options, &block), required: required, requires: requires)
13
13
  end
14
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)
15
+ def any_of(name, description: nil, required: true, requires: nil, **options, &block)
16
+ add_property(name, any_of_schema(description: description, **options, &block), required: required, requires: requires)
17
17
  end
18
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)
19
+ def one_of(name, description: nil, required: true, requires: nil, **options, &block)
20
+ add_property(name, one_of_schema(description: description, **options, &block), required: required, requires: requires)
21
21
  end
22
22
 
23
23
  def optional(name, description: nil, &block)
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ class Schema
5
+ module DSL
6
+ module Conditionals
7
+ def conditions
8
+ @conditions ||= []
9
+ end
10
+
11
+ def dependencies
12
+ @dependencies ||= {}
13
+ end
14
+
15
+ def dependent(property, &block)
16
+ builder = ConditionalBuilder.new
17
+ builder.instance_eval(&block)
18
+
19
+ dependencies[property.to_s] = builder
20
+ end
21
+
22
+ def given(**properties, &block)
23
+ raise ArgumentError, "given requires at least one property condition" if properties.empty?
24
+
25
+ if_schema = {
26
+ properties: properties.transform_keys(&:to_s).transform_values { |v| coerce_condition(v) },
27
+ required: properties.keys.map(&:to_s)
28
+ }
29
+
30
+ then_builder = ConditionalBuilder.new
31
+ else_builder = ConditionalBuilder.new
32
+
33
+ context = ConditionalContext.new(then_builder, else_builder)
34
+ context.instance_eval(&block)
35
+
36
+ condition = {if: if_schema, then: then_builder.to_schema}
37
+ condition[:else] = else_builder.to_schema unless else_builder.empty?
38
+
39
+ conditions << condition
40
+ end
41
+
42
+ private
43
+
44
+ def merge_conditions(schema, schema_class)
45
+ if schema_class.respond_to?(:conditions) && schema_class.conditions.any?
46
+ if schema_class.conditions.length == 1
47
+ schema.merge!(schema_class.conditions.first)
48
+ else
49
+ schema[:allOf] = schema_class.conditions
50
+ end
51
+ end
52
+
53
+ if schema_class.respond_to?(:dependencies) && schema_class.dependencies.any?
54
+ dependent_required = {}
55
+ dependent_schemas = {}
56
+
57
+ schema_class.dependencies.each do |property, builder|
58
+ if builder.validations_empty?
59
+ dependent_required[property] = builder.required_fields
60
+ else
61
+ dependent_schemas[property] = builder.to_schema
62
+ end
63
+ end
64
+
65
+ schema[:dependentRequired] = dependent_required if dependent_required.any?
66
+ schema[:dependentSchemas] = dependent_schemas if dependent_schemas.any?
67
+ end
68
+
69
+ schema
70
+ end
71
+
72
+ def coerce_condition(value)
73
+ case value
74
+ when Array then {enum: value}
75
+ when Regexp then {pattern: value.source}
76
+ when Hash then value
77
+ else {const: value}
78
+ end
79
+ end
80
+ end
81
+
82
+ class ConditionalContext
83
+ def initialize(then_builder, else_builder)
84
+ @then_builder = then_builder
85
+ @else_builder = else_builder
86
+ end
87
+
88
+ def requires(*fields)
89
+ @then_builder.requires(*fields)
90
+ end
91
+
92
+ def validates(field, **options)
93
+ @then_builder.validates(field, **options)
94
+ end
95
+
96
+ def otherwise(&block)
97
+ @else_builder.instance_eval(&block)
98
+ end
99
+ end
100
+
101
+ class ConditionalBuilder
102
+ def requires(*fields)
103
+ required.concat(fields.map(&:to_s))
104
+ end
105
+
106
+ VALIDATES_KEY_MAP = {
107
+ type: :type,
108
+ const: :const,
109
+ enum: :enum,
110
+ not_value: :not,
111
+ min_length: :minLength,
112
+ max_length: :maxLength,
113
+ pattern: :pattern,
114
+ minimum: :minimum,
115
+ maximum: :maximum
116
+ }.freeze
117
+
118
+ def validates(field, **options)
119
+ constraints = {}
120
+
121
+ options.each do |key, value|
122
+ schema_key = VALIDATES_KEY_MAP[key]
123
+ raise ArgumentError, "unknown validates option: #{key.inspect}" unless schema_key
124
+
125
+ case key
126
+ when :type then constraints[:type] = value.to_s
127
+ when :not_value then constraints[:not] = {const: value}
128
+ when :pattern then constraints[:pattern] = value.is_a?(Regexp) ? value.source : value
129
+ else constraints[schema_key] = value
130
+ end
131
+ end
132
+
133
+ validations[field.to_s] = constraints
134
+ end
135
+
136
+ def to_schema
137
+ schema = {}
138
+
139
+ schema[:required] = required if required.any?
140
+ schema[:properties] = validations if validations.any?
141
+
142
+ schema
143
+ end
144
+
145
+ def empty?
146
+ required.empty? && validations.empty?
147
+ end
148
+
149
+ def required_fields
150
+ required.dup
151
+ end
152
+
153
+ def validations_empty?
154
+ validations.empty?
155
+ end
156
+
157
+ private
158
+
159
+ def required
160
+ @required ||= []
161
+ end
162
+
163
+ def validations
164
+ @validations ||= {}
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -4,24 +4,24 @@ module RubyLLM
4
4
  class Schema
5
5
  module DSL
6
6
  module PrimitiveTypes
7
- def string(name, description: nil, required: true, **options)
8
- add_property(name, string_schema(description: description, **options), required: required)
7
+ def string(name, description: nil, required: true, requires: nil, **options)
8
+ add_property(name, string_schema(description: description, **options), required: required, requires: requires)
9
9
  end
10
10
 
11
- def number(name, description: nil, required: true, **options)
12
- add_property(name, number_schema(description: description, **options), required: required)
11
+ def number(name, description: nil, required: true, requires: nil, **options)
12
+ add_property(name, number_schema(description: description, **options), required: required, requires: requires)
13
13
  end
14
14
 
15
- def integer(name, description: nil, required: true, **options)
16
- add_property(name, integer_schema(description: description, **options), required: required)
15
+ def integer(name, description: nil, required: true, requires: nil, **options)
16
+ add_property(name, integer_schema(description: description, **options), required: required, requires: requires)
17
17
  end
18
18
 
19
- def boolean(name, description: nil, required: true, **options)
20
- add_property(name, boolean_schema(description: description, **options), required: required)
19
+ def boolean(name, description: nil, required: true, requires: nil, **options)
20
+ add_property(name, boolean_schema(description: description, **options), required: required, requires: requires)
21
21
  end
22
22
 
23
- def null(name, description: nil, required: true, **options)
24
- add_property(name, null_schema(description: description, **options), required: required)
23
+ def null(name, description: nil, required: true, requires: nil, **options)
24
+ add_property(name, null_schema(description: description, **options), required: required, requires: requires)
25
25
  end
26
26
  end
27
27
  end
@@ -64,13 +64,15 @@ module RubyLLM
64
64
  schema_class_to_inline_schema(result).merge(description ? {description: description} : {})
65
65
  # Block didn't return reference or schema, so we build an inline object schema
66
66
  else
67
- {
67
+ schema = {
68
68
  type: "object",
69
69
  properties: sub_schema.properties,
70
70
  required: sub_schema.required_properties,
71
71
  additionalProperties: sub_schema.additional_properties,
72
72
  description: description
73
73
  }.compact
74
+
75
+ merge_conditions(schema, sub_schema)
74
76
  end
75
77
  end
76
78
  end
@@ -118,21 +120,19 @@ module RubyLLM
118
120
 
119
121
  def determine_object_reference(of, description = nil)
120
122
  result = case of
121
- when Symbol
122
- reference(of)
123
- when Class
124
- if schema_class?(of)
125
- schema_class_to_inline_schema(of)
126
- else
127
- raise InvalidObjectTypeError, "Invalid object type: #{of.inspect}. Class must inherit from RubyLLM::Schema."
128
- end
129
- else
130
- if schema_class?(of)
131
- schema_class_to_inline_schema(of)
132
- else
133
- raise InvalidObjectTypeError, "Invalid object type: #{of.inspect}. Must be a symbol reference, a Schema class, or a Schema instance."
134
- end
135
- end
123
+ when Symbol
124
+ reference(of)
125
+ when Class
126
+ raise InvalidObjectTypeError, "Invalid object type: #{of.inspect}. Class must inherit from RubyLLM::Schema." unless schema_class?(of)
127
+
128
+ schema_class_to_inline_schema(of)
129
+
130
+ else
131
+ raise InvalidObjectTypeError, "Invalid object type: #{of.inspect}. Must be a symbol reference, a Schema class, or a Schema instance." unless schema_class?(of)
132
+
133
+ schema_class_to_inline_schema(of)
134
+
135
+ end
136
136
 
137
137
  description ? result.merge(description: description) : result
138
138
  end
@@ -147,7 +147,7 @@ module RubyLLM
147
147
  schema_builder.methods.grep(/_schema$/).each do |schema_method|
148
148
  type_name = schema_method.to_s.sub(/_schema$/, "")
149
149
 
150
- context.define_singleton_method(type_name) do |name = nil, **options, &blk|
150
+ context.define_singleton_method(type_name) do |_name = nil, **options, &blk|
151
151
  schemas << schema_builder.send(schema_method, **options, &blk)
152
152
  end
153
153
  end
@@ -164,10 +164,10 @@ module RubyLLM
164
164
  def schema_class_to_inline_schema(schema_class_or_instance)
165
165
  # Handle both Schema classes and Schema instances
166
166
  schema_class = if schema_class_or_instance.is_a?(Class)
167
- schema_class_or_instance
168
- else
169
- schema_class_or_instance.class
170
- end
167
+ schema_class_or_instance
168
+ else
169
+ schema_class_or_instance.class
170
+ end
171
171
 
172
172
  # Directly convert schema class to inline object schema
173
173
  {
@@ -178,11 +178,14 @@ module RubyLLM
178
178
  }.tap do |schema|
179
179
  # For instances, prefer instance description over class description
180
180
  description = if schema_class_or_instance.is_a?(Class)
181
- schema_class.description
182
- else
183
- schema_class_or_instance.instance_variable_get(:@description) || schema_class.description
184
- end
181
+ schema_class.description
182
+ else
183
+ schema_class_or_instance.instance_variable_get(:@description) || schema_class.description
184
+ end
185
+
185
186
  schema[:description] = description if description
187
+
188
+ merge_conditions(schema, schema_class)
186
189
  end
187
190
  end
188
191
  end
@@ -9,12 +9,16 @@ module RubyLLM
9
9
  sub_schema = Class.new(Schema)
10
10
  sub_schema.class_eval(&)
11
11
 
12
- definitions[name] = {
12
+ schema = {
13
13
  type: "object",
14
14
  properties: sub_schema.properties,
15
15
  required: sub_schema.required_properties,
16
16
  additionalProperties: sub_schema.additional_properties
17
17
  }
18
+
19
+ merge_conditions(schema, sub_schema)
20
+
21
+ definitions[name] = schema
18
22
  end
19
23
 
20
24
  def reference(schema_name)
@@ -27,9 +31,23 @@ module RubyLLM
27
31
 
28
32
  private
29
33
 
30
- def add_property(name, definition, required:)
31
- properties[name.to_sym] = definition
32
- required_properties << name.to_sym if required
34
+ def add_property(name, definition, required:, requires: nil)
35
+ property_name = name.to_sym
36
+
37
+ properties[property_name] = definition
38
+ if required
39
+ required_properties << property_name unless required_properties.include?(property_name)
40
+ else
41
+ required_properties.delete(property_name)
42
+ end
43
+
44
+ if requires
45
+ builder = ConditionalBuilder.new
46
+ builder.requires(*Array(requires))
47
+ dependencies[name.to_s] = builder
48
+ end
49
+
50
+ nil
33
51
  end
34
52
 
35
53
  def primitive_type?(type)
@@ -3,6 +3,7 @@
3
3
  require_relative "dsl/schema_builders"
4
4
  require_relative "dsl/primitive_types"
5
5
  require_relative "dsl/complex_types"
6
+ require_relative "dsl/conditionals"
6
7
  require_relative "dsl/utilities"
7
8
 
8
9
  module RubyLLM
@@ -11,6 +12,7 @@ module RubyLLM
11
12
  include SchemaBuilders
12
13
  include PrimitiveTypes
13
14
  include ComplexTypes
15
+ include Conditionals
14
16
  include Utilities
15
17
  end
16
18
  end
@@ -13,18 +13,10 @@ module RubyLLM
13
13
  end
14
14
 
15
15
  # Raised when an invalid array type is specified
16
- class InvalidArrayTypeError < Error
17
- def initialize(message)
18
- super
19
- end
20
- end
16
+ class InvalidArrayTypeError < Error; end
21
17
 
22
18
  # Raised when an invalid object type is specified
23
- class InvalidObjectTypeError < Error
24
- def initialize(message)
25
- super
26
- end
27
- end
19
+ class InvalidObjectTypeError < Error; end
28
20
 
29
21
  # Raised when schema definition is invalid
30
22
  class InvalidSchemaError < Error; end
@@ -4,7 +4,7 @@ module RubyLLM
4
4
  class Schema
5
5
  module JsonOutput
6
6
  def to_json_schema
7
- validate! # Validate schema before generating JSON
7
+ validate! # Validate schema before generating JSON
8
8
 
9
9
  schema_hash = {
10
10
  type: "object",
@@ -18,6 +18,8 @@ module RubyLLM
18
18
  # Only include $defs if there are definitions
19
19
  schema_hash["$defs"] = self.class.definitions unless self.class.definitions.empty?
20
20
 
21
+ self.class.send(:merge_conditions, schema_hash, self.class)
22
+
21
23
  {
22
24
  name: @name,
23
25
  description: @description || self.class.description,
@@ -26,7 +28,7 @@ module RubyLLM
26
28
  end
27
29
 
28
30
  def to_json(*_args)
29
- validate! # Validate schema before generating JSON string
31
+ validate! # Validate schema before generating JSON string
30
32
  JSON.pretty_generate(to_json_schema)
31
33
  end
32
34
  end
@@ -45,9 +45,7 @@ module RubyLLM
45
45
  return if marks[node] == BLACK
46
46
 
47
47
  # If node has a temporary mark, we found a cycle
48
- if marks[node] == GRAY
49
- raise ValidationError, "Circular reference detected involving '#{node}'"
50
- end
48
+ raise ValidationError, "Circular reference detected involving '#{node}'" if marks[node] == GRAY
51
49
 
52
50
  # Mark node with temporary mark
53
51
  marks[node] = GRAY
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RubyLlm
3
+ module RubyLLM
4
4
  class Schema
5
- VERSION = "0.3.0"
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  end
@@ -48,6 +48,7 @@ module RubyLLM
48
48
 
49
49
  def additional_properties(value = nil)
50
50
  return @additional_properties ||= false if value.nil?
51
+
51
52
  @additional_properties = value
52
53
  end
53
54
 
@@ -1,53 +1,8 @@
1
- # 1. Bump the version
2
- # Update VERSION in lib/ruby_llm/schema/version.rb
3
-
4
- # 2. Update Gemfile.lock
5
- # bundle install
6
-
7
- # 3. Commit everything
8
- # git add .
9
- # git commit -m "Bump version to X.Y.Z"
10
-
11
- # 4. Push to main
12
- # git push origin main
13
-
14
- # 5. Trigger the release
15
- # bundle exec rake release:prepare
16
-
17
- # 6. Delete the release branch locally and remotely
18
- # git branch -d release/X.Y.Z
19
- # git push origin --delete release/X.Y.Z
1
+ # frozen_string_literal: true
20
2
 
21
3
  namespace :release do
22
- desc "Prepare and push the release branch to trigger the automated pipeline"
4
+ desc 'Prepare for release'
23
5
  task :prepare do
24
- abort "Git working directory not clean. Commit or stash changes first." unless `git status --porcelain`.strip.empty?
25
- abort "Not on main branch. Releases must be run from main branch" unless `git rev-parse --abbrev-ref HEAD`.strip == "main"
26
-
27
- require_relative "../ruby_llm/schema/version"
28
- version = RubyLlm::Schema::VERSION or abort "Could not determine version"
29
-
30
- branch = "release/#{version}"
31
-
32
- if system("git rev-parse --quiet --verify refs/tags/v#{version} > /dev/null 2>&1")
33
- abort "Tag v#{version} already exists. Bump the version first."
34
- end
35
-
36
- if system("git show-ref --verify --quiet refs/heads/#{branch}")
37
- abort "Local branch #{branch} already exists. Remove it or choose a new version."
38
- end
39
-
40
- sh "git fetch origin"
41
-
42
- if system("git ls-remote --exit-code --heads origin #{branch} > /dev/null 2>&1")
43
- abort "Release branch #{branch} already exists on origin. Remove it or choose a new version."
44
- end
45
-
46
- sh "git checkout -b #{branch}"
47
- sh "git push -u origin #{branch}"
48
-
49
- puts "Release branch #{branch} pushed. GitHub Actions will run tests and publish if they pass."
50
- ensure
51
- system "git checkout main"
6
+ sh 'overcommit --run'
52
7
  end
53
8
  end
metadata CHANGED
@@ -1,58 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm-schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Friis
8
- autorequire:
9
- bindir: exe
8
+ bindir: bin
10
9
  cert_chain: []
11
- date: 2026-01-08 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: rspec
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '3.0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '3.0'
27
- - !ruby/object:Gem::Dependency
28
- name: standard
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- description:
10
+ 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.
42
14
  email:
43
15
  - d@friis.me
44
16
  executables: []
45
17
  extensions: []
46
18
  extra_rdoc_files: []
47
19
  files:
48
- - ".rspec"
49
- - LICENSE.txt
20
+ - LICENSE
50
21
  - README.md
51
- - RELEASE.md
52
- - Rakefile
53
22
  - lib/ruby_llm/schema.rb
54
23
  - lib/ruby_llm/schema/dsl.rb
55
24
  - lib/ruby_llm/schema/dsl/complex_types.rb
25
+ - lib/ruby_llm/schema/dsl/conditionals.rb
56
26
  - lib/ruby_llm/schema/dsl/primitive_types.rb
57
27
  - lib/ruby_llm/schema/dsl/schema_builders.rb
58
28
  - lib/ruby_llm/schema/dsl/utilities.rb
@@ -62,15 +32,15 @@ files:
62
32
  - lib/ruby_llm/schema/validator.rb
63
33
  - lib/ruby_llm/schema/version.rb
64
34
  - lib/tasks/release.rake
65
- homepage: https://github.com/danielfriis/ruby_llm-schema
35
+ homepage: https://github.com/crmne/ruby_llm-schema#readme
66
36
  licenses:
67
37
  - MIT
68
38
  metadata:
69
- homepage_uri: https://github.com/danielfriis/ruby_llm-schema
70
- source_code_uri: https://github.com/danielfriis/ruby_llm-schema
71
- changelog_uri: https://github.com/danielfriis/ruby_llm-schema/blob/main/CHANGELOG.md
39
+ homepage_uri: https://github.com/crmne/ruby_llm-schema#readme
40
+ source_code_uri: https://github.com/crmne/ruby_llm-schema
41
+ changelog_uri: https://github.com/crmne/ruby_llm-schema/releases
42
+ bug_tracker_uri: https://github.com/crmne/ruby_llm-schema/issues
72
43
  rubygems_mfa_required: 'true'
73
- post_install_message:
74
44
  rdoc_options: []
75
45
  require_paths:
76
46
  - lib
@@ -78,15 +48,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
48
  requirements:
79
49
  - - ">="
80
50
  - !ruby/object:Gem::Version
81
- version: 3.1.0
51
+ version: 3.1.3
82
52
  required_rubygems_version: !ruby/object:Gem::Requirement
83
53
  requirements:
84
54
  - - ">="
85
55
  - !ruby/object:Gem::Version
86
56
  version: '0'
87
57
  requirements: []
88
- rubygems_version: 3.4.19
89
- signing_key:
58
+ rubygems_version: 4.0.10
90
59
  specification_version: 4
91
- summary: A simple and clean Ruby DSL for creating JSON schemas.
60
+ summary: A simple Ruby DSL for creating JSON schemas.
92
61
  test_files: []
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --require spec_helper
2
- --color
3
- --format documentation
data/RELEASE.md DELETED
@@ -1,8 +0,0 @@
1
- # Release process
2
-
3
- 1. Bump the version in `lib/ruby_llm/schema/version.rb`
4
- 2. Run `bundle install` to update the gemspec
5
- 3. Commit the changes with a message like "Bump version to X.Y.Z"
6
- 4. Run `bundle exec rake release:prepare` to create a release branch and push it to GitHub
7
- 5. Github Actions will run the tests and publish the gem if they pass
8
- 6. Delete the release branch: `git branch -d release/<version> && git push origin --delete release/<version>`
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
- require "standard/rake"
6
-
7
- # Load custom tasks
8
- Dir.glob("lib/tasks/**/*.rake").each { |r| load r }
9
-
10
- RSpec::Core::RakeTask.new(:spec)
11
-
12
- task default: %i[standard spec]
File without changes