ruby_llm-schema 0.1.6 → 0.1.7
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 +85 -14
- data/Rakefile +7 -0
- data/lib/ruby_llm/schema/dsl.rb +183 -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: 4706281dcd180f4d6e2b385cc42b79d73629d8eb8a7b5723ddde3b2786a5022e
|
4
|
+
data.tar.gz: eb083d6fa029a0b107f7a728c86e9f8342a1b9d0c3d0a46d8d4d33adaacd756a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc52ea4b47664561909278ece104676ae5053572aa791fe6985deb1ecdf40421e49b3b02529ba2a02cbb1df068484d6d162ec0d4c8d525105111ced98451fcbf
|
7
|
+
data.tar.gz: df555cdab1fb1c786ee0425fb5a1c0dd61bd5ed6c35dfc53bb355cdad27b462c5dbd84cb5d8fb32db6fdbe2c364916f0c8d2ed32565bff87c8ca17b79915ee03
|
data/README.md
CHANGED
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
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
4
|
|
5
|
+
## Use Cases
|
6
|
+
|
7
|
+
Structured output is a powerful tool for LLMs to generate consistent and predictable responses.
|
8
|
+
|
9
|
+
Some ideal use cases:
|
10
|
+
|
11
|
+
- Extracting *metadata, topics, and summary* from articles or blog posts
|
12
|
+
- Organizing unstructured feedback or reviews with *sentiment and summary*
|
13
|
+
- Defining structured *actions* from user messages or emails
|
14
|
+
- Extracting *entities and relationships* from documents
|
15
|
+
|
5
16
|
### Simple Example
|
6
17
|
|
7
18
|
```ruby
|
@@ -122,29 +133,83 @@ end
|
|
122
133
|
puts person_schema.to_json
|
123
134
|
```
|
124
135
|
|
125
|
-
##
|
136
|
+
## Schema Property Types
|
137
|
+
|
138
|
+
A schema is a collection of properties, which can be of different types. Each type has its own set of properties you can set.
|
139
|
+
|
140
|
+
All property types can (along with the required `name` key) be set with a `description` and a `required` flag (default is `true`).
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
string :name, description: "Person's full name"
|
144
|
+
number :age, description: "Person's age", required: false
|
145
|
+
boolean :is_active, description: "Whether the person is active"
|
146
|
+
null :placeholder, description: "A placeholder property"
|
147
|
+
```
|
148
|
+
|
149
|
+
⚠️ 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.
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
any_of :name, description: "Person's full name" do
|
153
|
+
string
|
154
|
+
null
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
### Strings
|
159
|
+
|
160
|
+
String types support the following properties:
|
161
|
+
|
162
|
+
- `enum`: an array of allowed values (e.g. `enum: ["on", "off"]`)
|
163
|
+
- `pattern`: a regex pattern (e.g. `pattern: "\\d+"`)
|
164
|
+
- `format`: a format string (e.g. `format: "email"`)
|
165
|
+
- `min_length`: the minimum length of the string (e.g. `min_length: 3`)
|
166
|
+
- `max_length`: the maximum length of the string (e.g. `max_length: 10`)
|
167
|
+
|
168
|
+
Please consult the LLM provider documentation for the available formats and patterns.
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
string :name, description: "Person's full name"
|
172
|
+
string :email, format: "email"
|
173
|
+
string :phone, pattern: "\\d+"
|
174
|
+
string :status, enum: ["on", "off"]
|
175
|
+
string :code, min_length: 3, max_length: 10
|
176
|
+
```
|
177
|
+
|
178
|
+
### Numbers
|
126
179
|
|
127
|
-
|
180
|
+
Number types support the following properties:
|
181
|
+
|
182
|
+
- `multiple_of`: a multiple of the number (e.g. `multiple_of: 0.01`)
|
183
|
+
- `minimum`: the minimum value of the number (e.g. `minimum: 0`)
|
184
|
+
- `maximum`: the maximum value of the number (e.g. `maximum: 100`)
|
128
185
|
|
129
186
|
```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
|
187
|
+
number :price, minimum: 0, maximum: 100
|
188
|
+
number :amount, multiple_of: 0.01
|
189
|
+
```
|
136
190
|
|
137
|
-
|
138
|
-
number :price, minimum: 0, maximum: 100 # Number with range constraints
|
139
|
-
number :amount, multiple_of: 0.01 # Number with precision constraints
|
191
|
+
### Booleans
|
140
192
|
|
141
|
-
|
142
|
-
boolean :
|
143
|
-
null :placeholder # Null type
|
193
|
+
```ruby
|
194
|
+
boolean :is_active
|
144
195
|
```
|
145
196
|
|
197
|
+
Boolean types doesn't support any additional properties.
|
198
|
+
|
199
|
+
### Null
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
null :placeholder
|
203
|
+
```
|
204
|
+
|
205
|
+
Null types doesn't support any additional properties.
|
206
|
+
|
146
207
|
### Arrays
|
147
208
|
|
209
|
+
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.
|
210
|
+
|
211
|
+
An array can have a `min_items` and `max_items` option to set the minimum and maximum number of items in the array.
|
212
|
+
|
148
213
|
```ruby
|
149
214
|
array :tags, of: :string # Array of strings
|
150
215
|
array :scores, of: :number # Array of numbers
|
@@ -160,6 +225,8 @@ end
|
|
160
225
|
|
161
226
|
### Objects
|
162
227
|
|
228
|
+
Objects types expect a block with the properties of the object.
|
229
|
+
|
163
230
|
```ruby
|
164
231
|
object :user do
|
165
232
|
string :name
|
@@ -174,6 +241,8 @@ end
|
|
174
241
|
|
175
242
|
### Union Types (anyOf)
|
176
243
|
|
244
|
+
Union types are a way to specify that a property can be one of several types.
|
245
|
+
|
177
246
|
```ruby
|
178
247
|
any_of :value do
|
179
248
|
string
|
@@ -189,6 +258,8 @@ end
|
|
189
258
|
|
190
259
|
### Schema Definitions and References
|
191
260
|
|
261
|
+
You can define sub-schemas and reference them in other schemas.
|
262
|
+
|
192
263
|
```ruby
|
193
264
|
class MySchema < RubyLLM::Schema
|
194
265
|
define :location do
|
data/Rakefile
CHANGED
@@ -0,0 +1,183 @@
|
|
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, description: nil, required: true, &block)
|
45
|
+
add_property(name, build_property_schema(:object, description: description, &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
|
+
sub_schema = Class.new(Schema)
|
127
|
+
sub_schema.class_eval(&)
|
128
|
+
|
129
|
+
{
|
130
|
+
type: "object",
|
131
|
+
properties: sub_schema.properties,
|
132
|
+
required: sub_schema.required_properties,
|
133
|
+
additionalProperties: additional_properties,
|
134
|
+
description: options[:description]
|
135
|
+
}.compact
|
136
|
+
when :any_of
|
137
|
+
schemas = collect_property_schemas_from_block(&)
|
138
|
+
{
|
139
|
+
anyOf: schemas
|
140
|
+
}.compact
|
141
|
+
else
|
142
|
+
raise InvalidSchemaTypeError, type
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def add_property(name, definition, required:)
|
149
|
+
properties[name.to_sym] = definition
|
150
|
+
required_properties << name.to_sym if required
|
151
|
+
end
|
152
|
+
|
153
|
+
def determine_array_items(of, &)
|
154
|
+
return collect_property_schemas_from_block(&).first if block_given?
|
155
|
+
return build_property_schema(of) if primitive_type?(of)
|
156
|
+
return reference(of) if of.is_a?(Symbol)
|
157
|
+
|
158
|
+
raise InvalidArrayTypeError, of
|
159
|
+
end
|
160
|
+
|
161
|
+
def collect_property_schemas_from_block(&block)
|
162
|
+
schemas = []
|
163
|
+
schema_builder = self # Capture the current context that has build_property_schema
|
164
|
+
|
165
|
+
context = Object.new
|
166
|
+
context.define_singleton_method(:string) { |name = nil, **options| schemas << schema_builder.build_property_schema(:string, **options) }
|
167
|
+
context.define_singleton_method(:number) { |name = nil, **options| schemas << schema_builder.build_property_schema(:number, **options) }
|
168
|
+
context.define_singleton_method(:integer) { |name = nil, **options| schemas << schema_builder.build_property_schema(:integer, **options) }
|
169
|
+
context.define_singleton_method(:boolean) { |name = nil, **options| schemas << schema_builder.build_property_schema(:boolean, **options) }
|
170
|
+
context.define_singleton_method(:null) { |name = nil, **options| schemas << schema_builder.build_property_schema(:null, **options) }
|
171
|
+
context.define_singleton_method(:object) { |name = nil, **options, &blk| schemas << schema_builder.build_property_schema(:object, **options, &blk) }
|
172
|
+
context.define_singleton_method(:any_of) { |name = nil, **options, &blk| schemas << schema_builder.build_property_schema(:any_of, **options, &blk) }
|
173
|
+
|
174
|
+
context.instance_eval(&block)
|
175
|
+
schemas
|
176
|
+
end
|
177
|
+
|
178
|
+
def primitive_type?(type)
|
179
|
+
type.is_a?(Symbol) && PRIMITIVE_TYPES.include?(type)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
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.7
|
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
|