lex-bedrock 0.1.3 → 0.2.1

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: 68b06b9530a460c09dfba0bcdf216de10a7f95b17fb2faede82d9fcfe77abc0c
4
- data.tar.gz: ae606b05bb7ba03720fb34cc61286372163221efa87d1771ccd87fcbf3a1252f
3
+ metadata.gz: ee5a83a0ee7afd53d1d8f496bd8382332b1312939b3a49cbdf8c86b2269ff6ab
4
+ data.tar.gz: 467176f83f6f58de4a7640bea5f2fcd6b75be4e2a8d5a77d4fa8f3fe82854c2b
5
5
  SHA512:
6
- metadata.gz: bd75d1cc333a34ce86b9cd7ca854288c0ada18945e35b55050368a6ae74f7481072c4dc73a9b81549787caf9de898964200dec11dfdccf683abc504ba6bc3685
7
- data.tar.gz: 15a7c72e46e0b97cdda8e2c5c1f31c4fd5b3d418bca5bab1a426944008f3021ea48031b98a1ecfdedeaea7be01b2d05395a194fbefbdc877a2a77e785d21e7c0
6
+ metadata.gz: 106a987eb8c0d61d3c8fc1e409b0168d95277db70f98fba5ee8ebd0dbe9b74c3770837b052b6cb04b32aae8bfd639511de8b907dfaccee09ee72c563792ae051
7
+ data.tar.gz: d8c81a605d36fd3e6b572580ad4a092a1a7bf913e5edf51e5e26302facf20cd2248da4311fcbf1867fe6cfc35eea78bea61c71c4c7ca0ff43c91d99b920482f6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.1] - 2026-03-31
4
+
5
+ ### Added
6
+ - `Helpers::Usage` — standardized usage hash normalization from SDK structs (`normalize`) and parsed JSON (`from_json`)
7
+ - `Runners::Invoke#invoke_model` now returns a `usage:` key with standardized token counts extracted from the response body
8
+
9
+ ### Changed
10
+ - `Runners::Converse#create` returns `usage:` as a plain hash (`{ input_tokens:, output_tokens:, cache_read_tokens:, cache_write_tokens: }`) instead of raw SDK struct
11
+ - `Runners::Converse#create_stream` returns the same standardized usage hash
12
+ - `Runners::Invoke#invoke_model` wrapped in `Helpers::Errors.with_retry` for production reliability
13
+
14
+ ## [0.2.0] - 2026-03-31
15
+
16
+ ### Added
17
+ - `Helpers::Errors` — Bedrock exception classification (`ThrottlingException`, `AccessDeniedException`, `ModelNotReadyException`) with exponential backoff retry (`with_retry`)
18
+ - `Helpers::Credentials` — AWS credential caching with TTL-based refresh (3000s default); falls back to SDK default provider chain when no explicit keys given
19
+ - `Helpers::Thinking` — helpers to build `additional_model_request_fields` for extended thinking (`build_thinking_fields`, `sanitize_inference_config`)
20
+ - `Helpers::ModelRegistry` — canonical-to-Bedrock ID mapping for 11 Anthropic models, `resolve`/`known?`/`all` API
21
+ - `Runners::Converse#create_stream` — streaming Converse via `converse_stream` with per-event block callback and full accumulation fallback
22
+ - `Runners::Converse#create_with_thinking` — convenience method that wires thinking betas and config into a single call
23
+ - `Runners::Tokens#count_tokens` — token counting via `CountTokensCommand` with `anthropic_beta`/`thinking` body params
24
+ - `Runners::Profiles` — inference profile listing, lookup, and canonical model ID resolution via `ListInferenceProfiles`/`GetInferenceProfile`
25
+
26
+ ### Changed
27
+ - `Runners::Converse#create` now accepts `top_p`, `top_k`, `stop_sequences`, `tool_config`, `guardrail_config`, `additional_model_request_fields`
28
+ - `Helpers::Client` — `bedrock_runtime_client`/`bedrock_client` now accept a pre-built `credentials:` kwarg; `access_key_id` is now optional (nil triggers SDK default provider chain)
29
+ - `Helpers::Client` — added `region_for_model` for env-var and settings-driven per-model region routing
30
+ - All runner calls wrapped in `Helpers::Errors.with_retry` for production reliability
31
+ - `Legion::Extensions::Bedrock::Client` now includes `Runners::Tokens` and `Runners::Profiles`
32
+
3
33
  ## [0.1.3] - 2026-03-30
