lex-llm-anthropic 0.2.13 → 0.2.15

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: 7ea542d2f08da7b03e8f42e683dca5638bdaf07966f3389cdfcccfa22f8581b0
4
- data.tar.gz: b0e902241e5dd1c94c3742f9cee76d4aa2a41516829ce5c481084e70378826cc
3
+ metadata.gz: b5592d5457b4e1cc4b1b64b4133ca43f99a6de148044d70606c4dfa794b724e0
4
+ data.tar.gz: b05ebe4ac3373d36e6fd2ad71716c372a50ac9c50641e88ac710cdb66ba6c261
5
5
  SHA512:
6
- metadata.gz: 333cff908f11b23fe522c2bb79743d879b5adf0d7ce952e0b989becb9da775b58a528ba7f52ec42b7f7ef188d4d7b3db29db9700df4b17e0435c0e4de0087b1c
7
- data.tar.gz: 57ad771fb6b041d74e1459bb46e5cd471f8f37f416b014bcc6873f97aca3fe0aea74c7a943ddfb3d8418bbf6ade69a0cda8fa6cc755134df53eabf9abeaaf1f1
6
+ metadata.gz: a123eae076f23634c8ce0437bbdc382b53e7cdecfec5357441bf4b92b73285959911076a525bf584c726f30aceb35b4d3f9fb275039adfb7c2bd02f5fc721a50
7
+ data.tar.gz: 131feec5dbb20ec692f3f2e5f8bf83b1b777e16ae7c6798e8931410449eb31be9008b89e0f3ba5f8e27da6d72003de0bf639b2f37b3525b67f5fa72f3f3fb992
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.15 - 2026-06-05
4
+
5
+ - **Fix RuboCop cyclomatic complexity** — Extract `extract_hash_budget` helper to reduce `thinking_budget` cyclomatic complexity from 8 to 6, meeting the 7-line threshold.
6
+ - **Add budget_tokens support** — `extract_hash_budget` now checks `:budget_tokens` and `'budget_tokens'` keys (Anthropic API canonical) in addition to legacy `:budget`/`'budget'`.
7
+ - **Spec and RuboCop compliance** — All 28 specs passing, 0 RuboCop offenses.
8
+
9
+ ## 0.2.14 - 2026-06-05
10
+
11
+ - **Fix RuboCop cyclomatic complexity** — Extract `extract_hash_budget` helper to reduce `thinking_budget` complexity from 8 to 6, meeting the 7-line threshold.
12
+ - **Fix Style/IfUnlessModifier** — Split conditional return in `thinking_budget` to avoid modifier form exceeding max line length.
13
+
3
14
  ## 0.2.13 - 2026-06-02
4
15
 
5
16
  - **Fix invalid anthropic-version header** — Default `api_version` was `'2023-10-02'` (typo), which Anthropic rejects. Changed to `'2023-10-16'` (anthropic.rb)
data/README.md CHANGED
@@ -1,10 +1,36 @@
1
1
  # lex-llm-anthropic
2
2
 
3
- LegionIO LLM provider extension for Anthropic.
3
+ LegionIO LLM provider extension for **Anthropic Claude** models.
4
4
 
5
- This gem lives under `Legion::Extensions::Llm::Anthropic` and depends on `lex-llm >= 0.4.3` for shared provider contracts, response normalization, fleet responder helpers, and schema primitives. It does not require `legion-llm` at runtime.
5
+ This gem provides the `:anthropic` provider family for LegionIO's LLM routing layer, connecting to the [Anthropic Messages API](https://docs.anthropic.com/en/docs/api/messages). It handles chat completions, streaming, tool use, extended thinking, model discovery, and fleet request consumption.
6
6
 
