activeagent 1.0.1 → 1.0.3

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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -4
  3. data/lib/active_agent/base.rb +3 -2
  4. data/lib/active_agent/concerns/provider.rb +6 -2
  5. data/lib/active_agent/concerns/rescue.rb +39 -0
  6. data/lib/active_agent/concerns/streaming.rb +2 -1
  7. data/lib/active_agent/concerns/view.rb +1 -1
  8. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/api/traces_controller.rb +117 -0
  9. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/application_controller.rb +54 -0
  10. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/dashboard_controller.rb +126 -0
  11. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/traces_controller.rb +103 -0
  12. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/agent_execution_job.rb +56 -0
  13. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/application_job.rb +14 -0
  14. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_cleanup_job.rb +49 -0
  15. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_provision_job.rb +65 -0
  16. data/lib/active_agent/dashboard/app/jobs/active_agent/process_telemetry_traces_job.rb +77 -0
  17. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent.rb +256 -0
  18. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_run.rb +113 -0
  19. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_template.rb +208 -0
  20. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_version.rb +60 -0
  21. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/application_record.rb +46 -0
  22. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_action.rb +125 -0
  23. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_snapshot.rb +83 -0
  24. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_run.rb +52 -0
  25. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_session.rb +169 -0
  26. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/session_recording.rb +193 -0
  27. data/lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb +198 -0
  28. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/_trace_detail.html.erb +105 -0
  29. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/index.html.erb +135 -0
  30. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/metrics.html.erb +143 -0
  31. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/show.html.erb +36 -0
  32. data/lib/active_agent/dashboard/app/views/layouts/active_agent/dashboard/application.html.erb +94 -0
  33. data/lib/active_agent/dashboard/config/routes.rb +78 -0
  34. data/lib/active_agent/dashboard/engine.rb +39 -0
  35. data/lib/active_agent/dashboard.rb +151 -0
  36. data/lib/active_agent/providers/_base_provider.rb +2 -1
  37. data/lib/active_agent/providers/anthropic/request.rb +3 -1
  38. data/lib/active_agent/providers/anthropic/transforms.rb +88 -0
  39. data/lib/active_agent/providers/anthropic_provider.rb +14 -4
  40. data/lib/active_agent/providers/azure/_types.rb +5 -0
  41. data/lib/active_agent/providers/azure/options.rb +111 -0
  42. data/lib/active_agent/providers/azure_open_ai_provider.rb +2 -0
  43. data/lib/active_agent/providers/azure_openai_provider.rb +2 -0
  44. data/lib/active_agent/providers/azure_provider.rb +133 -0
  45. data/lib/active_agent/providers/azureopenai_provider.rb +2 -0
  46. data/lib/active_agent/providers/bedrock/_types.rb +8 -0
  47. data/lib/active_agent/providers/bedrock/bearer_client.rb +109 -0
  48. data/lib/active_agent/providers/bedrock/options.rb +77 -0
  49. data/lib/active_agent/providers/bedrock_provider.rb +84 -0
  50. data/lib/active_agent/providers/common/messages/_types.rb +6 -2
  51. data/lib/active_agent/providers/concerns/exception_handler.rb +1 -0
  52. data/lib/active_agent/providers/gemini/_types.rb +19 -0
  53. data/lib/active_agent/providers/gemini/options.rb +41 -0
  54. data/lib/active_agent/providers/gemini_provider.rb +94 -0
  55. data/lib/active_agent/providers/mock/request.rb +1 -4
  56. data/lib/active_agent/providers/open_ai/chat/transforms.rb +37 -1
  57. data/lib/active_agent/providers/open_ai/chat_provider.rb +5 -3
  58. data/lib/active_agent/providers/requesty/_types.rb +16 -0
  59. data/lib/active_agent/providers/requesty/options.rb +39 -0
  60. data/lib/active_agent/providers/requesty_provider.rb +63 -0
  61. data/lib/active_agent/providers/ruby_llm/_types.rb +77 -0
  62. data/lib/active_agent/providers/ruby_llm/embedding_request.rb +16 -0
  63. data/lib/active_agent/providers/ruby_llm/messages/_types.rb +109 -0
  64. data/lib/active_agent/providers/ruby_llm/messages/assistant.rb +27 -0
  65. data/lib/active_agent/providers/ruby_llm/messages/base.rb +48 -0
  66. data/lib/active_agent/providers/ruby_llm/messages/system.rb +18 -0
  67. data/lib/active_agent/providers/ruby_llm/messages/tool.rb +24 -0
  68. data/lib/active_agent/providers/ruby_llm/messages/user.rb +18 -0
  69. data/lib/active_agent/providers/ruby_llm/options.rb +28 -0
  70. data/lib/active_agent/providers/ruby_llm/request.rb +30 -0
  71. data/lib/active_agent/providers/ruby_llm/tool_proxy.rb +45 -0
  72. data/lib/active_agent/providers/ruby_llm_provider.rb +407 -0
  73. data/lib/active_agent/railtie.rb +32 -1
  74. data/lib/active_agent/telemetry/configuration.rb +213 -0
  75. data/lib/active_agent/telemetry/instrumentation.rb +155 -0
  76. data/lib/active_agent/telemetry/reporter.rb +176 -0
  77. data/lib/active_agent/telemetry/span.rb +267 -0
  78. data/lib/active_agent/telemetry/tracer.rb +184 -0
  79. data/lib/active_agent/telemetry.rb +162 -0
  80. data/lib/active_agent/version.rb +1 -1
  81. data/lib/active_agent.rb +2 -0
  82. data/lib/generators/active_agent/dashboard/install/install_generator.rb +96 -0
  83. data/lib/generators/active_agent/dashboard/install/templates/initializer.rb +89 -0
  84. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_runs.rb +42 -0
  85. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_templates.rb +38 -0
  86. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_versions.rb +22 -0
  87. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agents.rb +53 -0
  88. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_sandbox_runs.rb +28 -0
  89. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_sandbox_sessions.rb +43 -0
  90. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_session_recordings.rb +44 -0
  91. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_telemetry_traces.rb +56 -0
  92. data/lib/generators/active_agent/dashboard/install_generator.rb +64 -0
  93. data/lib/generators/active_agent/dashboard/templates/active_agent_dashboard.rb.erb +30 -0
  94. data/lib/generators/active_agent/dashboard/templates/create_active_agent_telemetry_traces.rb.erb +30 -0
  95. metadata +120 -17