4
34
 
5
35
  ### Changed
@@ -4,6 +4,8 @@ require 'legion/extensions/bedrock/helpers/client'
4
4
  require 'legion/extensions/bedrock/runners/models'
5
5
  require 'legion/extensions/bedrock/runners/converse'
6
6
  require 'legion/extensions/bedrock/runners/invoke'
7
+ require 'legion/extensions/bedrock/runners/tokens'
8
+ require 'legion/extensions/bedrock/runners/profiles'
7
9
 
8
10
  module Legion
9
11
  module Extensions
@@ -12,6 +14,8 @@ module Legion
12
14
  include Legion::Extensions::Bedrock::Runners::Models
13
15
  include Legion::Extensions::Bedrock::Runners::Converse
14
16
  include Legion::Extensions::Bedrock::Runners::Invoke
17
+ include Legion::Extensions::Bedrock::Runners::Tokens
18
+ include Legion::Extensions::Bedrock::Runners::Profiles
15
19
 
16
20
  attr_reader :config
17
21
 
@@ -12,29 +12,53 @@ module Legion
12
12
 
13
13
  module_function
14
14
 
15
- def bedrock_runtime_client(access_key_id:, secret_access_key:, region: DEFAULT_REGION,
16
- session_token: nil, **)
17
- opts = {
18
- access_key_id: access_key_id,
19
- secret_access_key: secret_access_key,
20
- region: region
21
- }
22
- opts[:session_token] = session_token if session_token
23
-
24
- Aws::BedrockRuntime::Client.new(**opts)
15
+ def bedrock_runtime_client(access_key_id: nil, secret_access_key: nil,
16
+ region: DEFAULT_REGION, session_token: nil,
17
+ credentials: nil, **)
18
+ Aws::BedrockRuntime::Client.new(
19
+ region:,
20
+ credentials: credentials || build_credentials(access_key_id:, secret_access_key:,
21
+ session_token:)
22
+ )
25
23
  end
26
24
 
27
- def bedrock_client(access_key_id:, secret_access_key:, region: DEFAULT_REGION,
28
- session_token: nil, **)
29
- opts = {
30
- access_key_id: access_key_id,
31
- secret_access_key: secret_access_key,
32
- region: region
33
- }
34
- opts[:session_token] = session_token if session_token
25
+ def bedrock_client(access_key_id: nil, secret_access_key: nil,
26
+ region: DEFAULT_REGION, session_token: nil,
27
+ credentials: nil, **)
28
+ Aws::Bedrock::Client.new(
29
+ region:,
30
+ credentials: credentials || build_credentials(access_key_id:, secret_access_key:,
31
+ session_token:)
32
+ )
33
+ end
34
+
35
+ def region_for_model(model_id:, region: nil)
36
+ return region if region
37
+
38
+ env_key = "BEDROCK_REGION_#{model_id.upcase.gsub(/[^A-Z0-9]/, '_')}"
39
+ env_val = ENV.fetch(env_key, nil)
40
+ return env_val if env_val
41
+
42
+ settings_region = begin
43
+ Legion::Settings[:bedrock]&.dig(:model_regions, model_id.to_sym)
44
+ rescue StandardError => _e
45
+ nil
46
+ end
35
47
 
36
- Aws::Bedrock::Client.new(**opts)
48
+ settings_region || DEFAULT_REGION
37
49
  end
50
+
51
+ def build_credentials(access_key_id:, secret_access_key:, session_token:)
52
+ return nil if access_key_id.nil?
53
+
54
+ if session_token
55
+ Aws::Credentials.new(access_key_id, secret_access_key, session_token)
56
+ else
57
+ Aws::Credentials.new(access_key_id, secret_access_key)
58
+ end
59
+ end
60
+
61
+ private_class_method :build_credentials
38
62
  end
39
63
  end
