Skip to content

Commit 4a3774f

Browse files
authored
Refactor: Leverage Native Gem Types Across All Providers (#271)
* Refactor the Anthropic Translation Layer to use Native Gem Types * Refactor OpenAI's Responses Provider to use Native Gem Types * Refactor OpenAI's Chat Provider to use Native Gem Types * Refactor OpenAI's Embedding to use Native Gem Types * Refactor OpenRouter to use OpenAI's Native Gem Types * Refactor Ollama to use OpenAI's Native Gem Types * Refactor request handling across various providers to improve parameter normalization and cleanup processes * Add Transforms tests * Add tests for the response translations
1 parent 4fab7fc commit 4a3774f

197 files changed

Lines changed: 8410 additions & 72599 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
/test/dummy/storage/
1414
/test/dummy/tmp/
1515
/tmp/
16+
/wip/
1617
docs/.vitepress/cache
1718
docs/.vitepress/dist
1819
docs/parts/examples/*.md

lib/active_agent/providers/_base_provider.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def resolve_prompt
177177
with_exception_handling { api_prompt_execute(api_parameters) }
178178
end
179179

180-
process_prompt_finished(api_response.as_json&.deep_symbolize_keys)
180+
process_prompt_finished(api_response)
181181
end
182182

183183
# Orchestrates the complete embedding request lifecycle.

lib/active_agent/providers/anthropic/_types.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# frozen_string_literal: true
22

3-
require_relative "requests/_types"
4-
53
require_relative "options"
64
require_relative "request"
75

@@ -11,6 +9,8 @@ module Anthropic
119
# ActiveModel type for casting and serializing Anthropic Request objects.
1210
#
1311
# Handles conversion between Hash, Request, and serialized formats for API calls.
12+
# The Request class now delegates to the official Anthropic gem model, eliminating
13+
# the need for maintaining nested type definitions.
1414
class RequestType < ActiveModel::Type::Value
1515
# Casts input to Request object.
1616
#

lib/active_agent/providers/anthropic/request.rb

Lines changed: 135 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,161 @@
11
# frozen_string_literal: true
22

3-
require "active_agent/providers/common/model"
4-
require_relative "_types"
3+
require "delegate"
4+
require "json"
5+
require_relative "transforms"
56

67
module ActiveAgent
78
module Providers
89
module Anthropic
9-
class Request < Common::BaseModel
10-
# Required parameters
11-
attribute :model, :string
12-
attribute :messages, Requests::Messages::MessagesType.new
13-
attribute :max_tokens, :integer, fallback: 4096
14-
15-
# Optional parameters - Prompting
16-
attribute :system, Requests::Messages::SystemType.new
17-
attribute :temperature, :float
18-
attribute :top_k, :integer
19-
attribute :top_p, :float
20-
attribute :stop_sequences, default: -> { [] } # Array of strings
21-
22-
# Optional parameters - Tools
23-
attribute :tools # Array of tool definitions
24-
attribute :tool_choice, Requests::ToolChoice::ToolChoiceType.new
25-
26-
# Optional parameters - Thinking
27-
attribute :thinking, Requests::ThinkingConfig::ThinkingConfigType.new
28-
29-
# Optional parameters - Streaming
30-
attribute :stream, :boolean, default: false
31-
32-
# Optional parameters - Metadata
33-
attribute :metadata, Requests::MetadataType.new
34-
35-
# Optional parameters - Context Management
36-
attribute :context_management, Requests::ContextManagementConfigType.new
37-
38-
# Optional parameters - Container
39-
attribute :container, Requests::ContainerParamsType.new
40-
41-
# Optional parameters - Service tier
42-
attribute :service_tier, :string
43-
44-
# Optional parameters - MCP Servers
45-
attribute :mcp_servers, default: -> { [] } # Array of MCP server definitions
46-
47-
# Common Format Compatibility
48-
attribute :response_format, Requests::ResponseFormatType.new
49-
50-
# Validations for required fields
51-
validates :model, :messages, :max_tokens, presence: true
10+
# Request wrapper that delegates to Anthropic gem model.
11+
#
12+
# Uses SimpleDelegator to wrap ::Anthropic::Models::MessageCreateParams,
13+
# eliminating the need to maintain duplicate attribute definitions while
14+
# providing convenience transformations and custom fields.
15+
#
16+
# All standard Anthropic API fields are automatically available via delegation:
17+
# - model, messages, max_tokens
18+
# - system, temperature, top_k, top_p, stop_sequences
19+
# - tools, tool_choice, thinking
20+
# - stream, metadata, context_management, container, service_tier, mcp_servers
21+
#
22+
# Custom fields managed separately:
23+
# - response_format (simulated JSON mode feature)
24+
#
25+
# @example Basic usage
26+
# request = Request.new(
27+
# model: "claude-3-5-haiku-latest",
28+
# messages: [{role: "user", content: "Hello"}]
29+
# )
30+
# request.model #=> "claude-3-5-haiku-latest"
31+
# request.max_tokens #=> 4096 (default)
32+
#
33+
# @example With transformations
34+
# # String content is automatically normalized
35+
# request = Request.new(
36+
# model: "...",
37+
# messages: [{role: "user", content: "Hi"}]
38+
# )
39+
# # Internally becomes: [{type: "text", text: "Hi"}]
40+
#
41+
# @example Custom field
42+
# request = Request.new(
43+
# model: "...",
44+
# messages: [...],
45+
# response_format: {type: "json_object"}
46+
# )
47+
# request.response_format #=> {type: "json_object"}
48+
class Request < SimpleDelegator
49+
# Default max_tokens value when not specified
50+
DEFAULT_MAX_TOKENS = 4096
51+
52+
# Default values for optional parameters
53+
DEFAULTS = {
54+
max_tokens: DEFAULT_MAX_TOKENS,
55+
stop_sequences: [],
56+
mcp_servers: []
57+
}.freeze
58+
59+
# @return [Hash, nil] simulated JSON response format configuration
60+
attr_reader :response_format
61+
62+
# @return [Boolean, nil] whether to stream the response
63+
attr_reader :stream
64+
65+
# @param params [Hash]
66+
# @option params [String] :model required
67+
# @option params [Array<Hash>] :messages required
68+
# @option params [Integer] :max_tokens (4096)
69+
# @option params [Hash] :response_format custom field for JSON mode simulation
70+
# @raise [ArgumentError] when gem model validation fails
71+
def initialize(**params)
72+
# Step 1: Extract custom fields that gem doesn't support
73+
@response_format = params.delete(:response_format)
74+
@stream = params.delete(:stream)
75+
76+
# Step 2: Map common format 'instructions' to Anthropic's 'system'
77+
if params.key?(:instructions)
78+
params[:system] = params.delete(:instructions)
79+
end
5280

53-
# Validations for numeric parameters
54-
validates :max_tokens, numericality: { greater_than_or_equal_to: 1 }, allow_nil: true
55-
validates :temperature, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }, allow_nil: true
56-
validates :top_k, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
57-
validates :top_p, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }, allow_nil: true
81+
# Step 3: Apply defaults
82+
params = apply_defaults(params)
5883

59-
# Validations for specific values
60-
validates :service_tier, inclusion: { in: %w[auto standard_only] }, allow_nil: true
84+
# Step 4: Transform params for gem compatibility
85+
transformed = Transforms.normalize_params(params)
6186

62-
# Custom validations
63-
validate :validate_stop_sequences
64-
validate :validate_tools_format
65-
validate :validate_mcp_servers_format
87+
# Step 5: Create gem model - this validates all parameters!
88+
gem_model = ::Anthropic::Models::MessageCreateParams.new(**transformed)
6689

67-
# Common Format Compatibility
68-
alias_attribute :instructions, :system
90+
# Step 6: Delegate all method calls to gem model
91+
super(gem_model)
92+
rescue ArgumentError => e
93+
# Re-raise with more context
94+
raise ArgumentError, "Invalid Anthropic request parameters: #{e.message}"
95+
end
6996

70-
# Handle merging in the common format
71-
def message=(value)
72-
self.messages ||= []
73-
self.messages << Requests::Messages::MessageType.new.cast(value)
97+
# Serializes request for API call.
98+
#
99+
# Uses gem's JSON serialization and delegates cleanup to Transforms module.
100+
#
101+
# @return [Hash]
102+
def serialize
103+
# Use gem's JSON serialization (handles all nested objects)
104+
hash = Anthropic::Transforms.gem_to_hash(__getobj__)
105+
106+
# Delegate cleanup to transforms module
107+
Transforms.cleanup_serialized_request(hash, DEFAULTS, __getobj__)
74108
end
75109

76-
private
110+
# Accessor for system instructions.
111+
#
112+
# Must override SimpleDelegator's method_missing because Ruby's Kernel.system
113+
# conflicts with delegation. The gem stores data in @data instance variable.
114+
#
115+
# @return [String, Array, nil]
116+
def system
117+
__getobj__.instance_variable_get(:@data)[:system]
118+
end
77119

78-
def validate_stop_sequences
79-
return if stop_sequences.nil? || stop_sequences.empty?
120+
# @param value [String, Array]
121+
def system=(value)
122+
__getobj__.instance_variable_get(:@data)[:system] = value
123+
end
80124

81-
unless stop_sequences.is_a?(Array)
82-
errors.add(:stop_sequences, "must be an array")
83-
end
125+
# Alias for system (common format compatibility).
126+
#
127+
# @return [String, Array, nil]
128+
def instructions
129+
system
84130
end
85131

86-
def validate_tools_format
87-
return if tools.nil?
132+
# @param value [String, Array]
133+
def instructions=(value)
134+
self.system = value
135+
end
88136

89-
unless tools.is_a?(Array)
90-
errors.add(:tools, "must be an array")
91-
end
137+
# Removes the last message from the messages array.
138+
#
139+
# Used for JSON format simulation to remove the lead-in assistant message.
140+
#
141+
# @return [void]
142+
def pop_message!
143+
new_messages = messages.dup
144+
new_messages.pop
145+
self.messages = new_messages
92146
end
93147

94-
def validate_mcp_servers_format
95-
return if mcp_servers.nil? || mcp_servers.empty?
148+
private
96149

97-
unless mcp_servers.is_a?(Array)
98-
errors.add(:mcp_servers, "must be an array")
99-
return
150+
# @param params [Hash]
151+
# @return [Hash]
152+
def apply_defaults(params)
153+
# Only apply defaults for keys that aren't present
154+
DEFAULTS.each do |key, value|
155+
params[key] = value unless params.key?(key)
100156
end
101157

102-
if mcp_servers.length > 20
103-
errors.add(:mcp_servers, "can have at most 20 servers")
104-
end
158+
params
105159
end
106160
end
107161
end

0 commit comments

Comments
 (0)