@@ -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-5-v1:0"
19
+ #
20
+ # @example Agent usage
21
+ # class SummaryAgent < ApplicationAgent
22
+ # generate_with :bedrock, model: "eu.anthropic.claude-sonnet-5-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
- tool_info = "[Tool Use: #{block[:name]}]\nID: #{block[:id]}\nInput: #{JSON.pretty_generate(block[:input])}"
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
- tool_info = "[MCP Tool Use: #{block[:name]}]\nID: #{block[:id]}\nServer: #{block[:server_name]}\nInput: #{JSON.pretty_generate(block[:input] || {})}"
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
@@ -55,6 +55,7 @@ module ActiveAgent
55
55
  yield
56
56
  rescue => exception
57
57
  rescue_with_handler(exception) || raise
58
+ nil # Discard handler return value to prevent polluting raw_response
58
59
  end
59
60
 
60
61
  # Bubbles up exceptions to the Agent's rescue_from if a handler is defined.
@@ -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
@@ -0,0 +1,94 @@
1
+ require_relative "_base_provider"
2
+
3
+ require_gem!(:openai, __FILE__)
4
+
5
+ require_relative "open_ai_provider"
6
+ require_relative "gemini/_types"
7
+
8
+ module ActiveAgent
9
+ module Providers
10
+ # Provides access to Google's Gemini API via OpenAI-compatible endpoint.
11
+ #
12
+ # Extends OpenAI provider to work with Gemini's OpenAI-compatible API,
13
+ # enabling access to Gemini models through a familiar interface.
14
+ #
15
+ # @see OpenAI::ChatProvider
16
+ # @see https://ai.google.dev/gemini-api/docs/openai
17
+ class GeminiProvider < OpenAI::ChatProvider
18
+ # @return [String]
19
+ def self.service_name
20
+ "Gemini"
21
+ end
22
+
23
+ # @return [Class]
24
+ def self.options_klass
25
+ namespace::Options
26
+ end
27
+
28
+ # @return [ActiveModel::Type::Value]
29
+ def self.prompt_request_type
30
+ namespace::RequestType.new
31
+ end
32
+
33
+ # @return [ActiveModel::Type::Value]
34
+ def self.embed_request_type
35
+ namespace::Embedding::RequestType.new
36
+ end
37
+
38
+ protected
39
+
40
+ # Executes chat completion request with Gemini-specific error handling.
41
+ #
42
+ # @see OpenAI::ChatProvider#api_prompt_execute
43
+ # @param parameters [Hash]
44
+ # @return [Object, nil] response object or nil for streaming
45
+ # @raise [OpenAI::Errors::APIConnectionError] when Gemini API unreachable
46
+ def api_prompt_execute(parameters)
47
+ super
48
+
49
+ rescue ::OpenAI::Errors::APIConnectionError => exception
50
+ log_connection_error(exception)
51
+ raise exception
52
+ end
53
+
54
+ # Executes embedding request with Gemini-specific error handling.
55
+ #
56
+ # @param parameters [Hash]
57
+ # @return [Hash] symbolized API response
58
+ # @raise [OpenAI::Errors::APIConnectionError] when Gemini API unreachable
59
+ def api_embed_execute(parameters)
60
+ client.embeddings.create(**parameters).as_json.deep_symbolize_keys
61
+ rescue ::OpenAI::Errors::APIConnectionError => exception
62
+ log_connection_error(exception)
63
+ raise exception
64
+ end
65
+
66
+ # Merges streaming delta into the message with role cleanup.
67
+ #
68
+ # Overrides parent to handle Gemini's role copying behavior which duplicates
69
+ # the role field in every streaming chunk, requiring manual cleanup to prevent
70
+ # message corruption.
71
+ #
72
+ # @see OpenAI::ChatProvider#message_merge_delta
73
+ # @param message [Hash]
74
+ # @param delta [Hash]
75
+ # @return [Hash]
76
+ def message_merge_delta(message, delta)
77
+ message[:role] = delta.delete(:role) if delta[:role]
78
+
79
+ hash_merge_delta(message, delta)
80
+ end
81
+
82
+ # Logs connection failures with Gemini API details for debugging.
83
+ #
84
+ # @param error [Exception]
85
+ # @return [void]
86
+ def log_connection_error(error)
87
+ instrument("connection_error.provider.active_agent",
88
+ uri_base: options.base_url,
89
+ exception: error.class,
90
+ message: error.message)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -22,16 +22,13 @@ module ActiveAgent
22
22
  attribute :stream, :boolean, default: false
