Class: OpenRouter::Schema

Inherits:
Object
  • Object
show all
Defined in:
lib/open_router/schema.rb

Defined Under Namespace

Classes: ItemsBuilder, SchemaBuilder

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, schema_definition = {}, strict: true) ⇒ Schema

Returns a new instance of Schema.

Raises:

  • (ArgumentError)


15
16
17
18
19
20
21
22
# File 'lib/open_router/schema.rb', line 15

def initialize(name, schema_definition = {}, strict: true)
  @name = name
  @strict = strict
  raise ArgumentError, "Schema definition must be a hash" unless schema_definition.is_a?(Hash)

  @schema = schema_definition
  validate_schema!
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



13
14
15
# File 'lib/open_router/schema.rb', line 13

def name
  @name
end

#schemaObject (readonly)

Returns the value of attribute schema.



13
14
15
# File 'lib/open_router/schema.rb', line 13

def schema
  @schema
end

#strictObject (readonly)

Returns the value of attribute strict.



13
14
15
# File 'lib/open_router/schema.rb', line 13

def strict
  @strict
end

Class Method Details

.define(name, strict: true, &block) ⇒ Object

Class method for defining schemas with a DSL



25
26
27
28
29
# File 'lib/open_router/schema.rb', line 25

def self.define(name, strict: true, &block)
  builder = SchemaBuilder.new
  builder.instance_eval(&block) if block_given?
  new(name, builder.to_h, strict:)
end

Instance Method Details

#enforce_all_required(node) ⇒ Object

OpenRouter / OpenAI strict mode requires EVERY object — at every nesting level, including nested objects and array items — to list all of its properties in its required array. Forcing this only at the top level makes nested objects come back with required: [], which strict providers reject with a 400. Walk the schema and enforce it recursively.

(Optional fields are expressed in strict mode by adding "null" to the property's type union, not by omitting them from required.)



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/open_router/schema.rb', line 48

def enforce_all_required(node)
  case node
  when Hash
    transformed = node.each_with_object({}) { |(key, value), acc| acc[key] = enforce_all_required(value) }

    props = transformed[:properties] || transformed["properties"]
    if props.is_a?(Hash) && props.any?
      key = transformed.key?(:properties) ? :required : "required"
      transformed[key] = props.keys.map(&:to_s)
    end

    transformed
  when Array
    node.map { |element| enforce_all_required(element) }
  else
    node
  end
end

#get_format_instructions(forced: false) ⇒ Object

Generate format instructions for model prompting



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/open_router/schema.rb', line 96

def get_format_instructions(forced: false)
  schema_json = to_h.to_json

  if forced
    <<~INSTRUCTIONS
      You must format your output as a JSON value that conforms exactly to the following JSON Schema specification:

      #{schema_json}

      CRITICAL: Your entire response must be valid JSON that matches this schema. Do not include any text before or after the JSON. Return ONLY the JSON value itself - no other text, explanations, or formatting.

      example format:
      ```json
      {"field1": "value1", "field2": "value2"}
      ```

      Important guidelines:
      - Ensure all required fields match the schema exactly
      - Use proper JSON formatting (no trailing commas)
      - All string values must be properly quoted
    INSTRUCTIONS
  else
    <<~INSTRUCTIONS
      Please format your output as a JSON value that conforms to the following JSON Schema specification:

      #{schema_json}

      Your response should be valid JSON that matches this schema structure exactly.

      example format:
      ```json
      {"field1": "value1", "field2": "value2"}
      ```

      Important guidelines:
      - Ensure all required fields match the schema
      - Use proper JSON formatting (no trailing commas)
      - Return ONLY the JSON - no other text or explanations
    INSTRUCTIONS
  end
end

#pure_schemaObject

Get the pure JSON Schema (respects required flags) for testing/validation



68
69
70
# File 'lib/open_router/schema.rb', line 68

def pure_schema
  @schema
end

#to_hObject

Convert to the format expected by OpenRouter API



32
33
34
35
36
37
38
# File 'lib/open_router/schema.rb', line 32

def to_h
  {
    name: @name,
    strict: @strict,
    schema: enforce_all_required(@schema)
  }
end

#to_json(*args) ⇒ Object



72
73
74
# File 'lib/open_router/schema.rb', line 72

def to_json(*args)
  to_h.to_json(*args)
end

#validate(data) ⇒ Object

Validate data against this schema



82
83
84
85
86
# File 'lib/open_router/schema.rb', line 82

def validate(data)
  return true unless defined?(JSON::Validator)

  JSON::Validator.validate(@schema, data)
end

#validation_available?Boolean

Check if JSON schema validation is available

Returns:

  • (Boolean)


77
78
79
# File 'lib/open_router/schema.rb', line 77

def validation_available?
  !!defined?(JSON::Validator)
end

#validation_errors(data) ⇒ Object

Get validation errors for data



89
90
91
92
93
# File 'lib/open_router/schema.rb', line 89

def validation_errors(data)
  return [] unless defined?(JSON::Validator)

  JSON::Validator.fully_validate(@schema, data)
end