40
64
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Bedrock
6
+ module Helpers
7
+ module Credentials
8
+ CREDENTIAL_TTL = 3000 # seconds — refresh before the typical 1-hour STS TTL
9
+
10
+ @credential_cache = {} # rubocop:disable ThreadSafety/MutableClassInstanceVariable
11
+ @cache_mutex = ::Mutex.new
12
+
13
+ module_function
14
+
15
+ def resolve(access_key_id: nil, secret_access_key: nil,
16
+ session_token: nil, region: 'us-east-2', **)
17
+ return default_credentials if access_key_id.nil?
18
+
19
+ cache_key = "#{access_key_id}/#{region}"
20
+ @cache_mutex.synchronize do
21
+ entry = @credential_cache[cache_key]
22
+ if entry.nil? || stale?(entry)
23
+ @credential_cache[cache_key] = {
24
+ credentials: build_static(
25
+ access_key_id:,
26
+ secret_access_key:,
27
+ session_token:
28
+ ),
29
+ fetched_at: ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
30
+ }
31
+ end
32
+ @credential_cache[cache_key][:credentials]
33
+ end
34
+ end
35
+
36
+ def clear_cache!
37
+ @cache_mutex.synchronize { @credential_cache.clear }
38
+ end
39
+
40
+ def default_credentials
41
+ Aws::Credentials.new(nil, nil) # triggers SDK default chain
42
+ end
43
+
44
+ def build_static(access_key_id:, secret_access_key:, session_token:)
45
+ if session_token
46
+ Aws::Credentials.new(access_key_id, secret_access_key, session_token)
47
+ else
48
+ Aws::Credentials.new(access_key_id, secret_access_key)
49
+ end
50
+ end
51
+
52
+ def stale?(entry)
53
+ elapsed = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - entry[:fetched_at]
54
+ elapsed >= CREDENTIAL_TTL
55
+ end
56
+
57
+ private_class_method :default_credentials, :build_static, :stale?
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Bedrock
6
+ module Helpers
7
+ module Errors
8
+ THROTTLING_ERRORS = %w[
9
+ Aws::BedrockRuntime::Errors::ThrottlingException
10
+ Aws::BedrockRuntime::Errors::ServiceUnavailableException
11
+ Aws::Bedrock::Errors::ThrottlingException
12
+ Aws::Bedrock::Errors::ServiceUnavailableException
13
+ ].freeze
14
+
15
+ ACCESS_ERRORS = %w[
16
+ Aws::BedrockRuntime::Errors::AccessDeniedException
17
+ Aws::Bedrock::Errors::AccessDeniedException
18
+ ].freeze
19
+
20
+ MODEL_ERRORS = %w[
21
+ Aws::BedrockRuntime::Errors::ModelNotReadyException
22
+ Aws::BedrockRuntime::Errors::ModelTimeoutException
23
+ Aws::BedrockRuntime::Errors::ModelErrorException
24
+ ].freeze
25
+
26
+ MAX_RETRIES = 3
27
+ BASE_DELAY = 0.5 # seconds
28
+ MAX_DELAY = 16.0 # seconds
29
+
30
+ module_function
31
+
32
+ def throttling_error?(exception)
33
+ THROTTLING_ERRORS.include?(exception.class.name)
34
+ end
35
+
36
+ def access_error?(exception)
37
+ ACCESS_ERRORS.include?(exception.class.name)
38
+ end
39
+
40
+ def model_error?(exception)
41
+ MODEL_ERRORS.include?(exception.class.name)
42
+ end
43
+
44
+ def retryable?(exception)
45
+ throttling_error?(exception)
46
+ end
47
+
48
+ def with_retry(max_retries: MAX_RETRIES)
49
+ attempts = 0
50
+ begin
51
+ yield
52
+ rescue StandardError => e
53
+ attempts += 1
54
+ raise unless retryable?(e) && attempts <= max_retries
55
+
56
+ delay = [BASE_DELAY * (2**(attempts - 1)), MAX_DELAY].min
57
+ sleep(delay)
58
+ retry
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Bedrock
6
+ module Helpers
7
+ module ModelRegistry
8
+ # Maps canonical model names to their AWS Bedrock cross-region inference profile IDs.
9
+ # Use the us.* cross-region IDs for best availability.
10
+ MODELS = {
11
+ 'claude-3-5-haiku-20241022' => 'us.anthropic.claude-3-5-haiku-20241022-v1:0',
12
+ 'claude-haiku-4-5-20251001' => 'us.anthropic.claude-haiku-4-5-20251001-v1:0',
13
+ 'claude-3-5-sonnet-20241022' => 'anthropic.claude-3-5-sonnet-20241022-v2:0',
14
+ 'claude-3-7-sonnet-20250219' => 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
15
+ 'claude-sonnet-4-20250514' => 'us.anthropic.claude-sonnet-4-20250514-v1:0',
16
+ 'claude-sonnet-4-5-20250929' => 'us.anthropic.claude-sonnet-4-5-20250929-v1:0',
17
+ 'claude-sonnet-4-6' => 'us.anthropic.claude-sonnet-4-6',
18
+ 'claude-opus-4-20250514' => 'us.anthropic.claude-opus-4-20250514-v1:0',
19
+ 'claude-opus-4-1-20250805' => 'us.anthropic.claude-opus-4-1-20250805-v1:0',
20
+ 'claude-opus-4-5-20251101' => 'us.anthropic.claude-opus-4-5-20251101-v1:0',
21
+ 'claude-opus-4-6' => 'us.anthropic.claude-opus-4-6-v1'
22
+ }.freeze
23
+
24
+ module_function
25
+
26
+ def resolve(model_id)
27
+ return model_id if bedrock_id?(model_id)
28
+
29
+ MODELS.fetch(model_id, model_id)
30
+ end
31
+
32
+ def known?(model_id)
33
+ MODELS.key?(model_id)
34
+ end
35
+
36
+ def all
37
+ MODELS.dup
38
+ end
39
+
40
+ def bedrock_id?(model_id)
41
+ model_id.include?('.') || model_id.include?(':')
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Bedrock
6
+ module Helpers
7
+ module Thinking
8
+ THINKING_BETA = 'interleaved-thinking-2025-05-14'
9
+ CONTEXT_1M_BETA = 'context-1m-2025-08-07'
10
+ TOOL_SEARCH_BETA = 'tool-search-tool-2025-10-19'
11
+
12
+ module_function
13
+
14
+ def build_thinking_fields(budget_tokens: nil, adaptive: false, extra_betas: [])
15
+ betas = [THINKING_BETA] + Array(extra_betas)
16
+ fields = { anthropic_beta: betas }
17
+
18
+ thinking = if adaptive || budget_tokens.nil?
19
+ { type: 'adaptive' }
20
+ else
21
+ { type: 'enabled', budget_tokens: }
22
+ end
23
+
24
+ fields[:thinking] = thinking
25
+ fields
26
+ end
27
+
28
+ def sanitize_inference_config(inference_config:, thinking_enabled:)
29
+ return inference_config unless thinking_enabled
30
+
31
+ inference_config.except(:temperature)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Bedrock
6
+ module Helpers
7
+ module Usage
8
+ EMPTY_USAGE = {
9
+ input_tokens: 0,
10
+ output_tokens: 0,
11
+ cache_read_tokens: 0,
12
+ cache_write_tokens: 0
13
+ }.freeze
14
+
15
+ module_function
16
+
17
+ def normalize(sdk_usage)
18
+ return EMPTY_USAGE.dup unless sdk_usage
19
+
20
+ {
21
+ input_tokens: sdk_usage.input_tokens || 0,
22
+ output_tokens: sdk_usage.output_tokens || 0,
23
+ cache_read_tokens: 0,
24
+ cache_write_tokens: 0
25
+ }
26
+ end
27
+
28
+ def from_json(parsed)
29
+ return EMPTY_USAGE.dup unless parsed.is_a?(Hash)
30
+
31
+ usage = parsed['usage'] || {}
32
+ {
33
+ input_tokens: usage['input_tokens'] || 0,
34
+ output_tokens: usage['output_tokens'] || 0,
35
+ cache_read_tokens: 0,
36
+ cache_write_tokens: 0
37
+ }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/extensions/bedrock/helpers/client'
4
+ require 'legion/extensions/bedrock/helpers/errors'
5
+ require 'legion/extensions/bedrock/helpers/thinking'
6
+ require 'legion/extensions/bedrock/helpers/usage'
4
7
 