23
23
  attribute :tools # Array of tool definitions
24
24
  attribute :tool_choice # Tool choice configuration
25
+ attribute :response_format
25
26
 
26
27
  # Common Format Compatibility
27
28
  def message=(value)
28
29
  self.messages ||= []
29
30
  self.messages << value
30
31
  end
31
-
32
- def response_format
33
- { type: "text" }
34
- end
35
32
  end
36
33
  end
37
34
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/hash/keys"
4
+ require "uri"
4
5
 
5
6
  module ActiveAgent
6
7
  module Providers
@@ -124,10 +125,19 @@ module ActiveAgent
124
125
  { type: "text", text: msg_hash[:text] },
125
126
  { type: "image_url", image_url: { url: msg_hash[:image] } }
126
127
  ]
128
+ elsif msg_hash.key?(:text) && msg_hash.key?(:document)
129
+ # Shorthand with both text and document: { text: "...", document: "url" }
130
+ [
131
+ { type: "text", text: msg_hash[:text] },
132
+ build_file_content(msg_hash[:document])
133
+ ]
127
134
  elsif msg_hash.key?(:image)
128
135
  # Shorthand with only image: { image: "url" }
129
136
  # Text comes from adjacent prompt arguments
130
137
  [ { type: "image_url", image_url: { url: msg_hash[:image] } } ]
138
+ elsif msg_hash.key?(:document)
139
+ # Shorthand with only document: { document: "url" }
140
+ [ build_file_content(msg_hash[:document]) ]
131
141
  elsif msg_hash.key?(:text)
132
142
  # Shorthand: { text: "..." } or { role: "...", text: "..." }
133
143
  msg_hash[:text]
@@ -137,13 +147,39 @@ module ActiveAgent
137
147
  end
138
148
 
139
149
  # Create appropriate message param based on role and content
140
- extra_params = msg_hash.except(:role, :content, :text, :image)
150
+ extra_params = msg_hash.except(:role, :content, :text, :image, :document)
141
151
  create_message_param(role, content, extra_params)
142
152
  else
143
153
  raise ArgumentError, "Cannot normalize #{message.class} to message"
144
154
  end
145
155
  end
146
156
 
