lex-llm 0.6.9 → 0.6.10

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: d325676c4cc272b839d2e0ba4d678a1aab7ec05268e5e7d37ac302234d25ae4e
4
- data.tar.gz: a8a2475af20b60ea5d53dc2c5da7ceb0ea2f25d8c094079df73b41cce04ea577
3
+ metadata.gz: e441fc6b1e568e1e7cde529aecb14655cfc6df845accdfbf706cade536faa25e
4
+ data.tar.gz: fa14725026a56fe266e5c7b4be232fc1b750bf35293830514a6ad103b1da3abc
5
5
  SHA512:
6
- metadata.gz: 5535f26e2755347ca1024c662eeb7e575515bfa824fe36a2f3958558d21548f393b426f0261a35f3935ccd222099ad8dedaef614af693a0b2d0a74e2353e91ae
7
- data.tar.gz: 59fd2029653e72366a5384d36f34639348b12ecba38ba3e2ad19ff0a20e5ffcf6d7ea434e02fcfc8994fa0f373264efac90d532800cd3ae14f2036f10f9f64c8
6
+ metadata.gz: f14ac394687d2059d90562529518085a4cced1732e9baa85c5847ad2fffd4ea89efeb735167ebae6533d4b335f9ddd7fcc8982c0bdac137add3d35a48010b19e
7
+ data.tar.gz: ed81c83224bdcbbf18b0b4fc6f8b9e2bf0886ed44d80a2dde363d4920a3a4957d4f56f34429b6ea7b7fd5ba96fd4a5b5e60fb9bc0e59cca556b1ed11e7314080
data/.rubocop.yml CHANGED
@@ -51,3 +51,6 @@ RSpec/LeakyLocalVariable:
51
51
  RSpec/SpecFilePathFormat:
52
52
  Exclude:
53
53
  - 'spec/legion/extensions/llm/conformance/**/*'
54
+
55
+ Style/ArrayIntersect:
56
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.10 - 2026-07-09
4
+
5
+ ### Added
6
+ - `ThinkingConfig#resolved_budget` and `#resolved_effort` — cross-axis derivation so provider translators get whichever thinking axis they need (Anthropic `budget_tokens` vs OpenAI `effort`) even when the client supplied only the other. A client dialect supplies only one axis (Anthropic = budget only; OpenAI = effort only), so a single `EFFORT_BUDGET` map (SSOT) drives the conversion in both directions: effort -> budget is exact (`low` = 1024, `medium` = 8192, `high` = 16384), budget -> effort maps by band boundary. Explicitly-set values always win over derivation, and both accessors return `nil` when neither axis is configured. `to_h` stays faithful to what was actually set (no fabrication). This is the foundation for fixing thinking being silently dropped on cross-provider routes (e.g. a Claude budget request routed to OpenAI effort).
7
+
3
8
  ## 0.6.9 - 2026-07-05
4
9
 
5
10
  ### Added
@@ -50,6 +50,16 @@ module Legion
50
50
  # Mirrors lex-llm Thinking::Config.
51
51
  class ThinkingConfig
52
52
  INCLUDES = Thinking
53
+
54
+ # SSOT for the effort<->budget conversion. A client dialect supplies only
55
+ # ONE axis (Anthropic = budget_tokens only; OpenAI = effort only), but a
56
+ # provider translator may need the OTHER. This single map lets every
57
+ # provider ask for whichever axis it needs and always get a usable value,
58
+ # so thinking survives any client x provider pair (best-effort, never
59
+ # silently dropped). effort -> budget is exact; budget -> effort uses the
60
+ # band boundaries below.
61
+ EFFORT_BUDGET = { 'low' => 1024, 'medium' => 8192, 'high' => 16_384 }.freeze
62
+
53
63
  attr_reader :effort, :budget
54
64
 
55
65
  def initialize(effort: nil, budget: nil)
@@ -70,7 +80,8 @@ module Legion
70
80
  build(effort: h[:effort], budget: h[:budget])
71
81
  end
72
82
 
