Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.7.1 (2026-04-22)

### Changed (behavioral, follow-up to v0.7.0)

- **`Step::Base#run_once` no longer swallows adapter-phase `ArgumentError` as `:input_error`.** The previous blanket `rescue ArgumentError` was there to convert DSL misconfiguration (e.g. missing `prompt`) into an `:input_error` Result. Side effect: programmer bugs in adapter code that raised `ArgumentError` (wrong arity, bad config argument) were silently coerced into `:input_error` and retried as if the user had given bad input. Now the rescue is narrowed to the Runner-construction phase only — DSL configuration errors still produce `:input_error` (the `prompt has not been set` case is regression-tested), but `ArgumentError` raised from adapter code during `Runner#call` propagates to the caller. Input-type validation failures continue to produce `:input_error` through `InputValidator`'s own scoped rescue, unchanged.

## 0.7.0 (2026-04-21)

### Breaking changes
Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
ruby_llm-contract (0.7.0)
ruby_llm-contract (0.7.1)
dry-types (~> 1.7)
ruby_llm (~> 1.0)
ruby_llm-schema (~> 0.3)
Expand Down Expand Up @@ -258,7 +258,7 @@ CHECKSUMS
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
ruby_llm (1.14.0) sha256=57c6f7034fc4a44504ea137d70f853b07824f1c1cdbe774ab3ab3522e7098deb
ruby_llm-contract (0.7.0)
ruby_llm-contract (0.7.1)
ruby_llm-schema (0.3.0) sha256=a591edc5ca1b7f0304f0e2261de61ba4b3bea17be09f5cf7558153adfda3dec6
ruby_parser (3.22.0) sha256=1eb4937cd9eb220aa2d194e352a24dba90aef00751e24c8dfffdb14000f15d23
rubycritic (4.12.0) sha256=024fed90fe656fa939f6ea80aab17569699ac3863d0b52fd72cb99892247abc8
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,9 @@ Full procedure with examples: **[Optimizing retry_policy](docs/guide/optimizing_

## Roadmap

**v0.7 (current):** Sharpened retry semantics. `DEFAULT_RETRY_ON` now targets LLM output variance only (`:validation_failed`, `:parse_error`); transport errors are delegated to ruby_llm's Faraday retry. `AdapterCaller` narrowed to let programmer errors propagate instead of masking them as retries. Breaking change — see [CHANGELOG](CHANGELOG.md) for migration.
**v0.7.1 (current):** Follow-up — `Step::Base#run_once` no longer masks adapter-phase `ArgumentError` as `:input_error`. Programmer bugs in adapter code now propagate; DSL misconfiguration still becomes `:input_error` via narrower rescue.

**v0.7.0:** Sharpened retry semantics. `DEFAULT_RETRY_ON` now targets LLM output variance only (`:validation_failed`, `:parse_error`); transport errors are delegated to ruby_llm's Faraday retry. `AdapterCaller` narrowed to let programmer errors propagate instead of masking them as retries. Breaking change — see [CHANGELOG](CHANGELOG.md) for migration.

**v0.6:** "What should I do?" — `Step.recommend` returns optimal model, reasoning effort, and retry chain. Per-attempt `reasoning_effort` in retry policies.

Expand Down
40 changes: 28 additions & 12 deletions lib/ruby_llm/contract/step/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,20 +186,36 @@ def resolve_adapter(context)
"{ |c| c.default_adapter = ... } or pass context: { adapter: ... }"
end

# ADR-0021 deliverable 2: narrow ArgumentError rescue to DSL-setup phase only.
#
# DSL misconfiguration (e.g. `prompt has not been set`, missing required
# attributes) surfaces as ArgumentError when constructing Runner. We catch
# that and return :input_error — these are contract-definition issues the
# caller can handle as "bad input to the step definition".
#
# Runner#call itself does NOT get a blanket rescue: input-type validation
# failures return :input_error from within InputValidator; adapter/runtime
# programmer bugs (NoMethodError, adapter-code ArgumentError) must propagate
# instead of being silently masked as :input_error.
def run_once(input, adapter:, model:, context_temperature: nil, extra_options: {})
effective_temp = context_temperature || temperature
Runner.new(
input_type: input_type, output_type: output_type,
prompt_block: prompt, contract_definition: effective_contract,
adapter: adapter, model: model, output_schema: output_schema,
max_output: max_output, max_input: max_input, max_cost: max_cost,
on_unknown_pricing: on_unknown_pricing,
temperature: effective_temp, extra_options: extra_options,
observers: class_observers
).call(input)
rescue ArgumentError => e
Result.new(status: :input_error, raw_output: nil, parsed_output: nil,
validation_errors: [e.message])
runner =
begin
Runner.new(
input_type: input_type, output_type: output_type,
prompt_block: prompt, contract_definition: effective_contract,
adapter: adapter, model: model, output_schema: output_schema,
max_output: max_output, max_input: max_input, max_cost: max_cost,
on_unknown_pricing: on_unknown_pricing,
temperature: effective_temp, extra_options: extra_options,
observers: class_observers
)
rescue ArgumentError => e
return Result.new(status: :input_error, raw_output: nil, parsed_output: nil,
validation_errors: [e.message])
end

runner.call(input)
end

def log_result(result)
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_llm/contract/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

module RubyLLM
module Contract
VERSION = "0.7.0"
VERSION = "0.7.1"
end
end
26 changes: 26 additions & 0 deletions spec/ruby_llm/contract/step/retry_integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -513,5 +513,31 @@

expect { step.run("hi", context: { adapter: adapter }) }.to raise_error(NoMethodError)
end

# ADR-0021 deliverable 2: programmer ArgumentError from adapter code must
# propagate, not be silently coerced into :input_error by Step::Base#run_once.
# Before the fix, a blanket `rescue ArgumentError` around the whole runner
# chain masked adapter bugs as "bad user input".
it "propagates ArgumentError from adapter code (programmer bug, not bad input)" do
adapter = Object.new
adapter.define_singleton_method(:call) do |**_opts|
raise ArgumentError, "adapter called with wrong arity — this is a bug"
end

step = Class.new(RubyLLM::Contract::Step::Base) { prompt "{input}" }

expect { step.run("hi", context: { adapter: adapter }) }
.to raise_error(ArgumentError, /adapter called with wrong arity/)
end

it "still converts DSL misconfiguration ArgumentError to :input_error (prompt missing)" do
adapter = RubyLLM::Contract::Adapters::Test.new(response: "ok")
step = Class.new(RubyLLM::Contract::Step::Base) { output_type String }

result = step.run("hi", context: { adapter: adapter })

expect(result.status).to eq(:input_error)
expect(result.validation_errors.first).to include("prompt has not been set")
end
end
end