ruby_llm-contract 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2803caa3f400a1c3ca1f2a3c062bf1e79080d25f4dd3d9a652300007eb363d21
4
- data.tar.gz: 27cfd8b34f7ddeb2e2af72142c2d38994a84cd50a2ecd494c173d9fdaf220329
3
+ metadata.gz: c8111e9c77abab44b943129932bf6f225f619c930568bfe848c96b916cb72224
4
+ data.tar.gz: 4d54273d0b0dfd2fc4a44a34ff681aaa767956bbe89230659e975a91bf2a3821
5
5
  SHA512:
6
- metadata.gz: f587bc603e2270a6e96979a62421bb0ac45cf68ae1d0ca53518bca6e394a72fd6ab2b8cb3622e0f98104fa0212aa3fe45fb5814a2aea6560b0ae2ae57a2f3041
7
- data.tar.gz: b6dd0147f09e8936ac2198f95a26188913095cbb26d3a073a233c7822cb5441b04567ae88a1c68dacd79a792fe84e6479fd771282c1a51bfedb3fa0609ff0ebb
6
+ metadata.gz: d5226bdd266a5676bbf4bef0b71580387825f15eb77e9739426e6845b62d6410d22583d2f3f3ad47d2eacca8980fb0763a8a23bf1c1f04c723cae5d4f021ed68
7
+ data.tar.gz: 2ee39172b68a94fd8dae1b633d17cc1e0bfac4f82413a0d4233d50f1851b860527248887ca35ebf88ef6b1fc69de78f6fc2775712c75e272891f36d44ba30f57
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.2 (2026-03-23)
4
+
5
+ Fixes from first real-world integration (persona_tool).
6
+
7
+ - **`around_call` fires per-run** — not per-attempt. With retry_policy, callback fires once with final result. Signature: `around_call { |step, input, result| ... }`
8
+ - **`Result#trace` always `Trace` object** — never bare Hash. `result.trace.model` works on success AND failure.
9
+ - **`around_call` exception safe** — warns and returns result instead of crashing.
10
+ - **`model` DSL** — `model "gpt-4o-mini"` per-step. Priority: context > step DSL > global config.
11
+ - **Test adapter `raw_output` always String** — Hash/Array normalized to `.to_json`.
12
+ - **`Trace#dig`** — `trace.dig(:usage, :input_tokens)` works.
13
+
3
14
  ## 0.2.1 (2026-03-23)
4
15
 
5
16
  Production DX improvements from first real-world integration (persona_tool).
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby_llm-contract (0.2.1)
4
+ ruby_llm-contract (0.2.2)
5
5
  dry-types (~> 1.7)
6
6
  ruby_llm (~> 1.0)
7
7
  ruby_llm-schema (~> 0.3)
@@ -165,7 +165,7 @@ CHECKSUMS
165
165
  rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
166
166
  ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
167
167
  ruby_llm (1.14.0) sha256=57c6f7034fc4a44504ea137d70f853b07824f1c1cdbe774ab3ab3522e7098deb
168
- ruby_llm-contract (0.2.1)
168
+ ruby_llm-contract (0.2.2)
169
169
  ruby_llm-schema (0.3.0) sha256=a591edc5ca1b7f0304f0e2261de61ba4b3bea17be09f5cf7558153adfda3dec6
170
170
  unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
171
171
  unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
@@ -20,7 +20,7 @@ module RubyLLM
20
20
 
21
21
  def normalize_response(response)
22
22
  case response
23
- when Hash, Array then response
23
+ when Hash, Array then response.to_json
24
24
  when nil then ""
25
25
  else response.to_s
26
26
  end
@@ -25,6 +25,13 @@ module RubyLLM
25
25
  public_send(key)
26
26
  end
27
27
 
28
+ def dig(key, *rest)
29
+ value = self[key]
30
+ return value if rest.empty? || value.nil?
31
+
32
+ value.dig(*rest)
33
+ end
34
+
28
35
  def to_h
