lib/anthropic/bedrock/client.rb (122 lines of code) (raw):
# frozen_string_literal: true
module Anthropic
module Bedrock
class Client < Anthropic::Client
DEFAULT_VERSION = "bedrock-2023-05-31"
# @return [Anthropic::Resources::Messages]
attr_reader :messages
# @return [Anthropic::Resources::Completions]
attr_reader :completions
# @return [Anthropic::Resources::Beta]
attr_reader :beta
# @return [String]
attr_reader :aws_region
# @return [Aws::Credentials]
attr_reader :aws_credentials
# @!attribute [r] signer
# @return [Aws::Sigv4::Signer]
# @!visibility private
# Creates and returns a new client for interacting with the AWS Bedrock API for Anthropic models.
#
# AWS credentials are resolved according to the AWS SDK's default resolution order, described at
# https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html#credchain or https://github.com/aws/aws-sdk-ruby?tab=readme-ov-file#configuration
#
# @param aws_region [String, nil] Enforce the AWS Region to use. If unset, the region is set according to the
# AWS SDK's default resolution order, described at https://github.com/aws/aws-sdk-ruby?tab=readme-ov-file#configuration
#
# @param aws_access_key [String, nil] Optional AWS access key to use for authentication. Overrides profile and credential provider chain
#
# @param aws_secret_key [String, nil] Optional AWS secret access key to use for authentication. Overrides profile and credential provider chain
#
# @param aws_session_token [String, nil] Optional AWS session token to use for authentication. Overrides profile and credential provider chain
#
# @param aws_profile [String, nil] Optional AWS profile to use for authentication. Overrides the credential provider chain
#
# @param base_url [String, nil] Override the default base URL for the API, e.g., `"https://api.example.com/v2/"`
#
# @param max_retries [Integer] The maximum number of times to retry a request if it fails
#
# @param timeout [Float] The number of seconds to wait for a response before timing out
#
# @param initial_retry_delay [Float] The number of seconds to wait before retrying a request
#
# @param max_retry_delay [Float] The maximum number of seconds to wait before retrying a request
#
def initialize(
aws_region: nil,
base_url: nil,
max_retries: DEFAULT_MAX_RETRIES,
timeout: DEFAULT_TIMEOUT_IN_SECONDS,
initial_retry_delay: DEFAULT_INITIAL_RETRY_DELAY,
max_retry_delay: DEFAULT_MAX_RETRY_DELAY,
aws_access_key: nil,
aws_secret_key: nil,
aws_session_token: nil,
aws_profile: nil
)
begin
require("aws-sdk-bedrockruntime")
rescue LoadError
raise <<~MSG
In order to access Anthropic models on Bedrock you must require the `aws-sdk-bedrockruntime` gem.
You can install it by adding the following to your Gemfile:
gem "aws-sdk-bedrockruntime"
and then running `bundle install`.
Alternatively, if you are not using Bundler, simply run:
gem install aws-sdk-bedrockruntime
MSG
end
@aws_region, @aws_credentials = resolve_region_and_credentials(
aws_region: aws_region,
aws_secret_key: aws_secret_key,
aws_access_key: aws_access_key,
aws_session_token: aws_session_token,
aws_profile: aws_profile
)
@signer = Aws::Sigv4::Signer.new(
service: "bedrock",
region: @aws_region,
credentials: @aws_credentials
)
base_url ||= ENV.fetch(
"ANTHROPIC_BEDROCK_BASE_URL",
"https://bedrock-runtime.#{@aws_region}.amazonaws.com"
)
super(
base_url: base_url,
timeout: timeout,
max_retries: max_retries,
initial_retry_delay: initial_retry_delay,
max_retry_delay: max_retry_delay,
)
@messages = Anthropic::Resources::Messages.new(client: self)
@completions = Anthropic::Resources::Completions.new(client: self)
@beta = Anthropic::Resources::Beta.new(client: self)
end
# @private
#
# @param req [Hash{Symbol=>Object}] .
#
# @option req [Symbol] :method
#
# @option req [String, Array<String>] :path
#
# @option req [Hash{String=>Array<String>, String, nil}, nil] :query
#
# @option req [Hash{String=>String, nil}, nil] :headers
#
# @option req [Object, nil] :body
#
# @option req [Symbol, nil] :unwrap
#
# @option req [Class, nil] :page
#
# @option req [Anthropic::Converter, Class, nil] :model
#
# @param opts [Hash{Symbol=>Object}] .
#
# @option opts [String, nil] :idempotency_key
#
# @option opts [Hash{String=>Array<String>, String, nil}, nil] :extra_query
#
# @option opts [Hash{String=>String, nil}, nil] :extra_headers
#
# @option opts [Hash{Symbol=>Object}, nil] :extra_body
#
# @option opts [Integer, nil] :max_retries
#
# @option opts [Float, nil] :timeout
#
# @return [Hash{Symbol=>Object}]
#
private def build_request(req, opts)
fit_req_to_bedrock_specs!(req)
request_input = super
signed_request = @signer.sign_request(
http_method: request_input[:method],
url: request_input[:url],
headers: request_input[:headers],
body: request_input[:body]
)
request_input[:headers].merge!(signed_request.headers)
request_input
end
# @param aws_region [String, nil]
#
# @param aws_secret_key [String, nil]
#
# @param aws_access_key [String, nil]
#
# @param aws_session_token [String, nil]
#
# @param aws_profile [String, nil]
#
# @return [Array<String, Aws::Credentials>]
#
private def resolve_region_and_credentials(
aws_region:,
aws_secret_key:,
aws_access_key:,
aws_session_token:,
aws_profile:
)
client_options = {
access_key_id: aws_access_key,
secret_access_key: aws_secret_key,
session_token: aws_session_token,
profile: aws_profile
}
unless aws_region.nil?
client_options[:region] = aws_region
end
bedrock_client = Aws::BedrockRuntime::Client.new(client_options)
[bedrock_client.config.region, bedrock_client.config.credentials]
end
# @private
#
# Overrides request components for Bedrock-specific request-shape requirements.
#
# @param request_components [Hash{Symbol=>Object}] .
#
# @option request_components [Symbol] :method
#
# @option request_components [String, Array<String>] :path
#
# @option request_components [Hash{String=>Array<String>, String, nil}, nil] :query
#
# @option request_components [Hash{String=>String, nil}, nil] :headers
#
# @option request_components [Object, nil] :body
#
# @option request_components [Symbol, nil] :unwrap
#
# @option request_components [Class, nil] :page
#
# @option request_components [Anthropic::Converter, Class, nil] :model
#
# @return [Hash{Symbol=>Object}]
#
private def fit_req_to_bedrock_specs!(request_components)
if (body = request_components[:body]).is_a?(Hash)
body[:anthropic_version] ||= DEFAULT_VERSION
body.transform_keys!("anthropic-beta": :anthropic_beta)
end
case request_components[:path]
in %r{^v1/messages/batches}
raise Anthropic::Error, "The Batch API is not supported in Bedrock yet"
in %r{v1/messages/count_tokens}
raise Anthropic::Error, "Token counting is not supported in Bedrock yet"
in %r{v1/models\?beta=true}
raise Anthropic::Error,
"Please instead use https://docs.anthropic.com/en/api/claude-on-amazon-bedrock#list-available-models to list available models on Bedrock."
else
# no-op
end
if %w[
v1/complete
v1/messages
v1/messages?beta=true
].include?(request_components[:path]) && request_components[:method] == :post && body.is_a?(Hash)
model = body.delete(:model)
model = URI.encode_www_form_component(model.to_s)
stream = body.delete(:stream) || false
request_components[:path] =
stream ? "model/#{model}/invoke-with-response-stream" : "model/#{model}/invoke"
end
request_components
end
end
end
end