Module: RubyLLM::Contract::Step::Dsl

Included in:
Base
Defined in:
lib/ruby_llm/contract/step/dsl.rb

Overview

Extracted from Base to reduce class length. DSL accessor methods for step definition (input_type, output_type, prompt, etc.).

Constant Summary collapse

UNSET =

Sentinel signalling “explicitly reset” (‘some_attr(:default)`). Distinguishes reset (lookup stops at this class, returns nil) from “never set” (lookup falls through to superclass).

Object.new

Instance Method Summary collapse

Instance Method Details

#around_call(&block) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
# File 'lib/ruby_llm/contract/step/dsl.rb', line 259

def around_call(&block)
  if block
    return @around_call = block
  end

  if defined?(@around_call) && @around_call
    @around_call
  elsif superclass.respond_to?(:around_call)
    superclass.around_call
  end
end

#attachment_token_estimate(n = nil) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/ruby_llm/contract/step/dsl.rb', line 172

def attachment_token_estimate(n = nil)
  if n == :default
    @attachment_token_estimate = UNSET
    return nil
  end

  if n
    validate_positive!("attachment_token_estimate", n)
    return @attachment_token_estimate = n
  end

  inherited_value_with_reset(:attachment_token_estimate)
end

#class_observersObject



122
123
124
125
126
# File 'lib/ruby_llm/contract/step/dsl.rb', line 122

def class_observers
  own = defined?(@class_observers) ? @class_observers : []
  inherited = superclass.respond_to?(:class_observers) ? superclass.class_observers : []
  inherited + own
end

#class_validatesObject



112
113
114
115
116
# File 'lib/ruby_llm/contract/step/dsl.rb', line 112

def class_validates
  own = defined?(@class_validates) ? @class_validates : []
  inherited = superclass.respond_to?(:class_validates) ? superclass.class_validates : []
  inherited + own
end

#contract(&block) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/ruby_llm/contract/step/dsl.rb', line 91

def contract(&block)
  return @contract_definition = Definition.new(&block) if block

  if defined?(@contract_definition) && @contract_definition
    @contract_definition
  elsif superclass.respond_to?(:contract)
    superclass.contract
  else
    Definition.new
  end
end

#inherited_value(name) ⇒ Object

Walks the inheritance chain for a class-level DSL attribute. Returns the first explicitly-set value found, or nil.



18
19
20
21
22
23
# File 'lib/ruby_llm/contract/step/dsl.rb', line 18

def inherited_value(name)
  ivar = :"@#{name}"
  return instance_variable_get(ivar) if instance_variable_defined?(ivar)

  superclass.public_send(name) if superclass.respond_to?(name)
end

#inherited_value_with_reset(name) ⇒ Object

Like ‘inherited_value`, but honours the `UNSET` sentinel — when this class has been reset via `some_attr(:default)`, returns nil without falling through to the superclass.



28
29
30
31
32
33
34
35
36
37
38
# File 'lib/ruby_llm/contract/step/dsl.rb', line 28

def inherited_value_with_reset(name)
  ivar = :"@#{name}"
  if instance_variable_defined?(ivar)
    value = instance_variable_get(ivar)
    return value unless value.equal?(UNSET)

    return nil
  end

  superclass.public_send(name) if superclass.respond_to?(name)
end

#input_type(type = nil) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
# File 'lib/ruby_llm/contract/step/dsl.rb', line 40

def input_type(type = nil)
  return @input_type = type if type

  if defined?(@input_type)
    @input_type
  elsif superclass.respond_to?(:input_type)
    superclass.input_type
  else
    String
  end
end

#max_cost(amount = nil, on_unknown_pricing: nil) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/ruby_llm/contract/step/dsl.rb', line 146

def max_cost(amount = nil, on_unknown_pricing: nil)
  if amount == :default
    @max_cost = UNSET
    @on_unknown_pricing = nil
    return nil
  end

  if amount
    validate_positive!("max_cost", amount)

    if on_unknown_pricing && !%i[refuse warn].include?(on_unknown_pricing)
      raise ArgumentError, "on_unknown_pricing must be :refuse or :warn, got #{on_unknown_pricing.inspect}"
    end

    @max_cost = amount
    @on_unknown_pricing = on_unknown_pricing || :refuse
    return @max_cost
  end

  inherited_value_with_reset(:max_cost)
end

#max_input(tokens = nil) ⇒ Object



137
138
139
140
141
142
143
144
# File 'lib/ruby_llm/contract/step/dsl.rb', line 137

def max_input(tokens = nil)
  if tokens
    validate_positive!("max_input", tokens)
    return @max_input = tokens
  end

  inherited_value(:max_input)
end

#max_output(tokens = nil) ⇒ Object



128
129
130
131
132
133
134
135
# File 'lib/ruby_llm/contract/step/dsl.rb', line 128

def max_output(tokens = nil)
  if tokens
    validate_positive!("max_output", tokens)
    return @max_output = tokens
  end

  inherited_value(:max_output)
end

#model(name = nil) ⇒ Object



199
200
201
202
203
204
205
206
207
208
# File 'lib/ruby_llm/contract/step/dsl.rb', line 199

def model(name = nil)
  if name == :default
    @model = UNSET
    return nil
  end

  return @model = name if name

  inherited_value_with_reset(:model)
end

#observe(description, &block) ⇒ Object



118
119
120
# File 'lib/ruby_llm/contract/step/dsl.rb', line 118

def observe(description, &block)
  (@class_observers ||= []) << Invariant.new(description, block)
end

#on_unknown_attachment_size(mode = nil) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/ruby_llm/contract/step/dsl.rb', line 186

def on_unknown_attachment_size(mode = nil)
  if mode
    unless %i[refuse warn].include?(mode)
      raise ArgumentError,
            "on_unknown_attachment_size must be :refuse or :warn, got #{mode.inspect}"
    end

    return @on_unknown_attachment_size = mode
  end

  inherited_value(:on_unknown_attachment_size) || :refuse
end

#on_unknown_pricingObject



168
169
170
# File 'lib/ruby_llm/contract/step/dsl.rb', line 168

def on_unknown_pricing
  inherited_value(:on_unknown_pricing) || :refuse
end

#output_schema(&block) ⇒ Object



66
67
68
69
70
71
72
73
74
75
# File 'lib/ruby_llm/contract/step/dsl.rb', line 66

def output_schema(&block)
  if block
    require "ruby_llm/schema"
    @output_schema = ::RubyLLM::Schema.create(&block)
  elsif defined?(@output_schema)
    @output_schema
  elsif superclass.respond_to?(:output_schema)
    superclass.output_schema
  end
end

#output_type(type = nil) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/ruby_llm/contract/step/dsl.rb', line 52

def output_type(type = nil)
  return @output_type = type if type

  if defined?(@output_type)
    @output_type
  elsif defined?(@output_schema) && @output_schema
    RubyLLM::Contract::Types::Hash
  elsif superclass.respond_to?(:output_type)
    superclass.output_type
  else
    Hash
  end
end

#prompt(text = nil, &block) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/ruby_llm/contract/step/dsl.rb', line 77

def prompt(text = nil, &block)
  if text
    @prompt_block = proc { user text }
  elsif block
    @prompt_block = block
  elsif defined?(@prompt_block) && @prompt_block
    @prompt_block
  elsif superclass.respond_to?(:prompt)
    superclass.prompt
  else
    raise(ArgumentError, "prompt has not been set")
  end
end

#reasoning_effort(value = nil) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/ruby_llm/contract/step/dsl.rb', line 240

def reasoning_effort(value = nil)
  return (thinking && thinking[:effort]) if value.nil?

  # Alias is scoped to the effort dimension only. `:default` on the
  # alias clears effort but PRESERVES any previously-set budget — the
  # name does not suggest "wipe the whole thinking config." Use the
  # full `thinking(effort: :default)` to clear everything.
  if value == :default
    current_budget = thinking && thinking[:budget]
    if current_budget
      @thinking = { budget: current_budget }
      return nil
    end
    return thinking(effort: :default)
  end

  thinking(effort: value)
end

#retry_policy(models: nil, attempts: nil, retry_on: nil, &block) ⇒ Object



271
272
273
274
275
276
277
278
279
280
281
# File 'lib/ruby_llm/contract/step/dsl.rb', line 271

def retry_policy(models: nil, attempts: nil, retry_on: nil, &block)
  if block || models || attempts || retry_on
    return @retry_policy = RetryPolicy.new(models: models, attempts: attempts, retry_on: retry_on, &block)
  end

  if defined?(@retry_policy) && @retry_policy
    @retry_policy
  elsif superclass.respond_to?(:retry_policy)
    superclass.retry_policy
  end
end

#temperature(value = nil) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/ruby_llm/contract/step/dsl.rb', line 210

def temperature(value = nil)
  if value == :default
    @temperature = UNSET
    return nil
  end

  # NOTE: `value` may be 0 (a legitimate setting); use `nil?` rather
  # than truthiness to distinguish "no arg passed" from "explicit 0".
  unless value.nil?
    unless value.is_a?(Numeric) && value >= 0 && value <= 2
      raise ArgumentError, "temperature must be 0.0-2.0, got #{value}"
    end

    return @temperature = value
  end

  inherited_value_with_reset(:temperature)
end

#thinking(effort: nil, budget: nil) ⇒ Object



229
230
231
232
233
234
235
236
237
238
# File 'lib/ruby_llm/contract/step/dsl.rb', line 229

def thinking(effort: nil, budget: nil)
  if effort == :default
    @thinking = UNSET
    return nil
  end

  return @thinking = { effort: effort, budget: budget }.compact if effort || budget

  inherited_value_with_reset(:thinking)
end

#validate(description, &block) ⇒ Object

Raises:

  • (ArgumentError)


103
104
105
106
107
108
109
110
# File 'lib/ruby_llm/contract/step/dsl.rb', line 103

def validate(description, &block)
  # `nil.to_s.empty?` is already true - the explicit nil branch was
  # redundant. `caller` pushes the backtrace to user code instead
  # of DSL internals (per Codex review of 0.10.0).
  raise ArgumentError, "validate description must be a non-empty string", caller if description.to_s.empty?

  (@class_validates ||= []) << Invariant.new(description, block)
end