activeagent 0.6.0rc4 → 0.6.0

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: 805b4d2cce3173788f3bb281e40973d8782891937b5220654b7dad53a9f701ec
4
- data.tar.gz: 968f534f94d4e32541f1d87e21854add0f4bf8c9e851ba75f7599291a0b384b9
3
+ metadata.gz: 56c61e9a6a6173d572792952121413f72d49d9b9bcee932a350666d845236465
4
+ data.tar.gz: 86267a64dea6c8eef7bf890fe10fcb1ef1a2b2d09ddeab3f0a9d1d25bb6465fd
5
5
  SHA512:
6
- metadata.gz: ec13c2dd2d8deb4fd4adb1a6f302de6a7bb5057fad1cd45f27fdecc6b8bea7b5496f3078e7a1f40168de9b4efaa8e4a2abf1cf1bd2865fa929912c58adce561c
7
- data.tar.gz: a564772159ac558c8350d770977fc9b7b509c814435a748cac61af89bd87dc196614b45cc9f33db7bce8951568f659730bfe4cf95fd274f145ae00bb78510af6
6
+ metadata.gz: c173eac89c75335d474c533a41f3137ef2279c5aef1a90d60c93270383dd77c1e15b557c1e006e41c6f15f47f0fce12502c924fd939d58cac85978b97d749d2f
7
+ data.tar.gz: 6bd6d686a0a1a2613206f8b6d50d093a343e7913bd0dbdaac669abb95895a0306f114cc77e2cea01280981b5b5fff403d0edc6de1b1912cfc822ecc2935de961
data/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
  <source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/2bad263a-c09f-40b6-94ba-fff8e346d65d">
3
3
  <img alt="activeagents_banner" src="https://github.com/user-attachments/assets/0ebbaa2f-c6bf-4d40-bb77-931015a14be3">
4
4
  </picture>
5
- *Build AI in Rails*
6
5
 
7
6
 
7
+ > *Build AI in Rails*
8
8
  >
9
9
  > *Now Agents are Controllers*
10
10
  >
@@ -280,7 +280,7 @@ module ActiveAgent
280
280
  def initialize # :nodoc:
281
281
  super
282
282
  @_prompt_was_called = false
283
- @_context = ActiveAgent::ActionPrompt::Prompt.new(options: self.class.options || {}, agent_instance: self)
283
+ @_context = ActiveAgent::ActionPrompt::Prompt.new(options: self.class.options&.deep_dup || {}, agent_instance: self)
284
284
  end
285
285
 
286
286
  def process(method_name, *args) # :nodoc:
@@ -444,7 +444,10 @@ module ActiveAgent
444
444
  :presence_penalty, :response_format, :seed, :stop, :tools_choice, :plugins,
445
445
 
446
446
  # OpenRouter Provider Settings
447
- :data_collection, :require_parameters, :only, :ignore, :quantizations, :sort, :max_price
447
+ :data_collection, :require_parameters, :only, :ignore, :quantizations, :sort, :max_price,
448
+
449
+ # Built-in Tools Support (OpenAI Responses API)
450
+ :tools
448
451
  )
449
452
  # Handle explicit options parameter
450
453
  explicit_options = prompt_options[:options] || {}
@@ -7,6 +7,7 @@ module ActiveAgent
7
7
  included do
8
8
  include ActiveSupport::Callbacks
9
9
  define_callbacks :generation, skip_after_callbacks_if_terminated: true
10
+ define_callbacks :embedding, skip_after_callbacks_if_terminated: true
10
11
  end
11
12
 
12
13
  module ClassMethods
@@ -18,6 +19,14 @@ module ActiveAgent
18
19
  set_callback(:generation, callback, name, options)
19
20
  end
20
21
  end
22
+
23
+ # # Defines a callback that will get called right before/after/around the
24
+ # # embedding provider method.
25
+ define_method "#{callback}_embedding" do |*names, &blk|
26
+ _insert_callbacks(names, blk) do |name, options|
27
+ set_callback(:embedding, callback, name, options)
28
+ end
29
+ end
21
30
  end