5
8
  module Legion
6
9
  module Extensions
@@ -9,31 +12,136 @@ module Legion
9
12
  module Converse
10
13
  def create(model_id:, messages:, access_key_id:, secret_access_key:, # rubocop:disable Metrics/ParameterLists
11
14
  system: nil, max_tokens: 1024, temperature: nil,
15
+ top_p: nil, top_k: nil, stop_sequences: nil,
16
+ tool_config: nil, guardrail_config: nil,
17
+ additional_model_request_fields: nil,
12
18
  region: Helpers::Client::DEFAULT_REGION, **)
13
19
  client = Helpers::Client.bedrock_runtime_client(
14
- access_key_id: access_key_id,
15
- secret_access_key: secret_access_key,
16
- region: region
20
+ access_key_id:,
21
+ secret_access_key:,
22
+ region:
17
23
  )
18
24
 
19
- inference_config = { max_tokens: max_tokens }
20
- inference_config[:temperature] = temperature if temperature
21
-
22
- request = {
23
- model_id: model_id,
24
- messages: messages,
25
- inference_config: inference_config
26
- }
27
- request[:system] = [{ text: system }] if system
25
+ request = build_converse_request(
26
+ model_id:, messages:, system:, max_tokens:, temperature:,
27
+ top_p:, top_k:, stop_sequences:, tool_config:,
28
+ guardrail_config:, additional_model_request_fields:
29
+ )
28
30
 
