Skip to content

Commit a9ef325

Browse files
committed
Refactor the Anthropic Translation Layer to use Native Gem Types
1 parent 22864b2 commit a9ef325

51 files changed

Lines changed: 860 additions & 1741 deletions

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: 151 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,180 @@
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
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+
# Extract custom fields that gem doesn't support
73+
@response_format = params.delete(:response_format)
74+
@stream = params.delete(:stream)
75+
76+
# Map common format 'instructions' to Anthropic's 'system'
77+
if params.key?(:instructions)
78+
params[:system] = params.delete(:instructions)
79+
end
4380

44-
# Optional parameters - MCP Servers
45-
attribute :mcp_servers, default: -> { [] } # Array of MCP server definitions
81+
# Apply defaults
82+
params = apply_defaults(params)
4683

47-
# Common Format Compatibility
48-
attribute :response_format, Requests::ResponseFormatType.new
84+
# Transform params for gem compatibility
85+
transformed = Transforms.normalize_params(params)
4986

50-
# Validations for required fields
51-
validates :model, :messages, :max_tokens, presence: true
87+
# Create gem model - this validates all parameters!
88+
gem_model = ::Anthropic::Models::MessageCreateParams.new(**transformed)
5289

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
90+
# 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
5896

59-
# Validations for specific values
60-
validates :service_tier, inclusion: { in: %w[auto standard_only] }, allow_nil: true
97+
# Serializes request for API call with content compression.
98+
#
99+
# Uses gem's JSON serialization, then removes response-only fields
100+
# and compresses single text blocks to string shorthand for efficiency.
101+
#
102+
# @return [Hash]
103+
def serialize
104+
# Use gem's JSON serialization (handles all nested objects)
105+
hash = Anthropic::Transforms.gem_to_hash(__getobj__)
106+
107+
# Clean up messages - remove response-only fields
108+
if hash[:messages]
109+
hash[:messages].each do |msg|
110+
msg.delete(:id)
111+
msg.delete(:model)
112+
msg.delete(:stop_reason)
113+
msg.delete(:stop_sequence)
114+
msg.delete(:type)
115+
msg.delete(:usage)
116+
end
117+
end
61118

62-
# Custom validations
63-
validate :validate_stop_sequences
64-
validate :validate_tools_format
65-
validate :validate_mcp_servers_format
119+
# Apply content compression for API efficiency
120+
Transforms.compress_content(hash)
66121

67-
# Common Format Compatibility
68-
alias_attribute :instructions, :system
122+
# Remove provider-internal fields that should not be in API request
123+
hash.delete(:mcp_servers) # Provider-level config, not API param
124+
hash.delete(:stop_sequences) if hash[:stop_sequences] == []
69125

70-
# Handle merging in the common format
71-
def message=(value)
72-
self.messages ||= []
73-
self.messages << Requests::Messages::MessageType.new.cast(value)
126+
hash
74127
end
75128

76-
private
129+
# Accessor for system instructions.
130+
#
131+
# Must override SimpleDelegator's method_missing because Ruby's Kernel.system
132+
# conflicts with delegation. The gem stores data in @data instance variable.
133+
#
134+
# @return [String, Array, nil]
135+
def system
136+
__getobj__.instance_variable_get(:@data)[:system]
137+
end
77138

78-
def validate_stop_sequences
79-
return if stop_sequences.nil? || stop_sequences.empty?
139+
# @param value [String, Array]
140+
def system=(value)
141+
__getobj__.instance_variable_get(:@data)[:system] = value
142+
end
80143

81-
unless stop_sequences.is_a?(Array)
82-
errors.add(:stop_sequences, "must be an array")
83-
end
144+
# Alias for system (common format compatibility).
145+
#
146+
# @return [String, Array, nil]
147+
def instructions
148+
system
84149
end
85150

86-
def validate_tools_format
87-
return if tools.nil?
151+
# @param value [String, Array]
152+
def instructions=(value)
153+
self.system = value
154+
end
88155

89-
unless tools.is_a?(Array)
90-
errors.add(:tools, "must be an array")
91-
end
156+
# Removes the last message from the messages array.
157+
#
158+
# Used for JSON format simulation to remove the lead-in assistant message.
159+
#
160+
# @return [void]
161+
def pop_message!
162+
new_messages = messages.dup
163+
new_messages.pop
164+
self.messages = new_messages
92165
end
93166

94-
def validate_mcp_servers_format
95-
return if mcp_servers.nil? || mcp_servers.empty?
167+
private
96168

97-
unless mcp_servers.is_a?(Array)
98-
errors.add(:mcp_servers, "must be an array")
99-
return
169+
# @param params [Hash]
170+
# @return [Hash]
171+
def apply_defaults(params)
172+
# Only apply defaults for keys that aren't present
173+
DEFAULTS.each do |key, value|
174+
params[key] = value unless params.key?(key)
100175
end
101176

102-
if mcp_servers.length > 20
103-
errors.add(:mcp_servers, "can have at most 20 servers")
104-
end
177+
params
105178
end
106179
end
107180
end

0 commit comments

Comments
 (0)