From 36f9694822e6e5fff849d86b14e64ac1b2ee3c0f Mon Sep 17 00:00:00 2001 From: Vojtech Rinik Date: Fri, 5 Dec 2025 13:21:48 +0100 Subject: [PATCH 1/4] strict handling --- lib/ruby_llm/schema/json_output.rb | 7 +++++-- .../ruby_llm/schema/entry_points/class_inheritance_spec.rb | 3 ++- spec/ruby_llm/schema/entry_points/factory_spec.rb | 3 ++- spec/ruby_llm/schema/entry_points/helpers_spec.rb | 3 ++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/ruby_llm/schema/json_output.rb b/lib/ruby_llm/schema/json_output.rb index baf40a5..a468eea 100644 --- a/lib/ruby_llm/schema/json_output.rb +++ b/lib/ruby_llm/schema/json_output.rb @@ -10,10 +10,13 @@ def to_json_schema type: "object", properties: self.class.properties, required: self.class.required_properties, - additionalProperties: self.class.additional_properties, - strict: self.class.strict + additionalProperties: self.class.additional_properties } + # Only include strict when truthy (for OpenAI compatibility) + # Omitting it makes schemas portable to Anthropic which rejects the strict property + schema_hash[:strict] = true if self.class.strict + # Only include $defs if there are definitions schema_hash["$defs"] = self.class.definitions unless self.class.definitions.empty? diff --git a/spec/ruby_llm/schema/entry_points/class_inheritance_spec.rb b/spec/ruby_llm/schema/entry_points/class_inheritance_spec.rb index 72f7aa0..cf74f77 100644 --- a/spec/ruby_llm/schema/entry_points/class_inheritance_spec.rb +++ b/spec/ruby_llm/schema/entry_points/class_inheritance_spec.rb @@ -45,7 +45,8 @@ configured_output = configured_schema.new.to_json_schema expect(configured_output[:schema][:additionalProperties]).to eq(true) - expect(configured_output[:schema][:strict]).to eq(false) + # strict key should be omitted when false (for Anthropic compatibility) + expect(configured_output[:schema]).not_to have_key(:strict) end it "renders structured JSON schema" do diff --git a/spec/ruby_llm/schema/entry_points/factory_spec.rb b/spec/ruby_llm/schema/entry_points/factory_spec.rb index 3045c1a..23144f9 100644 --- a/spec/ruby_llm/schema/entry_points/factory_spec.rb +++ b/spec/ruby_llm/schema/entry_points/factory_spec.rb @@ -45,7 +45,8 @@ configured_output = configured_schema.new.to_json_schema expect(configured_output[:schema][:additionalProperties]).to eq(true) - expect(configured_output[:schema][:strict]).to eq(false) + # strict key should be omitted when false (for Anthropic compatibility) + expect(configured_output[:schema]).not_to have_key(:strict) end it "renders structured JSON schema" do diff --git a/spec/ruby_llm/schema/entry_points/helpers_spec.rb b/spec/ruby_llm/schema/entry_points/helpers_spec.rb index c52de32..2cd7d0c 100644 --- a/spec/ruby_llm/schema/entry_points/helpers_spec.rb +++ b/spec/ruby_llm/schema/entry_points/helpers_spec.rb @@ -46,7 +46,8 @@ end.to_json_schema expect(configured_output[:schema][:additionalProperties]).to eq(true) - expect(configured_output[:schema][:strict]).to eq(false) + # strict key should be omitted when false (for Anthropic compatibility) + expect(configured_output[:schema]).not_to have_key(:strict) end it "renders structured JSON schema" do From 47cc58a5fe2bf0c7794a76a565c649ca002ed81a Mon Sep 17 00:00:00 2001 From: Vojtech Rinik Date: Tue, 9 Dec 2025 09:43:25 +0100 Subject: [PATCH 2/4] using skip_strict instead --- lib/ruby_llm/schema.rb | 5 +++++ lib/ruby_llm/schema/json_output.rb | 5 ++--- spec/ruby_llm/schema/entry_points/class_inheritance_spec.rb | 3 +-- spec/ruby_llm/schema/entry_points/factory_spec.rb | 3 +-- spec/ruby_llm/schema/entry_points/helpers_spec.rb | 3 +-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/ruby_llm/schema.rb b/lib/ruby_llm/schema.rb index 65d12a4..e1b0419 100644 --- a/lib/ruby_llm/schema.rb +++ b/lib/ruby_llm/schema.rb @@ -58,6 +58,11 @@ def strict(value = nil) @strict = value end + def skip_strict(value = nil) + return @skip_strict ||= false if value.nil? + @skip_strict = value + end + def validate! validator = Validator.new(self) validator.validate! diff --git a/lib/ruby_llm/schema/json_output.rb b/lib/ruby_llm/schema/json_output.rb index a468eea..7783564 100644 --- a/lib/ruby_llm/schema/json_output.rb +++ b/lib/ruby_llm/schema/json_output.rb @@ -13,9 +13,8 @@ def to_json_schema additionalProperties: self.class.additional_properties } - # Only include strict when truthy (for OpenAI compatibility) - # Omitting it makes schemas portable to Anthropic which rejects the strict property - schema_hash[:strict] = true if self.class.strict + # Include strict unless skip_strict is set + schema_hash[:strict] = self.class.strict unless self.class.skip_strict # Only include $defs if there are definitions schema_hash["$defs"] = self.class.definitions unless self.class.definitions.empty? diff --git a/spec/ruby_llm/schema/entry_points/class_inheritance_spec.rb b/spec/ruby_llm/schema/entry_points/class_inheritance_spec.rb index cf74f77..72f7aa0 100644 --- a/spec/ruby_llm/schema/entry_points/class_inheritance_spec.rb +++ b/spec/ruby_llm/schema/entry_points/class_inheritance_spec.rb @@ -45,8 +45,7 @@ configured_output = configured_schema.new.to_json_schema expect(configured_output[:schema][:additionalProperties]).to eq(true) - # strict key should be omitted when false (for Anthropic compatibility) - expect(configured_output[:schema]).not_to have_key(:strict) + expect(configured_output[:schema][:strict]).to eq(false) end it "renders structured JSON schema" do diff --git a/spec/ruby_llm/schema/entry_points/factory_spec.rb b/spec/ruby_llm/schema/entry_points/factory_spec.rb index 23144f9..3045c1a 100644 --- a/spec/ruby_llm/schema/entry_points/factory_spec.rb +++ b/spec/ruby_llm/schema/entry_points/factory_spec.rb @@ -45,8 +45,7 @@ configured_output = configured_schema.new.to_json_schema expect(configured_output[:schema][:additionalProperties]).to eq(true) - # strict key should be omitted when false (for Anthropic compatibility) - expect(configured_output[:schema]).not_to have_key(:strict) + expect(configured_output[:schema][:strict]).to eq(false) end it "renders structured JSON schema" do diff --git a/spec/ruby_llm/schema/entry_points/helpers_spec.rb b/spec/ruby_llm/schema/entry_points/helpers_spec.rb index 2cd7d0c..c52de32 100644 --- a/spec/ruby_llm/schema/entry_points/helpers_spec.rb +++ b/spec/ruby_llm/schema/entry_points/helpers_spec.rb @@ -46,8 +46,7 @@ end.to_json_schema expect(configured_output[:schema][:additionalProperties]).to eq(true) - # strict key should be omitted when false (for Anthropic compatibility) - expect(configured_output[:schema]).not_to have_key(:strict) + expect(configured_output[:schema][:strict]).to eq(false) end it "renders structured JSON schema" do From 82617e5bb8e6200f1f7d359e3028c088ecd04038 Mon Sep 17 00:00:00 2001 From: Vojtech Rinik Date: Sat, 13 Dec 2025 09:37:58 +0100 Subject: [PATCH 3/4] Allow strict to be nil to omit from output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refactor strict method to use splat args for cleaner nil handling - When strict is nil, omit it from JSON schema output - Remove skip_strict method (replaced by strict nil) - Add minimal test suite for strict getter/setter behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- lib/ruby_llm/schema.rb | 14 +++++--------- lib/ruby_llm/schema/json_output.rb | 3 +-- spec/ruby_llm/schema/strict_spec.rb | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 spec/ruby_llm/schema/strict_spec.rb diff --git a/lib/ruby_llm/schema.rb b/lib/ruby_llm/schema.rb index e1b0419..cac5603 100644 --- a/lib/ruby_llm/schema.rb +++ b/lib/ruby_llm/schema.rb @@ -51,16 +51,12 @@ def additional_properties(value = nil) @additional_properties = value end - def strict(value = nil) - if value.nil? - return @strict.nil? ? (@strict = true) : @strict + def strict(*args) + if args.empty? + instance_variable_defined?(:@strict) ? @strict : true + else + @strict = args.first end - @strict = value - end - - def skip_strict(value = nil) - return @skip_strict ||= false if value.nil? - @skip_strict = value end def validate! diff --git a/lib/ruby_llm/schema/json_output.rb b/lib/ruby_llm/schema/json_output.rb index 7783564..0fb57a7 100644 --- a/lib/ruby_llm/schema/json_output.rb +++ b/lib/ruby_llm/schema/json_output.rb @@ -13,8 +13,7 @@ def to_json_schema additionalProperties: self.class.additional_properties } - # Include strict unless skip_strict is set - schema_hash[:strict] = self.class.strict unless self.class.skip_strict + schema_hash[:strict] = self.class.strict unless self.class.strict.nil? # Only include $defs if there are definitions schema_hash["$defs"] = self.class.definitions unless self.class.definitions.empty? diff --git a/spec/ruby_llm/schema/strict_spec.rb b/spec/ruby_llm/schema/strict_spec.rb new file mode 100644 index 0000000..b8c33a4 --- /dev/null +++ b/spec/ruby_llm/schema/strict_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe RubyLLM::Schema, ".strict" do + it "defaults to true when not set" do + schema = Class.new(RubyLLM::Schema) + expect(schema.strict).to eq(true) + end + + it "returns nil when set to nil" do + schema = Class.new(RubyLLM::Schema) { strict nil } + expect(schema.strict).to eq(nil) + end + + it "returns true when set to true" do + schema = Class.new(RubyLLM::Schema) { strict true } + expect(schema.strict).to eq(true) + end + + it "returns false when set to false" do + schema = Class.new(RubyLLM::Schema) { strict false } + expect(schema.strict).to eq(false) + end +end From aaf1b5dcedb49aa5bea5fa4c900699d8c710da1b Mon Sep 17 00:00:00 2001 From: Vojtech Rinik Date: Sat, 13 Dec 2025 14:33:52 +0100 Subject: [PATCH 4/4] Test strict via JSON output for better coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- spec/ruby_llm/schema/strict_spec.rb | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/spec/ruby_llm/schema/strict_spec.rb b/spec/ruby_llm/schema/strict_spec.rb index b8c33a4..1cbf5eb 100644 --- a/spec/ruby_llm/schema/strict_spec.rb +++ b/spec/ruby_llm/schema/strict_spec.rb @@ -3,23 +3,27 @@ require "spec_helper" RSpec.describe RubyLLM::Schema, ".strict" do - it "defaults to true when not set" do + it "outputs strict: true by default" do schema = Class.new(RubyLLM::Schema) - expect(schema.strict).to eq(true) + output = schema.new.to_json_schema + expect(output[:schema][:strict]).to eq(true) end - it "returns nil when set to nil" do + it "omits strict from output when set to nil" do schema = Class.new(RubyLLM::Schema) { strict nil } - expect(schema.strict).to eq(nil) + output = schema.new.to_json_schema + expect(output[:schema]).not_to have_key(:strict) end - it "returns true when set to true" do + it "outputs strict: true when set to true" do schema = Class.new(RubyLLM::Schema) { strict true } - expect(schema.strict).to eq(true) + output = schema.new.to_json_schema + expect(output[:schema][:strict]).to eq(true) end - it "returns false when set to false" do + it "outputs strict: false when set to false" do schema = Class.new(RubyLLM::Schema) { strict false } - expect(schema.strict).to eq(false) + output = schema.new.to_json_schema + expect(output[:schema][:strict]).to eq(false) end end