29
- response = client.converse(**request)
31
+ response = Helpers::Errors.with_retry { client.converse(**request) }
30
32
  {
31
33
  result: response.output,
32
- usage: response.usage,
34
+ usage: Helpers::Usage.normalize(response.usage),
33
35
  stop_reason: response.stop_reason
34
36
  }
35
37
  end
36
38
 
39
+ def create_stream(model_id:, messages:, access_key_id:, secret_access_key:, # rubocop:disable Metrics/ParameterLists
40
+ system: nil, max_tokens: 1024, temperature: nil,
41
+ top_p: nil, top_k: nil, stop_sequences: nil,
42
+ tool_config: nil, guardrail_config: nil,
43
+ additional_model_request_fields: nil,
44
+ region: Helpers::Client::DEFAULT_REGION, **,
45
+ &block)
46
+ client = Helpers::Client.bedrock_runtime_client(
47
+ access_key_id:,
48
+ secret_access_key:,
49
+ region:
50
+ )
51
+
52
+ request = build_converse_request(
53
+ model_id:, messages:, system:, max_tokens:, temperature:,
54
+ top_p:, top_k:, stop_sequences:, tool_config:,
55
+ guardrail_config:, additional_model_request_fields:
56
+ )
57
+
58
+ accumulated_text = +''
59
+ final_usage = nil
60
+ final_stop = nil
61
+
62
+ Helpers::Errors.with_retry do
63
+ client.converse_stream(**request) do |stream|
64
+ stream.on_content_block_delta_event do |event|
65
+ delta = event.delta
66
+ text = delta.respond_to?(:text) ? delta.text : nil
67
+ next if text.nil?
68
+
69
+ accumulated_text << text
70
+ block&.call(type: :delta, text:)
71
+ end
72
+
73
+ stream.on_message_stop_event do |event|
74
+ final_stop = event.stop_reason
75
+ block&.call(type: :stop, stop_reason: final_stop)
76
+ end
77
+
78
+ stream.on_metadata_event do |event|
79
+ final_usage = event.usage
80
+ block&.call(type: :usage, usage: final_usage)
81
+ end
82
+ end
83
+ end
84
+
85
+ {
86
+ result: accumulated_text,
87
+ usage: Helpers::Usage.normalize(final_usage),
88
+ stop_reason: final_stop
89
+ }
90
+ end
91
+
92
+ def create_with_thinking(model_id:, messages:, access_key_id:, secret_access_key:, # rubocop:disable Metrics/ParameterLists
93
+ budget_tokens: nil, adaptive: false, extra_betas: [],
94
+ system: nil, max_tokens: 16_000,
95
+ region: Helpers::Client::DEFAULT_REGION, **opts)
96
+ thinking_fields = Helpers::Thinking.build_thinking_fields(
97
+ budget_tokens:, adaptive:, extra_betas:
98
+ )
99
+
100
+ # Merge with any caller-supplied additional_model_request_fields
101
+ amrf = opts.delete(:additional_model_request_fields) || {}
102
+ merged_amrf = thinking_fields.merge(amrf) do |_key, thinking_val, caller_val|
103
+ thinking_val.is_a?(Array) ? thinking_val | Array(caller_val) : caller_val
104
+ end
105
+
106
+ create(
107
+ model_id:, messages:, access_key_id:, secret_access_key:,
108
+ system:, max_tokens:, region:,
109
+ additional_model_request_fields: merged_amrf,
110
+ **opts
111
+ )
112
+ end
113
+
114
+ private
115
+
116
+ def build_converse_request(model_id:, messages:, system:, max_tokens:, # rubocop:disable Metrics/ParameterLists
117
+ temperature:, top_p:, top_k:, stop_sequences:,
118
+ tool_config:, guardrail_config:,
119
+ additional_model_request_fields:)
120
+ inference_config = { max_tokens: }
121
+ inference_config[:temperature] = temperature if temperature
122
+ inference_config[:top_p] = top_p if top_p
123
+ inference_config[:stop_sequences] = stop_sequences if stop_sequences
124
+
125
+ request = {
126
+ model_id:,
127
+ messages:,
128
+ inference_config:
129
+ }
130
+ request[:system] = [{ text: system }] if system
131
+ request[:tool_config] = tool_config if tool_config
132
+ request[:guardrail_config] = guardrail_config if guardrail_config
133
+ request[:additional_model_request_fields] = additional_model_request_fields \
134
+ if additional_model_request_fields
135
+
136
+ # top_k goes in additional_model_request_fields for Anthropic models
137
+ if top_k
138
+ request[:additional_model_request_fields] ||= {}
139
+ request[:additional_model_request_fields][:top_k] = top_k
140
+ end
141
+
142
+ request
143
+ end
144
+
37
145
  include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
