activeagent 1.0.0.rc1 → 1.0.1
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 +102 -1
- data/lib/active_agent/providers/_base_provider.rb +94 -82
- data/lib/active_agent/providers/anthropic/_types.rb +2 -2
- data/lib/active_agent/providers/anthropic/options.rb +4 -6
- data/lib/active_agent/providers/anthropic/request.rb +157 -78
- data/lib/active_agent/providers/anthropic/transforms.rb +482 -0
- data/lib/active_agent/providers/anthropic_provider.rb +159 -59
- data/lib/active_agent/providers/common/messages/_types.rb +46 -3
- data/lib/active_agent/providers/common/messages/assistant.rb +20 -4
- 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/concerns/previewable.rb +39 -5
- data/lib/active_agent/providers/concerns/tool_choice_clearing.rb +62 -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 +444 -0
- data/lib/active_agent/providers/open_ai/chat_provider.rb +95 -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 +116 -135
- data/lib/active_agent/providers/open_ai/responses/transforms.rb +363 -0
- data/lib/active_agent/providers/open_ai/responses_provider.rb +115 -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 +252 -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 +164 -0
- data/lib/active_agent/providers/open_router_provider.rb +23 -0
- data/lib/active_agent/version.rb +1 -1
- metadata +17 -160
- 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
|
@@ -5,6 +5,7 @@ require_relative "_base_provider"
|
|
|
5
5
|
require_gem!(:anthropic, __FILE__)
|
|
6
6
|
|
|
7
7
|
require_relative "anthropic/_types"
|
|
8
|
+
require_relative "anthropic/transforms"
|
|
8
9
|
|
|
9
10
|
module ActiveAgent
|
|
10
11
|
module Providers
|
|
@@ -16,6 +17,17 @@ module ActiveAgent
|
|
|
16
17
|
#
|
|
17
18
|
# @see BaseProvider
|
|
18
19
|
class AnthropicProvider < BaseProvider
|
|
20
|
+
# Lead-in message for JSON response format emulation
|
|
21
|
+
JSON_RESPONSE_FORMAT_LEAD_IN = "Here is the JSON requested:\n{"
|
|
22
|
+
|
|
23
|
+
attr_internal :json_format_retry_count
|
|
24
|
+
|
|
25
|
+
def initialize(kwargs = {})
|
|
26
|
+
super
|
|
27
|
+
|
|
28
|
+
self.json_format_retry_count = kwargs[:max_retries] || ::Anthropic::Client::DEFAULT_MAX_RETRIES
|
|
29
|
+
end
|
|
30
|
+
|
|
19
31
|
# @todo Add support for Anthropic::BedrockClient and Anthropic::VertexClient
|
|
20
32
|
# @return [Anthropic::Client]
|
|
21
33
|
def client
|
|
@@ -24,10 +36,10 @@ module ActiveAgent
|
|
|
24
36
|
|
|
25
37
|
protected
|
|
26
38
|
|
|
27
|
-
#
|
|
39
|
+
# Removes forced tool choice after first use to prevent endless looping.
|
|
28
40
|
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
41
|
+
# Clears tool_choice when the specified tool has already been called in the
|
|
42
|
+
# conversation, preventing the model from being forced to call it repeatedly.
|
|
31
43
|
#
|
|
32
44
|
# @see BaseProvider#prepare_prompt_request
|
|
33
45
|
# @return [Request]
|
|
@@ -38,74 +50,109 @@ module ActiveAgent
|
|
|
38
50
|
super
|
|
39
51
|
end
|
|
40
52
|
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
53
|
+
# Extracts function names from Anthropic's tool_use content blocks.
|
|
54
|
+
#
|
|
55
|
+
# @return [Array<String>]
|
|
56
|
+
def extract_used_function_names
|
|
57
|
+
message_stack.pluck(:content).flatten.select { _1[:type] == "tool_use" }.pluck(:name)
|
|
58
|
+
end
|
|
44
59
|
|
|
45
|
-
|
|
60
|
+
# Checks if tool_choice requires the model to call any tool.
|
|
61
|
+
#
|
|
62
|
+
# @return [Boolean] true if tool_choice type is :any
|
|
63
|
+
def tool_choice_forces_required?
|
|
64
|
+
return false unless request.tool_choice.respond_to?(:type)
|
|
46
65
|
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
request.tool_choice.type == :any
|
|
67
|
+
end
|
|
49
68
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
# Checks if tool_choice requires a specific tool to be called.
|
|
70
|
+
#
|
|
71
|
+
# @return [Array<Boolean, String|nil>] [true, tool_name] if forcing a specific tool, [false, nil] otherwise
|
|
72
|
+
def tool_choice_forces_specific?
|
|
73
|
+
return [ false, nil ] unless request.tool_choice.respond_to?(:type)
|
|
74
|
+
return [ false, nil ] unless request.tool_choice.type == :tool
|
|
75
|
+
|
|
76
|
+
tool_name = request.tool_choice.respond_to?(:name) ? request.tool_choice.name : nil
|
|
77
|
+
[ true, tool_name ]
|
|
53
78
|
end
|
|
54
79
|
|
|
55
80
|
# @api private
|
|
56
81
|
def prepare_prompt_request_response_format
|
|
57
|
-
return unless request.response_format&.type == "json_object"
|
|
82
|
+
return unless request.response_format&.dig(:type) == "json_object"
|
|
58
83
|
|
|
59
84
|
self.message_stack.push({
|
|
60
85
|
role: "assistant",
|
|
61
|
-
content:
|
|
86
|
+
content: JSON_RESPONSE_FORMAT_LEAD_IN
|
|
62
87
|
})
|
|
63
88
|
end
|
|
64
89
|
|
|
90
|
+
# Selects between Anthropic's stable and beta message APIs.
|
|
91
|
+
#
|
|
92
|
+
# Uses beta API when explicitly requested via anthropic_beta option or when
|
|
93
|
+
# using MCP servers, which require beta features. Falls back to stable API
|
|
94
|
+
# for standard message creation.
|
|
95
|
+
#
|
|
96
|
+
# @see BaseProvider#api_prompt_executer
|
|
97
|
+
# @return [Anthropic::Messages, Anthropic::Resources::Beta::Messages]
|
|
65
98
|
def api_prompt_executer
|
|
66
|
-
|
|
99
|
+
# Use beta API when anthropic_beta option is set or when using MCP servers
|
|
100
|
+
if options.anthropic_beta.present? || request.mcp_servers&.any?
|
|
101
|
+
client.beta.messages
|
|
102
|
+
else
|
|
103
|
+
client.messages
|
|
104
|
+
end
|
|
67
105
|
end
|
|
68
106
|
|
|
69
|
-
#
|
|
107
|
+
# @see BaseProvider#api_response_normalize
|
|
108
|
+
# @param api_response [Anthropic::Models::Message]
|
|
109
|
+
# @return [Hash]
|
|
110
|
+
def api_response_normalize(api_response)
|
|
111
|
+
return api_response unless api_response
|
|
112
|
+
|
|
113
|
+
Anthropic::Transforms.gem_to_hash(api_response)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Processes streaming chunks and builds message incrementally in message_stack.
|
|
70
117
|
#
|
|
71
118
|
# Handles chunk types: message_start, content_block_start, content_block_delta,
|
|
72
119
|
# content_block_stop, message_delta, message_stop. Manages text deltas,
|
|
73
120
|
# tool use inputs, and Claude's thinking/signature blocks.
|
|
74
121
|
#
|
|
75
|
-
# @
|
|
122
|
+
# @see BaseProvider#process_stream_chunk
|
|
123
|
+
# @param api_response_chunk [Anthropic::StreamEvent]
|
|
76
124
|
# @return [void]
|
|
77
125
|
def process_stream_chunk(api_response_chunk)
|
|
78
|
-
|
|
79
|
-
chunk_type = api_response_chunk[:type].to_sym
|
|
126
|
+
chunk_type = api_response_chunk[:type]&.to_sym
|
|
80
127
|
|
|
81
|
-
instrument("
|
|
128
|
+
instrument("stream_chunk.active_agent", chunk_type:)
|
|
82
129
|
|
|
83
130
|
broadcast_stream_open
|
|
84
131
|
|
|
85
132
|
case chunk_type
|
|
86
133
|
# Message Created
|
|
87
134
|
when :message_start
|
|
88
|
-
api_message = api_response_chunk.
|
|
135
|
+
api_message = Anthropic::Transforms.gem_to_hash(api_response_chunk.message)
|
|
89
136
|
self.message_stack.push(api_message)
|
|
90
137
|
broadcast_stream_update(message_stack.last)
|
|
91
138
|
|
|
92
139
|
# -> Content Block Create
|
|
93
140
|
when :content_block_start
|
|
94
|
-
api_content = api_response_chunk.
|
|
141
|
+
api_content = Anthropic::Transforms.gem_to_hash(api_response_chunk.content_block)
|
|
95
142
|
self.message_stack.last[:content].push(api_content)
|
|
96
143
|
broadcast_stream_update(message_stack.last, api_content[:text])
|
|
97
144
|
|
|
98
145
|
# -> -> Content Block Append
|
|
99
146
|
when :content_block_delta
|
|
100
|
-
index = api_response_chunk.
|
|
147
|
+
index = api_response_chunk.index
|
|
101
148
|
content = self.message_stack.last[:content][index]
|
|
102
|
-
api_delta = api_response_chunk.
|
|
149
|
+
api_delta = api_response_chunk.delta
|
|
103
150
|
|
|
104
|
-
case
|
|
151
|
+
case api_delta.type.to_sym
|
|
105
152
|
# -> -> -> Content Text Append
|
|
106
153
|
when :text_delta
|
|
107
|
-
content[:text] += api_delta
|
|
108
|
-
broadcast_stream_update(message_stack.last, api_delta
|
|
154
|
+
content[:text] += api_delta.text
|
|
155
|
+
broadcast_stream_update(message_stack.last, api_delta.text)
|
|
109
156
|
|
|
110
157
|
# -> -> -> Content Function Call Append
|
|
111
158
|
when :input_json_delta
|
|
@@ -113,21 +160,29 @@ module ActiveAgent
|
|
|
113
160
|
when :thinking_delta, :signature_delta
|
|
114
161
|
# TODO: Add with thinking rendering support
|
|
115
162
|
else
|
|
116
|
-
|
|
163
|
+
raise "Unexpected delta type: #{api_delta.type}"
|
|
117
164
|
end
|
|
118
165
|
# -> Content Block Completed [Full Block]
|
|
119
166
|
when :content_block_stop
|
|
120
|
-
index = api_response_chunk.
|
|
121
|
-
api_content = api_response_chunk.
|
|
167
|
+
index = api_response_chunk.index
|
|
168
|
+
api_content = Anthropic::Transforms.gem_to_hash(api_response_chunk.content_block)
|
|
122
169
|
self.message_stack.last[:content][index] = api_content
|
|
123
170
|
|
|
124
171
|
# Message Delta
|
|
125
172
|
when :message_delta
|
|
126
|
-
|
|
173
|
+
delta = Anthropic::Transforms.gem_to_hash(api_response_chunk.delta)
|
|
174
|
+
self.message_stack.last.merge!(delta)
|
|
127
175
|
|
|
128
176
|
# Message Completed [Full Message]
|
|
129
177
|
when :message_stop
|
|
130
|
-
|
|
178
|
+
api_message = Anthropic::Transforms.gem_to_hash(api_response_chunk.message)
|
|
179
|
+
|
|
180
|
+
# Handle _json_buf (gem >= 1.14.0)
|
|
181
|
+
api_message[:content]&.each do |content_block|
|
|
182
|
+
content_block.delete(:_json_buf) if content_block[:type] == "tool_use"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
self.message_stack[-1] = api_message
|
|
131
186
|
|
|
132
187
|
# Once we are finished, close out and run tooling callbacks (Recursive)
|
|
133
188
|
process_prompt_finished if message_stack.last[:stop_reason]
|
|
@@ -137,12 +192,12 @@ module ActiveAgent
|
|
|
137
192
|
# TODO: https://docs.claude.com/en/docs/build-with-claude/streaming#error-events
|
|
138
193
|
else
|
|
139
194
|
# No-Op: Looks like internal tracking from gem wrapper
|
|
140
|
-
return if api_response_chunk.
|
|
141
|
-
|
|
195
|
+
return if api_response_chunk.respond_to?(:snapshot)
|
|
196
|
+
raise "Unexpected chunk type: #{api_response_chunk.type}"
|
|
142
197
|
end
|
|
143
198
|
end
|
|
144
199
|
|
|
145
|
-
# Executes tool calls and
|
|
200
|
+
# Executes tool calls and appends user message with results to message_stack.
|
|
146
201
|
#
|
|
147
202
|
# @param api_function_calls [Array<Hash>] with :name, :input, and :id keys
|
|
148
203
|
# @return [void]
|
|
@@ -151,58 +206,103 @@ module ActiveAgent
|
|
|
151
206
|
process_tool_call_function(api_function_call)
|
|
152
207
|
end
|
|
153
208
|
|
|
154
|
-
|
|
209
|
+
api_message = ::Anthropic::Models::MessageParam.new(role: "user", content:)
|
|
210
|
+
message = Anthropic::Transforms.gem_to_hash(api_message)
|
|
155
211
|
|
|
156
|
-
message_stack.push(message
|
|
212
|
+
message_stack.push(message)
|
|
157
213
|
end
|
|
158
214
|
|
|
159
|
-
# Executes a single tool call
|
|
215
|
+
# Executes a single tool call via callback.
|
|
160
216
|
#
|
|
161
217
|
# @param api_function_call [Hash] with :name, :input, and :id keys
|
|
162
|
-
# @return [Anthropic::
|
|
218
|
+
# @return [Anthropic::Models::ToolResultBlockParam]
|
|
163
219
|
def process_tool_call_function(api_function_call)
|
|
164
|
-
instrument("
|
|
220
|
+
instrument("tool_call.active_agent", tool_name: api_function_call[:name]) do
|
|
221
|
+
results = tools_function.call(
|
|
222
|
+
api_function_call[:name], **api_function_call[:input]
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
::Anthropic::Models::ToolResultBlockParam.new(
|
|
226
|
+
type: "tool_result",
|
|
227
|
+
tool_use_id: api_function_call[:id],
|
|
228
|
+
content: results.to_json,
|
|
229
|
+
is_error: false
|
|
230
|
+
)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Processes completed API response and handles JSON format retries.
|
|
235
|
+
#
|
|
236
|
+
# When response_format is json_object and the response fails JSON validation,
|
|
237
|
+
# recursively retries the request to obtain well-formed JSON.
|
|
238
|
+
#
|
|
239
|
+
# @see BaseProvider#process_prompt_finished
|
|
240
|
+
# @param api_response [Anthropic::Models::Message]
|
|
241
|
+
# @return [Common::PromptResponse, nil]
|
|
242
|
+
def process_prompt_finished(api_response = nil)
|
|
243
|
+
# Convert gem object to hash so that raw_response[:usage] works
|
|
244
|
+
api_response_hash = api_response ? Anthropic::Transforms.gem_to_hash(api_response) : nil
|
|
245
|
+
|
|
246
|
+
common_response = super(api_response_hash)
|
|
165
247
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
248
|
+
# If we failed to get the expected well formed JSON Object Response, recursively try again
|
|
249
|
+
if request.response_format&.dig(:type) == "json_object" && common_response.message.parsed_json.nil? && json_format_retry_count > 0
|
|
250
|
+
self.json_format_retry_count -= 1
|
|
169
251
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
252
|
+
resolve_prompt
|
|
253
|
+
else
|
|
254
|
+
common_response
|
|
255
|
+
end
|
|
174
256
|
end
|
|
175
257
|
|
|
176
|
-
#
|
|
258
|
+
# Reconstructs JSON responses that were split due to Anthropic format constraints.
|
|
177
259
|
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
260
|
+
# Anthropic's API doesn't natively support json_object response format, so we
|
|
261
|
+
# simulate it by having the assistant echo a JSON lead-in ("Here is the JSON requested:\n{"),
|
|
262
|
+
# then send the response back for completion. This method detects and reverses
|
|
263
|
+
# that workaround by stripping the lead-in message and prepending "{" to the response.
|
|
180
264
|
#
|
|
181
|
-
# @
|
|
265
|
+
# @see BaseProvider#process_prompt_finished_extract_messages
|
|
266
|
+
# @param api_response [Hash] API response with content blocks
|
|
182
267
|
# @return [Array<Hash>, nil]
|
|
183
268
|
def process_prompt_finished_extract_messages(api_response)
|
|
184
269
|
return unless api_response
|
|
185
270
|
|
|
186
|
-
message
|
|
271
|
+
# Get the last message (may be either Hash or gem object)
|
|
272
|
+
last_message = request.messages.last
|
|
273
|
+
last_role = last_message.is_a?(Hash) ? last_message[:role] : last_message&.role
|
|
274
|
+
last_content = last_message.is_a?(Hash) ? last_message[:content] : last_message&.content
|
|
275
|
+
|
|
276
|
+
# Check if the last message in request is the JSON lead-in prompt
|
|
277
|
+
if last_role.to_sym == :assistant && last_content == JSON_RESPONSE_FORMAT_LEAD_IN
|
|
278
|
+
# Remove the lead-in message from the request
|
|
279
|
+
request.messages.pop
|
|
187
280
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
281
|
+
# Prepend "{" to the response's first content text
|
|
282
|
+
if api_response[:content]&.first&.dig(:text)
|
|
283
|
+
api_response[:content][0][:text] = "{#{api_response[:content][0][:text]}"
|
|
284
|
+
end
|
|
191
285
|
end
|
|
192
286
|
|
|
193
|
-
[
|
|
287
|
+
[ api_response ]
|
|
194
288
|
end
|
|
195
289
|
|
|
196
|
-
# Extracts
|
|
290
|
+
# Extracts tool_use blocks from message_stack and parses JSON inputs.
|
|
197
291
|
#
|
|
198
|
-
#
|
|
199
|
-
# input parameters for function execution.
|
|
292
|
+
# Handles JSON buffer parsing for gem versions and string inputs for gem >= 1.14.0.
|
|
200
293
|
#
|
|
294
|
+
# @see BaseProvider#process_prompt_finished_extract_function_calls
|
|
201
295
|
# @return [Array<Hash>] with :name, :input, and :id keys
|
|
202
296
|
def process_prompt_finished_extract_function_calls
|
|
203
297
|
message_stack.pluck(:content).flatten.select { _1 in { type: "tool_use" } }.map do |api_function_call|
|
|
204
298
|
json_buf = api_function_call.delete(:json_buf)
|
|
205
299
|
api_function_call[:input] = JSON.parse(json_buf, symbolize_names: true) if json_buf
|
|
300
|
+
|
|
301
|
+
# Handle case where :input is still a JSON string (gem >= 1.14.0)
|
|
302
|
+
if api_function_call[:input].is_a?(String)
|
|
303
|
+
api_function_call[:input] = JSON.parse(api_function_call[:input], symbolize_names: true)
|
|
304
|
+
end
|
|
305
|
+
|
|
206
306
|
api_function_call
|
|
207
307
|
end
|
|
208
308
|
end
|
|
@@ -37,7 +37,7 @@ module ActiveAgent
|
|
|
37
37
|
role = hash[:role]&.to_s
|
|
38
38
|
|
|
39
39
|
case role
|
|
40
|
-
when "system"
|
|
40
|
+
when "system", "developer"
|
|
41
41
|
nil # System messages are dropped in common format, replaced by Instructions
|
|
42
42
|
when "user", nil
|
|
43
43
|
# Handle both standard format and format with `text` key
|
|
@@ -63,6 +63,13 @@ module ActiveAgent
|
|
|
63
63
|
# Check if the value responds to to_common (provider-specific message)
|
|
64
64
|
if value.respond_to?(:to_common)
|
|
65
65
|
cast_message(value.to_common)
|
|
66
|
+
# Check if it's a gem model object that can be converted to hash
|
|
67
|
+
# Use JSON round-trip to ensure proper nested serialization
|
|
68
|
+
elsif value.respond_to?(:to_json)
|
|
69
|
+
hash = JSON.parse(value.to_json, symbolize_names: true)
|
|
70
|
+
cast_message(hash)
|
|
71
|
+
elsif value.respond_to?(:to_h)
|
|
72
|
+
cast_message(value.to_h)
|
|
66
73
|
else
|
|
67
74
|
raise ArgumentError, "Cannot cast #{value.class} to Message"
|
|
68
75
|
end
|
|
@@ -78,7 +85,7 @@ module ActiveAgent
|
|
|
78
85
|
when Hash
|
|
79
86
|
value
|
|
80
87
|
else
|
|
81
|
-
|
|
88
|
+
raise ArgumentError, "Cannot serialize #{value.class}"
|
|
82
89
|
end
|
|
83
90
|
end
|
|
84
91
|
end
|
|
@@ -88,7 +95,9 @@ module ActiveAgent
|
|
|
88
95
|
def cast(value)
|
|
89
96
|
case value
|
|
90
97
|
when Array
|
|
91
|
-
value.map { |v| message_type.cast(v) }.compact
|
|
98
|
+
messages = value.map { |v| message_type.cast(v) }.compact
|
|
99
|
+
# Split messages with array content into separate messages
|
|
100
|
+
messages.flat_map { |msg| split_content_blocks(msg) }
|
|
92
101
|
when nil
|
|
93
102
|
[]
|
|
94
103
|
else
|
|
@@ -116,6 +125,40 @@ module ActiveAgent
|
|
|
116
125
|
def message_type
|
|
117
126
|
@message_type ||= MessageType.new
|
|
118
127
|
end
|
|
128
|
+
|
|
129
|
+
# Splits an assistant message with array content into separate messages
|
|
130
|
+
# for each content block.
|
|
131
|
+
#
|
|
132
|
+
# @param message [Common::Messages::Base]
|
|
133
|
+
# @return [Array<Common::Messages::Base>]
|
|
134
|
+
def split_content_blocks(message)
|
|
135
|
+
# Only split assistant messages with array content
|
|
136
|
+
return [ message ] unless message.is_a?(Common::Messages::Assistant) && message.content.is_a?(Array)
|
|
137
|
+
|
|
138
|
+
message.content.map do |block|
|
|
139
|
+
case block[:type]&.to_s
|
|
140
|
+
when "text"
|
|
141
|
+
# Create a message for text blocks
|
|
142
|
+
Common::Messages::Assistant.new(role: "assistant", content: block[:text], name: message.name)
|
|
143
|
+
when "tool_use"
|
|
144
|
+
# Create a message with tool use info as string representation
|
|
145
|
+
tool_info = "[Tool Use: #{block[:name]}]\nID: #{block[:id]}\nInput: #{JSON.pretty_generate(block[:input])}"
|
|
146
|
+
Common::Messages::Assistant.new(role: "assistant", content: tool_info, name: message.name)
|
|
147
|
+
when "mcp_tool_use"
|
|
148
|
+
# Create a message with MCP tool use info
|
|
149
|
+
tool_info = "[MCP Tool Use: #{block[:name]}]\nID: #{block[:id]}\nServer: #{block[:server_name]}\nInput: #{JSON.pretty_generate(block[:input] || {})}"
|
|
150
|
+
Common::Messages::Assistant.new(role: "assistant", content: tool_info, name: message.name)
|
|
151
|
+
when "mcp_tool_result"
|
|
152
|
+
# Create a message with MCP tool result
|
|
153
|
+
result_info = "[MCP Tool Result]\n#{block[:content]}"
|
|
154
|
+
Common::Messages::Assistant.new(role: "assistant", content: result_info, name: message.name)
|
|
155
|
+
else
|
|
156
|
+
# For unknown block types, try to extract text
|
|
157
|
+
content = block[:text] || block.to_s
|
|
158
|
+
Common::Messages::Assistant.new(role: "assistant", content:, name: message.name)
|
|
159
|
+
end
|
|
160
|
+
end.compact
|
|
161
|
+
end
|
|
119
162
|
end
|
|
120
163
|
end
|
|
121
164
|
end
|
|
@@ -9,7 +9,7 @@ module ActiveAgent
|
|
|
9
9
|
# Represents messages sent by the AI assistant in a conversation.
|
|
10
10
|
class Assistant < Base
|
|
11
11
|
attribute :role, :string, as: "assistant"
|
|
12
|
-
attribute :content
|
|
12
|
+
attribute :content # Accept both string and array (provider-native formats)
|
|
13
13
|
attribute :name, :string
|
|
14
14
|
|
|
15
15
|
validates :content, presence: true
|
|
@@ -24,9 +24,16 @@ module ActiveAgent
|
|
|
24
24
|
# @param normalize_names [Symbol, nil] key normalization method (e.g., :underscore)
|
|
25
25
|
# @return [Hash, Array, nil] parsed JSON structure or nil if parsing fails
|
|
26
26
|
def parsed_json(symbolize_names: true, normalize_names: :underscore)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
# Handle array content (from content blocks) by searching through each block
|
|
28
|
+
content_str = if content.is_a?(Array)
|
|
29
|
+
content.map { |block| block.is_a?(Hash) ? block[:text] : block.to_s }.join("\n")
|
|
30
|
+
else
|
|
31
|
+
content.to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
start_char = [ content_str.index("{"), content_str.index("[") ].compact.min
|
|
35
|
+
end_char = [ content_str.rindex("}"), content_str.rindex("]") ].compact.max
|
|
36
|
+
content_stripped = content_str[start_char..end_char] if start_char && end_char
|
|
30
37
|
return unless content_stripped
|
|
31
38
|
|
|
32
39
|
content_parsed = JSON.parse(content_stripped)
|
|
@@ -48,6 +55,15 @@ module ActiveAgent
|
|
|
48
55
|
nil
|
|
49
56
|
end
|
|
50
57
|
|
|
58
|
+
# Returns content as a string, handling both string and array formats
|
|
59
|
+
def text
|
|
60
|
+
if content.is_a?(Array)
|
|
61
|
+
content.map { |block| block.is_a?(Hash) ? block[:text] : block.to_s }.join("\n")
|
|
62
|
+
else
|
|
63
|
+
content.to_s
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
51
67
|
alias_method :json_object, :parsed_json
|
|
52
68
|
alias_method :parse_json, :parsed_json
|
|
53
69
|
end
|