7
- Load it with `require 'legion/extensions/llm/anthropic'`.
7
+ **Namespace:** `Legion::Extensions::Llm::Anthropic`
8
+ **Provider family:** `:anthropic`
9
+ **Default model:** `claude-sonnet-4-6`
10
+ **Dependency:** `lex-llm >= 0.4.3` (shared provider contracts, response normalization, fleet responders)
11
+
12
+ ```ruby
13
+ require 'legion/extensions/llm/anthropic'
14
+ ```
15
+
16
+ ---
17
+
18
+ ## File Index
19
+
20
+ | File | Role |
21
+ |------|------|
22
+ | `anthropic.rb` | Entry point; namespace, `PROVIDER_FAMILY`, default settings, instance discovery |
23
+ | `anthropic/provider.rb` | `Provider` class — chat, stream, list_models, format/parse payloads |
24
+ | `anthropic/registry_event_builder.rb` | Builds registry event envelopes for model availability |
25
+ | `anthropic/registry_publisher.rb` | Publishes model events to `llm.registry` exchange (async, best-effort) |
26
+ | `anthropic/version.rb` | `VERSION` constant |
27
+ | `anthropic/actors/discovery_refresh.rb` | Periodic actor that refreshes Anthropic model list |
28
+ | `anthropic/actors/fleet_worker.rb` | Subscription actor for fleet request consumption |
29
+ | `anthropic/runners/fleet_worker.rb` | Runner entrypoint for fleet request execution |
30
+ | `anthropic/transport/exchanges/llm_registry.rb` | Topic exchange definition for `llm.registry` |
31
+ | `anthropic/transport/messages/registry_event.rb` | Transport message wrapper for registry events |
32
+
33
+ ---
8
34
 
9
35
  ## Installation
10
36
 
@@ -12,36 +38,83 @@ Load it with `require 'legion/extensions/llm/anthropic'`.
12
38
  gem 'lex-llm-anthropic', '~> 0.2'
13
39
  ```
14
40
 
15
- Anthropic credentials are discovered from `ANTHROPIC_API_KEY`, Claude config, configured provider instances, or an identity broker when one is available.
41
+ ---
16
42
 
17
- ## Provider
43
+ ## Architecture
18
44
 
19
- `Legion::Extensions::Llm::Anthropic::Provider` registers with `Legion::Extensions::Llm::Provider` as `:anthropic` and uses Anthropic's Messages API:
45
+ ### Provider (`Provider`)
20
46
 
21
- - chat and streaming: `/v1/messages`
22
- - model discovery: `/v1/models`
23
- - authentication headers: `x-api-key` and `anthropic-version`
24
- - tools: Anthropic `tools` and `tool_choice` payload fields
25
- - extended thinking: Anthropic `thinking` request field and returned thinking blocks
47
+ The `Provider` class extends the `lex-llm` base provider contract and implements:
26
48
 
27
- Anthropic embeddings are intentionally not exposed by this provider.
49
+ | Method | Description |
50
+ |--------|-------------|
51
+ | `chat(**kwargs)` | Synchronous chat completion via `/v1/messages` |
52
+ | `stream_chat(**kwargs)` | Streaming chat via `/v1/messages?stream=true` |
53
+ | `list_models` | Fetches available models from `/v1/models` |
54
+ | `format_payload(**)` | Builds Anthropic Messages API request body |
55
+ | `parse_response(response)` | Normalizes API response to Legion envelope |
56
+ | `parse_stream(response)` | Parses SSE stream into chunk events |
57
+ | `build_chunk(event, state)` | Accumulates streaming state (content, tool calls, thinking) |
28
58
 
29
- ## Configuration
59
+ **Supported capabilities:** `:completion`, `:streaming`, `:vision`, `:tools`
60
+
61
+ **API endpoints:**
62
+ - Chat & streaming: `POST /v1/messages`
63
+ - Model discovery: `GET /v1/models`
64
+
65
+ **Authentication headers:** `x-api-key`, `anthropic-version`
66
+
67
+ ### Instance Discovery (`discover_instances`)
68
+
69
+ The extension discovers and normalizes Anthropic credentials from four sources, in priority order:
70
+
71
+ 1. **Environment** — `ANTHROPIC_API_KEY`
72
+ 2. **Claude config** — `~/.claude/settings.json` under `anthropicApiKey`
73
+ 3. **Extension settings** — `Legion::Settings` at `extensions.llm.anthropic`
74
+ 4. **Identity broker** — `Legion::Identity::Broker.credential_for(:anthropic)`
75
+
76
+ Named instances are supported under `extensions.llm.anthropic.instances.<name>`. Generic keys (`api_key`, `endpoint`, `version`) are normalized to Anthropic-specific keys (`anthropic_api_key`, `anthropic_api_base`, `anthropic_version`). Duplicate credentials are deduplicated by fingerprint.
77
+
78
+ ### Default Settings
30
79
 
31
80
  ```ruby