157
+ # Builds a file content block for the Chat API file type
158
+ #
159
+ # Handles both URL and data URI formats:
160
+ # - URL: "http://example.com/document.pdf" → { type: "file", file: { filename: "...", url: "..." } }
161
+ # - Data URI: "data:application/pdf;base64,..." → { type: "file", file: { filename: "...", file_data: "..." } }
162
+ #
163
+ # @param document [String] URL or data URI
164
+ # @return [Hash] file content block
165
+ def build_file_content(document)
166
+ if document.start_with?("data:")
167
+ # Data URI - extract or infer filename from media type
168
+ media_type = document.match(%r{\Adata:([^;,]+)})&.[](1) || "application/octet-stream"
169
+ extension = media_type.split("/").last&.gsub(/[^a-zA-Z0-9]/, "_") || "bin"
170
+ filename = "document.#{extension}"
171
+ { type: "file", file: { filename: filename, file_data: document } }
172
+ else
173
+ # Regular URL
174
+ filename = File.basename(URI.parse(document).path.to_s)
175
+ filename = "document.pdf" if filename.empty?
176
+ { type: "file", file: { filename: filename, url: document } }
177
+ end
178
+ rescue URI::Error
179
+ # Fallback for invalid URLs
180
+ { type: "file", file: { filename: "document.pdf", url: document } }
181
+ end
182
+
147
183
  # Creates the appropriate gem message param class for the given role
148
184
  #
149
185
  # @param role [String] message role (developer, system, user, assistant, tool, function)
@@ -95,6 +95,8 @@ module ActiveAgent
95
95
  def process_stream_chunk(api_response_event)
96
96
  instrument("stream_chunk.active_agent")
97
97
 
98
+ broadcast_stream_open
99
+
98
100
  # Called Multiple Times: [Chunk<T>, T]<Content, ToolsCall>
99
101
  case api_response_event.type
100
102
  when :chunk
@@ -102,7 +104,7 @@ module ActiveAgent
102
104
 
103
105
  # If we have a delta, we need to update a message in the stack
104
106
  message = find_or_create_message(api_message.index)
105
- message = message_merge_delta(message, api_message.delta.as_json.deep_symbolize_keys)
107
+ message_merge_delta(message, api_message.delta.deep_to_h)
106
108
 
107
109
  # Stream back content changes as they come in
108
110
  if api_message.delta.content
@@ -138,7 +140,7 @@ module ActiveAgent
138
140
  def process_function_calls(api_function_calls)
139
141
  api_function_calls.each do |api_function_call|
140
142
  content = instrument("tool_call.active_agent", tool_name: api_function_call.dig(:function, :name)) do
141
- case api_function_call[:type]
143
+ case api_function_call[:type].to_s
142
144
  when "function"
143
145
  process_tool_call_function(api_function_call[:function])
144
146
  else
@@ -243,7 +245,7 @@ module ActiveAgent
243
245
  end
244
246
  end
245
247
  when String
246
- hash[key] += value
248
+ hash[key] += value.to_s
247
249
  else
248
250
  hash[key] = value
