activeagent 1.0.1 → 1.0.2
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/README.md +10 -4
- data/lib/active_agent/base.rb +3 -2
- data/lib/active_agent/concerns/provider.rb +6 -2
- data/lib/active_agent/concerns/rescue.rb +39 -0
- data/lib/active_agent/concerns/streaming.rb +2 -1
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/api/traces_controller.rb +117 -0
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/application_controller.rb +54 -0
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/dashboard_controller.rb +126 -0
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/traces_controller.rb +103 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/agent_execution_job.rb +56 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/application_job.rb +14 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_cleanup_job.rb +49 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_provision_job.rb +65 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/process_telemetry_traces_job.rb +77 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent.rb +256 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_run.rb +113 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_template.rb +208 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_version.rb +60 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/application_record.rb +46 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_action.rb +125 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_snapshot.rb +83 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_run.rb +52 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_session.rb +169 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/session_recording.rb +193 -0
- data/lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb +198 -0
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/_trace_detail.html.erb +105 -0
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/index.html.erb +135 -0
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/metrics.html.erb +143 -0
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/show.html.erb +36 -0
- data/lib/active_agent/dashboard/app/views/layouts/active_agent/dashboard/application.html.erb +94 -0
- data/lib/active_agent/dashboard/config/routes.rb +78 -0
- data/lib/active_agent/dashboard/engine.rb +39 -0
- data/lib/active_agent/dashboard.rb +151 -0
- data/lib/active_agent/providers/_base_provider.rb +2 -1
- data/lib/active_agent/providers/anthropic_provider.rb +14 -4
- data/lib/active_agent/providers/azure/_types.rb +5 -0
- data/lib/active_agent/providers/azure/options.rb +111 -0
- data/lib/active_agent/providers/azure_open_ai_provider.rb +2 -0
- data/lib/active_agent/providers/azure_openai_provider.rb +2 -0
- data/lib/active_agent/providers/azure_provider.rb +133 -0
- data/lib/active_agent/providers/azureopenai_provider.rb +2 -0
- data/lib/active_agent/providers/bedrock/_types.rb +8 -0
- data/lib/active_agent/providers/bedrock/bearer_client.rb +109 -0
- data/lib/active_agent/providers/bedrock/options.rb +77 -0
- data/lib/active_agent/providers/bedrock_provider.rb +84 -0
- data/lib/active_agent/providers/common/messages/_types.rb +6 -2
- data/lib/active_agent/providers/concerns/exception_handler.rb +1 -0
- data/lib/active_agent/providers/gemini/_types.rb +19 -0
- data/lib/active_agent/providers/gemini/options.rb +41 -0
- data/lib/active_agent/providers/gemini_provider.rb +94 -0
- data/lib/active_agent/providers/open_ai/chat/transforms.rb +37 -1
- data/lib/active_agent/providers/open_ai/chat_provider.rb +2 -0
- data/lib/active_agent/providers/ruby_llm/_types.rb +77 -0
- data/lib/active_agent/providers/ruby_llm/embedding_request.rb +16 -0
- data/lib/active_agent/providers/ruby_llm/messages/_types.rb +109 -0
- data/lib/active_agent/providers/ruby_llm/messages/assistant.rb +27 -0
- data/lib/active_agent/providers/ruby_llm/messages/base.rb +48 -0
- data/lib/active_agent/providers/ruby_llm/messages/system.rb +18 -0
- data/lib/active_agent/providers/ruby_llm/messages/tool.rb +24 -0
- data/lib/active_agent/providers/ruby_llm/messages/user.rb +18 -0
- data/lib/active_agent/providers/ruby_llm/options.rb +28 -0
- data/lib/active_agent/providers/ruby_llm/request.rb +30 -0
- data/lib/active_agent/providers/ruby_llm/tool_proxy.rb +45 -0
- data/lib/active_agent/providers/ruby_llm_provider.rb +407 -0
- data/lib/active_agent/railtie.rb +32 -1
- data/lib/active_agent/telemetry/configuration.rb +213 -0
- data/lib/active_agent/telemetry/instrumentation.rb +155 -0
- data/lib/active_agent/telemetry/reporter.rb +176 -0
- data/lib/active_agent/telemetry/span.rb +267 -0
- data/lib/active_agent/telemetry/tracer.rb +184 -0
- data/lib/active_agent/telemetry.rb +162 -0
- data/lib/active_agent/version.rb +1 -1
- data/lib/active_agent.rb +2 -0
- data/lib/generators/active_agent/dashboard/install/install_generator.rb +96 -0
- data/lib/generators/active_agent/dashboard/install/templates/initializer.rb +89 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_runs.rb +42 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_templates.rb +38 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_versions.rb +22 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agents.rb +53 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_sandbox_runs.rb +28 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_sandbox_sessions.rb +43 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_session_recordings.rb +44 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_telemetry_traces.rb +56 -0
- data/lib/generators/active_agent/dashboard/install_generator.rb +64 -0
- data/lib/generators/active_agent/dashboard/templates/active_agent_dashboard.rb.erb +30 -0
- data/lib/generators/active_agent/dashboard/templates/create_active_agent_telemetry_traces.rb.erb +30 -0
- metadata +99 -13
|
@@ -9,7 +9,8 @@ require_relative "concerns/tool_choice_clearing"
|
|
|
9
9
|
# @private
|
|
10
10
|
GEM_LOADERS = {
|
|
11
11
|
anthropic: [ "anthropic", "~> 1.12", "anthropic" ],
|
|
12
|
-
openai: [ "openai", "~> 0.34", "openai" ]
|
|
12
|
+
openai: [ "openai", "~> 0.34", "openai" ],
|
|
13
|
+
ruby_llm: [ "ruby_llm", ">= 1.0", "ruby_llm" ]
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
# Requires a provider's gem dependency.
|
|
@@ -189,9 +189,14 @@ module ActiveAgent
|
|
|
189
189
|
when :ping
|
|
190
190
|
# No-Op Keep Awake
|
|
191
191
|
when :overloaded_error
|
|
192
|
-
|
|
192
|
+
# TODO: https://docs.claude.com/en/docs/build-with-claude/streaming#error-events
|
|
193
|
+
|
|
194
|
+
# Higher-level convenience events from anthropic gem's MessageStream
|
|
195
|
+
when :text, :input_json, :citation, :thinking, :signature
|
|
196
|
+
# No-Op; Already handled via :content_block_delta
|
|
197
|
+
|
|
193
198
|
else
|
|
194
|
-
# No-Op
|
|
199
|
+
# No-Op; Internal tracking from gem wrapper
|
|
195
200
|
return if api_response_chunk.respond_to?(:snapshot)
|
|
196
201
|
raise "Unexpected chunk type: #{api_response_chunk.type}"
|
|
197
202
|
end
|
|
@@ -296,11 +301,16 @@ module ActiveAgent
|
|
|
296
301
|
def process_prompt_finished_extract_function_calls
|
|
297
302
|
message_stack.pluck(:content).flatten.select { _1 in { type: "tool_use" } }.map do |api_function_call|
|
|
298
303
|
json_buf = api_function_call.delete(:json_buf)
|
|
299
|
-
api_function_call[:input] = JSON.parse(json_buf, symbolize_names: true) if json_buf
|
|
304
|
+
api_function_call[:input] = JSON.parse(json_buf, symbolize_names: true) if json_buf.present?
|
|
300
305
|
|
|
301
306
|
# Handle case where :input is still a JSON string (gem >= 1.14.0)
|
|
307
|
+
# For tools with no parameters, input may be an empty string
|
|
302
308
|
if api_function_call[:input].is_a?(String)
|
|
303
|
-
api_function_call[:input]
|
|
309
|
+
if api_function_call[:input].present?
|
|
310
|
+
api_function_call[:input] = JSON.parse(api_function_call[:input], symbolize_names: true)
|
|
311
|
+
else
|
|
312
|
+
api_function_call[:input] = {}
|
|
313
|
+
end
|
|
304
314
|
end
|
|
305
315
|
|
|
306
316
|
api_function_call
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../open_ai/options"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module Azure
|
|
8
|
+
# Configuration options for Azure OpenAI Service.
|
|
9
|
+
#
|
|
10
|
+
# Azure OpenAI uses a different authentication and endpoint structure than standard OpenAI:
|
|
11
|
+
# - Endpoint: https://{resource}.openai.azure.com/openai/deployments/{deployment}/
|
|
12
|
+
# - Authentication: api-key header instead of Authorization: Bearer
|
|
13
|
+
# - API Version: Required query parameter
|
|
14
|
+
#
|
|
15
|
+
# You can configure Azure OpenAI in two ways:
|
|
16
|
+
#
|
|
17
|
+
# 1. Using azure_resource and deployment_id (standard Azure OpenAI):
|
|
18
|
+
# @example
|
|
19
|
+
# options = Azure::Options.new(
|
|
20
|
+
# api_key: ENV["AZURE_OPENAI_API_KEY"],
|
|
21
|
+
# azure_resource: "mycompany",
|
|
22
|
+
# deployment_id: "gpt-4-deployment",
|
|
23
|
+
# api_version: "2024-10-21"
|
|
24
|
+
# )
|
|
25
|
+
#
|
|
26
|
+
# 2. Using a direct host/base_url (for custom domains or Azure AI Foundry):
|
|
27
|
+
# @example
|
|
28
|
+
# options = Azure::Options.new(
|
|
29
|
+
# api_key: ENV["AZURE_OPENAI_API_KEY"],
|
|
30
|
+
# host: "https://mycompany.cognitiveservices.azure.com/openai/deployments/gpt-4",
|
|
31
|
+
# api_version: "2024-10-21"
|
|
32
|
+
# )
|
|
33
|
+
class Options < ActiveAgent::Providers::OpenAI::Options
|
|
34
|
+
DEFAULT_API_VERSION = "2024-10-21"
|
|
35
|
+
|
|
36
|
+
attribute :azure_resource, :string
|
|
37
|
+
attribute :deployment_id, :string
|
|
38
|
+
attribute :api_version, :string, fallback: DEFAULT_API_VERSION
|
|
39
|
+
|
|
40
|
+
validates :azure_resource, presence: true, unless: :explicit_host_provided?
|
|
41
|
+
validates :deployment_id, presence: true, unless: :explicit_host_provided?
|
|
42
|
+
|
|
43
|
+
def initialize(kwargs = {})
|
|
44
|
+
kwargs = kwargs.deep_symbolize_keys if kwargs.respond_to?(:deep_symbolize_keys)
|
|
45
|
+
kwargs[:api_version] ||= resolve_api_version(kwargs)
|
|
46
|
+
# Store explicit host before super processes kwargs
|
|
47
|
+
# host is aliased to base_url in parent, so check both
|
|
48
|
+
@explicit_host = kwargs[:host] || kwargs[:base_url]
|
|
49
|
+
super(kwargs)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns Azure-specific headers for authentication.
|
|
53
|
+
#
|
|
54
|
+
# Azure uses api-key header instead of Authorization: Bearer.
|
|
55
|
+
#
|
|
56
|
+
# @return [Hash] headers including api-key
|
|
57
|
+
def extra_headers
|
|
58
|
+
{ "api-key" => api_key }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns Azure-specific query parameters.
|
|
62
|
+
#
|
|
63
|
+
# Azure requires api-version as a query parameter.
|
|
64
|
+
#
|
|
65
|
+
# @return [Hash] query parameters including api-version
|
|
66
|
+
def extra_query
|
|
67
|
+
{ "api-version" => api_version }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Builds the base URL for Azure OpenAI API requests.
|
|
71
|
+
#
|
|
72
|
+
# If a direct host/base_url is provided, uses that directly.
|
|
73
|
+
# Otherwise, constructs the URL from azure_resource and deployment_id.
|
|
74
|
+
#
|
|
75
|
+
# @return [String] the Azure OpenAI endpoint URL
|
|
76
|
+
def base_url
|
|
77
|
+
if @explicit_host.present?
|
|
78
|
+
@explicit_host
|
|
79
|
+
elsif azure_resource.present? && deployment_id.present?
|
|
80
|
+
"https://#{azure_resource}.openai.azure.com/openai/deployments/#{deployment_id}"
|
|
81
|
+
else
|
|
82
|
+
raise ArgumentError, "Either host or azure_resource + deployment_id must be provided"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def explicit_host_provided?
|
|
89
|
+
@explicit_host.present?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def resolve_api_key(kwargs)
|
|
93
|
+
kwargs[:api_key] ||
|
|
94
|
+
kwargs[:access_token] ||
|
|
95
|
+
ENV["AZURE_OPENAI_API_KEY"] ||
|
|
96
|
+
ENV["AZURE_OPENAI_ACCESS_TOKEN"]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def resolve_api_version(kwargs)
|
|
100
|
+
kwargs[:api_version] ||
|
|
101
|
+
ENV["AZURE_OPENAI_API_VERSION"] ||
|
|
102
|
+
DEFAULT_API_VERSION
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Not used as part of Azure OpenAI
|
|
106
|
+
def resolve_organization_id(_settings) = nil
|
|
107
|
+
def resolve_project_id(_settings) = nil
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
require_relative "_base_provider"
|
|
2
|
+
|
|
3
|
+
require_gem!(:openai, __FILE__)
|
|
4
|
+
|
|
5
|
+
require_relative "open_ai_provider"
|
|
6
|
+
require_relative "azure/_types"
|
|
7
|
+
|
|
8
|
+
module ActiveAgent
|
|
9
|
+
module Providers
|
|
10
|
+
# Provider for Azure OpenAI Service via OpenAI-compatible API.
|
|
11
|
+
#
|
|
12
|
+
# Azure OpenAI uses the same API structure as OpenAI but with different
|
|
13
|
+
# authentication (api-key header) and endpoint configuration (resource + deployment).
|
|
14
|
+
#
|
|
15
|
+
# @example Configuration in active_agent.yml
|
|
16
|
+
# azure_openai:
|
|
17
|
+
# service: "AzureOpenAI"
|
|
18
|
+
# api_key: <%= ENV["AZURE_OPENAI_API_KEY"] %>
|
|
19
|
+
# azure_resource: "mycompany"
|
|
20
|
+
# deployment_id: "gpt-4-deployment"
|
|
21
|
+
# api_version: "2024-10-21"
|
|
22
|
+
#
|
|
23
|
+
# @see OpenAI::ChatProvider
|
|
24
|
+
class AzureProvider < OpenAI::ChatProvider
|
|
25
|
+
# @return [String]
|
|
26
|
+
def self.service_name
|
|
27
|
+
"AzureOpenAI"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [Class]
|
|
31
|
+
def self.options_klass
|
|
32
|
+
Azure::Options
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [ActiveModel::Type::Value]
|
|
36
|
+
def self.prompt_request_type
|
|
37
|
+
OpenAI::Chat::RequestType.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [ActiveModel::Type::Value]
|
|
41
|
+
def self.embed_request_type
|
|
42
|
+
OpenAI::Embedding::RequestType.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns a configured Azure OpenAI client.
|
|
46
|
+
#
|
|
47
|
+
# Uses a custom client subclass that handles Azure-specific authentication
|
|
48
|
+
# (api-key header instead of Authorization: Bearer).
|
|
49
|
+
#
|
|
50
|
+
# @return [AzureClient] the configured Azure client
|
|
51
|
+
def client
|
|
52
|
+
@client ||= AzureClient.new(
|
|
53
|
+
api_key: options.api_key,
|
|
54
|
+
base_url: options.base_url,
|
|
55
|
+
api_version: options.api_version,
|
|
56
|
+
max_retries: options.max_retries,
|
|
57
|
+
timeout: options.timeout,
|
|
58
|
+
initial_retry_delay: options.initial_retry_delay,
|
|
59
|
+
max_retry_delay: options.max_retry_delay
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Custom OpenAI client for Azure OpenAI Service.
|
|
64
|
+
#
|
|
65
|
+
# Azure uses different authentication headers (api-key instead of Authorization: Bearer)
|
|
66
|
+
# and requires api-version as a query parameter on all requests.
|
|
67
|
+
class AzureClient < ::OpenAI::Client
|
|
68
|
+
# @return [String]
|
|
69
|
+
attr_reader :api_version
|
|
70
|
+
|
|
71
|
+
# Creates a new Azure OpenAI client.
|
|
72
|
+
#
|
|
73
|
+
# @param api_key [String] Azure OpenAI API key
|
|
74
|
+
# @param base_url [String] Azure endpoint URL
|
|
75
|
+
# @param api_version [String] API version (e.g., "2024-10-21")
|
|
76
|
+
# @param max_retries [Integer] Maximum retry attempts
|
|
77
|
+
# @param timeout [Float] Request timeout in seconds
|
|
78
|
+
# @param initial_retry_delay [Float] Initial delay between retries
|
|
79
|
+
# @param max_retry_delay [Float] Maximum delay between retries
|
|
80
|
+
def initialize(
|
|
81
|
+
api_key:,
|
|
82
|
+
base_url:,
|
|
83
|
+
api_version:,
|
|
84
|
+
max_retries: self.class::DEFAULT_MAX_RETRIES,
|
|
85
|
+
timeout: self.class::DEFAULT_TIMEOUT_IN_SECONDS,
|
|
86
|
+
initial_retry_delay: self.class::DEFAULT_INITIAL_RETRY_DELAY,
|
|
87
|
+
max_retry_delay: self.class::DEFAULT_MAX_RETRY_DELAY
|
|
88
|
+
)
|
|
89
|
+
@api_version = api_version
|
|
90
|
+
|
|
91
|
+
super(
|
|
92
|
+
api_key: api_key,
|
|
93
|
+
base_url: base_url,
|
|
94
|
+
max_retries: max_retries,
|
|
95
|
+
timeout: timeout,
|
|
96
|
+
initial_retry_delay: initial_retry_delay,
|
|
97
|
+
max_retry_delay: max_retry_delay
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
# Azure uses api-key header instead of Authorization: Bearer.
|
|
104
|
+
#
|
|
105
|
+
# @return [Hash{String=>String}]
|
|
106
|
+
def auth_headers
|
|
107
|
+
return {} if @api_key.nil?
|
|
108
|
+
|
|
109
|
+
{ "api-key" => @api_key }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Builds request with Azure-specific query parameters.
|
|
113
|
+
#
|
|
114
|
+
# Injects api-version into extra_query for all requests.
|
|
115
|
+
#
|
|
116
|
+
# @param req [Hash] Request parameters
|
|
117
|
+
# @param opts [Hash] Request options
|
|
118
|
+
# @return [Hash] Built request
|
|
119
|
+
def build_request(req, opts)
|
|
120
|
+
# Inject api-version into extra_query
|
|
121
|
+
opts = opts.dup
|
|
122
|
+
opts[:extra_query] = (opts[:extra_query] || {}).merge("api-version" => @api_version)
|
|
123
|
+
|
|
124
|
+
super(req, opts)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Aliases for provider loading with different service name variations
|
|
130
|
+
AzureOpenAIProvider = AzureProvider
|
|
131
|
+
AzureOpenaiProvider = AzureProvider
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "options"
|
|
4
|
+
require_relative "bearer_client"
|
|
5
|
+
require_relative "../anthropic/_types"
|
|
6
|
+
|
|
7
|
+
# Bedrock uses the same request/response types as Anthropic.
|
|
8
|
+
# The BedrockClient handles all protocol translation internally.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAgent
|
|
4
|
+
module Providers
|
|
5
|
+
module Bedrock
|
|
6
|
+
# Client for AWS Bedrock using bearer token (API key) authentication.
|
|
7
|
+
#
|
|
8
|
+
# Subclasses Anthropic::Client directly to reuse its built-in bearer
|
|
9
|
+
# token support via the +auth_token+ parameter, while adding Bedrock-
|
|
10
|
+
# specific request transformations (URL path rewriting, anthropic_version
|
|
11
|
+
# injection) copied from Anthropic::BedrockClient.
|
|
12
|
+
#
|
|
13
|
+
# This avoids Anthropic::BedrockClient which requires SigV4 credentials
|
|
14
|
+
# and would fail when only a bearer token is available.
|
|
15
|
+
#
|
|
16
|
+
# @see https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys-use.html
|
|
17
|
+
class BearerClient < ::Anthropic::Client
|
|
18
|
+
BEDROCK_VERSION = "bedrock-2023-05-31"
|
|
19
|
+
|
|
20
|
+
# @return [String]
|
|
21
|
+
attr_reader :aws_region
|
|
22
|
+
|
|
23
|
+
# @param aws_region [String] AWS region for the Bedrock endpoint
|
|
24
|
+
# @param bearer_token [String] AWS Bedrock API key (bearer token)
|
|
25
|
+
# @param base_url [String, nil] Override the default Bedrock endpoint
|
|
26
|
+
# @param max_retries [Integer]
|
|
27
|
+
# @param timeout [Float]
|
|
28
|
+
# @param initial_retry_delay [Float]
|
|
29
|
+
# @param max_retry_delay [Float]
|
|
30
|
+
def initialize(
|
|
31
|
+
aws_region:,
|
|
32
|
+
bearer_token:,
|
|
33
|
+
base_url: nil,
|
|
34
|
+
max_retries: self.class::DEFAULT_MAX_RETRIES,
|
|
35
|
+
timeout: self.class::DEFAULT_TIMEOUT_IN_SECONDS,
|
|
36
|
+
initial_retry_delay: self.class::DEFAULT_INITIAL_RETRY_DELAY,
|
|
37
|
+
max_retry_delay: self.class::DEFAULT_MAX_RETRY_DELAY
|
|
38
|
+
)
|
|
39
|
+
@aws_region = aws_region
|
|
40
|
+
|
|
41
|
+
base_url ||= "https://bedrock-runtime.#{aws_region}.amazonaws.com"
|
|
42
|
+
|
|
43
|
+
super(
|
|
44
|
+
auth_token: bearer_token,
|
|
45
|
+
api_key: nil,
|
|
46
|
+
base_url: base_url,
|
|
47
|
+
max_retries: max_retries,
|
|
48
|
+
timeout: timeout,
|
|
49
|
+
initial_retry_delay: initial_retry_delay,
|
|
50
|
+
max_retry_delay: max_retry_delay
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
@messages = ::Anthropic::Resources::Messages.new(client: self)
|
|
54
|
+
@completions = ::Anthropic::Resources::Completions.new(client: self)
|
|
55
|
+
@beta = ::Anthropic::Resources::Beta.new(client: self)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Intercepts request building to apply Bedrock-specific transformations
|
|
61
|
+
# before the parent class processes the request.
|
|
62
|
+
def build_request(req, opts)
|
|
63
|
+
fit_req_to_bedrock_specs!(req)
|
|
64
|
+
req = super
|
|
65
|
+
body = req.fetch(:body)
|
|
66
|
+
req[:body] = StringIO.new(body.to_a.join) if body.is_a?(Enumerator)
|
|
67
|
+
req
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Rewrites Anthropic API paths to Bedrock endpoint paths and injects
|
|
71
|
+
# the Bedrock anthropic_version field.
|
|
72
|
+
#
|
|
73
|
+
# Adapted from Anthropic::Helpers::Bedrock::Client#fit_req_to_bedrock_specs!
|
|
74
|
+
def fit_req_to_bedrock_specs!(request_components)
|
|
75
|
+
if (body = request_components[:body]).is_a?(Hash)
|
|
76
|
+
body[:anthropic_version] ||= BEDROCK_VERSION
|
|
77
|
+
body.transform_keys!("anthropic-beta": :anthropic_beta)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
case request_components[:path]
|
|
81
|
+
in %r{^v1/messages/batches}
|
|
82
|
+
raise NotImplementedError, "The Batch API is not supported in Bedrock yet"
|
|
83
|
+
in %r{v1/messages/count_tokens}
|
|
84
|
+
raise NotImplementedError, "Token counting is not supported in Bedrock yet"
|
|
85
|
+
in %r{v1/models\?beta=true}
|
|
86
|
+
raise NotImplementedError,
|
|
87
|
+
"Please instead use https://docs.anthropic.com/en/api/claude-on-amazon-bedrock#list-available-models " \
|
|
88
|
+
"to list available models on Bedrock."
|
|
89
|
+
else
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if %w[
|
|
93
|
+
v1/complete
|
|
94
|
+
v1/messages
|
|
95
|
+
v1/messages?beta=true
|
|
96
|
+
].include?(request_components[:path]) && request_components[:method] == :post && body.is_a?(Hash)
|
|
97
|
+
model = body.delete(:model)
|
|
98
|
+
model = URI.encode_www_form_component(model.to_s)
|
|
99
|
+
stream = body.delete(:stream) || false
|
|
100
|
+
request_components[:path] =
|
|
101
|
+
stream ? "model/#{model}/invoke-with-response-stream" : "model/#{model}/invoke"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
request_components
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_agent/providers/common/model"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module Bedrock
|
|
8
|
+
# Configuration for AWS Bedrock provider.
|
|
9
|
+
#
|
|
10
|
+
# AWS credentials are resolved in order:
|
|
11
|
+
# 1. Explicit options (aws_access_key, aws_secret_key)
|
|
12
|
+
# 2. Environment variables (AWS_REGION, AWS_ACCESS_KEY_ID, etc.)
|
|
13
|
+
# 3. AWS SDK default chain (profiles, IAM roles, instance metadata)
|
|
14
|
+
#
|
|
15
|
+
# Unlike the Anthropic provider, no API key is needed — authentication
|
|
16
|
+
# is handled entirely through AWS credentials.
|
|
17
|
+
#
|
|
18
|
+
# @example Minimal config (uses SDK default chain)
|
|
19
|
+
# Bedrock::Options.new(aws_region: "eu-west-2")
|
|
20
|
+
#
|
|
21
|
+
# @example Explicit credentials
|
|
22
|
+
# Bedrock::Options.new(
|
|
23
|
+
# aws_region: "eu-west-2",
|
|
24
|
+
# aws_access_key: "AKIA...",
|
|
25
|
+
# aws_secret_key: "..."
|
|
26
|
+
# )
|
|
27
|
+
#
|
|
28
|
+
# @example With profile
|
|
29
|
+
# Bedrock::Options.new(
|
|
30
|
+
# aws_region: "eu-west-2",
|
|
31
|
+
# aws_profile: "my-profile"
|
|
32
|
+
# )
|
|
33
|
+
class Options < Common::BaseModel
|
|
34
|
+
attribute :aws_region, :string
|
|
35
|
+
attribute :aws_access_key, :string
|
|
36
|
+
attribute :aws_secret_key, :string
|
|
37
|
+
attribute :aws_session_token, :string
|
|
38
|
+
attribute :aws_profile, :string
|
|
39
|
+
attribute :aws_bearer_token, :string
|
|
40
|
+
attribute :base_url, :string
|
|
41
|
+
attribute :anthropic_beta, :string
|
|
42
|
+
|
|
43
|
+
attribute :max_retries, :integer, default: ::Anthropic::Client::DEFAULT_MAX_RETRIES
|
|
44
|
+
attribute :timeout, :float, default: ::Anthropic::Client::DEFAULT_TIMEOUT_IN_SECONDS
|
|
45
|
+
attribute :initial_retry_delay, :float, default: ::Anthropic::Client::DEFAULT_INITIAL_RETRY_DELAY
|
|
46
|
+
attribute :max_retry_delay, :float, default: ::Anthropic::Client::DEFAULT_MAX_RETRY_DELAY
|
|
47
|
+
|
|
48
|
+
def initialize(kwargs = {})
|
|
49
|
+
kwargs = kwargs.deep_symbolize_keys if kwargs.respond_to?(:deep_symbolize_keys)
|
|
50
|
+
|
|
51
|
+
super(**deep_compact(kwargs.except(:default_url_options).merge(
|
|
52
|
+
aws_region: kwargs[:aws_region] || ENV["AWS_REGION"] || ENV["AWS_DEFAULT_REGION"],
|
|
53
|
+
aws_access_key: kwargs[:aws_access_key] || ENV["AWS_ACCESS_KEY_ID"],
|
|
54
|
+
aws_secret_key: kwargs[:aws_secret_key] || ENV["AWS_SECRET_ACCESS_KEY"],
|
|
55
|
+
aws_session_token: kwargs[:aws_session_token] || ENV["AWS_SESSION_TOKEN"],
|
|
56
|
+
aws_profile: kwargs[:aws_profile] || ENV["AWS_PROFILE"],
|
|
57
|
+
aws_bearer_token: kwargs[:aws_bearer_token] || ENV["AWS_BEARER_TOKEN_BEDROCK"]
|
|
58
|
+
)))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Bedrock handles authentication at the client level (SigV4 or bearer token),
|
|
62
|
+
# so no extra headers are needed in request options.
|
|
63
|
+
def extra_headers
|
|
64
|
+
{}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Excludes sensitive AWS credentials from serialized output.
|
|
68
|
+
# The provider's client() method reads credentials directly from options attributes.
|
|
69
|
+
def serialize
|
|
70
|
+
attributes.symbolize_keys.except(
|
|
71
|
+
:aws_access_key, :aws_secret_key, :aws_session_token, :aws_profile, :aws_bearer_token
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "anthropic_provider"
|
|
4
|
+
require_relative "bedrock/_types"
|
|
5
|
+
|
|
6
|
+
module ActiveAgent
|
|
7
|
+
module Providers
|
|
8
|
+
# Provider for Anthropic models hosted on AWS Bedrock.
|
|
9
|
+
#
|
|
10
|
+
# Inherits all functionality from AnthropicProvider (streaming, tool use,
|
|
11
|
+
# multimodal, JSON format emulation) and overrides only the client
|
|
12
|
+
# construction to use Anthropic::BedrockClient for AWS authentication.
|
|
13
|
+
#
|
|
14
|
+
# @example Configuration in active_agent.yml
|
|
15
|
+
# bedrock:
|
|
16
|
+
# service: "Bedrock"
|
|
17
|
+
# aws_region: "eu-west-2"
|
|
18
|
+
# model: "eu.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
|
19
|
+
#
|
|
20
|
+
# @example Agent usage
|
|
21
|
+
# class SummaryAgent < ApplicationAgent
|
|
22
|
+
# generate_with :bedrock, model: "eu.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
|
23
|
+
#
|
|
24
|
+
# def summarize
|
|
25
|
+
# prompt(message: params[:message])
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @see AnthropicProvider
|
|
30
|
+
class BedrockProvider < AnthropicProvider
|
|
31
|
+
# @return [String]
|
|
32
|
+
def self.service_name
|
|
33
|
+
"Bedrock"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Class]
|
|
37
|
+
def self.options_klass
|
|
38
|
+
Bedrock::Options
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [ActiveModel::Type::Value]
|
|
42
|
+
def self.prompt_request_type
|
|
43
|
+
Anthropic::RequestType.new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns a configured Bedrock client.
|
|
47
|
+
#
|
|
48
|
+
# When a bearer token is available (via +aws_bearer_token+ option or
|
|
49
|
+
# +AWS_BEARER_TOKEN_BEDROCK+ env var), uses {Bedrock::BearerClient}
|
|
50
|
+
# which sends an +Authorization: Bearer+ header.
|
|
51
|
+
#
|
|
52
|
+
# Otherwise, falls back to {Anthropic::BedrockClient} which handles
|
|
53
|
+
# SigV4 signing, credential resolution, and Bedrock URL path rewriting.
|
|
54
|
+
#
|
|
55
|
+
# @return [Bedrock::BearerClient, Anthropic::Helpers::Bedrock::Client]
|
|
56
|
+
def client
|
|
57
|
+
@client ||= if options.aws_bearer_token.present?
|
|
58
|
+
Bedrock::BearerClient.new(
|
|
59
|
+
aws_region: options.aws_region,
|
|
60
|
+
bearer_token: options.aws_bearer_token,
|
|
61
|
+
base_url: options.base_url.presence,
|
|
62
|
+
max_retries: options.max_retries,
|
|
63
|
+
timeout: options.timeout,
|
|
64
|
+
initial_retry_delay: options.initial_retry_delay,
|
|
65
|
+
max_retry_delay: options.max_retry_delay
|
|
66
|
+
)
|
|
67
|
+
else
|
|
68
|
+
::Anthropic::BedrockClient.new(
|
|
69
|
+
aws_region: options.aws_region,
|
|
70
|
+
aws_access_key: options.aws_access_key,
|
|
71
|
+
aws_secret_key: options.aws_secret_key,
|
|
72
|
+
aws_session_token: options.aws_session_token,
|
|
73
|
+
aws_profile: options.aws_profile,
|
|
74
|
+
base_url: options.base_url.presence,
|
|
75
|
+
max_retries: options.max_retries,
|
|
76
|
+
timeout: options.timeout,
|
|
77
|
+
initial_retry_delay: options.initial_retry_delay,
|
|
78
|
+
max_retry_delay: options.max_retry_delay
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -142,11 +142,15 @@ module ActiveAgent
|
|
|
142
142
|
Common::Messages::Assistant.new(role: "assistant", content: block[:text], name: message.name)
|
|
143
143
|
when "tool_use"
|
|
144
144
|
# Create a message with tool use info as string representation
|
|
145
|
-
|
|
145
|
+
input = block[:input]
|
|
146
|
+
input_str = input.nil? || input.empty? ? "{}" : JSON.pretty_generate(input)
|
|
147
|
+
tool_info = "[Tool Use: #{block[:name]}]\nID: #{block[:id]}\nInput: #{input_str}"
|
|
146
148
|
Common::Messages::Assistant.new(role: "assistant", content: tool_info, name: message.name)
|
|
147
149
|
when "mcp_tool_use"
|
|
148
150
|
# Create a message with MCP tool use info
|
|
149
|
-
|
|
151
|
+
input = block[:input]
|
|
152
|
+
input_str = input.nil? || input.empty? ? "{}" : JSON.pretty_generate(input)
|
|
153
|
+
tool_info = "[MCP Tool Use: #{block[:name]}]\nID: #{block[:id]}\nServer: #{block[:server_name]}\nInput: #{input_str}"
|
|
150
154
|
Common::Messages::Assistant.new(role: "assistant", content: tool_info, name: message.name)
|
|
151
155
|
when "mcp_tool_result"
|
|
152
156
|
# Create a message with MCP tool result
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "options"
|
|
4
|
+
require_relative "../open_ai/chat/_types"
|
|
5
|
+
require_relative "../open_ai/embedding/_types"
|
|
6
|
+
|
|
7
|
+
module ActiveAgent
|
|
8
|
+
module Providers
|
|
9
|
+
module Gemini
|
|
10
|
+
# Reuse OpenAI Chat request type (same API format)
|
|
11
|
+
RequestType = OpenAI::Chat::RequestType
|
|
12
|
+
|
|
13
|
+
# Reuse OpenAI Embedding types (same API format)
|
|
14
|
+
module Embedding
|
|
15
|
+
RequestType = OpenAI::Embedding::RequestType
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../open_ai/options"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module Gemini
|
|
8
|
+
# Configuration options for Gemini provider
|
|
9
|
+
#
|
|
10
|
+
# Extends OpenAI::Options with Gemini-specific settings including
|
|
11
|
+
# the default base URL for Gemini's OpenAI-compatible API endpoint.
|
|
12
|
+
#
|
|
13
|
+
# @example Basic configuration
|
|
14
|
+
# options = Options.new(api_key: 'your-api-key')
|
|
15
|
+
#
|
|
16
|
+
# @example With environment variable
|
|
17
|
+
# # Set GEMINI_API_KEY or GOOGLE_API_KEY
|
|
18
|
+
# options = Options.new({})
|
|
19
|
+
#
|
|
20
|
+
# @see https://ai.google.dev/gemini-api/docs/openai
|
|
21
|
+
class Options < ActiveAgent::Providers::OpenAI::Options
|
|
22
|
+
GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"
|
|
23
|
+
|
|
24
|
+
attribute :base_url, :string, fallback: GEMINI_BASE_URL
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def resolve_api_key(kwargs)
|
|
29
|
+
kwargs[:api_key] ||
|
|
30
|
+
kwargs[:access_token] ||
|
|
31
|
+
ENV["GEMINI_API_KEY"] ||
|
|
32
|
+
ENV["GOOGLE_API_KEY"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Not used as part of Gemini
|
|
36
|
+
def resolve_organization_id(kwargs) = nil
|
|
37
|
+
def resolve_project_id(kwargs) = nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|