ruby_llm-schema 0.2.5 → 0.3.1

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: 224352dd84018a682d0d2ccaa116bd203dfd026b2f139a6d539133b488d5c6c0
4
- data.tar.gz: 64ebb57d1efdde8341acef3dcb3bdb817ed0b9c2186fea3aa8e0b36779dbfd6d
3
+ metadata.gz: fceccc2292efb7c17ce5457a08d6a18ddc3debd920052a1d8e368d74e450547e
4
+ data.tar.gz: b16fafc541af3b9f1a1e3a96f81be21175a380606f2e40325afa29d76108daea
5
5
  SHA512:
6
- metadata.gz: d49388efee637cfc97ef27b4c4924cd997a5dd5976f2a7cd90b3b6d373d6305a109ae7340aa072843dc81187b1378a290f84d6e7d179197e0e02a5d65bf0691b
7
- data.tar.gz: 80990f6709a2fdb77ab207c664f71f525dacf01242fcc469aa0f427926e7be60390b0f6da221a2588045a71502a814bb0982baea3d4d54513662b66213b8f461
6
+ metadata.gz: 984b11c9fcfcaf68f0892a9a7b9c10064436aa59279f0a0992ebae5c6d20fbcfd1e6afbef8376bf7a9239aaaea246dd9f8c7f6a3d9521509789c8e334e3e0363
7
+ data.tar.gz: e9658cff5e3e1f0912eab47fe8438a5dfd5bf52928dd0198ff39131ecf1aeb5d3b2a8e08344feab0b363ed6a9a27747c1489fcfadefd8f30af4d03c77fb601d8
data/README.md CHANGED
@@ -1,21 +1,24 @@
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
+ [![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)
6
6
 
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.
7
+ A Ruby DSL for creating JSON schemas with a clean, Rails-inspired API.
8
+
9
+ Originally created by [Daniel Friis](https://github.com/danielfriis).
8
10
 
9
11
  ## Use Cases
10
12
 
11
- Structured output is a powerful tool for LLMs to generate consistent and predictable responses.
13
+ JSON Schema is useful wherever Ruby code needs to describe structured data in a portable format.
12
14
 
13
15
  Some ideal use cases:
14
16
 
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
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
19
22
 
20
23
  ### Simple Example
21
24
 
@@ -24,22 +27,22 @@ class PersonSchema < RubyLLM::Schema
24
27
  string :name, description: "Person's full name"
25
28
  number :age, description: "Age in years", minimum: 0, maximum: 120
26
29
  boolean :active, required: false
27
-
30
+
28
31
  object :address do
29
32
  string :street
30
33
  string :city
31
34
  string :country, required: false
32
35
  end
33
-
36
+
34
37
  array :tags, of: :string, description: "User tags"
35
-
38
+
36
39
  array :contacts do
37
40
  object do
38
41
  string :email, format: "email"
39
42
  string :phone, required: false
40
43
  end
41
44
  end
42
-
45
+
43
46
  any_of :status do
44
47
  string enum: ["active", "pending", "inactive"]
45
48
  null
@@ -51,6 +54,62 @@ schema = PersonSchema.new
51
54
  puts schema.to_json
52
55
  ```
53
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
+
54
113
  ## Installation
55
114
 
56
115
  Add this line to your application's Gemfile:
@@ -82,12 +141,12 @@ class PersonSchema < RubyLLM::Schema
82
141
  string :name, description: "Person's full name"
83
142
  number :age
84
143
  boolean :active, required: false
85
-
144
+
86
145
  object :address do
87
146
  string :street
88
147
  string :city
89
148
  end
90
-
149
+
91
150
  array :tags, of: :string
92
151
  end
93
152
 
@@ -102,12 +161,12 @@ PersonSchema = RubyLLM::Schema.create do
102
161
  string :name, description: "Person's full name"
103
162
  number :age
104
163
  boolean :active, required: false
105
-
164
+
106
165
  object :address do
107
166
  string :street
108
167
  string :city
109
168
  end
110
-
169
+
111
170
  array :tags, of: :string
112
171
  end
113
172
 
@@ -125,12 +184,12 @@ person_schema = schema "PersonData", description: "A person object" do
125
184
  string :name, description: "Person's full name"
126
185
  number :age
127
186
  boolean :active, required: false
128
-
187
+
129
188
  object :address do
130
189
  string :street
131
190
  string :city
132
191
  end
133
-
192
+
134
193
  array :tags, of: :string
135
194
  end
136
195
 
@@ -250,7 +309,7 @@ Union types are a way to specify that a property can be one of several types.
250
309
  ```ruby
251
310
  any_of :value do
252
311
  string
253
- number
312
+ number
254
313
  null
255
314
  end
256
315
 
@@ -270,7 +329,7 @@ class MySchema < RubyLLM::Schema
270
329
  string :latitude
271
330
  string :longitude
272
331
  end
273
-
332
+
274
333
  # Using a reference in an array
275
334
  array :coordinates, of: :location
276
335
 
@@ -305,7 +364,7 @@ class CompanySchema < RubyLLM::Schema
305
364
  # Using 'of' parameter
306
365
  object :ceo, of: PersonSchema
307
366
  array :employees, of: PersonSchema
308
-
367
+
309
368
  # Using Schema.new in block
310
369
  object :founder do
311
370
  PersonSchema.new
@@ -118,21 +118,19 @@ module RubyLLM
118
118
 
119
119
  def determine_object_reference(of, description = nil)
120
120
  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
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
136
134
 
137
135
  description ? result.merge(description: description) : result
138
136
  end
@@ -147,7 +145,7 @@ module RubyLLM
147
145
  schema_builder.methods.grep(/_schema$/).each do |schema_method|
148
146
  type_name = schema_method.to_s.sub(/_schema$/, "")
149
147
 
150
- context.define_singleton_method(type_name) do |name = nil, **options, &blk|
148
+ context.define_singleton_method(type_name) do |_name = nil, **options, &blk|
151
149
  schemas << schema_builder.send(schema_method, **options, &blk)
152
150
  end
153
151
  end
@@ -164,10 +162,10 @@ module RubyLLM
164
162
  def schema_class_to_inline_schema(schema_class_or_instance)
165
163
  # Handle both Schema classes and Schema instances
166
164
  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
165
+ schema_class_or_instance
166
+ else
167
+ schema_class_or_instance.class
168
+ end
171
169
 
172
170
  # Directly convert schema class to inline object schema
173
171
  {
@@ -178,10 +176,10 @@ module RubyLLM
178
176
  }.tap do |schema|
179
177
  # For instances, prefer instance description over class description
180
178
  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
179
+ schema_class.description
180
+ else
181
+ schema_class_or_instance.instance_variable_get(:@description) || schema_class.description
182
+ end
185
183
  schema[:description] = description if description
186
184
  end
187
185
  end
@@ -28,8 +28,16 @@ module RubyLLM
28
28
  private
29
29
 
30
30
  def add_property(name, definition, required:)
31
- properties[name.to_sym] = definition
32
- required_properties << name.to_sym if 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
33
41
  end
34
42
 
35
43
  def primitive_type?(type)
@@ -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,16 +4,17 @@ 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",
11
11
  properties: self.class.properties,
12
12
  required: self.class.required_properties,
13
- additionalProperties: self.class.additional_properties,
14
- strict: self.class.strict
13
+ additionalProperties: self.class.additional_properties
15
14
  }
16
15
 
16
+ schema_hash[:strict] = self.class.strict unless self.class.strict.nil?
17
+
17
18
  # Only include $defs if there are definitions
18
19
  schema_hash["$defs"] = self.class.definitions unless self.class.definitions.empty?
19
20
 
@@ -25,7 +26,7 @@ module RubyLLM
25
26
  end
26
27
 
27
28
  def to_json(*_args)
28
- validate! # Validate schema before generating JSON string
29
+ validate! # Validate schema before generating JSON string
29
30
  JSON.pretty_generate(to_json_schema)
30
31
  end
31
32
  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.2.5"
5
+ VERSION = '0.3.1'
6
6
  end
7
7
  end
@@ -48,14 +48,16 @@ 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
 
54
- def strict(value = nil)
55
- if value.nil?
56
- return @strict.nil? ? (@strict = true) : @strict
55
+ def strict(*args)
56
+ if args.empty?
57
+ instance_variable_defined?(:@strict) ? @strict : true
58
+ else
59
+ @strict = args.first
57
60
  end
58
- @strict = value
59
61
  end
60
62
 
61
63
  def validate!
@@ -1,33 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  namespace :release do
2
- desc "Prepare and push the release branch to trigger the automated pipeline"
4
+ desc 'Prepare for release'
3
5
  task :prepare do
4
- abort "Git working directory not clean. Commit or stash changes first." unless `git status --porcelain`.strip.empty?
5
- abort "Not on main branch. Releases must be run from main branch" unless `git rev-parse --abbrev-ref HEAD`.strip == "main"
6
-
7
- require_relative "../ruby_llm/schema/version"
8
- version = RubyLlm::Schema::VERSION or abort "Could not determine version"
9
-
10
- branch = "release/#{version}"
11
-
12
- if system("git rev-parse --quiet --verify refs/tags/v#{version} > /dev/null 2>&1")
13
- abort "Tag v#{version} already exists. Bump the version first."
14
- end
15
-
16
- if system("git show-ref --verify --quiet refs/heads/#{branch}")
17
- abort "Local branch #{branch} already exists. Remove it or choose a new version."
18
- end
19
-
20
- sh "git fetch origin"
21
-
22
- if system("git ls-remote --exit-code --heads origin #{branch} > /dev/null 2>&1")
23
- abort "Release branch #{branch} already exists on origin. Remove it or choose a new version."
24
- end
25
-
26
- sh "git checkout -b #{branch}"
27
- sh "git push -u origin #{branch}"
28
-
29
- puts "Release branch #{branch} pushed. GitHub Actions will run tests and publish if they pass."
30
- ensure
31
- system "git checkout main"
6
+ sh 'overcommit --run'
32
7
  end
33
8
  end
metadata CHANGED
@@ -1,55 +1,24 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm-schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.3.1
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: 2025-11-12 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
@@ -62,15 +31,15 @@ files:
62
31
  - lib/ruby_llm/schema/validator.rb
63
32
  - lib/ruby_llm/schema/version.rb
64
33
  - lib/tasks/release.rake
65
- homepage: https://github.com/danielfriis/ruby_llm-schema
34
+ homepage: https://github.com/crmne/ruby_llm-schema#readme
66
35
  licenses:
67
36
  - MIT
68
37
  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
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
72
42
  rubygems_mfa_required: 'true'
73
- post_install_message:
74
43
  rdoc_options: []
75
44
  require_paths:
76
45
  - lib
@@ -78,15 +47,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
47
  requirements:
79
48
  - - ">="
80
49
  - !ruby/object:Gem::Version
81
- version: 3.1.0
50
+ version: 3.1.3
82
51
  required_rubygems_version: !ruby/object:Gem::Requirement
83
52
  requirements:
84
53
  - - ">="
85
54
  - !ruby/object:Gem::Version
86
55
  version: '0'
87
56
  requirements: []
88
- rubygems_version: 3.4.19
89
- signing_key:
57
+ rubygems_version: 4.0.10
90
58
  specification_version: 4
91
- summary: A simple and clean Ruby DSL for creating JSON schemas.
59
+ summary: A simple Ruby DSL for creating JSON schemas.
92
60
  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