22
31
  end
23
32
  end
@@ -50,6 +50,18 @@ module ActiveAgent
50
50
  end
51
51
  end
52
52
 
53
+ def embed_now
54
+ processed_agent.handle_exceptions do
55
+ processed_agent.run_callbacks(:embedding) do
56
+ processed_agent.embed
57
+ end
58
+ end
59
+ end
60
+
61
+ def embed_later(options = {})
62
+ enqueue_generation :embed_now, options
63
+ end
64
+
53
65
  private
54
66
 
55
67
  def processed_agent
@@ -4,7 +4,7 @@ begin
4
4
  gem "ruby-anthropic", "~> 0.4.2"
5
5
  require "anthropic"
6
6
  rescue LoadError
7
- raise LoadError, "The 'ruby-anthropic' gem is required for AnthropicProvider. Please add it to your Gemfile and run `bundle install`."
7
+ raise LoadError, "The 'ruby-anthropic ~> 0.4.2' gem is required for AnthropicProvider. Please add it to your Gemfile and run `bundle install`."
8
8
  end
9
9
 
10
10
  require "active_agent/action_prompt/action"
@@ -60,7 +60,7 @@ module ActiveAgent
60
60
  end
61
61
 
62
62
  def format_error_message(error)
63
- message = if error.respond_to?(:response)
63
+ message = if error.respond_to?(:response) && error.response
64
64
  error.response[:body]
65
65
  elsif error.respond_to?(:message)
66
66
  error.message
@@ -9,7 +9,56 @@ module ActiveAgent
9
9
  @access_token ||= config["api_key"] || config["access_token"] || ENV["OLLAMA_API_KEY"] || ENV["OLLAMA_ACCESS_TOKEN"]
10
10
  @model_name = config["model"]
11
11
  @host = config["host"] || "http://localhost:11434"
12
- @client = OpenAI::Client.new(uri_base: @host, access_token: @access_token, log_errors: true)
12
+ @api_version = config["api_version"] || "v1"
13
+ @client = OpenAI::Client.new(uri_base: @host, access_token: @access_token, log_errors: Rails.env.development?, api_version: @api_version)
14
+ end
15
+
16
+ protected
17
+
18
+ def format_error_message(error)
19
+ # Check for various connection-related errors
20
+ connection_errors = [
21
+ Errno::ECONNREFUSED,
22
+ Errno::EADDRNOTAVAIL,
23
+ Errno::EHOSTUNREACH,
24
+ Net::OpenTimeout,
25
+ Net::ReadTimeout,
26
+ SocketError,
27
+ Faraday::ConnectionFailed
28
+ ]
29
+
30
+ if connection_errors.any? { |klass| error.is_a?(klass) } ||
31
+ (error.message&.include?("Failed to open TCP connection") ||
32
+ error.message&.include?("Connection refused"))
33
+ "Unable to connect to Ollama at #{@host}. Please ensure Ollama is running on the configured host and port.\n" \
34
+ "You can start Ollama with: `ollama serve`\n" \
35
+ "Or update your configuration to point to the correct host."
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ def embeddings_parameters(input: prompt.message.content, model: "nomic-embed-text")
42
+ {
43
+ model: @config["embedding_model"] || model,
44
+ input: input
45
+ }
46
+ end
47
+
48
+ def embeddings_response(response, request_params = nil)
49
+ # Ollama can return either format:
50
+ # 1. OpenAI-compatible: { "data": [{ "embedding": [...] }] }
51
+ # 2. Native Ollama: { "embedding": [...] }
52
+ embedding = response.dig("data", 0, "embedding") || response.dig("embedding")
53
+
54
+ message = ActiveAgent::ActionPrompt::Message.new(content: embedding, role: "assistant")
55
+
56
+ @response = ActiveAgent::GenerationProvider::Response.new(
57
+ prompt: prompt,
58
+ message: message,
59
+ raw_response: response,
60
+ raw_request: request_params
61
+ )
13
62
  end
14
63
  end
15
64
  end
@@ -2,7 +2,7 @@ begin
2
2
  gem "ruby-openai", ">= 8.1.0"
3
3
  require "openai"
4
4
  rescue LoadError
5
- raise LoadError, "The 'ruby-openai' gem is required for OpenAIProvider. Please add it to your Gemfile and run `bundle install`."
5
+ raise LoadError, "The 'ruby-openai >= 8.1.0' gem is required for OpenAIProvider. Please add it to your Gemfile and run `bundle install`."
6
6
  end
7
7
 
8
8
  require "active_agent/action_prompt/action"
@@ -31,7 +31,7 @@ module ActiveAgent
31
31
  organization_id: @organization_id,
32
32
  admin_token: @admin_token,
33
33
  log_errors: Rails.env.development?
34
- )
34
+ )
35
35
 
36
36
  @model_name = config["model"] || "gpt-4o-mini"
37
37
  end
@@ -92,10 +92,39 @@ module ActiveAgent
92
92
 
93
93
  private
94
94
 
95
- # Now using modules, but we can override build_provider_parameters for OpenAI-specific needs
96
- # The prompt_parameters method comes from ParameterBuilder module
97
- # The format_tools method comes from ToolManagement module
98
- # The provider_messages method comes from MessageFormatting module
95
+ # Override from ParameterBuilder to add web_search_options for Chat API
96
+ def build_provider_parameters
97
+ params = {}
98
+
99
+ # Check if we're using a model that supports web_search_options in Chat API
100
+ if chat_api_web_search_model? && @prompt.options[:web_search]
101
+ params[:web_search_options] = build_web_search_options(@prompt.options[:web_search])
102
+ end
103
+
104
+ params
105
+ end
106
+
107
+ def chat_api_web_search_model?
108
+ model = @prompt.options[:model] || @model_name
109
+ [ "gpt-4o-search-preview", "gpt-4o-mini-search-preview" ].include?(model)
110
+ end
111
+
112
+ def build_web_search_options(web_search_config)
113
+ options = {}
114
+
115
+ if web_search_config.is_a?(Hash)
116
+ options[:search_context_size] = web_search_config[:search_context_size] if web_search_config[:search_context_size]
117
+
118
+ if web_search_config[:user_location]
119
+ options[:user_location] = {
120
+ type: "approximate",
121
+ approximate: web_search_config[:user_location]
122
+ }
123
+ end
124
+ end
125
+
126
+ options
127
+ end
99
128
 
100
129
  def chat_response(response, request_params = nil)
101
130
  return @response if prompt.options[:stream]
@@ -123,7 +152,7 @@ module ActiveAgent
123
152
  role: message_json["role"].intern,
124
153
  action_requested: message_json["finish_reason"] == "tool_calls",
125
154
  raw_actions: message_json["tool_calls"] || [],
126
- content_type: prompt.output_schema.present? ? "application/json" : "text/plain",
155
+ content_type: prompt.output_schema.present? ? "application/json" : "text/plain"
127
156
  )
128
157
 
129
158
  @response = ActiveAgent::GenerationProvider::Response.new(
@@ -161,14 +190,65 @@ module ActiveAgent
161
190
  end
162
191
 
163
192
  def responses_parameters(model: @prompt.options[:model] || @model_name, messages: @prompt.messages, temperature: @prompt.options[:temperature] || @config["temperature"] || 0.7, tools: @prompt.actions, structured_output: @prompt.output_schema)
193
+ # Build tools array, combining action tools with built-in tools
194
+ tools_array = build_tools_for_responses(tools)
195
+
164
196
  {
165
197
  model: model,
166
198
  input: ActiveAgent::GenerationProvider::ResponsesAdapter.new(@prompt).input,
167
- tools: tools.presence,
199
+ tools: tools_array.presence,
168
200
  text: structured_output
169
201
  }.compact
170
202
  end
171
203
 
204
+ def build_tools_for_responses(action_tools)
205
+ tools = []
206
+
207
+ # Start with action tools (user-defined functions) if any
208
+ tools.concat(action_tools) if action_tools.present?
209
+
210
+ # Add built-in tools if specified in options[:tools]
211
+ if @prompt.options[:tools].present?
212
+ built_in_tools = @prompt.options[:tools]
213
+ built_in_tools = [ built_in_tools ] unless built_in_tools.is_a?(Array)
214
+
215
+ built_in_tools.each do |tool|
216
+ next unless tool.is_a?(Hash)
217
+
218
+ case tool[:type]
219
+ when "web_search_preview", "web_search"
220
+ web_search_tool = { type: "web_search_preview" }
221
+ web_search_tool[:search_context_size] = tool[:search_context_size] if tool[:search_context_size]
222
+ web_search_tool[:user_location] = tool[:user_location] if tool[:user_location]
223
+ tools << web_search_tool
224
+
225
+ when "image_generation"
226
+ image_gen_tool = { type: "image_generation" }
227
+ image_gen_tool[:size] = tool[:size] if tool[:size]
228
+ image_gen_tool[:quality] = tool[:quality] if tool[:quality]
229
+ image_gen_tool[:format] = tool[:format] if tool[:format]
230
+ image_gen_tool[:compression] = tool[:compression] if tool[:compression]
231
+ image_gen_tool[:background] = tool[:background] if tool[:background]
232
+ image_gen_tool[:partial_images] = tool[:partial_images] if tool[:partial_images]
233
+ tools << image_gen_tool
234
+
235
+ when "mcp"
236
+ mcp_tool = { type: "mcp" }
237
+ mcp_tool[:server_label] = tool[:server_label] if tool[:server_label]
238
+ mcp_tool[:server_description] = tool[:server_description] if tool[:server_description]
239
+ mcp_tool[:server_url] = tool[:server_url] if tool[:server_url]
240
+ mcp_tool[:connector_id] = tool[:connector_id] if tool[:connector_id]
241
+ mcp_tool[:authorization] = tool[:authorization] if tool[:authorization]
242
+ mcp_tool[:require_approval] = tool[:require_approval] if tool[:require_approval]
243
+ mcp_tool[:allowed_tools] = tool[:allowed_tools] if tool[:allowed_tools]
244
+ tools << mcp_tool
245
+ end
246
+ end
247
+ end
248
+
249
+ tools
250
+ end
251
+
172
252
  def embeddings_parameters(input: prompt.message.content, model: "text-embedding-3-large")
173
253
  {
174
254
  model: model,
@@ -1,3 +1,3 @@
1
1
  module ActiveAgent
2
- VERSION = "0.6.0rc4"
2
+ VERSION = "0.6.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeagent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0rc4
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Bowen
@@ -249,6 +249,34 @@ dependencies:
249
249
  - - ">="
250
250
  - !ruby/object:Gem::Version
251
251
  version: '0'
252
+ - !ruby/object:Gem::Dependency
253
+ name: cuprite
254
+ requirement: !ruby/object:Gem::Requirement
255
+ requirements:
256
+ - - "~>"
257
+ - !ruby/object:Gem::Version
258
+ version: '0.15'
259
+ type: :development
260
+ prerelease: false
261
+ version_requirements: !ruby/object:Gem::Requirement
262
+ requirements:
263
+ - - "~>"
264
+ - !ruby/object:Gem::Version
265
+ version: '0.15'
266
+ - !ruby/object:Gem::Dependency
267
+ name: capybara
268
+ requirement: !ruby/object:Gem::Requirement
269
+ requirements:
270
+ - - "~>"
271
+ - !ruby/object:Gem::Version
272
+ version: '3.40'
273
+ type: :development
274
+ prerelease: false
275
+ version_requirements: !ruby/object:Gem::Requirement
276
+ requirements:
277
+ - - "~>"
278
+ - !ruby/object:Gem::Version
279
+ version: '3.40'
252
280
  description: The only agent-oriented AI framework designed for Rails, where Agents
253
281
  are Controllers. Build AI features with less complexity using the MVC conventions
254
282
  you love.