Skip to content

Commit 1902480

Browse files
committed
Fix reference in objects
1 parent 526f76e commit 1902480

File tree

3 files changed

+80
-13
lines changed

3 files changed

+80
-13
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,14 @@ class MySchema < RubyLLM::Schema
271271
string :longitude
272272
end
273273

274+
# Using a reference in an array
274275
array :coordinates, of: :location
275-
276-
object :home_location do
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
277282
reference :location
278283
end
279284
end

lib/ruby_llm/schema/dsl.rb

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ def null(name = nil, description: nil, required: true)
4141
end
4242

4343
# 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)
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)
4646
end
4747

4848
def array(name, of: nil, description: nil, required: true, min_items: nil, max_items: nil, &block)
@@ -123,16 +123,26 @@ def build_property_schema(type, **options, &)
123123
when :null
124124
{type: "null", description: options[:description]}.compact
125125
when :object
126-
sub_schema = Class.new(Schema)
127-
sub_schema.class_eval(&)
126+
# If the reference option is provided, return the reference
127+
return reference(options[:reference]) if options[:reference]
128128

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
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
136146
when :any_of
137147
schemas = collect_property_schemas_from_block(&)
138148
{

spec/ruby_llm/schema_spec.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,58 @@
220220
object_schema = any_of_schemas.find { |s| s[:type] == "object" }
221221
expect(object_schema[:properties][:nested_field]).to eq({type: "string"})
222222
end
223+
224+
it "supports reference to a defined schema by block" do
225+
schema_class.define :address do
226+
string :street
227+
string :city
228+
end
229+
230+
schema_class.object :user do
231+
string :name
232+
object :address do
233+
reference :address
234+
end
235+
end
236+
237+
instance = schema_class.new
238+
json_output = instance.to_json_schema
239+
240+
expect(json_output[:schema][:properties][:user][:properties][:address]).to eq({"$ref" => "#/$defs/address"})
241+
expect(json_output[:schema]["$defs"][:address]).to eq({
242+
type: "object",
243+
properties: {
244+
street: {type: "string"},
245+
city: {type: "string"}
246+
},
247+
required: %i[street city]
248+
})
249+
end
250+
251+
it "supports reference to a defined schema by reference option" do
252+
schema_class.define :address do
253+
string :street
254+
string :city
255+
end
256+
257+
schema_class.object :user do
258+
string :name
259+
object :address, reference: :address
260+
end
261+
262+
instance = schema_class.new
263+
json_output = instance.to_json_schema
264+
265+
expect(json_output[:schema][:properties][:user][:properties][:address]).to eq({"$ref" => "#/$defs/address"})
266+
expect(json_output[:schema]["$defs"][:address]).to eq({
267+
type: "object",
268+
properties: {
269+
street: {type: "string"},
270+
city: {type: "string"}
271+
},
272+
required: %i[street city]
273+
})
274+
end
223275
end
224276

225277
# ===========================================

0 commit comments

Comments
 (0)