activeagent 1.0.2 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a1deba19c874b6abb36731bcee93bddf79bc193a3443a234cc811d9a4cc8383
4
- data.tar.gz: 6f8847ab0d5eebf0ce26a8425f7630c884f0b9af71384c67325e3c06a8b0a8ae
3
+ metadata.gz: 90c3dbcc6a09014256fc36ac0bf88de83aa6a85d95c688be1eb289dde397c633
4
+ data.tar.gz: 6bdf588db4ab2809efaddde5ee7b06fd58ad59e1b5b3479e6c653828d90a4ad9
5
5
  SHA512:
6
- metadata.gz: 767f039bd0af4f600a34becaac7bb2d04d2f8b768aef11242191f8a355b4a442c9c10690fe25a1a7f8e0da8b27e7216025c6f882c6d6002e5da9d0a9e876f1cb
7
- data.tar.gz: 30725fb2369bd3b8dc23aa9d41e7d8a9843c3c8dd0aa629ace4bbd4cc217077339684ae0925cc6ea330c1e018b444a66057bcf5854b5bebf2bcb6a14bf57a322
6
+ metadata.gz: a70e79c1a1b0f683ac3322476b9f2cdaa70deaf3f8e035d2d5307e8fb6561ea5cc5b2589edfa1b2e3a68e28a2db0806d591d1f6726cbd6ab39ed512a072388db
7
+ data.tar.gz: a94dd4a891041044b02117ddc2bcf448cbf1c31c764fbaf8e3cd88b0b81cddf636bb90cac2ddfcb4706ec91b46da2490fdb24fd1e967d9aa200e95ca655a8caf
@@ -142,7 +142,7 @@ module ActiveAgent
142
142
 
143
143
  return if !strict && !lookup_context.exists?(template_name, _prefixes, false, [], **options)
144
144
 
145
- template = lookup_context.find_template(template_name, _prefixes, false, [], **options)
145
+ template = lookup_context.find(template_name, _prefixes, false, [], **options)
146
146
 
147
147
  render_to_string(template: template.virtual_path, locals:, layout: false).chomp.presence
148
148
  end
@@ -71,7 +71,7 @@ module ActiveAgent
71
71
  ].freeze
72
72
 
73
73
  # Available providers
74
- PROVIDERS = %w[openai anthropic ollama openrouter].freeze
74
+ PROVIDERS = %w[openai anthropic ollama openrouter requesty].freeze
75
75
 
76
76
  # Returns the configuration as a hash for versioning
77
77
  def configuration_snapshot
@@ -71,7 +71,9 @@ module ActiveAgent
71
71
  # @raise [ArgumentError] when gem model validation fails
72
72
  def initialize(**params)
73
73
  # Step 1: Extract custom fields that gem doesn't support
74
- @response_format = params.delete(:response_format)
74
+ # Read response_format without deleting - normalize_params will delete and convert it
75
+ # to output_config for json_schema, or drop it for other types.
76
+ @response_format = params[:response_format]
75
77
  @stream = params.delete(:stream)
76
78
  anthropic_beta = params.delete(:anthropic_beta)
77
79
 
@@ -31,6 +31,12 @@ module ActiveAgent
31
31
  params[:tools] = normalize_tools(params[:tools]) if params[:tools]
32
32
  params[:tool_choice] = normalize_tool_choice(params[:tool_choice]) if params[:tool_choice]
33
33
 
34
+ # Normalize json_schema response_format → output_config (Anthropic's native structured output field)
35
+ if params[:response_format]
36
+ output_config = normalize_response_format(params.delete(:response_format))
37
+ params[:output_config] = output_config if output_config
38
+ end
39
+
34
40
  # Handle mcps parameter (common format) -> transforms to mcp_servers (provider format)
35
41
  if params[:mcps]
36
42
  params[:mcp_servers] = normalize_mcp_servers(params.delete(:mcps))
@@ -159,6 +165,87 @@ module ActiveAgent
159
165
  end
160
166
  end
161
167
 
168
+ # Normalizes response_format to Anthropic output_config structure.
169
+ #
170
+ # Supported (ActiveAgent common format):
171
+ # { type: "json_schema", json_schema: { name: "...", schema: {...}, strict: true } }
172
+ # → { format: { type: "json_schema", schema: {...} } }
173
+ #
174
+ # Notes:
175
+ # - Anthropic requires `additionalProperties: false` on all object schemas.
176
+ # This is auto-injected into any object schemas that don't have it set.
177
+ # - Anthropic does not use OpenAI's `name` or `strict` fields in output_config.format.
178
+ # - json_object is not handled here; it remains prompt-emulated.
179
+ # - text is not handled here; Anthropic returns plain text by default.
180
+ #
181
+ # @param format [Hash, Symbol, String] ActiveAgent common response_format
182
+ # @return [Hash] Anthropic output_config hash, or nil if not applicable
183
+ def normalize_response_format(format)
184
+ case format
185
+ when Hash
186
+ format_hash = format.deep_symbolize_keys
187
+
188
+ if format_hash[:type].to_s == "json_schema"
189
+ schema = format_hash[:json_schema]&.dig(:schema)
190
+ if schema
191
+ schema = inject_additional_properties(schema.deep_dup)
192
+ end
193
+ {
194
+ format: {
195
+ type: "json_schema",
196
+ schema: schema
197
+ }.compact
198
+ }
199
+ elsif format_hash[:type].to_s == "json_object"
200
+ # json_object is not handled here; it remains prompt-emulated.
201
+ nil
202
+ elsif format_hash[:type].to_s == "text"
203
+ # text is not handled here; it remains prompt-emulated.
204
+ nil
205
+ else
206
+ # Pass through (already properly structured or Anthropic native format)
207
+ format_hash
208
+ end
209
+ when Symbol, String
210
+ # Bare :json_schema without a schema cannot use native output_config.
211
+ # Anthropic requires a valid JSON Schema — return nil to fall back.
212
+ nil
213
+ else
214
+ format
215
+ end
216
+ end
217
+
218
+ # Recursively injects `additionalProperties: false` into all object schemas.
219
+ #
220
+ # Anthropic's structured output API requires this field on all object types.
221
+ # Only inserts when not already explicitly set by the user.
222
+ #
223
+ # @param schema [Hash] JSON Schema hash
224
+ # @return [Hash] schema with additionalProperties injected
225
+ def inject_additional_properties(schema)
226
+ return schema unless schema.is_a?(Hash)
227
+
228
+ if schema[:type] == "object" && !schema.key?(:additionalProperties)
229
+ schema[:additionalProperties] = false
230
+ end
231
+
232
+ # Recurse into nested schemas
233
+ schema[:properties]&.each_value { |prop| inject_additional_properties(prop) }
234
+ schema[:items]&.then { |items| inject_additional_properties(items) }
235
+ schema[:anyOf]&.each { |s| inject_additional_properties(s) }
236
+ schema[:oneOf]&.each { |s| inject_additional_properties(s) }
237
+ schema[:allOf]&.each { |s| inject_additional_properties(s) }
238
+
239
+ if schema[:definitions]
240
+ schema[:definitions].each_value { |defn| inject_additional_properties(defn) }
241
+ end
242
+ if schema[:"$defs"]
243
+ schema[:"$defs"].each_value { |defn| inject_additional_properties(defn) }
244
+ end
245
+
246
+ schema
247
+ end
248
+
162
249
  # Merges consecutive same-role messages into single messages with multiple content blocks.
163
250
  #
164
251
  # Required by Anthropic API - consecutive messages with the same role must be combined.
@@ -437,6 +524,7 @@ module ActiveAgent
437
524
  msg.delete(:model)
438
525
  msg.delete(:stop_reason)
439
526
  msg.delete(:stop_sequence)
527
+ msg.delete(:stop_details)
440
528
  msg.delete(:type)
441
529
  msg.delete(:usage)
442
530
  end
@@ -15,11 +15,11 @@ module ActiveAgent
15
15
  # bedrock:
16
16
  # service: "Bedrock"
17
17
  # aws_region: "eu-west-2"
18
- # model: "eu.anthropic.claude-sonnet-4-5-20250929-v1:0"
18
+ # model: "eu.anthropic.claude-sonnet-5-v1:0"
19
19
  #
20
20
  # @example Agent usage
21
21
  # class SummaryAgent < ApplicationAgent
22
- # generate_with :bedrock, model: "eu.anthropic.claude-sonnet-4-5-20250929-v1:0"
22
+ # generate_with :bedrock, model: "eu.anthropic.claude-sonnet-5-v1:0"
23
23
  #
24
24
  # def summarize
25
25
  # prompt(message: params[:message])
@@ -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
@@ -104,7 +104,7 @@ module ActiveAgent
104
104
 
105
105
  # If we have a delta, we need to update a message in the stack
106
106
  message = find_or_create_message(api_message.index)
107
- message = message_merge_delta(message, api_message.delta.as_json.deep_symbolize_keys)
107
+ message_merge_delta(message, api_message.delta.deep_to_h)
108
108
 
109
109
  # Stream back content changes as they come in
110
110
  if api_message.delta.content
@@ -140,7 +140,7 @@ module ActiveAgent
140
140
  def process_function_calls(api_function_calls)
141
141
  api_function_calls.each do |api_function_call|
142
142
  content = instrument("tool_call.active_agent", tool_name: api_function_call.dig(:function, :name)) do
143
- case api_function_call[:type]
143
+ case api_function_call[:type].to_s
144
144
  when "function"
145
145
  process_tool_call_function(api_function_call[:function])
146
146
  else
@@ -245,7 +245,7 @@ module ActiveAgent
245
245
  end
246
246
  end
247
247
  when String
248
- hash[key] += value
248
+ hash[key] += value.to_s
249
249
  else
250
250
  hash[key] = value
251
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
@@ -1,3 +1,3 @@
1
1
  module ActiveAgent
2
- VERSION = "1.0.2"
2
+ VERSION = "1.0.3"
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.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Bowen
@@ -269,16 +269,30 @@ dependencies:
269
269
  requirements:
270
270
  - - ">="
271
271
  - !ruby/object:Gem::Version
272
- version: '0'
272
+ version: '6.4'
273
273
  type: :development
274
274
  prerelease: false
275
275
  version_requirements: !ruby/object:Gem::Requirement
276
276
  requirements:
277
277
  - - ">="
278
278
  - !ruby/object:Gem::Version
279
- version: '0'
279
+ version: '6.4'
280
280
  - !ruby/object:Gem::Dependency
281
281
  name: webmock
282
+ requirement: !ruby/object:Gem::Requirement
283
+ requirements:
284
+ - - ">="
285
+ - !ruby/object:Gem::Version
286
+ version: '3.26'
287
+ type: :development
288
+ prerelease: false
289
+ version_requirements: !ruby/object:Gem::Requirement
290
+ requirements:
291
+ - - ">="
292
+ - !ruby/object:Gem::Version
293
+ version: '3.26'
294
+ - !ruby/object:Gem::Dependency
295
+ name: cgi
282
296
  requirement: !ruby/object:Gem::Requirement
283
297
  requirements:
284
298
  - - ">="
@@ -297,14 +311,14 @@ dependencies:
297
311
  requirements:
298
312
  - - ">="
299
313
  - !ruby/object:Gem::Version
300
- version: '0'
314
+ version: '3.2'
301
315
  type: :development
302
316
  prerelease: false
303
317
  version_requirements: !ruby/object:Gem::Requirement
304
318
  requirements:
305
319
  - - ">="
306
320
  - !ruby/object:Gem::Version
307
- version: '0'
321
+ version: '3.2'
308
322
  - !ruby/object:Gem::Dependency
309
323
  name: pry
310
324
  requirement: !ruby/object:Gem::Requirement
@@ -497,6 +511,9 @@ files:
497
511
  - lib/active_agent/providers/open_router_provider.rb
498
512
  - lib/active_agent/providers/openai_provider.rb
499
513
  - lib/active_agent/providers/openrouter_provider.rb
514
+ - lib/active_agent/providers/requesty/_types.rb
515
+ - lib/active_agent/providers/requesty/options.rb
516
+ - lib/active_agent/providers/requesty_provider.rb
500
517
  - lib/active_agent/providers/ruby_llm/_types.rb
501
518
  - lib/active_agent/providers/ruby_llm/embedding_request.rb
502
519
  - lib/active_agent/providers/ruby_llm/messages/_types.rb
@@ -576,7 +593,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
576
593
  - !ruby/object:Gem::Version
577
594
  version: '0'
578
595
  requirements: []
579
- rubygems_version: 3.6.9
596
+ rubygems_version: 4.0.6
580
597
  specification_version: 4
581
598
  summary: Rails AI Agents Framework
582
599
  test_files: []