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
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/hash/keys"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module OpenAI
|
|
8
|
+
module Responses
|
|
9
|
+
# Provides transformation methods for normalizing response parameters
|
|
10
|
+
# to OpenAI gem's native format
|
|
11
|
+
#
|
|
12
|
+
# Handles input normalization, message conversion, and response format
|
|
13
|
+
# transformation for the Responses API.
|
|
14
|
+
module Transforms
|
|
15
|
+
class << self
|
|
16
|
+
# Converts gem model object to hash via JSON round-trip
|
|
17
|
+
#
|
|
18
|
+
# @param gem_object [Object]
|
|
19
|
+
# @return [Hash] with symbolized keys
|
|
20
|
+
def gem_to_hash(gem_object)
|
|
21
|
+
JSON.parse(gem_object.to_json, symbolize_names: true)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Simplifies input for cleaner API requests
|
|
25
|
+
#
|
|
26
|
+
# Unwraps single-element arrays:
|
|
27
|
+
# - `["text"]` → `"text"`
|
|
28
|
+
# - `[{type: "input_text", text: "..."}]` → `"..."`
|
|
29
|
+
# - `[{role: "user", content: "..."}]` → `"..."`
|
|
30
|
+
#
|
|
31
|
+
# @param input [Array, String, Hash]
|
|
32
|
+
# @return [String, Array, Hash]
|
|
33
|
+
def simplify_input(input)
|
|
34
|
+
return input unless input.is_a?(Array)
|
|
35
|
+
|
|
36
|
+
# Single string element - unwrap it
|
|
37
|
+
if input.size == 1 && input[0].is_a?(String)
|
|
38
|
+
return input[0]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Single content object {type: "input_text", text: "..."} - unwrap to string
|
|
42
|
+
if input.size == 1 &&
|
|
43
|
+
input[0].is_a?(Hash) &&
|
|
44
|
+
input[0][:type] == "input_text" &&
|
|
45
|
+
input[0][:text].is_a?(String) &&
|
|
46
|
+
input[0].keys.sort == [ :text, :type ]
|
|
47
|
+
return input[0][:text]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Single message with string content - simplify to string
|
|
51
|
+
if input.size == 1 &&
|
|
52
|
+
input[0].is_a?(Hash) &&
|
|
53
|
+
input[0][:role] == "user" &&
|
|
54
|
+
input[0][:content].is_a?(String)
|
|
55
|
+
return input[0][:content]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
input
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Normalizes response_format to OpenAI Responses API text parameter
|
|
62
|
+
#
|
|
63
|
+
# Maps common response_format structures to Responses API format.
|
|
64
|
+
# Returns ResponseTextConfig object to preserve proper nesting.
|
|
65
|
+
#
|
|
66
|
+
# @param format [Hash, Symbol, String]
|
|
67
|
+
# @return [OpenAI::Models::Responses::ResponseTextConfig]
|
|
68
|
+
def normalize_response_format(format)
|
|
69
|
+
text_hash = case format
|
|
70
|
+
when Hash
|
|
71
|
+
if format[:type] == "json_schema" || format[:type] == :json_schema
|
|
72
|
+
# json_schema format: map to Responses API structure
|
|
73
|
+
{
|
|
74
|
+
format: {
|
|
75
|
+
type: "json_schema",
|
|
76
|
+
name: format[:name] || format[:json_schema]&.dig(:name),
|
|
77
|
+
schema: format[:schema] || format[:json_schema]&.dig(:schema),
|
|
78
|
+
strict: format[:strict] || format[:json_schema]&.dig(:strict)
|
|
79
|
+
}.compact
|
|
80
|
+
}
|
|
81
|
+
elsif format[:type] == "json_object" || format[:type] == :json_object
|
|
82
|
+
# json_object format
|
|
83
|
+
{ format: { type: "json_object" } }
|
|
84
|
+
elsif format[:type]
|
|
85
|
+
# Other simple type formats (text, etc.) - wrap in format key
|
|
86
|
+
{ format: { type: format[:type].to_s } }
|
|
87
|
+
else
|
|
88
|
+
# Pass through other hash formats (already has format key or complex structure)
|
|
89
|
+
format
|
|
90
|
+
end
|
|
91
|
+
when Symbol, String
|
|
92
|
+
# Simple format types
|
|
93
|
+
{ format: { type: format.to_s } }
|
|
94
|
+
else
|
|
95
|
+
format
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Convert hash to ResponseTextConfig object to preserve nesting
|
|
99
|
+
::OpenAI::Models::Responses::ResponseTextConfig.new(**text_hash)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Normalizes input/messages to gem-compatible format
|
|
103
|
+
#
|
|
104
|
+
# Handles various input formats:
|
|
105
|
+
# - `"text"` → string (passthrough)
|
|
106
|
+
# - `{role: "user", content: "..."}` → wrapped in array
|
|
107
|
+
# - `[{text: "..."}, {image: "url"}]` → wrapped as user message with content array
|
|
108
|
+
# - `["msg1", "msg2"]` → array of user messages
|
|
109
|
+
#
|
|
110
|
+
# @param input [String, Hash, Array, Object]
|
|
111
|
+
# @return [String, Array<Hash>]
|
|
112
|
+
def normalize_input(input)
|
|
113
|
+
# String inputs pass through unchanged
|
|
114
|
+
return input if input.is_a?(String)
|
|
115
|
+
|
|
116
|
+
# Single hash should be wrapped in an array
|
|
117
|
+
if input.is_a?(Hash)
|
|
118
|
+
return [ normalize_message(input) ]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Handle arrays
|
|
122
|
+
return input unless input.respond_to?(:map)
|
|
123
|
+
|
|
124
|
+
# Check if this is an array of content items (strings or text/image/document hashes)
|
|
125
|
+
# Content items don't have a :role key (messages do)
|
|
126
|
+
# BUT NOT a single string (which should have been caught above)
|
|
127
|
+
all_content_items = input.size > 1 && input.all? do |item|
|
|
128
|
+
if item.is_a?(String)
|
|
129
|
+
true
|
|
130
|
+
elsif item.is_a?(Hash)
|
|
131
|
+
# If it has a role, it's a message, not a content item
|
|
132
|
+
!item.key?(:role) && (item.key?(:text) || item.key?(:image) || item.key?(:document))
|
|
133
|
+
else
|
|
134
|
+
false
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if all_content_items
|
|
139
|
+
# These are multiple content items, wrap in a user message
|
|
140
|
+
content = input.map { |item| normalize_message(item) }
|
|
141
|
+
return [ { role: "user", content: content } ]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Otherwise treat as array of messages
|
|
145
|
+
input.map { |item| normalize_message(item, context: :input) }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Normalizes a single message to hash format
|
|
149
|
+
#
|
|
150
|
+
# Handles shorthand formats:
|
|
151
|
+
# - `{text: "..."}` → user message
|
|
152
|
+
# - `{image: "url"}` → input_image content part
|
|
153
|
+
# - `{document: "url"}` → input_file content part
|
|
154
|
+
#
|
|
155
|
+
# @param message [Hash, String, Object]
|
|
156
|
+
# @param context [Symbol] :input for messages, :content for content parts
|
|
157
|
+
# @return [Hash, String]
|
|
158
|
+
def normalize_message(message, context: :content)
|
|
159
|
+
# If it's our custom model object, serialize it
|
|
160
|
+
if message.respond_to?(:serialize)
|
|
161
|
+
message.serialize
|
|
162
|
+
elsif message.is_a?(Hash)
|
|
163
|
+
# If it has a role, it's a message - convert :text to :content
|
|
164
|
+
if message.key?(:role)
|
|
165
|
+
normalized = message.dup
|
|
166
|
+
if normalized.key?(:text) && !normalized.key?(:content)
|
|
167
|
+
normalized[:content] = normalized.delete(:text)
|
|
168
|
+
end
|
|
169
|
+
return normalized
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Expand shorthand formats to full structures for content items
|
|
173
|
+
if message.key?(:image)
|
|
174
|
+
{ type: "input_image", image_url: message[:image] }
|
|
175
|
+
elsif message.key?(:document)
|
|
176
|
+
document_value = message[:document]
|
|
177
|
+
if document_value.start_with?("data:")
|
|
178
|
+
{ type: "input_file", filename: "document.pdf", file_data: document_value }
|
|
179
|
+
else
|
|
180
|
+
{ type: "input_file", file_url: document_value }
|
|
181
|
+
end
|
|
182
|
+
elsif message.key?(:text) && message.size == 1
|
|
183
|
+
# Single :text key without :role - treat as user message
|
|
184
|
+
{ role: "user", content: message[:text] }
|
|
185
|
+
elsif message.key?(:text)
|
|
186
|
+
# Bare text content item with other keys
|
|
187
|
+
{ type: "input_text", text: message[:text] }
|
|
188
|
+
else
|
|
189
|
+
message
|
|
190
|
+
end
|
|
191
|
+
elsif message.is_a?(String)
|
|
192
|
+
# Context matters: in input array, strings become messages; in content array, they become input_text
|
|
193
|
+
if context == :input
|
|
194
|
+
{ role: "user", content: message }
|
|
195
|
+
else
|
|
196
|
+
{ type: "input_text", text: message }
|
|
197
|
+
end
|
|
198
|
+
else
|
|
199
|
+
# Pass through anything else
|
|
200
|
+
message
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Cleans up serialized request for API submission
|
|
205
|
+
#
|
|
206
|
+
# Removes default values and simplifies input where possible.
|
|
207
|
+
#
|
|
208
|
+
# @param hash [Hash] serialized request
|
|
209
|
+
# @param defaults [Hash] default values to remove
|
|
210
|
+
# @param gem_object [Object] original gem object
|
|
211
|
+
# @return [Hash] cleaned request hash
|
|
212
|
+
def cleanup_serialized_request(hash, defaults, gem_object)
|
|
213
|
+
# Remove default values that shouldn't be in the request body
|
|
214
|
+
defaults.each do |key, value|
|
|
215
|
+
hash.delete(key) if hash[key] == value
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Simplify input when possible for cleaner API requests
|
|
219
|
+
hash[:input] = simplify_input(hash[:input]) if hash[:input]
|
|
220
|
+
|
|
221
|
+
hash
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
require_relative "_base"
|
|
2
2
|
require_relative "responses/_types"
|
|
3
|
+
require_relative "responses/transforms"
|
|
3
4
|
|
|
4
5
|
module ActiveAgent
|
|
5
6
|
module Providers
|
|
6
7
|
module OpenAI
|
|
7
|
-
#
|
|
8
|
+
# Provider implementation for OpenAI's Responses API
|
|
8
9
|
#
|
|
9
|
-
# Uses the
|
|
10
|
-
#
|
|
10
|
+
# Uses the responses endpoint for improved streaming and structured function
|
|
11
|
+
# calling compared to the chat completions endpoint.
|
|
11
12
|
#
|
|
12
13
|
# @see Base
|
|
13
14
|
# @see https://platform.openai.com/docs/api-reference/responses
|
|
@@ -29,17 +30,34 @@ module ActiveAgent
|
|
|
29
30
|
client.responses
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
#
|
|
33
|
+
# @see BaseProvider#api_response_normalize
|
|
34
|
+
# @param api_response [OpenAI::Models::Responses::Response]
|
|
35
|
+
# @return [Hash] normalized response hash
|
|
36
|
+
def api_response_normalize(api_response)
|
|
37
|
+
return api_response unless api_response
|
|
38
|
+
|
|
39
|
+
Responses::Transforms.gem_to_hash(api_response)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Processes streaming response chunks from the Responses API
|
|
33
43
|
#
|
|
34
|
-
#
|
|
35
|
-
# response.
|
|
36
|
-
#
|
|
37
|
-
#
|
|
44
|
+
# Event types handled:
|
|
45
|
+
# - `:"response.created"`, `:"response.in_progress"` - response lifecycle
|
|
46
|
+
# - `:"response.output_item.added"` - message or function call added
|
|
47
|
+
# - `:"response.content_part.added"` - content part started
|
|
48
|
+
# - `:"response.output_text.delta"` - incremental text updates
|
|
49
|
+
# - `:"response.output_text.done"` - complete text
|
|
50
|
+
# - `:"response.function_call_arguments.delta"` - function argument updates
|
|
51
|
+
# - `:"response.function_call_arguments.done"` - complete function arguments
|
|
52
|
+
# - `:"response.content_part.done"` - content part completed
|
|
53
|
+
# - `:"response.output_item.done"` - message or function call completed
|
|
54
|
+
# - `:"response.completed"` - response finished
|
|
38
55
|
#
|
|
39
|
-
# @param api_response_event [Hash] streaming
|
|
56
|
+
# @param api_response_event [Hash] streaming chunk with :type key
|
|
40
57
|
# @return [void]
|
|
58
|
+
# @see Base#process_stream_chunk
|
|
41
59
|
def process_stream_chunk(api_response_event)
|
|
42
|
-
instrument("
|
|
60
|
+
instrument("stream_chunk.active_agent", chunk_type: api_response_event.type)
|
|
43
61
|
|
|
44
62
|
case api_response_event.type
|
|
45
63
|
# Response Created
|
|
@@ -55,14 +73,14 @@ module ActiveAgent
|
|
|
55
73
|
|
|
56
74
|
# -> -> -> Content Text Append
|
|
57
75
|
when :"response.output_text.delta"
|
|
58
|
-
message = message_stack.find { _1[:id] == api_response_event
|
|
59
|
-
message[:content] += api_response_event
|
|
60
|
-
broadcast_stream_update(message, api_response_event
|
|
76
|
+
message = message_stack.find { _1[:id] == api_response_event.item_id }
|
|
77
|
+
message[:content] += api_response_event.delta
|
|
78
|
+
broadcast_stream_update(message, api_response_event.delta)
|
|
61
79
|
|
|
62
80
|
# -> -> -> Content Text Completed [Full Text]
|
|
63
81
|
when :"response.output_text.done"
|
|
64
|
-
message = message_stack.find { _1[:id] == api_response_event
|
|
65
|
-
message[:content] = api_response_event
|
|
82
|
+
message = message_stack.find { _1[:id] == api_response_event.item_id }
|
|
83
|
+
message[:content] = api_response_event.text
|
|
66
84
|
broadcast_stream_update(message, nil) # Don't double send content
|
|
67
85
|
|
|
68
86
|
# -> -> -> Content Function Call Append
|
|
@@ -81,11 +99,17 @@ module ActiveAgent
|
|
|
81
99
|
# Once we are finished, close out and run tooling callbacks (Recursive)
|
|
82
100
|
process_prompt_finished
|
|
83
101
|
else
|
|
84
|
-
|
|
102
|
+
raise "Unexpected Response Chunk Type: #{api_response_event.type}"
|
|
85
103
|
end
|
|
86
104
|
end
|
|
87
105
|
|
|
88
|
-
# Processes output item added events from streaming response
|
|
106
|
+
# Processes output item added events from streaming response
|
|
107
|
+
#
|
|
108
|
+
# Handles message and function_call item types. For messages, adds to stack
|
|
109
|
+
# with empty content. For function calls, waits for completion event.
|
|
110
|
+
#
|
|
111
|
+
# Required because API returns empty array instead of empty string for
|
|
112
|
+
# initial message content due to serialization bug.
|
|
89
113
|
#
|
|
90
114
|
# @param api_response_event [Hash] response chunk with :item key
|
|
91
115
|
# @return [void]
|
|
@@ -93,15 +117,19 @@ module ActiveAgent
|
|
|
93
117
|
case api_response_event.item.type
|
|
94
118
|
when :message
|
|
95
119
|
# PATCH: API returns an empty array instead of empty string due to a bug in their serialization
|
|
96
|
-
|
|
120
|
+
item_hash = Responses::Transforms.gem_to_hash(api_response_event.item).compact_blank
|
|
121
|
+
message_stack << { content: "" }.merge(item_hash)
|
|
97
122
|
when :function_call
|
|
98
123
|
# No-Op: Wait for FC to Land (-> response.output_item.done)
|
|
99
124
|
else
|
|
100
|
-
|
|
125
|
+
raise "Unexpected Item Type: #{api_response_event.item.type}"
|
|
101
126
|
end
|
|
102
127
|
end
|
|
103
128
|
|
|
104
|
-
# Processes output item completion events from streaming response
|
|
129
|
+
# Processes output item completion events from streaming response
|
|
130
|
+
#
|
|
131
|
+
# For function calls, adds completed item to message stack.
|
|
132
|
+
# For messages, no action needed as content already updated via delta events.
|
|
105
133
|
#
|
|
106
134
|
# @param api_response_event [Hash] response chunk with completed :item
|
|
107
135
|
# @return [void]
|
|
@@ -110,35 +138,54 @@ module ActiveAgent
|
|
|
110
138
|
when :message
|
|
111
139
|
# No-Op: Message Up to Date
|
|
112
140
|
when :function_call
|
|
113
|
-
|
|
141
|
+
item_hash = Responses::Transforms.gem_to_hash(api_response_event.item)
|
|
142
|
+
message_stack << item_hash
|
|
114
143
|
else
|
|
115
|
-
|
|
144
|
+
raise "Unexpected Item Type: #{api_response_event.item.type}"
|
|
116
145
|
end
|
|
117
146
|
end
|
|
118
147
|
|
|
119
|
-
# Executes function calls and creates output messages for conversation continuation
|
|
148
|
+
# Executes function calls and creates output messages for conversation continuation
|
|
120
149
|
#
|
|
121
150
|
# @param api_function_calls [Array<Hash>] function calls with :call_id and :name keys
|
|
122
151
|
# @return [void]
|
|
152
|
+
# @see Base#process_function_calls
|
|
123
153
|
def process_function_calls(api_function_calls)
|
|
124
154
|
api_function_calls.each do |api_function_call|
|
|
125
|
-
instrument("
|
|
155
|
+
output = instrument("tool_call.active_agent", tool_name: api_function_call[:name]) do
|
|
156
|
+
process_tool_call_function(api_function_call).to_json
|
|
157
|
+
end
|
|
126
158
|
|
|
127
|
-
|
|
159
|
+
# Create native gem input item for function call output
|
|
160
|
+
message = ::OpenAI::Models::Responses::ResponseInputItem::FunctionCallOutput.new(
|
|
128
161
|
call_id: api_function_call[:call_id],
|
|
129
|
-
output:
|
|
162
|
+
output:
|
|
130
163
|
)
|
|
131
164
|
|
|
132
|
-
message_stack
|
|
165
|
+
# Convert to hash for message_stack
|
|
166
|
+
message_stack.push(Responses::Transforms.gem_to_hash(message))
|
|
133
167
|
end
|
|
134
168
|
end
|
|
135
169
|
|
|
170
|
+
# Converts OpenAI gem response object to hash for storage.
|
|
171
|
+
#
|
|
172
|
+
# @param api_response [OpenAI::Models::Responses::Response]
|
|
173
|
+
# @return [Common::PromptResponse, nil]
|
|
174
|
+
def process_prompt_finished(api_response = nil)
|
|
175
|
+
# Convert gem object to hash so that raw_response["usage"] works
|
|
176
|
+
api_response_hash = api_response ? Responses::Transforms.gem_to_hash(api_response) : nil
|
|
177
|
+
super(api_response_hash)
|
|
178
|
+
end
|
|
179
|
+
|
|
136
180
|
# Extracts messages from completed API response.
|
|
137
181
|
#
|
|
138
|
-
# @param api_response [Hash]
|
|
139
|
-
# @return [Array, nil] output array from response
|
|
182
|
+
# @param api_response [Hash] converted response hash
|
|
183
|
+
# @return [Array, nil] output array from response.output or nil
|
|
140
184
|
def process_prompt_finished_extract_messages(api_response)
|
|
141
|
-
api_response
|
|
185
|
+
return unless api_response
|
|
186
|
+
|
|
187
|
+
# Response is already a hash from process_prompt_finished
|
|
188
|
+
api_response[:output]
|
|
142
189
|
end
|
|
143
190
|
|
|
144
191
|
# Extracts function calls from message stack.
|
|
@@ -60,10 +60,8 @@ module ActiveAgent
|
|
|
60
60
|
# @see https://platform.openai.com/docs/guides/migrate-to-responses
|
|
61
61
|
def prompt
|
|
62
62
|
if api_version == :chat || context[:audio].present?
|
|
63
|
-
instrument("api_routing.provider.active_agent", api_type: :chat, api_version: api_version, has_audio: context[:audio].present?)
|
|
64
63
|
OpenAI::ChatProvider.new(raw_options).prompt
|
|
65
64
|
else # api_version == :responses || true
|
|
66
|
-
instrument("api_routing.provider.active_agent", api_type: :responses, api_version: api_version)
|
|
67
65
|
OpenAI::ResponsesProvider.new(raw_options).prompt
|
|
68
66
|
end
|
|
69
67
|
end
|
|
@@ -89,7 +87,6 @@ module ActiveAgent
|
|
|
89
87
|
# @param parameters [Hash] The embedding request parameters
|
|
90
88
|
# @return [Object] The embedding response from OpenAI
|
|
91
89
|
def api_embed_execute(parameters)
|
|
92
|
-
instrument("embeddings_request.provider.active_agent")
|
|
93
90
|
client.embeddings.create(**parameters).as_json.deep_symbolize_keys
|
|
94
91
|
end
|
|
95
92
|
end
|
|
@@ -8,8 +8,25 @@ require_relative "options"
|
|
|
8
8
|
module ActiveAgent
|
|
9
9
|
module Providers
|
|
10
10
|
module OpenRouter
|
|
11
|
-
#
|
|
11
|
+
# ActiveModel type for casting and serializing OpenRouter requests
|
|
12
|
+
#
|
|
13
|
+
# Handles conversion between hashes, Request objects, and serialized
|
|
14
|
+
# request hashes for the OpenRouter API.
|
|
15
|
+
#
|
|
16
|
+
# @example Type casting
|
|
17
|
+
# type = RequestType.new
|
|
18
|
+
# request = type.cast({ model: "openai/gpt-4", messages: "Hello" })
|
|
19
|
+
# # => #<Request ...>
|
|
20
|
+
#
|
|
21
|
+
# @example Serialization
|
|
22
|
+
# serialized = type.serialize(request)
|
|
23
|
+
# # => { model: "openai/gpt-4", messages: [...] }
|
|
12
24
|
class RequestType < ActiveModel::Type::Value
|
|
25
|
+
# Casts value to Request object
|
|
26
|
+
#
|
|
27
|
+
# @param value [Request, Hash, nil]
|
|
28
|
+
# @return [Request, nil]
|
|
29
|
+
# @raise [ArgumentError] if value cannot be cast
|
|
13
30
|
def cast(value)
|
|
14
31
|
case value
|
|
15
32
|
when Request
|
|
@@ -23,6 +40,11 @@ module ActiveAgent
|
|
|
23
40
|
end
|
|
24
41
|
end
|
|
25
42
|
|
|
43
|
+
# Serializes Request to hash for API submission
|
|
44
|
+
#
|
|
45
|
+
# @param value [Request, Hash, nil]
|
|
46
|
+
# @return [Hash, nil]
|
|
47
|
+
# @raise [ArgumentError] if value cannot be serialized
|
|
26
48
|
def serialize(value)
|
|
27
49
|
case value
|
|
28
50
|
when Request
|
|
@@ -36,6 +58,10 @@ module ActiveAgent
|
|
|
36
58
|
end
|
|
37
59
|
end
|
|
38
60
|
|
|
61
|
+
# Deserializes value from storage
|
|
62
|
+
#
|
|
63
|
+
# @param value [Object]
|
|
64
|
+
# @return [Request, nil]
|
|
39
65
|
def deserialize(value)
|
|
40
66
|
cast(value)
|
|
41
67
|
end
|
|
@@ -8,11 +8,48 @@ require_relative "requests/provider_preferences"
|
|
|
8
8
|
module ActiveAgent
|
|
9
9
|
module Providers
|
|
10
10
|
module OpenRouter
|
|
11
|
+
# Configuration options for OpenRouter provider
|
|
12
|
+
#
|
|
13
|
+
# Extends OpenAI::Options with OpenRouter-specific settings including
|
|
14
|
+
# HTTP-Referer and X-Title headers for app identification and ranking.
|
|
15
|
+
#
|
|
16
|
+
# @example Basic configuration
|
|
17
|
+
# options = Options.new(
|
|
18
|
+
# api_key: 'sk-or-v1-...',
|
|
19
|
+
# app_name: 'MyApp',
|
|
20
|
+
# site_url: 'https://myapp.com'
|
|
21
|
+
# )
|
|
22
|
+
#
|
|
23
|
+
# @example Rails auto-configuration
|
|
24
|
+
# # Automatically resolves app_name from Rails.application
|
|
25
|
+
# # and site_url from routes.default_url_options
|
|
26
|
+
# options = Options.new(api_key: ENV['OPENROUTER_API_KEY'])
|
|
27
|
+
#
|
|
28
|
+
# @see https://openrouter.ai/docs/api-keys OpenRouter API Keys
|
|
29
|
+
# @see https://openrouter.ai/docs/rankings OpenRouter App Rankings
|
|
11
30
|
class Options < ActiveAgent::Providers::OpenAI::Options
|
|
31
|
+
# @!attribute base_url
|
|
32
|
+
# @return [String] API endpoint (default: "https://openrouter.ai/api/v1")
|
|
12
33
|
attribute :base_url, :string, as: "https://openrouter.ai/api/v1"
|
|
34
|
+
|
|
35
|
+
# @!attribute app_name
|
|
36
|
+
# @return [String] application name for X-Title header (default: "ActiveAgent" or Rails app name)
|
|
13
37
|
attribute :app_name, :string, fallback: "ActiveAgent"
|
|
38
|
+
|
|
39
|
+
# @!attribute site_url
|
|
40
|
+
# @return [String] site URL for HTTP-Referer header (default: "https://activeagents.ai/" or Rails URL)
|
|
14
41
|
attribute :site_url, :string, fallback: "https://activeagents.ai/"
|
|
15
42
|
|
|
43
|
+
# Creates new OpenRouter options with auto-resolution
|
|
44
|
+
#
|
|
45
|
+
# Automatically resolves app_name from Rails application name and
|
|
46
|
+
# site_url from Rails routes/ActionMailer default_url_options.
|
|
47
|
+
#
|
|
48
|
+
# @param kwargs [Hash] configuration options
|
|
49
|
+
# @option kwargs [String] :api_key OpenRouter API key
|
|
50
|
+
# @option kwargs [String] :app_name application name for rankings
|
|
51
|
+
# @option kwargs [String] :site_url site URL for rankings
|
|
52
|
+
# @return [Options]
|
|
16
53
|
def initialize(kwargs = {})
|
|
17
54
|
kwargs = kwargs.deep_symbolize_keys if kwargs.respond_to?(:deep_symbolize_keys)
|
|
18
55
|
|
|
@@ -22,11 +59,22 @@ module ActiveAgent
|
|
|
22
59
|
)))
|
|
23
60
|
end
|
|
24
61
|
|
|
62
|
+
# Serializes options for API requests
|
|
63
|
+
#
|
|
64
|
+
# Excludes app_name and site_url as they're sent via headers.
|
|
65
|
+
#
|
|
66
|
+
# @return [Hash] serialized options
|
|
25
67
|
def serialize
|
|
26
68
|
super.except(:app_name, :site_url)
|
|
27
69
|
end
|
|
28
70
|
|
|
29
|
-
#
|
|
71
|
+
# Returns extra headers for OpenRouter API
|
|
72
|
+
#
|
|
73
|
+
# Maps app_name and site_url to OpenRouter's required headers:
|
|
74
|
+
# - HTTP-Referer: site_url
|
|
75
|
+
# - X-Title: app_name
|
|
76
|
+
#
|
|
77
|
+
# @return [Hash] headers hash
|
|
30
78
|
def extra_headers
|
|
31
79
|
deep_compact(
|
|
32
80
|
"http-referer" => site_url.presence,
|