ruby_llm-schema 0.1.6 → 0.1.8
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 +96 -16
- data/Rakefile +7 -0
- data/lib/ruby_llm/schema/dsl.rb +193 -0
- data/lib/ruby_llm/schema/helpers.rb +1 -1
- data/lib/ruby_llm/schema/json_output.rb +29 -0
- data/lib/ruby_llm/schema/validator.rb +6 -6
- data/lib/ruby_llm/schema/version.rb +1 -1
- data/lib/ruby_llm/schema.rb +18 -193
- data/lib/tasks/release.rake +12 -13
- metadata +4 -3
- data/lib/ruby_llm/schema/property_schema_collector.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a5add3bd7f57004c32b88a3fb676370622b0d94931b3e8ad46b2aa021da96f7
|
4
|
+
data.tar.gz: 1d371d0595ba66e8e0a31b42f524be07569cf837eb2a044a560cad8f412ea17d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e558d54dbe37fde9df32d7c0aa3116d95687e8f1b76e9d6260f10025242671ce67bdb5f44f6020a757dc38ec693e764c11e20388420c6fd65f7ccc7efa391394
|
7
|
+
data.tar.gz: d6ba597b33675f573bb3b9d728f128a2bfc431a47e6c87ef2ed37d8c5866a8475eae5f20eb1b670edf31dfd3bdf7d7e79a6329ef68550538930eb4a8aa13030e
|
data/README.md
CHANGED
@@ -1,7 +1,22 @@
|
|
1
1
|
# RubyLLM::Schema
|
2
2
|
|
3
|
+
[](https://rubygems.org/gems/ruby_llm-schema)
|
4
|
+
[](https://github.com/danielfriis/ruby_llm-schema/blob/main/LICENSE.txt)
|
5
|
+
[](https://github.com/danielfriis/ruby_llm-schema/actions/workflows/ci.yml)
|
6
|
+
|
3
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.
|
4
8
|
|
9
|
+
## Use Cases
|
10
|
+
|
11
|
+
Structured output is a powerful tool for LLMs to generate consistent and predictable responses.
|
12
|
+
|
13
|
+
Some ideal use cases:
|
14
|
+
|
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
|
19
|
+
|
5
20
|
### Simple Example
|
6
21
|
|
7
22
|
```ruby
|
@@ -122,29 +137,83 @@ end
|
|
122
137
|
puts person_schema.to_json
|
123
138
|
```
|
124
139
|
|
125
|
-
##
|
140
|
+
## Schema Property Types
|
141
|
+
|
142
|
+
A schema is a collection of properties, which can be of different types. Each type has its own set of properties you can set.
|
143
|
+
|
144
|
+
All property types can (along with the required `name` key) be set with a `description` and a `required` flag (default is `true`).
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
string :name, description: "Person's full name"
|
148
|
+
number :age, description: "Person's age", required: false
|
149
|
+
boolean :is_active, description: "Whether the person is active"
|
150
|
+
null :placeholder, description: "A placeholder property"
|
151
|
+
```
|
152
|
+
|
153
|
+
⚠️ 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.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
any_of :name, description: "Person's full name" do
|
157
|
+
string
|
158
|
+
null
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
### Strings
|
163
|
+
|
164
|
+
String types support the following properties:
|
165
|
+
|
166
|
+
- `enum`: an array of allowed values (e.g. `enum: ["on", "off"]`)
|
167
|
+
- `pattern`: a regex pattern (e.g. `pattern: "\\d+"`)
|
168
|
+
- `format`: a format string (e.g. `format: "email"`)
|
169
|
+
- `min_length`: the minimum length of the string (e.g. `min_length: 3`)
|
170
|
+
- `max_length`: the maximum length of the string (e.g. `max_length: 10`)
|
171
|
+
|
172
|
+
Please consult the LLM provider documentation for the available formats and patterns.
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
string :name, description: "Person's full name"
|
176
|
+
string :email, format: "email"
|
177
|
+
string :phone, pattern: "\\d+"
|
178
|
+
string :status, enum: ["on", "off"]
|
179
|
+
string :code, min_length: 3, max_length: 10
|
180
|
+
```
|
181
|
+
|
182
|
+
### Numbers
|
183
|
+
|
184
|
+
Number types support the following properties:
|
126
185
|
|
127
|
-
|
186
|
+
- `multiple_of`: a multiple of the number (e.g. `multiple_of: 0.01`)
|
187
|
+
- `minimum`: the minimum value of the number (e.g. `minimum: 0`)
|
188
|
+
- `maximum`: the maximum value of the number (e.g. `maximum: 100`)
|
128
189
|
|
129
190
|
```ruby
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
string :email, format: "email" # String with format validation
|
134
|
-
string :code, min_length: 3, max_length: 10 # String with length constraints
|
135
|
-
string :pattern_field, pattern: "\\d+" # String with regex pattern
|
191
|
+
number :price, minimum: 0, maximum: 100
|
192
|
+
number :amount, multiple_of: 0.01
|
193
|
+
```
|
136
194
|
|
137
|
-
|
138
|
-
number :price, minimum: 0, maximum: 100 # Number with range constraints
|
139
|
-
number :amount, multiple_of: 0.01 # Number with precision constraints
|
195
|
+
### Booleans
|
140
196
|
|
141
|
-
|
142
|
-
boolean :
|
143
|
-
null :placeholder # Null type
|
197
|
+
```ruby
|
198
|
+
boolean :is_active
|
144
199
|
```
|
145
200
|
|
201
|
+
Boolean types doesn't support any additional properties.
|
202
|
+
|
203
|
+
### Null
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
null :placeholder
|
207
|
+
```
|
208
|
+
|
209
|
+
Null types doesn't support any additional properties.
|
210
|
+
|
146
211
|
### Arrays
|
147
212
|
|
213
|
+
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.
|
214
|
+
|
215
|
+
An array can have a `min_items` and `max_items` option to set the minimum and maximum number of items in the array.
|
216
|
+
|
148
217
|
```ruby
|
149
218
|
array :tags, of: :string # Array of strings
|
150
219
|
array :scores, of: :number # Array of numbers
|
@@ -160,6 +229,8 @@ end
|
|
160
229
|
|
161
230
|
### Objects
|
162
231
|
|
232
|
+
Objects types expect a block with the properties of the object.
|
233
|
+
|
163
234
|
```ruby
|
164
235
|
object :user do
|
165
236
|
string :name
|
@@ -174,6 +245,8 @@ end
|
|
174
245
|
|
175
246
|
### Union Types (anyOf)
|
176
247
|
|
248
|
+
Union types are a way to specify that a property can be one of several types.
|
249
|
+
|
177
250
|
```ruby
|
178
251
|
any_of :value do
|
179
252
|
string
|
@@ -189,6 +262,8 @@ end
|
|
189
262
|
|
190
263
|
### Schema Definitions and References
|
191
264
|
|
265
|
+
You can define sub-schemas and reference them in other schemas.
|
266
|
+
|
192
267
|
```ruby
|
193
268
|
class MySchema < RubyLLM::Schema
|
194
269
|
define :location do
|
@@ -196,9 +271,14 @@ class MySchema < RubyLLM::Schema
|
|
196
271
|
string :longitude
|
197
272
|
end
|
198
273
|
|
274
|
+
# Using a reference in an array
|
199
275
|
array :coordinates, of: :location
|
200
|
-
|
201
|
-
object
|
276
|
+
|
277
|
+
# Using a reference in an object via the `reference` option
|
278
|
+
object :home_location, reference: :location
|
279
|
+
|
280
|
+
# Using a reference in an object via block
|
281
|
+
object :user do
|
202
282
|
reference :location
|
203
283
|
end
|
204
284
|
end
|
data/Rakefile
CHANGED
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
class Schema
|
5
|
+
module DSL
|
6
|
+
# Primitive type methods
|
7
|
+
def string(name = nil, enum: nil, description: nil, required: true, min_length: nil, max_length: nil, pattern: nil, format: nil)
|
8
|
+
options = {
|
9
|
+
enum: enum,
|
10
|
+
description: description,
|
11
|
+
minLength: min_length,
|
12
|
+
maxLength: max_length,
|
13
|
+
pattern: pattern,
|
14
|
+
format: format
|
15
|
+
}.compact
|
16
|
+
|
17
|
+
add_property(name, build_property_schema(:string, **options), required: required)
|
18
|
+
end
|
19
|
+
|
20
|
+
def number(name = nil, description: nil, required: true, minimum: nil, maximum: nil, multiple_of: nil)
|
21
|
+
options = {
|
22
|
+
description: description,
|
23
|
+
minimum: minimum,
|
24
|
+
maximum: maximum,
|
25
|
+
multipleOf: multiple_of
|
26
|
+
}.compact
|
27
|
+
|
28
|
+
add_property(name, build_property_schema(:number, **options), required: required)
|
29
|
+
end
|
30
|
+
|
31
|
+
def integer(name = nil, description: nil, required: true)
|
32
|
+
add_property(name, build_property_schema(:integer, description: description), required: required)
|
33
|
+
end
|
34
|
+
|
35
|
+
def boolean(name = nil, description: nil, required: true)
|
36
|
+
add_property(name, build_property_schema(:boolean, description: description), required: required)
|
37
|
+
end
|
38
|
+
|
39
|
+
def null(name = nil, description: nil, required: true)
|
40
|
+
add_property(name, build_property_schema(:null, description: description), required: required)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Complex type methods
|
44
|
+
def object(name = nil, reference: nil, description: nil, required: true, &block)
|
45
|
+
add_property(name, build_property_schema(:object, description: description, reference: reference, &block), required: required)
|
46
|
+
end
|
47
|
+
|
48
|
+
def array(name, of: nil, description: nil, required: true, min_items: nil, max_items: nil, &block)
|
49
|
+
items = determine_array_items(of, &block)
|
50
|
+
|
51
|
+
add_property(name, {
|
52
|
+
type: "array",
|
53
|
+
description: description,
|
54
|
+
items: items,
|
55
|
+
minItems: min_items,
|
56
|
+
maxItems: max_items
|
57
|
+
}.compact, required: required)
|
58
|
+
end
|
59
|
+
|
60
|
+
def any_of(name = nil, required: true, description: nil, &block)
|
61
|
+
schemas = collect_property_schemas_from_block(&block)
|
62
|
+
|
63
|
+
add_property(name, {
|
64
|
+
description: description,
|
65
|
+
anyOf: schemas
|
66
|
+
}.compact, required: required)
|
67
|
+
end
|
68
|
+
|
69
|
+
def optional(name, description: nil, &block)
|
70
|
+
any_of(name, description: description) do
|
71
|
+
instance_eval(&block)
|
72
|
+
null
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Schema definition and reference methods
|
77
|
+
def define(name, &)
|
78
|
+
sub_schema = Class.new(Schema)
|
79
|
+
sub_schema.class_eval(&)
|
80
|
+
|
81
|
+
definitions[name] = {
|
82
|
+
type: "object",
|
83
|
+
properties: sub_schema.properties,
|
84
|
+
required: sub_schema.required_properties
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def reference(schema_name)
|
89
|
+
{"$ref" => "#/$defs/#{schema_name}"}
|
90
|
+
end
|
91
|
+
|
92
|
+
# Schema building methods
|
93
|
+
def build_property_schema(type, **options, &)
|
94
|
+
case type
|
95
|
+
when :string
|
96
|
+
{
|
97
|
+
type: "string",
|
98
|
+
enum: options[:enum],
|
99
|
+
description: options[:description],
|
100
|
+
minLength: options[:minLength],
|
101
|
+
maxLength: options[:maxLength],
|
102
|
+
pattern: options[:pattern],
|
103
|
+
format: options[:format]
|
104
|
+
}.compact
|
105
|
+
when :number
|
106
|
+
{
|
107
|
+
type: "number",
|
108
|
+
description: options[:description],
|
109
|
+
minimum: options[:minimum],
|
110
|
+
maximum: options[:maximum],
|
111
|
+
multipleOf: options[:multipleOf]
|
112
|
+
}.compact
|
113
|
+
when :integer
|
114
|
+
{
|
115
|
+
type: "integer",
|
116
|
+
description: options[:description],
|
117
|
+
minimum: options[:minimum],
|
118
|
+
maximum: options[:maximum],
|
119
|
+
multipleOf: options[:multipleOf]
|
120
|
+
}.compact
|
121
|
+
when :boolean
|
122
|
+
{type: "boolean", description: options[:description]}.compact
|
123
|
+
when :null
|
124
|
+
{type: "null", description: options[:description]}.compact
|
125
|
+
when :object
|
126
|
+
# If the reference option is provided, return the reference
|
127
|
+
return reference(options[:reference]) if options[:reference]
|
128
|
+
|
129
|
+
sub_schema = Class.new(Schema)
|
130
|
+
|
131
|
+
# Evaluate the block and capture the result
|
132
|
+
result = sub_schema.class_eval(&)
|
133
|
+
|
134
|
+
# If the block returned a reference and no properties were added, use the reference
|
135
|
+
if result.is_a?(Hash) && result["$ref"] && sub_schema.properties.empty?
|
136
|
+
result.merge(options[:description] ? {description: options[:description]} : {})
|
137
|
+
else
|
138
|
+
{
|
139
|
+
type: "object",
|
140
|
+
properties: sub_schema.properties,
|
141
|
+
required: sub_schema.required_properties,
|
142
|
+
additionalProperties: additional_properties,
|
143
|
+
description: options[:description]
|
144
|
+
}.compact
|
145
|
+
end
|
146
|
+
when :any_of
|
147
|
+
schemas = collect_property_schemas_from_block(&)
|
148
|
+
{
|
149
|
+
anyOf: schemas
|
150
|
+
}.compact
|
151
|
+
else
|
152
|
+
raise InvalidSchemaTypeError, type
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def add_property(name, definition, required:)
|
159
|
+
properties[name.to_sym] = definition
|
160
|
+
required_properties << name.to_sym if required
|
161
|
+
end
|
162
|
+
|
163
|
+
def determine_array_items(of, &)
|
164
|
+
return collect_property_schemas_from_block(&).first if block_given?
|
165
|
+
return build_property_schema(of) if primitive_type?(of)
|
166
|
+
return reference(of) if of.is_a?(Symbol)
|
167
|
+
|
168
|
+
raise InvalidArrayTypeError, of
|
169
|
+
end
|
170
|
+
|
171
|
+
def collect_property_schemas_from_block(&block)
|
172
|
+
schemas = []
|
173
|
+
schema_builder = self # Capture the current context that has build_property_schema
|
174
|
+
|
175
|
+
context = Object.new
|
176
|
+
context.define_singleton_method(:string) { |name = nil, **options| schemas << schema_builder.build_property_schema(:string, **options) }
|
177
|
+
context.define_singleton_method(:number) { |name = nil, **options| schemas << schema_builder.build_property_schema(:number, **options) }
|
178
|
+
context.define_singleton_method(:integer) { |name = nil, **options| schemas << schema_builder.build_property_schema(:integer, **options) }
|
179
|
+
context.define_singleton_method(:boolean) { |name = nil, **options| schemas << schema_builder.build_property_schema(:boolean, **options) }
|
180
|
+
context.define_singleton_method(:null) { |name = nil, **options| schemas << schema_builder.build_property_schema(:null, **options) }
|
181
|
+
context.define_singleton_method(:object) { |name = nil, **options, &blk| schemas << schema_builder.build_property_schema(:object, **options, &blk) }
|
182
|
+
context.define_singleton_method(:any_of) { |name = nil, **options, &blk| schemas << schema_builder.build_property_schema(:any_of, **options, &blk) }
|
183
|
+
|
184
|
+
context.instance_eval(&block)
|
185
|
+
schemas
|
186
|
+
end
|
187
|
+
|
188
|
+
def primitive_type?(type)
|
189
|
+
type.is_a?(Symbol) && PRIMITIVE_TYPES.include?(type)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,29 @@
|
|
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
|
+
{
|
10
|
+
name: @name,
|
11
|
+
description: @description || self.class.description,
|
12
|
+
schema: {
|
13
|
+
:type => "object",
|
14
|
+
:properties => self.class.properties,
|
15
|
+
:required => self.class.required_properties,
|
16
|
+
:additionalProperties => self.class.additional_properties,
|
17
|
+
:strict => self.class.strict,
|
18
|
+
"$defs" => self.class.definitions
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_json(*_args)
|
24
|
+
validate! # Validate schema before generating JSON string
|
25
|
+
JSON.pretty_generate(to_json_schema)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -32,7 +32,7 @@ module RubyLLM
|
|
32
32
|
|
33
33
|
# Initialize all nodes as WHITE (no mark)
|
34
34
|
marks = Hash.new { WHITE }
|
35
|
-
|
35
|
+
|
36
36
|
# Visit each unmarked node
|
37
37
|
definitions.each_key do |node|
|
38
38
|
visit(node, definitions, marks) if marks[node] == WHITE
|
@@ -43,7 +43,7 @@ module RubyLLM
|
|
43
43
|
def visit(node, definitions, marks)
|
44
44
|
# If node has a permanent mark, return
|
45
45
|
return if marks[node] == BLACK
|
46
|
-
|
46
|
+
|
47
47
|
# If node has a temporary mark, we found a cycle
|
48
48
|
if marks[node] == GRAY
|
49
49
|
raise ValidationError, "Circular reference detected involving '#{node}'"
|
@@ -51,7 +51,7 @@ module RubyLLM
|
|
51
51
|
|
52
52
|
# Mark node with temporary mark
|
53
53
|
marks[node] = GRAY
|
54
|
-
|
54
|
+
|
55
55
|
# Visit all adjacent nodes (dependencies)
|
56
56
|
definition = definitions[node]
|
57
57
|
if definition && definition[:properties]
|
@@ -62,14 +62,14 @@ module RubyLLM
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
# Mark node with permanent mark
|
67
67
|
marks[node] = BLACK
|
68
68
|
end
|
69
69
|
|
70
70
|
def extract_references(property)
|
71
71
|
references = []
|
72
|
-
|
72
|
+
|
73
73
|
case property
|
74
74
|
when Hash
|
75
75
|
if property["$ref"]
|
@@ -92,4 +92,4 @@ module RubyLLM
|
|
92
92
|
end
|
93
93
|
end
|
94
94
|
end
|
95
|
-
end
|
95
|
+
end
|
data/lib/ruby_llm/schema.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "schema/version"
|
4
|
-
require_relative "schema/property_schema_collector"
|
5
4
|
require_relative "schema/errors"
|
6
5
|
require_relative "schema/helpers"
|
7
6
|
require_relative "schema/validator"
|
7
|
+
require_relative "schema/dsl"
|
8
|
+
require_relative "schema/json_output"
|
8
9
|
require "json"
|
9
|
-
require "set"
|
10
10
|
|
11
11
|
module RubyLLM
|
12
12
|
class Schema
|
13
|
+
extend DSL
|
14
|
+
include JsonOutput
|
15
|
+
|
13
16
|
PRIMITIVE_TYPES = %i[string number integer boolean null].freeze
|
14
17
|
|
15
18
|
class << self
|
@@ -19,6 +22,18 @@ module RubyLLM
|
|
19
22
|
schema_class
|
20
23
|
end
|
21
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
|
+
|
22
37
|
def description(description = nil)
|
23
38
|
@description = description if description
|
24
39
|
@description
|
@@ -36,149 +51,6 @@ module RubyLLM
|
|
36
51
|
@strict = value
|
37
52
|
end
|
38
53
|
|
39
|
-
def string(name = nil, enum: nil, description: nil, required: true, min_length: nil, max_length: nil, pattern: nil, format: nil)
|
40
|
-
options = {
|
41
|
-
enum: enum,
|
42
|
-
description: description,
|
43
|
-
minLength: min_length,
|
44
|
-
maxLength: max_length,
|
45
|
-
pattern: pattern,
|
46
|
-
format: format
|
47
|
-
}.compact
|
48
|
-
|
49
|
-
add_property(name, build_property_schema(:string, **options), required: required)
|
50
|
-
end
|
51
|
-
|
52
|
-
def number(name = nil, description: nil, required: true, minimum: nil, maximum: nil, multiple_of: nil)
|
53
|
-
options = {
|
54
|
-
description: description,
|
55
|
-
minimum: minimum,
|
56
|
-
maximum: maximum,
|
57
|
-
multipleOf: multiple_of
|
58
|
-
}.compact
|
59
|
-
|
60
|
-
add_property(name, build_property_schema(:number, **options), required: required)
|
61
|
-
end
|
62
|
-
|
63
|
-
def integer(name = nil, description: nil, required: true)
|
64
|
-
add_property(name, build_property_schema(:integer, description: description), required: required)
|
65
|
-
end
|
66
|
-
|
67
|
-
def boolean(name = nil, description: nil, required: true)
|
68
|
-
add_property(name, build_property_schema(:boolean, description: description), required: required)
|
69
|
-
end
|
70
|
-
|
71
|
-
def null(name = nil, description: nil, required: true)
|
72
|
-
add_property(name, build_property_schema(:null, description: description), required: required)
|
73
|
-
end
|
74
|
-
|
75
|
-
def object(name = nil, description: nil, required: true, &block)
|
76
|
-
add_property(name, build_property_schema(:object, description: description, &block), required: required)
|
77
|
-
end
|
78
|
-
|
79
|
-
def array(name, of: nil, description: nil, required: true, min_items: nil, max_items: nil, &block)
|
80
|
-
items = determine_array_items(of, &block)
|
81
|
-
|
82
|
-
add_property(name, {
|
83
|
-
type: "array",
|
84
|
-
description: description,
|
85
|
-
items: items,
|
86
|
-
minItems: min_items,
|
87
|
-
maxItems: max_items
|
88
|
-
}.compact, required: required)
|
89
|
-
end
|
90
|
-
|
91
|
-
def any_of(name, required: true, description: nil, &block)
|
92
|
-
schemas = collect_property_schemas_from_block(&block)
|
93
|
-
|
94
|
-
add_property(name, {
|
95
|
-
description: description,
|
96
|
-
anyOf: schemas
|
97
|
-
}.compact, required: required)
|
98
|
-
end
|
99
|
-
|
100
|
-
def optional(name, description: nil, &block)
|
101
|
-
any_of(name, description: description) do
|
102
|
-
instance_eval(&block)
|
103
|
-
null
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def define(name, &)
|
108
|
-
sub_schema = Class.new(Schema)
|
109
|
-
sub_schema.class_eval(&)
|
110
|
-
|
111
|
-
definitions[name] = {
|
112
|
-
type: "object",
|
113
|
-
properties: sub_schema.properties,
|
114
|
-
required: sub_schema.required_properties
|
115
|
-
}
|
116
|
-
end
|
117
|
-
|
118
|
-
def reference(schema_name)
|
119
|
-
{"$ref" => "#/$defs/#{schema_name}"}
|
120
|
-
end
|
121
|
-
|
122
|
-
def properties
|
123
|
-
@properties ||= {}
|
124
|
-
end
|
125
|
-
|
126
|
-
def required_properties
|
127
|
-
@required_properties ||= []
|
128
|
-
end
|
129
|
-
|
130
|
-
def definitions
|
131
|
-
@definitions ||= {}
|
132
|
-
end
|
133
|
-
|
134
|
-
def build_property_schema(type, **options, &)
|
135
|
-
case type
|
136
|
-
when :string
|
137
|
-
{
|
138
|
-
type: "string",
|
139
|
-
enum: options[:enum],
|
140
|
-
description: options[:description],
|
141
|
-
minLength: options[:minLength],
|
142
|
-
maxLength: options[:maxLength],
|
143
|
-
pattern: options[:pattern],
|
144
|
-
format: options[:format]
|
145
|
-
}.compact
|
146
|
-
when :number
|
147
|
-
{
|
148
|
-
type: "number",
|
149
|
-
description: options[:description],
|
150
|
-
minimum: options[:minimum],
|
151
|
-
maximum: options[:maximum],
|
152
|
-
multipleOf: options[:multipleOf]
|
153
|
-
}.compact
|
154
|
-
when :integer
|
155
|
-
{
|
156
|
-
type: "integer",
|
157
|
-
description: options[:description],
|
158
|
-
minimum: options[:minimum],
|
159
|
-
maximum: options[:maximum],
|
160
|
-
multipleOf: options[:multipleOf]
|
161
|
-
}.compact
|
162
|
-
when :boolean
|
163
|
-
{type: "boolean", description: options[:description]}.compact
|
164
|
-
when :null
|
165
|
-
{type: "null", description: options[:description]}.compact
|
166
|
-
when :object
|
167
|
-
sub_schema = Class.new(Schema)
|
168
|
-
sub_schema.class_eval(&)
|
169
|
-
|
170
|
-
{
|
171
|
-
type: "object",
|
172
|
-
properties: sub_schema.properties,
|
173
|
-
required: sub_schema.required_properties,
|
174
|
-
additionalProperties: additional_properties,
|
175
|
-
description: options[:description]
|
176
|
-
}.compact
|
177
|
-
else
|
178
|
-
raise InvalidSchemaTypeError, type
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
54
|
def validate!
|
183
55
|
validator = Validator.new(self)
|
184
56
|
validator.validate!
|
@@ -188,31 +60,6 @@ module RubyLLM
|
|
188
60
|
validator = Validator.new(self)
|
189
61
|
validator.valid?
|
190
62
|
end
|
191
|
-
|
192
|
-
private
|
193
|
-
|
194
|
-
def add_property(name, definition, required:)
|
195
|
-
properties[name.to_sym] = definition
|
196
|
-
required_properties << name.to_sym if required
|
197
|
-
end
|
198
|
-
|
199
|
-
def determine_array_items(of, &)
|
200
|
-
return collect_property_schemas_from_block(&).first if block_given?
|
201
|
-
return build_property_schema(of) if primitive_type?(of)
|
202
|
-
return reference(of) if of.is_a?(Symbol)
|
203
|
-
|
204
|
-
raise InvalidArrayTypeError, of
|
205
|
-
end
|
206
|
-
|
207
|
-
def collect_property_schemas_from_block(&)
|
208
|
-
collector = PropertySchemaCollector.new
|
209
|
-
collector.collect(&)
|
210
|
-
collector.schemas
|
211
|
-
end
|
212
|
-
|
213
|
-
def primitive_type?(type)
|
214
|
-
type.is_a?(Symbol) && PRIMITIVE_TYPES.include?(type)
|
215
|
-
end
|
216
63
|
end
|
217
64
|
|
218
65
|
def initialize(name = nil, description: nil)
|
@@ -220,28 +67,6 @@ module RubyLLM
|
|
220
67
|
@description = description
|
221
68
|
end
|
222
69
|
|
223
|
-
def to_json_schema
|
224
|
-
validate! # Validate schema before generating JSON
|
225
|
-
|
226
|
-
{
|
227
|
-
name: @name,
|
228
|
-
description: @description || self.class.description,
|
229
|
-
schema: {
|
230
|
-
:type => "object",
|
231
|
-
:properties => self.class.properties,
|
232
|
-
:required => self.class.required_properties,
|
233
|
-
:additionalProperties => self.class.additional_properties,
|
234
|
-
:strict => self.class.strict,
|
235
|
-
"$defs" => self.class.definitions
|
236
|
-
}
|
237
|
-
}
|
238
|
-
end
|
239
|
-
|
240
|
-
def to_json(*_args)
|
241
|
-
validate! # Validate schema before generating JSON string
|
242
|
-
JSON.pretty_generate(to_json_schema)
|
243
|
-
end
|
244
|
-
|
245
70
|
def validate!
|
246
71
|
self.class.validate!
|
247
72
|
end
|
@@ -252,7 +77,7 @@ module RubyLLM
|
|
252
77
|
|
253
78
|
def method_missing(method_name, ...)
|
254
79
|
if respond_to_missing?(method_name)
|
255
|
-
send(method_name, ...)
|
80
|
+
self.class.send(method_name, ...)
|
256
81
|
else
|
257
82
|
super
|
258
83
|
end
|
data/lib/tasks/release.rake
CHANGED
@@ -1,23 +1,22 @@
|
|
1
1
|
namespace :release do
|
2
2
|
desc "Release a new version of the gem"
|
3
|
-
task :version do
|
3
|
+
task :version, [:message] do |t, args|
|
4
4
|
# Load the current version from version.rb
|
5
|
-
require_relative
|
5
|
+
require_relative "../../lib/ruby_llm/schema/version"
|
6
6
|
version = RubyLlm::Schema::VERSION
|
7
|
-
|
7
|
+
|
8
8
|
puts "Releasing version #{version}..."
|
9
|
-
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
if release_message.empty?
|
16
|
-
system "git tag v#{version}"
|
9
|
+
|
10
|
+
# Create git tag with optional message
|
11
|
+
# rake release:version["Fix critical bug in schema validation"]
|
12
|
+
if args[:message]
|
13
|
+
system "git tag -a v#{version} -m \"#{args[:message]}\""
|
14
|
+
puts "Created annotated tag v#{version} with message: #{args[:message]}"
|
17
15
|
else
|
18
|
-
system "git tag
|
16
|
+
system "git tag v#{version}"
|
17
|
+
puts "Created lightweight tag v#{version}"
|
19
18
|
end
|
20
|
-
|
19
|
+
|
21
20
|
system "git push origin main"
|
22
21
|
system "git push origin v#{version}"
|
23
22
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_llm-schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Friis
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-08-
|
10
|
+
date: 2025-08-22 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rspec
|
@@ -48,9 +48,10 @@ files:
|
|
48
48
|
- README.md
|
49
49
|
- Rakefile
|
50
50
|
- lib/ruby_llm/schema.rb
|
51
|
+
- lib/ruby_llm/schema/dsl.rb
|
51
52
|
- lib/ruby_llm/schema/errors.rb
|
52
53
|
- lib/ruby_llm/schema/helpers.rb
|
53
|
-
- lib/ruby_llm/schema/
|
54
|
+
- lib/ruby_llm/schema/json_output.rb
|
54
55
|
- lib/ruby_llm/schema/validator.rb
|
55
56
|
- lib/ruby_llm/schema/version.rb
|
56
57
|
- lib/tasks/release.rake
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RubyLLM
|
4
|
-
class Schema
|
5
|
-
class PropertySchemaCollector
|
6
|
-
attr_reader :schemas
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@schemas = []
|
10
|
-
end
|
11
|
-
|
12
|
-
def collect(&)
|
13
|
-
instance_eval(&)
|
14
|
-
end
|
15
|
-
|
16
|
-
def string(**options)
|
17
|
-
@schemas << Schema.build_property_schema(:string, **options)
|
18
|
-
end
|
19
|
-
|
20
|
-
def number(**options)
|
21
|
-
@schemas << Schema.build_property_schema(:number, **options)
|
22
|
-
end
|
23
|
-
|
24
|
-
def integer(**options)
|
25
|
-
@schemas << Schema.build_property_schema(:integer, **options)
|
26
|
-
end
|
27
|
-
|
28
|
-
def boolean(**options)
|
29
|
-
@schemas << Schema.build_property_schema(:boolean, **options)
|
30
|
-
end
|
31
|
-
|
32
|
-
def null(**options)
|
33
|
-
@schemas << Schema.build_property_schema(:null, **options)
|
34
|
-
end
|
35
|
-
|
36
|
-
def object(...)
|
37
|
-
@schemas << Schema.build_property_schema(:object, ...)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|