249
251
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../open_ai/chat/_types"
4
+ require_relative "options"
5
+
6
+ module ActiveAgent
7
+ module Providers
8
+ module Requesty
9
+ # ActiveModel type for casting and serializing Requesty requests.
10
+ #
11
+ # Requesty is OpenAI-compatible, so requests use the same shape as the
12
+ # OpenAI Chat API. This delegates entirely to OpenAI::Chat::RequestType.
13
+ RequestType = ActiveAgent::Providers::OpenAI::Chat::RequestType
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../open_ai/options"
4
+
5
+ module ActiveAgent
6
+ module Providers
7
+ module Requesty
8
+ # Configuration options for the Requesty provider.
9
+ #
10
+ # Extends OpenAI::Options, overriding the base URL to point at Requesty's
11
+ # OpenAI-compatible gateway and resolving the API key from REQUESTY_API_KEY.
12
+ # Requesty does not use organization or project identifiers.
13
+ #
14
+ # @example Basic configuration
15
+ # options = Options.new(api_key: ENV["REQUESTY_API_KEY"])
16
+ #
17
+ # @see https://docs.requesty.ai
18
+ # @see https://app.requesty.ai/api-keys Requesty API Keys
19
+ class Options < ActiveAgent::Providers::OpenAI::Options
20
+ # @!attribute base_url
21
+ # @return [String] API endpoint (default: "https://router.requesty.ai/v1")
22
+ attribute :base_url, :string, as: "https://router.requesty.ai/v1"
23
+
24
+ private
25
+
26
+ def resolve_api_key(kwargs)
27
+ kwargs[:api_key] ||
28
+ kwargs[:access_token] ||
29
+ ENV["REQUESTY_API_KEY"] ||
30
+ ENV["REQUESTY_ACCESS_TOKEN"]
31
+ end
32
+
33
+ # Not used as part of Requesty
34
+ def resolve_organization_id(kwargs) = nil
35
+ def resolve_project_id(kwargs) = nil
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,63 @@
1
+ require_relative "_base_provider"
2
+
3
+ require_gem!(:openai, __FILE__)
4
+
5
+ require_relative "open_ai_provider"
6
+ require_relative "requesty/_types"
7
+
8
+ module ActiveAgent
9
+ module Providers
10
+ # Provides access to Requesty's OpenAI-compatible LLM gateway.
11
+ #
12
+ # Extends the OpenAI provider to work with Requesty's OpenAI-compatible API,
13
+ # enabling access to multiple AI models through a single interface using the
14
+ # +provider/model+ naming convention (e.g. +openai/gpt-4o-mini+).
15
+ #
16
+ # Requesty is a plain OpenAI-compatible gateway: requests, responses and
17
+ # transforms are identical to the OpenAI Chat API, so this provider reuses
18
+ # OpenAI::Chat::RequestType and OpenAI::Chat::Transforms directly. The only
19
+ # Requesty-specific configuration is the base URL and API key, which live in
20
+ # Requesty::Options.
21
+ #
22
+ # @example Configuration in active_agent.yml
23
+ # requesty:
24
+ # service: "Requesty"
25
+ # api_key: <%= ENV["REQUESTY_API_KEY"] %>
26
+ # model: "openai/gpt-4o-mini"
27
+ #
28
+ # @see OpenAI::ChatProvider
29
+ # @see https://docs.requesty.ai
30
+ class RequestyProvider < OpenAI::ChatProvider
31
+ # @return [String]
32
+ def self.service_name
33
+ "Requesty"
34
+ end
35
+
36
+ # @return [Class]
37
+ def self.options_klass
38
+ Requesty::Options
39
+ end
40
+
41
+ # @return [ActiveModel::Type::Value]
42
+ def self.prompt_request_type
43
+ OpenAI::Chat::RequestType.new
44
+ end
45
+
46
+ # @return [ActiveModel::Type::Value]
47
+ def self.embed_request_type
48
+ OpenAI::Embedding::RequestType.new
49
+ end
50
+
51
+ protected
52
+
53
+ # @see BaseProvider#api_response_normalize
54
+ # @param api_response [OpenAI::Models::ChatCompletion]
55
+ # @return [Hash] normalized response hash
56
+ def api_response_normalize(api_response)
57
+ return api_response unless api_response
58
+
59
+ OpenAI::Chat::Transforms.gem_to_hash(api_response)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "options"
4
+ require_relative "request"
5
+ require_relative "embedding_request"
6
+
7
+ module ActiveAgent
8
+ module Providers
9
+ module RubyLLM
10
+ # Type for Request model
11
+ class RequestType < ActiveModel::Type::Value
12
+ def cast(value)
13
+ case value
14
+ when Request
15
+ value
16
+ when Hash
17
+ Request.new(**value.deep_symbolize_keys)
18
+ when nil
19
+ nil
20
+ else
21
+ raise ArgumentError, "Cannot cast #{value.class} to Request"
22
+ end
23
+ end
24
+
25
+ def serialize(value)
26
+ case value
27
+ when Request
28
+ value.serialize
29
+ when Hash
30
+ value
31
+ when nil
32
+ nil
33
+ else
34
+ raise ArgumentError, "Cannot serialize #{value.class}"
35
+ end
36
+ end
37
+
38
+ def deserialize(value)
39
+ cast(value)
40
+ end
41
+ end
42
+
43
+ # Type for embedding requests
44
+ class EmbeddingRequestType < ActiveModel::Type::Value
45
+ def cast(value)
46
+ case value
47
+ when RubyLLM::EmbeddingRequest
48
+ value
49
+ when Hash
50
+ RubyLLM::EmbeddingRequest.new(**value.deep_symbolize_keys)
51
+ when nil
52
+ nil
53
+ else
54
+ raise ArgumentError, "Cannot cast #{value.class} to EmbeddingRequest"
55
+ end
56
+ end
57
+
58
+ def serialize(value)
59
+ case value
60
+ when RubyLLM::EmbeddingRequest
61
+ value.serialize
62
+ when Hash
63
+ value
64
+ when nil
65
+ nil
66
+ else
67
+ raise ArgumentError, "Cannot serialize #{value.class}"
68
+ end
69
+ end
70
+
71
+ def deserialize(value)
72
+ cast(value)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_agent/providers/common/model"
4
+
5
+ module ActiveAgent
6
+ module Providers
7
+ module RubyLLM
8
+ # Embedding request model for RubyLLM provider.
9
+ class EmbeddingRequest < Common::BaseModel
10
+ attribute :model, :string
11
+ attribute :input
12
+ attribute :dimensions, :integer
13
+ end
14
+ end
15
+ end
16
+ end