29
36
  { trace_id: @trace_id, total_latency_ms: @total_latency_ms,
30
37
  total_usage: @total_usage, step_traces: @step_traces,
@@ -63,14 +63,16 @@ module RubyLLM
63
63
  def run(input, context: {})
64
64
  warn_unknown_context_keys(context)
65
65
  adapter = resolve_adapter(context)
66
- default_model = context[:model] || RubyLLM::Contract.configuration.default_model
66
+ default_model = context[:model] || model || RubyLLM::Contract.configuration.default_model
67
67
  policy = retry_policy
68
68
 
69
- if policy
70
- run_with_retry(input, adapter: adapter, default_model: default_model, policy: policy)
71
- else
72
- run_once(input, adapter: adapter, model: default_model, context_temperature: context[:temperature])
73
- end
69
+ result = if policy
70
+ run_with_retry(input, adapter: adapter, default_model: default_model, policy: policy)
71
+ else
72
+ run_once(input, adapter: adapter, model: default_model, context_temperature: context[:temperature])
73
+ end
74
+
75
+ invoke_around_call(input, result)
74
76
  end
75
77
 
76
78
  def build_messages(input)
@@ -101,24 +103,28 @@ module RubyLLM
101
103
 
102
104
  def run_once(input, adapter:, model:, context_temperature: nil)
103
105
  effective_temp = context_temperature || temperature
104
- runner = Runner.new(
106
+ Runner.new(
105
107
  input_type: input_type, output_type: output_type,
106
108
  prompt_block: prompt, contract_definition: effective_contract,
107
109
  adapter: adapter, model: model, output_schema: output_schema,
108
110
  max_output: max_output, max_input: max_input, max_cost: max_cost,
109
111
  temperature: effective_temp
110
- )
111
-
112
- if around_call
113
- around_call.call(self, input) { runner.call(input) }
114
- else
115
- runner.call(input)
116
- end
112
+ ).call(input)
117
113
  rescue ArgumentError => e
118
114
  Result.new(status: :input_error, raw_output: nil, parsed_output: nil,
119
115
  validation_errors: [e.message])
120
116
  end
121
117
 
118
+ def invoke_around_call(input, result)
119
+ return result unless around_call
120
+
121
+ around_call.call(self, input, result)
122
+ result
123
+ rescue StandardError => e
124
+ warn "[ruby_llm-contract] around_call raised #{e.class}: #{e.message}"
125
+ result
126
+ end
127
+
122
128
  def effective_contract
123
129
  base = contract
124
130
  extra = class_validates
@@ -127,6 +127,18 @@ module RubyLLM
127
127
  end
128
128
  end
129
129
 
130
+ def model(name = nil)
131
+ if name
132
+ return @model = name
133
+ end
134
+
135
+ if defined?(@model)
136
+ @model
137
+ elsif superclass.respond_to?(:model)
138
+ superclass.model
139
+ end
140
+ end
141
+
130
142
  def temperature(value = nil)
131
143
  if value
132
144
  unless value.is_a?(Numeric) && value >= 0 && value <= 2
@@ -6,12 +6,12 @@ module RubyLLM
6
6
  class Result
7
7
  attr_reader :status, :raw_output, :parsed_output, :validation_errors, :trace
8
8
 
9
- def initialize(status:, raw_output:, parsed_output:, validation_errors: [], trace: {})
9
+ def initialize(status:, raw_output:, parsed_output:, validation_errors: [], trace: nil)
10
10
  @status = status
11
11
  @raw_output = raw_output
12
12
  @parsed_output = parsed_output
13
13
  @validation_errors = validation_errors.freeze
14
- @trace = trace.freeze
14
+ @trace = normalize_trace(trace)
15
15
  freeze
16
16
  end
17
17
 
@@ -23,6 +23,19 @@ module RubyLLM
23
23
  @status != :ok
24
24
  end
25
25
 
26
+ private
27
+
28
+ def normalize_trace(trace)
29
+ case trace
30
+ when Trace then trace
31
+ when Hash then Trace.new(**trace)
32
+ when nil then Trace.new
33
+ else trace
34
+ end.freeze
35
+ end
36
+
37
+ public
38
+
26
39
  def to_s
27
40
  if ok?
28
41
  "#{@status} (#{@trace})"
@@ -26,6 +26,13 @@ module RubyLLM
26
26
  public_send(key)
27
27
  end
28
28
 
29
+ def dig(key, *rest)
30
+ value = self[key]
31
+ return value if rest.empty? || value.nil?
32
+
33
+ value.dig(*rest)
34
+ end
35
+
29
36
  def key?(key)
30
37
  KNOWN_KEYS.include?(key.to_sym) && !public_send(key).nil?
31
38
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyLLM
4
4
  module Contract
5
- VERSION = "0.2.1"
5
+ VERSION = "0.2.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm-contract
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justyna