activeagent 1.0.0.rc1 → 1.0.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 +4 -4
- data/CHANGELOG.md +31 -1
- data/lib/active_agent/providers/_base_provider.rb +92 -82
- data/lib/active_agent/providers/anthropic/_types.rb +2 -2
- data/lib/active_agent/providers/anthropic/request.rb +135 -81
- data/lib/active_agent/providers/anthropic/transforms.rb +353 -0
- data/lib/active_agent/providers/anthropic_provider.rb +96 -53
- data/lib/active_agent/providers/common/messages/_types.rb +37 -1
- data/lib/active_agent/providers/common/responses/base.rb +118 -70
- data/lib/active_agent/providers/common/usage.rb +385 -0
- data/lib/active_agent/providers/concerns/instrumentation.rb +263 -0
- data/lib/active_agent/providers/log_subscriber.rb +64 -246
- data/lib/active_agent/providers/mock_provider.rb +23 -23
- data/lib/active_agent/providers/ollama/chat/request.rb +214 -35
- data/lib/active_agent/providers/ollama/chat/transforms.rb +135 -0
- data/lib/active_agent/providers/ollama/embedding/request.rb +160 -47
- data/lib/active_agent/providers/ollama/embedding/transforms.rb +160 -0
- data/lib/active_agent/providers/ollama_provider.rb +0 -1
- data/lib/active_agent/providers/open_ai/_base.rb +3 -2
- data/lib/active_agent/providers/open_ai/chat/_types.rb +13 -1
- data/lib/active_agent/providers/open_ai/chat/request.rb +132 -186
- data/lib/active_agent/providers/open_ai/chat/transforms.rb +364 -0
- data/lib/active_agent/providers/open_ai/chat_provider.rb +57 -36
- data/lib/active_agent/providers/open_ai/embedding/_types.rb +13 -2
- data/lib/active_agent/providers/open_ai/embedding/request.rb +38 -70
- data/lib/active_agent/providers/open_ai/embedding/transforms.rb +88 -0
- data/lib/active_agent/providers/open_ai/responses/_types.rb +1 -7
- data/lib/active_agent/providers/open_ai/responses/request.rb +100 -134
- data/lib/active_agent/providers/open_ai/responses/transforms.rb +228 -0
- data/lib/active_agent/providers/open_ai/responses_provider.rb +77 -30
- data/lib/active_agent/providers/open_ai_provider.rb +0 -3
- data/lib/active_agent/providers/open_router/_types.rb +27 -1
- data/lib/active_agent/providers/open_router/options.rb +49 -1
- data/lib/active_agent/providers/open_router/request.rb +232 -66
- data/lib/active_agent/providers/open_router/requests/_types.rb +0 -1
- data/lib/active_agent/providers/open_router/requests/messages/_types.rb +37 -40
- data/lib/active_agent/providers/open_router/requests/messages/content/file.rb +19 -3
- data/lib/active_agent/providers/open_router/requests/messages/content/files/details.rb +15 -4
- data/lib/active_agent/providers/open_router/requests/plugin.rb +19 -3
- data/lib/active_agent/providers/open_router/requests/plugins/pdf_config.rb +30 -8
- data/lib/active_agent/providers/open_router/requests/prediction.rb +17 -0
- data/lib/active_agent/providers/open_router/requests/provider_preferences/max_price.rb +41 -7
- data/lib/active_agent/providers/open_router/requests/provider_preferences.rb +60 -19
- data/lib/active_agent/providers/open_router/requests/response_format.rb +30 -2
- data/lib/active_agent/providers/open_router/transforms.rb +134 -0
- data/lib/active_agent/providers/open_router_provider.rb +9 -0
- data/lib/active_agent/version.rb +1 -1
- metadata +15 -159
- data/lib/active_agent/generation_provider/open_router/types.rb +0 -505
- data/lib/active_agent/generation_provider/xai_provider.rb +0 -144
- data/lib/active_agent/providers/anthropic/requests/_types.rb +0 -190
- data/lib/active_agent/providers/anthropic/requests/container_params.rb +0 -19
- data/lib/active_agent/providers/anthropic/requests/content/base.rb +0 -21
- data/lib/active_agent/providers/anthropic/requests/content/sources/base.rb +0 -22
- data/lib/active_agent/providers/anthropic/requests/context_management_config.rb +0 -18
- data/lib/active_agent/providers/anthropic/requests/messages/_types.rb +0 -189
- data/lib/active_agent/providers/anthropic/requests/messages/assistant.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/base.rb +0 -63
- data/lib/active_agent/providers/anthropic/requests/messages/content/_types.rb +0 -143
- data/lib/active_agent/providers/anthropic/requests/messages/content/base.rb +0 -21
- data/lib/active_agent/providers/anthropic/requests/messages/content/document.rb +0 -26
- data/lib/active_agent/providers/anthropic/requests/messages/content/image.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/content/redacted_thinking.rb +0 -21
- data/lib/active_agent/providers/anthropic/requests/messages/content/search_result.rb +0 -27
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/_types.rb +0 -171
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/base.rb +0 -22
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_base64.rb +0 -25
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_file.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_text.rb +0 -25
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_url.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_base64.rb +0 -27
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_file.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_url.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/content/text.rb +0 -22
- data/lib/active_agent/providers/anthropic/requests/messages/content/thinking.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/content/tool_result.rb +0 -24
- data/lib/active_agent/providers/anthropic/requests/messages/content/tool_use.rb +0 -28
- data/lib/active_agent/providers/anthropic/requests/messages/user.rb +0 -21
- data/lib/active_agent/providers/anthropic/requests/metadata.rb +0 -18
- data/lib/active_agent/providers/anthropic/requests/response_format.rb +0 -22
- data/lib/active_agent/providers/anthropic/requests/thinking_config/_types.rb +0 -60
- data/lib/active_agent/providers/anthropic/requests/thinking_config/base.rb +0 -20
- data/lib/active_agent/providers/anthropic/requests/thinking_config/disabled.rb +0 -16
- data/lib/active_agent/providers/anthropic/requests/thinking_config/enabled.rb +0 -20
- data/lib/active_agent/providers/anthropic/requests/tool_choice/_types.rb +0 -78
- data/lib/active_agent/providers/anthropic/requests/tool_choice/any.rb +0 -17
- data/lib/active_agent/providers/anthropic/requests/tool_choice/auto.rb +0 -17
- data/lib/active_agent/providers/anthropic/requests/tool_choice/base.rb +0 -20
- data/lib/active_agent/providers/anthropic/requests/tool_choice/none.rb +0 -16
- data/lib/active_agent/providers/anthropic/requests/tool_choice/tool.rb +0 -20
- data/lib/active_agent/providers/ollama/chat/requests/_types.rb +0 -3
- data/lib/active_agent/providers/ollama/chat/requests/messages/_types.rb +0 -116
- data/lib/active_agent/providers/ollama/chat/requests/messages/assistant.rb +0 -19
- data/lib/active_agent/providers/ollama/chat/requests/messages/user.rb +0 -19
- data/lib/active_agent/providers/ollama/embedding/requests/_types.rb +0 -83
- data/lib/active_agent/providers/ollama/embedding/requests/options.rb +0 -104
- data/lib/active_agent/providers/open_ai/chat/requests/_types.rb +0 -229
- data/lib/active_agent/providers/open_ai/chat/requests/audio.rb +0 -24
- data/lib/active_agent/providers/open_ai/chat/requests/messages/_types.rb +0 -123
- data/lib/active_agent/providers/open_ai/chat/requests/messages/assistant.rb +0 -42
- data/lib/active_agent/providers/open_ai/chat/requests/messages/base.rb +0 -78
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/_types.rb +0 -133
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/audio.rb +0 -35
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/base.rb +0 -24
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/file.rb +0 -26
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/files/_types.rb +0 -60
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/files/details.rb +0 -41
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/image.rb +0 -37
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/refusal.rb +0 -25
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/text.rb +0 -25
- data/lib/active_agent/providers/open_ai/chat/requests/messages/developer.rb +0 -25
- data/lib/active_agent/providers/open_ai/chat/requests/messages/function.rb +0 -25
- data/lib/active_agent/providers/open_ai/chat/requests/messages/system.rb +0 -25
- data/lib/active_agent/providers/open_ai/chat/requests/messages/tool.rb +0 -26
- data/lib/active_agent/providers/open_ai/chat/requests/messages/user.rb +0 -32
- data/lib/active_agent/providers/open_ai/chat/requests/prediction.rb +0 -46
- data/lib/active_agent/providers/open_ai/chat/requests/response_format.rb +0 -53
- data/lib/active_agent/providers/open_ai/chat/requests/stream_options.rb +0 -24
- data/lib/active_agent/providers/open_ai/chat/requests/tool_choice.rb +0 -26
- data/lib/active_agent/providers/open_ai/chat/requests/tools/_types.rb +0 -5
- data/lib/active_agent/providers/open_ai/chat/requests/tools/base.rb +0 -22
- data/lib/active_agent/providers/open_ai/chat/requests/tools/custom_tool.rb +0 -41
- data/lib/active_agent/providers/open_ai/chat/requests/tools/function_tool.rb +0 -51
- data/lib/active_agent/providers/open_ai/chat/requests/web_search_options.rb +0 -45
- data/lib/active_agent/providers/open_ai/embedding/requests/_types.rb +0 -49
- data/lib/active_agent/providers/open_ai/responses/requests/_types.rb +0 -231
- data/lib/active_agent/providers/open_ai/responses/requests/conversation.rb +0 -23
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/_types.rb +0 -264
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/assistant_message.rb +0 -22
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/base.rb +0 -89
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/code_interpreter_tool_call.rb +0 -30
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/computer_tool_call.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/computer_tool_call_output.rb +0 -33
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/_types.rb +0 -207
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/base.rb +0 -22
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_audio.rb +0 -26
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_file.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_image.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_text.rb +0 -25
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/custom_tool_call.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/custom_tool_call_output.rb +0 -27
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/developer_message.rb +0 -20
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/file_search_tool_call.rb +0 -25
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/function_call_output.rb +0 -32
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/function_tool_call.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/image_gen_tool_call.rb +0 -27
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/input_message.rb +0 -31
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/item_reference.rb +0 -23
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/local_shell_tool_call.rb +0 -26
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/local_shell_tool_call_output.rb +0 -33
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_approval_request.rb +0 -30
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_approval_response.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_list_tools.rb +0 -29
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_tool_call.rb +0 -35
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/output_message.rb +0 -35
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/reasoning.rb +0 -33
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/system_message.rb +0 -20
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/tool_call_base.rb +0 -27
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/tool_message.rb +0 -23
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/user_message.rb +0 -20
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/web_search_tool_call.rb +0 -24
- data/lib/active_agent/providers/open_ai/responses/requests/prompt_reference.rb +0 -23
- data/lib/active_agent/providers/open_ai/responses/requests/reasoning.rb +0 -23
- data/lib/active_agent/providers/open_ai/responses/requests/stream_options.rb +0 -20
- data/lib/active_agent/providers/open_ai/responses/requests/text/_types.rb +0 -89
- data/lib/active_agent/providers/open_ai/responses/requests/text/base.rb +0 -22
- data/lib/active_agent/providers/open_ai/responses/requests/text/json_object.rb +0 -20
- data/lib/active_agent/providers/open_ai/responses/requests/text/json_schema.rb +0 -48
- data/lib/active_agent/providers/open_ai/responses/requests/text/plain.rb +0 -20
- data/lib/active_agent/providers/open_ai/responses/requests/text.rb +0 -41
- data/lib/active_agent/providers/open_ai/responses/requests/tool_choice.rb +0 -26
- data/lib/active_agent/providers/open_ai/responses/requests/tools/_types.rb +0 -112
- data/lib/active_agent/providers/open_ai/responses/requests/tools/base.rb +0 -25
- data/lib/active_agent/providers/open_ai/responses/requests/tools/code_interpreter_tool.rb +0 -23
- data/lib/active_agent/providers/open_ai/responses/requests/tools/computer_tool.rb +0 -27
- data/lib/active_agent/providers/open_ai/responses/requests/tools/custom_tool.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/tools/file_search_tool.rb +0 -27
- data/lib/active_agent/providers/open_ai/responses/requests/tools/function_tool.rb +0 -29
- data/lib/active_agent/providers/open_ai/responses/requests/tools/image_generation_tool.rb +0 -37
- data/lib/active_agent/providers/open_ai/responses/requests/tools/local_shell_tool.rb +0 -21
- data/lib/active_agent/providers/open_ai/responses/requests/tools/mcp_tool.rb +0 -41
- data/lib/active_agent/providers/open_ai/responses/requests/tools/web_search_preview_tool.rb +0 -24
- data/lib/active_agent/providers/open_ai/responses/requests/tools/web_search_tool.rb +0 -25
- data/lib/active_agent/providers/open_ai/schema.yml +0 -65937
- data/lib/active_agent/providers/open_router/requests/message.rb +0 -1
- data/lib/active_agent/providers/open_router/requests/messages/assistant.rb +0 -20
- data/lib/active_agent/providers/open_router/requests/messages/user.rb +0 -30
|
@@ -1,107 +1,161 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
4
|
-
|
|
3
|
+
require "delegate"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "transforms"
|
|
5
6
|
|
|
6
7
|
module ActiveAgent
|
|
7
8
|
module Providers
|
|
8
9
|
module Anthropic
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
52
80
|
|
|
53
|
-
|
|
54
|
-
|
|
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)
|
|
58
83
|
|
|
59
|
-
|
|
60
|
-
|
|
84
|
+
# Step 4: Transform params for gem compatibility
|
|
85
|
+
transformed = Transforms.normalize_params(params)
|
|
61
86
|
|
|
62
|
-
|
|
63
|
-
|
|
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)
|
|
66
89
|
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
69
96
|
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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__)
|
|
74
108
|
end
|
|
75
109
|
|
|
76
|
-
|
|
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
|
|
77
119
|
|
|
78
|
-
|
|
79
|
-
|
|
120
|
+
# @param value [String, Array]
|
|
121
|
+
def system=(value)
|
|
122
|
+
__getobj__.instance_variable_get(:@data)[:system] = value
|
|
123
|
+
end
|
|
80
124
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
125
|
+
# Alias for system (common format compatibility).
|
|
126
|
+
#
|
|
127
|
+
# @return [String, Array, nil]
|
|
128
|
+
def instructions
|
|
129
|
+
system
|
|
84
130
|
end
|
|
85
131
|
|
|
86
|
-
|
|
87
|
-
|
|
132
|
+
# @param value [String, Array]
|
|
133
|
+
def instructions=(value)
|
|
134
|
+
self.system = value
|
|
135
|
+
end
|
|
88
136
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
92
146
|
end
|
|
93
147
|
|
|
94
|
-
|
|
95
|
-
return if mcp_servers.nil? || mcp_servers.empty?
|
|
148
|
+
private
|
|
96
149
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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)
|
|
100
156
|
end
|
|
101
157
|
|
|
102
|
-
|
|
103
|
-
errors.add(:mcp_servers, "can have at most 20 servers")
|
|
104
|
-
end
|
|
158
|
+
params
|
|
105
159
|
end
|
|
106
160
|
end
|
|
107
161
|
end
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/hash/keys"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module Anthropic
|
|
8
|
+
# Transforms between convenient input formats and Anthropic API structures.
|
|
9
|
+
#
|
|
10
|
+
# Provides bidirectional transformations:
|
|
11
|
+
# - Expand: shortcuts → API format (string → content blocks, consecutive messages → grouped)
|
|
12
|
+
# - Compress: API format → shortcuts (single content blocks → strings for efficiency)
|
|
13
|
+
module Transforms
|
|
14
|
+
class << self
|
|
15
|
+
# Converts gem model object to hash via JSON round-trip.
|
|
16
|
+
#
|
|
17
|
+
# This ensures proper nested serialization and symbolic keys.
|
|
18
|
+
#
|
|
19
|
+
# @param gem_object [Object] any object responding to .to_json
|
|
20
|
+
# @return [Hash]
|
|
21
|
+
def gem_to_hash(gem_object)
|
|
22
|
+
JSON.parse(gem_object.to_json, symbolize_names: true)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param params [Hash]
|
|
26
|
+
# @return [Hash]
|
|
27
|
+
def normalize_params(params)
|
|
28
|
+
params = params.dup
|
|
29
|
+
params[:messages] = normalize_messages(params[:messages]) if params[:messages]
|
|
30
|
+
params[:system] = normalize_system(params[:system]) if params[:system]
|
|
31
|
+
params
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Merges consecutive same-role messages into single messages with multiple content blocks.
|
|
35
|
+
#
|
|
36
|
+
# Required by Anthropic API - consecutive messages with the same role must be combined.
|
|
37
|
+
#
|
|
38
|
+
# @param messages [Array<Hash>]
|
|
39
|
+
# @return [Array<Hash>]
|
|
40
|
+
def normalize_messages(messages)
|
|
41
|
+
return messages unless messages.is_a?(Array)
|
|
42
|
+
|
|
43
|
+
grouped = []
|
|
44
|
+
|
|
45
|
+
messages.each do |msg|
|
|
46
|
+
msg_hash = msg.is_a?(Hash) ? msg.deep_symbolize_keys : { role: :user, content: msg }
|
|
47
|
+
|
|
48
|
+
# Extract role
|
|
49
|
+
role = msg_hash[:role]&.to_sym || :user
|
|
50
|
+
|
|
51
|
+
# Determine content
|
|
52
|
+
if msg_hash.key?(:content)
|
|
53
|
+
# Has explicit content key
|
|
54
|
+
content = normalize_content(msg_hash[:content])
|
|
55
|
+
elsif msg_hash.key?(:role) && msg_hash.keys.size > 1
|
|
56
|
+
# Has role + other keys (e.g., {role: "assistant", text: "..."})
|
|
57
|
+
# Treat everything except :role as content
|
|
58
|
+
content = normalize_content(msg_hash.except(:role))
|
|
59
|
+
elsif !msg_hash.key?(:role)
|
|
60
|
+
# No role or content - treat entire hash as content
|
|
61
|
+
content = normalize_content(msg_hash)
|
|
62
|
+
else
|
|
63
|
+
# Only has role, no content
|
|
64
|
+
content = []
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if grouped.empty? || grouped.last[:role] != role
|
|
68
|
+
grouped << { role: role, content: content }
|
|
69
|
+
else
|
|
70
|
+
# Merge content from consecutive same-role messages
|
|
71
|
+
grouped.last[:content] += content
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
grouped
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Converts system content shortcuts to API format.
|
|
79
|
+
#
|
|
80
|
+
# Handles string, hash, or array inputs. Strings pass through unchanged
|
|
81
|
+
# since Anthropic accepts both string and structured formats.
|
|
82
|
+
#
|
|
83
|
+
# @param system [String, Array, Hash]
|
|
84
|
+
# @return [String, Array]
|
|
85
|
+
def normalize_system(system)
|
|
86
|
+
case system
|
|
87
|
+
when String
|
|
88
|
+
# Keep strings as-is - Anthropic accepts both string and array
|
|
89
|
+
system
|
|
90
|
+
when Array
|
|
91
|
+
# Normalize array of system blocks
|
|
92
|
+
system.map { |block| normalize_system_block(block) }
|
|
93
|
+
when Hash
|
|
94
|
+
# Single hash becomes array with one block
|
|
95
|
+
[ normalize_system_block(system) ]
|
|
96
|
+
else
|
|
97
|
+
system
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @param block [String, Hash]
|
|
102
|
+
# @return [Hash]
|
|
103
|
+
def normalize_system_block(block)
|
|
104
|
+
case block
|
|
105
|
+
when String
|
|
106
|
+
{ type: "text", text: block }
|
|
107
|
+
when Hash
|
|
108
|
+
hash = block.deep_symbolize_keys
|
|
109
|
+
# Add type if missing and can be inferred
|
|
110
|
+
hash[:type] ||= "text" if hash[:text]
|
|
111
|
+
hash
|
|
112
|
+
else
|
|
113
|
+
block
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Expands content shortcuts into structured content block arrays.
|
|
118
|
+
#
|
|
119
|
+
# Handles multiple input formats:
|
|
120
|
+
# - String → `[{type: "text", text: "..."}]`
|
|
121
|
+
# - Hash with multiple keys → separate blocks per content type
|
|
122
|
+
# - Array → normalized items
|
|
123
|
+
#
|
|
124
|
+
# @param content [String, Array, Hash]
|
|
125
|
+
# @return [Array<Hash>]
|
|
126
|
+
def normalize_content(content)
|
|
127
|
+
case content
|
|
128
|
+
when String
|
|
129
|
+
# String → array with single text block
|
|
130
|
+
[ { type: "text", text: content } ]
|
|
131
|
+
when Array
|
|
132
|
+
# Normalize each item in the array
|
|
133
|
+
content.flat_map { |item| normalize_content_item(item) }
|
|
134
|
+
when Hash
|
|
135
|
+
# Check if hash has multiple content keys (text, image, document)
|
|
136
|
+
# If so, expand into separate content blocks
|
|
137
|
+
hash = content.deep_symbolize_keys
|
|
138
|
+
content_keys = [ :text, :image, :document ]
|
|
139
|
+
found_keys = content_keys & hash.keys
|
|
140
|
+
|
|
141
|
+
if found_keys.size > 1
|
|
142
|
+
# Multiple content types - expand into array
|
|
143
|
+
found_keys.flat_map { |key| normalize_content_item({ key => hash[key] }) }
|
|
144
|
+
else
|
|
145
|
+
# Single content item
|
|
146
|
+
[ normalize_content_item(content) ]
|
|
147
|
+
end
|
|
148
|
+
when nil
|
|
149
|
+
[]
|
|
150
|
+
else
|
|
151
|
+
# Pass through other types (might be gem objects already)
|
|
152
|
+
[ content ]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Infers content block type from hash keys or converts string to text block.
|
|
157
|
+
#
|
|
158
|
+
# @param item [String, Hash]
|
|
159
|
+
# @return [Hash]
|
|
160
|
+
def normalize_content_item(item)
|
|
161
|
+
case item
|
|
162
|
+
when String
|
|
163
|
+
{ type: "text", text: item }
|
|
164
|
+
when Hash
|
|
165
|
+
hash = item.deep_symbolize_keys
|
|
166
|
+
|
|
167
|
+
# If type is specified, return as-is
|
|
168
|
+
return hash if hash[:type]
|
|
169
|
+
|
|
170
|
+
# Type inference based on keys
|
|
171
|
+
if hash[:text]
|
|
172
|
+
{ type: "text" }.merge(hash)
|
|
173
|
+
elsif hash[:image]
|
|
174
|
+
# Normalize image source format
|
|
175
|
+
source = normalize_source(hash[:image])
|
|
176
|
+
{ type: "image", source: source }.merge(hash.except(:image))
|
|
177
|
+
elsif hash[:document]
|
|
178
|
+
# Normalize document source format
|
|
179
|
+
source = normalize_source(hash[:document])
|
|
180
|
+
{ type: "document", source: source }.merge(hash.except(:document))
|
|
181
|
+
elsif hash[:tool_use_id]
|
|
182
|
+
# Tool result content
|
|
183
|
+
{ type: "tool_result" }.merge(hash)
|
|
184
|
+
elsif hash[:id] && hash[:name] && hash[:input]
|
|
185
|
+
# Tool use content
|
|
186
|
+
{ type: "tool_use" }.merge(hash)
|
|
187
|
+
else
|
|
188
|
+
# Unknown format - return as-is and let gem validate
|
|
189
|
+
hash
|
|
190
|
+
end
|
|
191
|
+
else
|
|
192
|
+
# Pass through (might be gem object)
|
|
193
|
+
item
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Converts image/document source shortcuts to API structure.
|
|
198
|
+
#
|
|
199
|
+
# Handles multiple formats:
|
|
200
|
+
# - Regular URL → `{type: "url", url: "..."}`
|
|
201
|
+
# - Data URI → `{type: "base64", media_type: "...", data: "..."}`
|
|
202
|
+
# - Hash with base64 → `{type: "base64", media_type: "...", data: "..."}`
|
|
203
|
+
#
|
|
204
|
+
# @param source [String, Hash]
|
|
205
|
+
# @return [Hash]
|
|
206
|
+
def normalize_source(source)
|
|
207
|
+
case source
|
|
208
|
+
when String
|
|
209
|
+
# Check if it's a data URI (e.g., "data:image/png;base64,...")
|
|
210
|
+
if source.start_with?("data:")
|
|
211
|
+
parse_data_uri(source)
|
|
212
|
+
else
|
|
213
|
+
# Regular URL → wrap in url source type
|
|
214
|
+
{ type: "url", url: source }
|
|
215
|
+
end
|
|
216
|
+
when Hash
|
|
217
|
+
hash = source.deep_symbolize_keys
|
|
218
|
+
# Already has type → return as-is
|
|
219
|
+
return hash if hash[:type]
|
|
220
|
+
|
|
221
|
+
# Has base64 data → add type
|
|
222
|
+
if hash[:data] && hash[:media_type]
|
|
223
|
+
{ type: "base64" }.merge(hash)
|
|
224
|
+
else
|
|
225
|
+
# Unknown format → return as-is
|
|
226
|
+
hash
|
|
227
|
+
end
|
|
228
|
+
else
|
|
229
|
+
source
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Extracts media type and data from data URI.
|
|
234
|
+
#
|
|
235
|
+
# Expected format: `data:[<media type>][;base64],<data>`
|
|
236
|
+
#
|
|
237
|
+
# @param data_uri [String] e.g., "data:image/png;base64,iVBORw0..."
|
|
238
|
+
# @return [Hash] `{type: "base64", media_type: "...", data: "..."}`
|
|
239
|
+
def parse_data_uri(data_uri)
|
|
240
|
+
# Extract media type and data from data URI
|
|
241
|
+
# Format: data:[<media type>][;base64],<data>
|
|
242
|
+
match = data_uri.match(%r{\Adata:([^;,]+)(?:;base64)?,(.+)\z})
|
|
243
|
+
|
|
244
|
+
if match
|
|
245
|
+
{
|
|
246
|
+
type: "base64",
|
|
247
|
+
media_type: match[1],
|
|
248
|
+
data: match[2]
|
|
249
|
+
}
|
|
250
|
+
else
|
|
251
|
+
# Invalid data URI - return as URL fallback
|
|
252
|
+
{ type: "url", url: data_uri }
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Converts single-element content arrays back to string shorthand.
|
|
257
|
+
#
|
|
258
|
+
# Reduces payload size by reversing the expansion done by normalize methods.
|
|
259
|
+
#
|
|
260
|
+
# @param hash [Hash]
|
|
261
|
+
# @return [Hash]
|
|
262
|
+
def compress_content(hash)
|
|
263
|
+
return hash unless hash.is_a?(Hash)
|
|
264
|
+
|
|
265
|
+
# Compress message content
|
|
266
|
+
hash[:messages]&.each do |msg|
|
|
267
|
+
compress_message_content!(msg)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Compress system content
|
|
271
|
+
if hash[:system].is_a?(Array)
|
|
272
|
+
hash[:system] = compress_system_content(hash[:system])
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
hash
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Converts single text block arrays to string shorthand.
|
|
279
|
+
#
|
|
280
|
+
# `[{type: "text", text: "hello"}]` → `"hello"`
|
|
281
|
+
#
|
|
282
|
+
# @param msg [Hash] message with :content key
|
|
283
|
+
# @return [void]
|
|
284
|
+
def compress_message_content!(msg)
|
|
285
|
+
content = msg[:content]
|
|
286
|
+
return unless content.is_a?(Array)
|
|
287
|
+
|
|
288
|
+
# Single text block → string shorthand
|
|
289
|
+
if content.one? && content.first.is_a?(Hash) && content.first[:type] == "text"
|
|
290
|
+
msg[:content] = content.first[:text]
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Cleans up serialized request for API submission.
|
|
295
|
+
#
|
|
296
|
+
# Removes response-only fields, applies content compression,
|
|
297
|
+
# removes provider-internal fields, and removes default values.
|
|
298
|
+
# Note: max_tokens is kept even if it matches default as Anthropic API requires it.
|
|
299
|
+
#
|
|
300
|
+
# @param hash [Hash] serialized request
|
|
301
|
+
# @param defaults [Hash] default values to remove
|
|
302
|
+
# @param gem_object [Object] original gem object (unused but for consistency)
|
|
303
|
+
# @return [Hash] cleaned request hash
|
|
304
|
+
def cleanup_serialized_request(hash, defaults, gem_object = nil)
|
|
305
|
+
# Remove response-only fields from messages
|
|
306
|
+
if hash[:messages]
|
|
307
|
+
hash[:messages].each do |msg|
|
|
308
|
+
msg.delete(:id)
|
|
309
|
+
msg.delete(:model)
|
|
310
|
+
msg.delete(:stop_reason)
|
|
311
|
+
msg.delete(:stop_sequence)
|
|
312
|
+
msg.delete(:type)
|
|
313
|
+
msg.delete(:usage)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Apply content compression for API efficiency
|
|
318
|
+
compress_content(hash)
|
|
319
|
+
|
|
320
|
+
# Remove provider-internal fields that should not be in API request
|
|
321
|
+
hash.delete(:mcp_servers) # Provider-level config, not API param
|
|
322
|
+
hash.delete(:stop_sequences) if hash[:stop_sequences] == []
|
|
323
|
+
|
|
324
|
+
# Remove default values (except max_tokens which is required by API)
|
|
325
|
+
defaults.each do |key, value|
|
|
326
|
+
next if key == :max_tokens # Anthropic API requires max_tokens
|
|
327
|
+
hash.delete(key) if hash[key] == value
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
hash
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
private
|
|
334
|
+
|
|
335
|
+
# Converts single text block to string.
|
|
336
|
+
#
|
|
337
|
+
# @param system [Array]
|
|
338
|
+
# @return [String, Array]
|
|
339
|
+
def compress_system_content(system)
|
|
340
|
+
return system unless system.is_a?(Array)
|
|
341
|
+
|
|
342
|
+
# Single text block → string shorthand
|
|
343
|
+
if system.one? && system.first.is_a?(Hash) && system.first[:type] == "text"
|
|
344
|
+
system.first[:text]
|
|
345
|
+
else
|
|
346
|
+
system
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|