73
- # Serialize to a Hash for AMQP/fleet/wire transport.
83
+ # Serialize to a Hash for AMQP/fleet/wire transport. Faithful to what was
84
+ # SET — never fabricates the missing axis (use resolved_* for that).
74
85
  def to_h
75
86
  { effort: effort, budget: budget }.compact
76
87
  end
@@ -79,6 +90,30 @@ module Legion
79
90
  def enabled?
80
91
  !effort.nil? || !budget.nil?
81
92
  end
93
+
94
+ # Budget for a provider that needs a token budget (e.g. Anthropic),
95
+ # derived from effort when budget was not explicitly set. nil only when
96
+ # neither axis is configured.
97
+ def resolved_budget
98
+ return budget unless budget.nil?
99
+ return nil if effort.nil?
100
+
101
+ EFFORT_BUDGET[effort.to_s.downcase] || EFFORT_BUDGET['medium']
102
+ end
103
+
104
+ # Effort for a provider that needs an effort level (e.g. OpenAI),
105
+ # derived from budget when effort was not explicitly set. nil only when
106
+ # neither axis is configured.
107
+ def resolved_effort
108
+ return effort unless effort.nil?
109
+ return nil if budget.nil?
110
+
111
+ b = budget.to_i
112
+ if b < EFFORT_BUDGET['medium'] then 'low'
113
+ elsif b < EFFORT_BUDGET['high'] then 'medium'
114
+ else 'high'
115
+ end
116
+ end
82
117
  end
83
118
 
84
119
  # Alias for convenience: Canonical::Thinking::Config
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Llm
6
- VERSION = '0.6.9'
6
+ VERSION = '0.6.10'
7
7
  end
8
8
  end
9
9
  end
@@ -147,5 +147,43 @@ RSpec.describe Legion::Extensions::Llm::Canonical::Thinking do
147
147
  expect(config.to_h).to eq(effort: 'low')
148
148
  end
149
149
  end
150
+
151
+ # SSOT / one-oracle for cross-provider thinking (NxN best-effort): a client
152
+ # dialect supplies only ONE axis (Anthropic = budget_tokens only, OpenAI =
153
+ # effort only). Every provider translator must still get a usable value for
154
+ # whichever axis IT needs, so thinking survives ANY client×provider pair
155
+ # instead of being silently dropped. The conversion lives HERE (the canonical
156
+ # config), not scattered/one-directional in each translator. Derived accessors
157
+ # do NOT mutate stored state or #to_h — they only fill the gap on read.
158
+ describe 'derived cross-axis accessors' do
159
+ it 'derives budget from effort when only effort was set' do
160
+ expect(config_class.build(effort: 'low').resolved_budget).to eq(1024)
161
+ expect(config_class.build(effort: 'medium').resolved_budget).to eq(8192)
162
+ expect(config_class.build(effort: 'high').resolved_budget).to eq(16_384)
163
+ end
164
+
165
+ it 'derives effort from budget by band (< medium=low, < high=medium, else high)' do
166
+ expect(config_class.build(budget: 500).resolved_effort).to eq('low') # < 8192
167
+ expect(config_class.build(budget: 10_000).resolved_effort).to eq('medium') # 8192..16383
168
+ expect(config_class.build(budget: 20_000).resolved_effort).to eq('high') # >= 16384
169
+ end
170
+
171
+ it 'prefers the explicitly-set value over derivation' do
172
+ c = config_class.build(effort: 'high', budget: 2048)
173
+ expect(c.resolved_budget).to eq(2048)
174
+ expect(c.resolved_effort).to eq('high')
175
+ end
176
+
177
+ it 'returns nil derivations when neither axis is set' do
178
+ c = config_class.build
179
+ expect(c.resolved_budget).to be_nil
180
+ expect(c.resolved_effort).to be_nil
181
+ end
182
+
183
+ it 'does not fabricate the missing axis in to_h (stored state stays faithful)' do
184
+ expect(config_class.build(effort: 'high').to_h).to eq(effort: 'high')
185
+ expect(config_class.build(budget: 2048).to_h).to eq(budget: 2048)
186
+ end
187
+ end
150
188
  end
151
189
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.9
4
+ version: 0.6.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO