activeagent 1.7.1 → 1.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14e5b0cec7ba170c446d1ed32ad5c9d0705ac8eae4fdc6e4e4a7d17bdae502a2
4
- data.tar.gz: 28e12f0ef3727a97fc231e5f7ace15ad4cb020b379fc76c757c2206c89f2b2e6
3
+ metadata.gz: 881ca4e27488bd719dda01c80e6241bf6d17082876039033c22f9265d1e559aa
4
+ data.tar.gz: 62526259e8a48d5243a480cf3ac6f795c1672b20e8569f7804fcf13af4d25ec4
5
5
  SHA512:
6
- metadata.gz: 9cd766d568ab510b043e4902c1c5fa73ce877a268761168509f2772190ab0f2d6f259fe62e4654aa2a002d20228f876fce82c7c2f5f3e1018cd6cb01935aed27
7
- data.tar.gz: 197cab65243e11561c7216f08e9b30369819e3ef73627d0f6e72092a7fc74c16a278638f5044e11f292149a1a39dbe11cab9593a41e7289049a3a90c6232a0a0
6
+ metadata.gz: 0d135cc35623442cdf38df2d588e323fec82f7e16c7d9c8fcf90d2add91bbb55476e07fe548c70ddb2b22cc709e684ab8ccc53dd337c0650800f9d6dd268f547
7
+ data.tar.gz: 76a0b25ef5cf125e99399ff61cef131d2fe6ae6dade18173601f73f0ec7ecedde6bfc0738af99ac490444b1a067cd136dc2caac34cc1f1cd6c01996cbdddf841
data/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.7.2] - 2026-09-29
11
+
12
+ Releases `activeagent` and `actionagent` 1.7.2 from one tag. A patch on 1.7.1:
13
+ the RubyLLM provider runs on ruby_llm 1.16 and 2.x, where 1.7.1 required 1.x.
14
+ Apps on the RubyLLM provider need ruby_llm 1.16 or later; earlier 1.x
15
+ releases lack the APIs the provider calls. No migrations.
16
+
17
+ ### Changed
18
+
19
+ - **The RubyLLM provider supports ruby_llm 1.16 and 2.x** (`activeagent`).
20
+ The adapter handles 2.x's tool interface, token limits, embedding model
21
+ objects, usage and finish reasons while keeping the 1.16 API working.
22
+ Requiring `>= 1.16, < 3` prevents Bundler from selecting an older 1.x
23
+ release without the APIs the adapter calls. Rails main can now resolve
24
+ RubyLLM 2.x alongside Active Storage's Marcel 2 dependency. CI also runs
25
+ the full suite with RubyLLM 1.16 to retain coverage of that version.
26
+ Unsupported-version errors name the loaded version and both bounds (#508).
27
+
10
28
  ## [1.7.1] - 2026-09-29
11
29
 
12
30
  Releases `activeagent` and `actionagent` 1.7.1 from one tag. A patch on 1.7.0:
@@ -10,8 +10,8 @@ require_relative "concerns/tool_choice_clearing"
10
10
  GEM_LOADERS = {
11
11
  anthropic: [ "anthropic", "~> 1.12", "anthropic" ],
12
12
  openai: [ "openai", "~> 0.34", "openai" ],
13
- # ruby_llm 2.0 renamed the APIs RubyLLMProvider calls.
14
- ruby_llm: [ "ruby_llm", "~> 1.0", "ruby_llm" ]
13
+ # Keep a tested floor: 1.2 lacks the provider APIs this adapter uses.
14
+ ruby_llm: [ "ruby_llm", [ ">= 1.16", "< 3" ], "ruby_llm" ]
15
15
  }
16
16
 
17
17
  # Requires a provider's gem dependency.
@@ -23,16 +23,17 @@ GEM_LOADERS = {
23
23
  # version is outside the supported range
24
24
  def require_gem!(type, file_name)
25
25
  gem_name, requirement, package_name = GEM_LOADERS.fetch(type)
26
+ requirements = Array(requirement)
26
27
  provider_name = file_name.split("/").last.delete_suffix(".rb").camelize
27
28
 
28
29
  begin
29
- gem(gem_name, requirement)
30
+ gem(gem_name, *requirements)
30
31
  require(package_name)
31
32
  rescue LoadError
32
33
  loaded = Gem.loaded_specs[gem_name]
33
34
  if loaded && !Gem::Requirement.new(requirement).satisfied_by?(loaded.version)
34
- raise LoadError, "#{provider_name} supports the '#{gem_name}' gem #{requirement}, but #{loaded.version} is loaded. " \
35
- "Add `gem \"#{gem_name}\", \"#{requirement}\"` to your Gemfile and run `bundle update #{gem_name}`."
35
+ raise LoadError, "#{provider_name} supports the '#{gem_name}' gem #{requirements.join(', ')}, but #{loaded.version} is loaded. " \
36
+ "Add `gem \"#{gem_name}\", #{requirements.map(&:inspect).join(', ')}` to your Gemfile and run `bundle update #{gem_name}`."
36
37
  end
37
38
 
38
39
  raise LoadError, "The '#{gem_name}' gem is required for #{provider_name}. Please add it to your Gemfile and run `bundle install`."
@@ -27,6 +27,11 @@ module ActiveAgent
27
27
  {}
28
28
  end
29
29
 
30
+ # RubyLLM 2 names for the same tool interface.
31
+ alias_method :parameters_schema, :params_schema
32
+ alias_method :declared_parameters, :parameters
33
+ alias_method :provider_options, :provider_params
34
+
30
35
  private
31
36
 
32
37
  def deep_stringify(obj)
@@ -62,14 +62,19 @@ module ActiveAgent
62
62
  schema = ruby_llm_schema(parameters[:response_format])
63
63
  kwargs[:schema] = schema if schema
64
64
 
65
- # Pass extra params (max_tokens, etc.) via RubyLLM's params: deep-merge
65
+ # RubyLLM 2 renamed params: and exposes a provider-neutral token limit.
66
66
  max_tokens = parameters[:max_tokens] || options.max_tokens
67
67
  if max_tokens
68
- kwargs[:params] = { max_tokens: max_tokens }
68
+ if ruby_llm_v2?
69
+ kwargs[:max_output_tokens] = max_tokens
70
+ else
71
+ kwargs[:params] = { max_tokens: max_tokens }
72
+ end
69
73
  end
70
74
 
71
75
  if parameters[:stream]
72
76
  stream_proc = parameters[:stream]
77
+ @stream_tool_calls = {}
73
78
 
74
79
  # For streaming, pass a block that forwards chunks
75
80
  @ruby_llm_provider.complete(messages, **kwargs) do |chunk|
@@ -95,7 +100,8 @@ module ActiveAgent
95
100
  inputs = input.is_a?(Array) ? input : [ input ]
96
101
 
97
102
  data = inputs.map.with_index do |text, index|
98
- embedding = @ruby_llm_provider.embed(text, model: model_id, dimensions: parameters[:dimensions])
103
+ embedding_model = ruby_llm_v2? ? @ruby_llm_model : model_id
104
+ embedding = @ruby_llm_provider.embed(text, model: embedding_model, dimensions: parameters[:dimensions])
99
105
 
100
106
  {
101
107
  object: "embedding",
@@ -138,12 +144,15 @@ module ActiveAgent
138
144
  # Handle tool calls in chunk
139
145
  if chunk.tool_calls&.any?
140
146
  message[:tool_calls] ||= []
141
- chunk.tool_calls.each do |_id, tool_call|
142
- existing = message[:tool_calls].find { |tc| tc[:id] == tool_call.id }
147
+ chunk.tool_calls.each do |key, tool_call|
148
+ # RubyLLM 2 keys OpenAI deltas by index; only the first delta
149
+ # includes the call ID and name. Keep each index tied to its call.
150
+ existing = message[:tool_calls].find { |tc| tool_call.id && tc[:id] == tool_call.id }
151
+ existing ||= @stream_tool_calls[key]
143
152
  if existing
144
153
  existing[:function][:arguments] += tool_call.arguments.to_s if tool_call.arguments
145
154
  else
146
- message[:tool_calls] << {
155
+ existing = {
147
156
  id: tool_call.id,
148
157
  type: "function",
149
158
  function: {
@@ -151,7 +160,9 @@ module ActiveAgent
151
160
  arguments: tool_call.arguments.to_s
152
161
  }
153
162
  }
163
+ message[:tool_calls] << existing
154
164
  end
165
+ @stream_tool_calls[key] = existing
155
166
  end
156
167
  end
157
168
 
@@ -254,6 +265,10 @@ module ActiveAgent
254
265
 
255
266
  private
256
267
 
268
+ def ruby_llm_v2?
269
+ Gem.loaded_specs.fetch("ruby_llm").version.segments.first >= 2
270
+ end
271
+
257
272
  # Resolves and caches the RubyLLM provider for the given model.
258
273
  #
259
274
  # Reuses the cached provider if the model hasn't changed (e.g., during
@@ -447,6 +462,8 @@ module ActiveAgent
447
462
  # Add stop_reason if available
448
463
  if response.respond_to?(:stop_reason) && response.stop_reason
449
464
  hash[:stop_reason] = response.stop_reason
465
+ elsif response.respond_to?(:finish_reason) && response.finish_reason
466
+ hash[:stop_reason] = { stop: "end_turn", tool_calls: "tool_use" }.fetch(response.finish_reason, response.finish_reason.to_s)
450
467
  elsif response.tool_calls&.any?
451
468
  hash[:stop_reason] = "tool_use"
452
469
  else
@@ -454,7 +471,10 @@ module ActiveAgent
454
471
  end
455
472
 
456
473
  # Add usage info if available
457
- if response.respond_to?(:input_tokens) && response.input_tokens
474
+ if response.respond_to?(:tokens)
475
+ tokens = response.tokens
476
+ hash[:usage] = { input_tokens: tokens.input, output_tokens: tokens.output } if tokens.input
477
+ elsif response.respond_to?(:input_tokens) && response.input_tokens
458
478
  hash[:usage] = {
459
479
  input_tokens: response.input_tokens,
460
480
  output_tokens: response.output_tokens
@@ -1,3 +1,3 @@
1
1
  module ActiveAgent
2
- VERSION = "1.7.1"
2
+ VERSION = "1.7.2"
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: 1.7.1
4
+ version: 1.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Bowen
@@ -204,16 +204,22 @@ dependencies:
204
204
  name: ruby_llm
205
205
  requirement: !ruby/object:Gem::Requirement
206
206
  requirements:
207
- - - "~>"
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ version: '1.16'
210
+ - - "<"
208
211
  - !ruby/object:Gem::Version
209
- version: '1.0'
212
+ version: '3'
210
213
  type: :development
211
214
  prerelease: false
212
215
  version_requirements: !ruby/object:Gem::Requirement
213
216
  requirements:
214
- - - "~>"
217
+ - - ">="
218
+ - !ruby/object:Gem::Version
219
+ version: '1.16'
220
+ - - "<"
215
221
  - !ruby/object:Gem::Version
216
- version: '1.0'
222
+ version: '3'
217
223
  - !ruby/object:Gem::Dependency
218
224
  name: capybara
219
225
  requirement: !ruby/object:Gem::Requirement