32
- Legion::Extensions::Llm.configure do |config|
33
- config.anthropic_api_key = ENV.fetch('ANTHROPIC_API_KEY')
34
- config.anthropic_version = '2023-06-01'
35
- end
81
+ {
82
+ default_model: 'claude-sonnet-4-6',
83
+ endpoint: 'https://api.anthropic.com',
84
+ api_version: '2023-10-16',
85
+ default_max_tokens: 4096,
86
+ tier: :frontier,
87
+ transport: :http,
88
+ credentials: { api_key: 'env://ANTHROPIC_API_KEY' },
89
+ usage: { inference: true, embedding: false, image: false },
90
+ limits: { concurrency: 4 },
91
+ fleet: {
92
+ enabled: false,
93
+ respond_to_requests: false,
94
+ capabilities: %i[chat stream_chat],
95
+ lanes: [],
96
+ concurrency: 4,
97
+ queue_suffix: nil
98
+ }
99
+ }
36
100
  ```
37
101
 
38
- `anthropic_api_base` can override the default `https://api.anthropic.com` endpoint for tests or compatible Anthropic gateways.
102
+ ### Registry Events (Availability Publishing)
103
+
104
+ After model discovery, discovered models are published to the `llm.registry` topic exchange so other LegionIO nodes can discover Anthropic availability.
105
+
106
+ | Class | Role |
107
+ |-------|------|
108
+ | `RegistryEventBuilder` | Builds the event envelope with model offering, health, and metadata |
109
+ | `RegistryPublisher` | Schedules async publish; checks transport readiness before sending |
110
+ | `Transport::Messages::RegistryEvent` | Message wrapper targeting the `llm.registry` exchange |
111
+ | `Transport::Exchanges::LlmRegistry` | Topic exchange definition (`llm.registry`) |
39
112
 
40
- Named instances can use generic provider keys such as `api_key`, `endpoint`, and `version`; the extension normalizes them to Anthropic-specific provider options during discovery.
113
+ Publishing is **best-effort** and requires transport to be loaded. Failures are logged at `debug` level and silently absorbed.
41
114
 
42
- ## Fleet Responder
115
+ ### Fleet Responder
43
116
 
44
- Provider instances can opt in to consuming Legion LLM fleet requests. The provider-owned fleet actor only starts when at least one configured instance enables `respond_to_requests`.
117
+ Provider instances can consume Legion LLM fleet requests. The fleet actor only starts when at least one configured instance enables `respond_to_requests`.
45
118
 
46
119
  ```yaml
47
120
  extensions:
@@ -57,4 +130,103 @@ extensions:
57
130
  - stream_chat
58
131
  ```
59
132
 
60
- Fleet execution is delegated to `Legion::Extensions::Llm::Fleet::ProviderResponder` from `lex-llm`. The responder owns provider invocation for this gem; routing and fleet request publication remain outside this provider extension.
133
+ | Component | Role |
134
+ |-----------|------|
135
+ | `Actor::FleetWorker` | Subscription actor; checks if any instance enables fleet responding |
136
+ | `Runners::FleetWorker` | Runner entrypoint; delegates to `lex-llm` `ProviderResponder` |
137
+
138
+ Fleet execution is delegated to `Legion::Extensions::Llm::Fleet::ProviderResponder` from `lex-llm`. Routing and fleet request publication are handled outside this extension.
139
+
140
+ ### Model Discovery Refresh
141
+
142
+ A periodic actor (`Actor::DiscoveryRefresh`) refreshes the Anthropic model list every 30 minutes (configurable via `extensions.llm.anthropic.discovery_interval`). It calls `Legion::LLM::Discovery.refresh_discovered_models!(provider: :anthropic)`.
143
+
144
+ ---
145
+
146
+ ## Configuration
147
+
148
+ ```ruby
149
+ Legion::Extensions::Llm.configure do |config|
150
+ config.anthropic_api_key = ENV.fetch('ANTHROPIC_API_KEY')
151
+ config.anthropic_version = '2023-10-16'
152
+ end
153
+ ```
154
+
155
+ **Configurable options:**
156
+
157
+ | Setting | Key | Description |
158
+ |---------|-----|-------------|
159
+ | API key | `anthropic_api_key` / `ANTHROPIC_API_KEY` | Required; API authentication |
160
+ | API version | `anthropic_version` | Defaults to `2023-10-16` |
161
+ | Endpoint | `anthropic_api_base` | Override `https://api.anthropic.com` |
162
+ | Max tokens | `default_max_tokens` | Defaults to `4096` |
163
+ | Discovery interval | `discovery_interval` (seconds) | Defaults to `1800` |
164
+
165
+ ---
166
+
167
+ ## Usage Examples
168
+
169
+ ### Chat (synchronous)
170
+
171
+ ```ruby
172
+ provider = Legion::Extensions::Llm::Anthropic::Provider.new(api_key: 'sk-ant-...')
173
+ response = provider.chat(model: 'claude-sonnet-4-6', messages: [{ role: 'user', content: 'Hello' }])
174
+ ```
175
+
176
+ ### Chat (streaming)
177
+
178
+ ```ruby
179
+ provider.stream_chat(model: 'claude-sonnet-4-6', messages: [{ role: 'user', content: 'Hello' }]) do |chunk|
180
+ print chunk[:content] if chunk[:content]
181
+ end
182
+ ```
183
+
184
+ ### With tools
185
+
186
+ ```ruby
187
+ response = provider.chat(
188
+ model: 'claude-sonnet-4-6',
189
+ messages: [{ role: 'user', content: 'What is the weather in SF?' }],
190
+ tools: [{ name: 'get_weather', description: '...', input_schema: { ... } }]
191
+ )
192
+ ```
193
+
194
+ ### Extended thinking
195
+
196
+ ```ruby
197
+ response = provider.chat(
198
+ model: 'claude-sonnet-4-6',
199
+ messages: [{ role: 'user', content: 'Solve this math problem...' }],
200
+ thinking: { budget_tokens: 4096, enabled: true }
201
+ )
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Dependencies
207
+
208
+ | Gem | Minimum version | Role |
209
+ |-----|-----------------|------|
210
+ | `lex-llm` | `>= 0.4.3` | Base provider contract, response normalization, fleet responder, auto-registration |
211
+ | `legion-logging` | (via lex-llm) | Logging helper for diagnostics |
212
+ | `legion-settings` | (via lex-llm) | Configuration access |
213
+ | `legion-transport` | (via lex-llm) | Message exchange for registry events |
214
+
215
+ ---
216
+
217
+ ## Testing
218
+
219
+ ```bash
220
+ bundle exec rspec # 28 examples
221
+ bundle exec rubocop # 0 offenses
222
+ ```
223
+
224
+ ---
225
+
226
+ ## Design Notes
227
+
228
+ - **No embeddings** — Anthropic embeddings are intentionally not exposed.
229
+ - **No `:claude` alias** — `provider_aliases` returns `[]`; only `:anthropic` is registered.
230
+ - **Prompt caching** — When `cache_enabled?` is true, system content and tool definitions are marked as cache breakpoints; early conversation turns are cacheable, the final message is never cached.
231
+ - **Thinking budget** — Supports `Integer`, `Hash` (with `:budget_tokens` or legacy `:budget`), and objects responding to `#budget`. Defaults to `1024`.
232
+ - **Context windows** — Static `CONTEXT_WINDOWS` map covers known Claude model families; `fetch_model_detail` and `infer_context_window` provide fallback inference.
@@ -231,12 +231,17 @@ module Legion
231
231
 
232
232
  def thinking_budget(thinking)
233
233
  return thinking if thinking.is_a?(Integer)
234
- return thinking[:budget] || thinking['budget'] if thinking.is_a?(Hash)
234
+ return extract_hash_budget(thinking) if thinking.is_a?(Hash)
235
235
  return thinking.budget if thinking.respond_to?(:budget) && thinking.budget
236
236
 
237
237
  1024
238
238
  end
239
239
 
240
+ # Anthropic API uses :budget_tokens, but legacy config may use :budget
241
+ def extract_hash_budget(thinking)
242
+ thinking[:budget_tokens] || thinking['budget_tokens'] || thinking[:budget] || thinking['budget']
243
+ end
244
+
240
245
  def thinking_block(thinking)
241
246
  return nil unless thinking
242
247
 
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Anthropic
7
- VERSION = '0.2.13'
7
+ VERSION = '0.2.15'
8
8
  end
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-anthropic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.13
4
+ version: 0.2.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO