lex-llm-anthropic 0.2.12 → 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 +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +194 -22
- data/lib/legion/extensions/llm/anthropic/actors/discovery_refresh.rb +47 -0
- data/lib/legion/extensions/llm/anthropic/provider.rb +7 -2
- data/lib/legion/extensions/llm/anthropic/version.rb +1 -1
- data/lib/legion/extensions/llm/anthropic.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b5592d5457b4e1cc4b1b64b4133ca43f99a6de148044d70606c4dfa794b724e0
|
|
4
|
+
data.tar.gz: b05ebe4ac3373d36e6fd2ad71716c372a50ac9c50641e88ac710cdb66ba6c261
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a123eae076f23634c8ce0437bbdc382b53e7cdecfec5357441bf4b92b73285959911076a525bf584c726f30aceb35b4d3f9fb275039adfb7c2bd02f5fc721a50
|
|
7
|
+
data.tar.gz: 131feec5dbb20ec692f3f2e5f8bf83b1b777e16ae7c6798e8931410449eb31be9008b89e0f3ba5f8e27da6d72003de0bf639b2f37b3525b67f5fa72f3f3fb992
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
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
|
+
|
|
14
|
+
## 0.2.13 - 2026-06-02
|
|
15
|
+
|
|
16
|
+
- **Fix invalid anthropic-version header** — Default `api_version` was `'2023-10-02'` (typo), which Anthropic rejects. Changed to `'2023-10-16'` (anthropic.rb)
|
|
17
|
+
- **Add per-provider discovery refresh actor** — New `actors/discovery_refresh.rb` that only refreshes Anthropic models, avoiding coupling to other providers' discovery cycles
|
|
18
|
+
|
|
3
19
|
## 0.2.12 - 2026-06-01
|
|
4
20
|
|
|
5
21
|
- Add `cache_control` markers to Anthropic Messages API requests for prompt caching
|
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
|
|
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
|
-
|
|
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
|
-
|
|
41
|
+
---
|
|
16
42
|
|
|
17
|
-
##
|
|
43
|
+
## Architecture
|
|
18
44
|
|
|
19
|
-
|
|
45
|
+
### Provider (`Provider`)
|
|
20
46
|
|
|
21
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
+
Publishing is **best-effort** and requires transport to be loaded. Failures are logged at `debug` level and silently absorbed.
|
|
41
114
|
|
|
42
|
-
|
|
115
|
+
### Fleet Responder
|
|
43
116
|
|
|
44
|
-
Provider instances can
|
|
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
|
-
|
|
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.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'legion/extensions/actors/every'
|
|
5
|
+
rescue LoadError => e
|
|
6
|
+
warn(e.message) if $VERBOSE
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
return unless defined?(Legion::Extensions::Actors::Every)
|
|
10
|
+
|
|
11
|
+
module Legion
|
|
12
|
+
module Extensions
|
|
13
|
+
module Llm
|
|
14
|
+
module Anthropic
|
|
15
|
+
module Actor
|
|
16
|
+
class DiscoveryRefresh < Legion::Extensions::Actors::Every # rubocop:disable Style/Documentation
|
|
17
|
+
include Legion::Logging::Helper
|
|
18
|
+
|
|
19
|
+
REFRESH_INTERVAL = 1800
|
|
20
|
+
|
|
21
|
+
def runner_class = self.class
|
|
22
|
+
def runner_function = 'manual'
|
|
23
|
+
def run_now? = true
|
|
24
|
+
def use_runner? = false
|
|
25
|
+
def check_subtask? = false
|
|
26
|
+
def generate_task? = false
|
|
27
|
+
|
|
28
|
+
def time
|
|
29
|
+
return REFRESH_INTERVAL unless defined?(Legion::Settings)
|
|
30
|
+
|
|
31
|
+
Legion::Settings.dig(:extensions, :llm, :anthropic, :discovery_interval) || REFRESH_INTERVAL
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def manual
|
|
35
|
+
log.debug('[anthropic][discovery_refresh] refreshing model list')
|
|
36
|
+
return unless defined?(Legion::LLM::Discovery)
|
|
37
|
+
|
|
38
|
+
Legion::LLM::Discovery.refresh_discovered_models!(provider: :anthropic)
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
handle_exception(e, level: :warn, handled: true, operation: 'anthropic.actor.discovery_refresh')
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -46,7 +46,7 @@ module Legion
|
|
|
46
46
|
def headers
|
|
47
47
|
identity_headers.merge({
|
|
48
48
|
'x-api-key' => config.anthropic_api_key,
|
|
49
|
-
'anthropic-version' => config.anthropic_version || settings[:api_version] || '2023-
|
|
49
|
+
'anthropic-version' => config.anthropic_version || settings[:api_version] || '2023-06-01'
|
|
50
50
|
}.compact)
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -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
|
|
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
|
|
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.
|
|
4
|
+
version: 0.2.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LegionIO
|
|
@@ -97,6 +97,7 @@ files:
|
|
|
97
97
|
- README.md
|
|
98
98
|
- lex-llm-anthropic.gemspec
|
|
99
99
|
- lib/legion/extensions/llm/anthropic.rb
|
|
100
|
+
- lib/legion/extensions/llm/anthropic/actors/discovery_refresh.rb
|
|
100
101
|
- lib/legion/extensions/llm/anthropic/actors/fleet_worker.rb
|
|
101
102
|
- lib/legion/extensions/llm/anthropic/provider.rb
|
|
102
103
|
- lib/legion/extensions/llm/anthropic/registry_event_builder.rb
|