38
146
  Legion::Extensions::Helpers.const_defined?(:Lex, false)
39
147
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'json'
4
4
  require 'legion/extensions/bedrock/helpers/client'
5
+ require 'legion/extensions/bedrock/helpers/errors'
6
+ require 'legion/extensions/bedrock/helpers/usage'
5
7
 
6
8
  module Legion
7
9
  module Extensions
@@ -12,21 +14,25 @@ module Legion
12
14
  content_type: 'application/json', accept: 'application/json',
13
15
  region: Helpers::Client::DEFAULT_REGION, **)
14
16
  client = Helpers::Client.bedrock_runtime_client(
15
- access_key_id: access_key_id,
16
- secret_access_key: secret_access_key,
17
- region: region
17
+ access_key_id:,
18
+ secret_access_key:,
19
+ region:
18
20
  )
19
21
 
20
- response = client.invoke_model(
21
- model_id: model_id,
22
- body: ::JSON.dump(body),
23
- content_type: content_type,
24
- accept: accept
25
- )
22
+ response = Helpers::Errors.with_retry do
23
+ client.invoke_model(
24
+ model_id:,
25
+ body: ::JSON.dump(body),
26
+ content_type:,
27
+ accept:
28
+ )
29
+ end
26
30
 
31
+ parsed = ::JSON.parse(response.body.read)
27
32
  {
28
- result: ::JSON.parse(response.body.read),
29
- content_type: response.content_type
33
+ result: parsed,
34
+ content_type: response.content_type,
35
+ usage: Helpers::Usage.from_json(parsed)
30
36
  }
31
37
  end
32
38
 
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/bedrock/helpers/client'
4
+ require 'legion/extensions/bedrock/helpers/errors'
5
+
6
+ module Legion
7
+ module Extensions
8
+ module Bedrock
9
+ module Runners
10
+ module Profiles
11
+ def list_inference_profiles(access_key_id:, secret_access_key:,
12
+ profile_type: 'SYSTEM_DEFINED',
13
+ region: Helpers::Client::DEFAULT_REGION, **)
14
+ client = Helpers::Client.bedrock_client(
15
+ access_key_id:,
16
+ secret_access_key:,
17
+ region:
18
+ )
19
+
20
+ response = Helpers::Errors.with_retry do
21
+ client.list_inference_profiles(type_equals: profile_type)
22
+ end
23
+
24
+ { inference_profiles: response.inference_profile_summaries }
25
+ end
26
+
27
+ def get_inference_profile(profile_id:, access_key_id:, secret_access_key:,
28
+ region: Helpers::Client::DEFAULT_REGION, **)
29
+ client = Helpers::Client.bedrock_client(
30
+ access_key_id:,
31
+ secret_access_key:,
32
+ region:
33
+ )
34
+
35
+ response = Helpers::Errors.with_retry do
36
+ client.get_inference_profile(inference_profile_identifier: profile_id)
37
+ end
38
+
39
+ { inference_profile: response }
40
+ end
41
+
42
+ def resolve_profile_id(canonical_model_id:, access_key_id:, secret_access_key:,
43
+ region: Helpers::Client::DEFAULT_REGION, **)
44
+ result = list_inference_profiles(
45
+ access_key_id:, secret_access_key:, region:
46
+ )
47
+ profiles = result[:inference_profiles]
48
+
49
+ matched = profiles.find do |p|
50
+ p.respond_to?(:models) &&
51
+ Array(p.models).any? { |m| m.model_arn.to_s.include?(canonical_model_id) }
52
+ end
53
+
54
+ matched ? matched.inference_profile_id : canonical_model_id
55
+ end
56
+
57
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
58
+ Legion::Extensions::Helpers.const_defined?(:Lex, false)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/bedrock/helpers/client'
4
+ require 'legion/extensions/bedrock/helpers/errors'
5
+
6
+ module Legion
7
+ module Extensions
8
+ module Bedrock
9
+ module Runners
10
+ module Tokens
11
+ def count_tokens(model_id:, messages:, access_key_id:, secret_access_key:, # rubocop:disable Metrics/ParameterLists
12
+ system: nil, tools: nil, anthropic_version: 'bedrock-2023-05-31',
13
+ anthropic_beta: nil, thinking: nil,
14
+ region: Helpers::Client::DEFAULT_REGION, **)
15
+ client = Helpers::Client.bedrock_runtime_client(
16
+ access_key_id:,
17
+ secret_access_key:,
18
+ region:
19
+ )
20
+
21
+ request = build_count_tokens_request(
22
+ model_id:, messages:, system:, tools:,
23
+ anthropic_version:, anthropic_beta:, thinking:
24
+ )
25
+
26
+ response = Helpers::Errors.with_retry { client.count_tokens(**request) }
27
+ {
28
+ input_token_count: response.input_tokens
29
+ }
30
+ end
31
+
32
+ private
33
+
34
+ def build_count_tokens_request(model_id:, messages:, system:, tools:,
35
+ anthropic_version:, anthropic_beta:, thinking:)
36
+ body_fields = { anthropic_version: }
37
+ body_fields[:anthropic_beta] = anthropic_beta if anthropic_beta
38
+ body_fields[:thinking] = thinking if thinking
39
+
40
+ request = {
41
+ model_id:,
42
+ messages:,
43
+ system: system ? [{ text: system }] : nil,
44
+ additional_model_request_fields: body_fields
45
+ }
46
+ request[:tools] = tools if tools
47
+ request.compact
48
+ end
49
+
50
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
51
+ Legion::Extensions::Helpers.const_defined?(:Lex, false)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Bedrock
6
- VERSION = '0.1.3'
6
+ VERSION = '0.2.1'
7
7
  end
8
8
  end
9
9
  end
@@ -2,9 +2,16 @@
2
2
 
3
3
  require 'legion/extensions/bedrock/version'
4
4
  require 'legion/extensions/bedrock/helpers/client'
5
+ require 'legion/extensions/bedrock/helpers/errors'
6
+ require 'legion/extensions/bedrock/helpers/credentials'
7
+ require 'legion/extensions/bedrock/helpers/thinking'
8
+ require 'legion/extensions/bedrock/helpers/model_registry'
9
+ require 'legion/extensions/bedrock/helpers/usage'
5
10
  require 'legion/extensions/bedrock/runners/models'
6
11
  require 'legion/extensions/bedrock/runners/converse'
7
12
  require 'legion/extensions/bedrock/runners/invoke'
13
+ require 'legion/extensions/bedrock/runners/tokens'
14
+ require 'legion/extensions/bedrock/runners/profiles'
8
15
 
9
16
  module Legion
10
17
  module Extensions
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-bedrock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -157,9 +157,16 @@ files:
157
157
  - lib/legion/extensions/bedrock.rb
158
158
  - lib/legion/extensions/bedrock/client.rb
159
159
  - lib/legion/extensions/bedrock/helpers/client.rb
160
+ - lib/legion/extensions/bedrock/helpers/credentials.rb
161
+ - lib/legion/extensions/bedrock/helpers/errors.rb
162
+ - lib/legion/extensions/bedrock/helpers/model_registry.rb
163
+ - lib/legion/extensions/bedrock/helpers/thinking.rb
164
+ - lib/legion/extensions/bedrock/helpers/usage.rb
160
165
  - lib/legion/extensions/bedrock/runners/converse.rb
161
166
  - lib/legion/extensions/bedrock/runners/invoke.rb
162
167
  - lib/legion/extensions/bedrock/runners/models.rb
168
+ - lib/legion/extensions/bedrock/runners/profiles.rb
169
+ - lib/legion/extensions/bedrock/runners/tokens.rb
163
170
  - lib/legion/extensions/bedrock/version.rb
164
171
  homepage: https://github.com/LegionIO/lex-bedrock
165
172
  licenses: