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,364 @@
|
|
|
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 Chat
|
|
9
|
+
# Provides transformation methods for normalizing chat parameters
|
|
10
|
+
# to OpenAI gem's native format
|
|
11
|
+
#
|
|
12
|
+
# Handles message normalization, shorthand formats, instructions mapping,
|
|
13
|
+
# and response format conversion for the Chat Completions 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
|
+
# Normalizes all request parameters for OpenAI Chat API
|
|
25
|
+
#
|
|
26
|
+
# Handles instructions mapping to developer messages, message normalization,
|
|
27
|
+
# and response_format conversion. This is the main entry point for parameter
|
|
28
|
+
# transformation.
|
|
29
|
+
#
|
|
30
|
+
# @param params [Hash]
|
|
31
|
+
# @return [Hash] normalized parameters
|
|
32
|
+
def normalize_params(params)
|
|
33
|
+
params = params.dup
|
|
34
|
+
|
|
35
|
+
# Map common format 'instructions' to developer messages
|
|
36
|
+
if params.key?(:instructions)
|
|
37
|
+
instructions_messages = normalize_instructions(params.delete(:instructions))
|
|
38
|
+
params[:messages] = instructions_messages + Array(params[:messages] || [])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Normalize messages for gem compatibility
|
|
42
|
+
params[:messages] = normalize_messages(params[:messages]) if params[:messages]
|
|
43
|
+
|
|
44
|
+
# Normalize response_format if present
|
|
45
|
+
params[:response_format] = normalize_response_format(params[:response_format]) if params[:response_format]
|
|
46
|
+
|
|
47
|
+
params
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Normalizes messages to OpenAI Chat API format using gem message classes
|
|
51
|
+
#
|
|
52
|
+
# Handles various input formats:
|
|
53
|
+
# - `"text"` → UserMessageParam
|
|
54
|
+
# - `[{role: "user", content: "..."}]` → array of message params
|
|
55
|
+
# - Merges consecutive same-role messages into single message
|
|
56
|
+
#
|
|
57
|
+
# @param messages [Array, String, Hash, nil]
|
|
58
|
+
# @return [Array<OpenAI::Models::Chat::ChatCompletionMessageParam>, nil]
|
|
59
|
+
def normalize_messages(messages)
|
|
60
|
+
case messages
|
|
61
|
+
when String
|
|
62
|
+
[ create_message_param("user", messages) ]
|
|
63
|
+
when Hash
|
|
64
|
+
[ normalize_message(messages) ]
|
|
65
|
+
when Array
|
|
66
|
+
grouped = []
|
|
67
|
+
|
|
68
|
+
messages.each do |msg|
|
|
69
|
+
normalized = normalize_message(msg)
|
|
70
|
+
|
|
71
|
+
if grouped.empty? || grouped.last.role != normalized.role
|
|
72
|
+
grouped << normalized
|
|
73
|
+
else
|
|
74
|
+
# Merge consecutive same-role messages
|
|
75
|
+
merged_content = merge_content(grouped.last.content, normalized.content)
|
|
76
|
+
grouped[-1] = create_message_param(grouped.last.role, merged_content)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
grouped
|
|
81
|
+
when nil
|
|
82
|
+
nil
|
|
83
|
+
else
|
|
84
|
+
raise ArgumentError, "Cannot normalize #{messages.class} to messages array"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Normalizes a single message to proper gem message param class
|
|
89
|
+
#
|
|
90
|
+
# Handles shorthand formats:
|
|
91
|
+
# - `"text"` → user message
|
|
92
|
+
# - `{text: "..."}` → user message
|
|
93
|
+
# - `{role: "system", text: "..."}` → system message
|
|
94
|
+
# - `{image: "url"}` → user message with image content part
|
|
95
|
+
# - `{text: "...", image: "url"}` → user message with text and image parts
|
|
96
|
+
#
|
|
97
|
+
# @param message [String, Hash, OpenAI::Models::Chat::ChatCompletionMessageParam]
|
|
98
|
+
# @return [OpenAI::Models::Chat::ChatCompletionMessageParam]
|
|
99
|
+
def normalize_message(message)
|
|
100
|
+
case message
|
|
101
|
+
when String
|
|
102
|
+
create_message_param("user", message)
|
|
103
|
+
when ::OpenAI::Models::Chat::ChatCompletionMessageParam
|
|
104
|
+
# Already a gem message param - pass through
|
|
105
|
+
message
|
|
106
|
+
when Hash
|
|
107
|
+
msg_hash = message.deep_symbolize_keys
|
|
108
|
+
role = msg_hash[:role]&.to_s || "user"
|
|
109
|
+
|
|
110
|
+
# Handle shorthand formats
|
|
111
|
+
content = if msg_hash.key?(:content)
|
|
112
|
+
# Standard format with explicit content
|
|
113
|
+
msg_hash[:content]
|
|
114
|
+
elsif msg_hash.key?(:text) && msg_hash.key?(:image)
|
|
115
|
+
# Shorthand with both text and image: { text: "...", image: "url" }
|
|
116
|
+
[
|
|
117
|
+
{ type: "text", text: msg_hash[:text] },
|
|
118
|
+
{ type: "image_url", image_url: { url: msg_hash[:image] } }
|
|
119
|
+
]
|
|
120
|
+
elsif msg_hash.key?(:image)
|
|
121
|
+
# Shorthand with only image: { image: "url" }
|
|
122
|
+
# Text comes from adjacent prompt arguments
|
|
123
|
+
[ { type: "image_url", image_url: { url: msg_hash[:image] } } ]
|
|
124
|
+
elsif msg_hash.key?(:text)
|
|
125
|
+
# Shorthand: { text: "..." } or { role: "...", text: "..." }
|
|
126
|
+
msg_hash[:text]
|
|
127
|
+
else
|
|
128
|
+
# No content specified
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Create appropriate message param based on role and content
|
|
133
|
+
extra_params = msg_hash.except(:role, :content, :text, :image)
|
|
134
|
+
create_message_param(role, content, extra_params)
|
|
135
|
+
else
|
|
136
|
+
raise ArgumentError, "Cannot normalize #{message.class} to message"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Creates the appropriate gem message param class for the given role
|
|
141
|
+
#
|
|
142
|
+
# @param role [String] message role (developer, system, user, assistant, tool, function)
|
|
143
|
+
# @param content [String, Array, Hash, nil]
|
|
144
|
+
# @param extra_params [Hash] additional parameters (tool_call_id, name, etc.)
|
|
145
|
+
# @return [OpenAI::Models::Chat::ChatCompletionMessageParam]
|
|
146
|
+
# @raise [ArgumentError] when role is unknown
|
|
147
|
+
def create_message_param(role, content, extra_params = {})
|
|
148
|
+
params = { role: role }
|
|
149
|
+
params[:content] = normalize_content(content) if content
|
|
150
|
+
params.merge!(extra_params)
|
|
151
|
+
|
|
152
|
+
case role.to_s
|
|
153
|
+
when "developer"
|
|
154
|
+
::OpenAI::Models::Chat::ChatCompletionDeveloperMessageParam.new(**params)
|
|
155
|
+
when "system"
|
|
156
|
+
::OpenAI::Models::Chat::ChatCompletionSystemMessageParam.new(**params)
|
|
157
|
+
when "user"
|
|
158
|
+
::OpenAI::Models::Chat::ChatCompletionUserMessageParam.new(**params)
|
|
159
|
+
when "assistant"
|
|
160
|
+
::OpenAI::Models::Chat::ChatCompletionAssistantMessageParam.new(**params)
|
|
161
|
+
when "tool"
|
|
162
|
+
::OpenAI::Models::Chat::ChatCompletionToolMessageParam.new(**params)
|
|
163
|
+
when "function"
|
|
164
|
+
::OpenAI::Models::Chat::ChatCompletionFunctionMessageParam.new(**params)
|
|
165
|
+
else
|
|
166
|
+
raise ArgumentError, "Unknown message role: #{role}"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Normalizes message content to Chat API format
|
|
171
|
+
#
|
|
172
|
+
# @param content [String, Array, Hash, nil]
|
|
173
|
+
# @return [String, Array, nil]
|
|
174
|
+
# @raise [ArgumentError] when content type is invalid
|
|
175
|
+
def normalize_content(content)
|
|
176
|
+
case content
|
|
177
|
+
when String
|
|
178
|
+
content
|
|
179
|
+
when Array
|
|
180
|
+
content.map { |part| normalize_content_part(part) }
|
|
181
|
+
when Hash
|
|
182
|
+
# Single content part as hash - wrap in array
|
|
183
|
+
[ normalize_content_part(content) ]
|
|
184
|
+
when nil
|
|
185
|
+
nil
|
|
186
|
+
else
|
|
187
|
+
raise ArgumentError, "Cannot normalize #{content.class} to content"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Normalizes a single content part
|
|
192
|
+
#
|
|
193
|
+
# Converts strings to proper content part format with type and text keys.
|
|
194
|
+
#
|
|
195
|
+
# @param part [Hash, String]
|
|
196
|
+
# @return [Hash] content part with symbolized keys
|
|
197
|
+
# @raise [ArgumentError] when part type is invalid
|
|
198
|
+
def normalize_content_part(part)
|
|
199
|
+
case part
|
|
200
|
+
when Hash
|
|
201
|
+
part.deep_symbolize_keys
|
|
202
|
+
when String
|
|
203
|
+
{ type: "text", text: part }
|
|
204
|
+
else
|
|
205
|
+
raise ArgumentError, "Cannot normalize #{part.class} to content part"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Merges two content values for consecutive same-role messages
|
|
210
|
+
#
|
|
211
|
+
# Preserves multiple text parts and mixed content as array structure
|
|
212
|
+
# rather than concatenating strings.
|
|
213
|
+
#
|
|
214
|
+
# @param content1 [String, Array, nil]
|
|
215
|
+
# @param content2 [String, Array, nil]
|
|
216
|
+
# @return [Array] merged content parts
|
|
217
|
+
def merge_content(content1, content2)
|
|
218
|
+
# Convert to arrays for consistent handling
|
|
219
|
+
arr1 = content_to_array(content1)
|
|
220
|
+
arr2 = content_to_array(content2)
|
|
221
|
+
|
|
222
|
+
merged = arr1 + arr2
|
|
223
|
+
|
|
224
|
+
# Keep as array of content parts - don't simplify to string
|
|
225
|
+
# This preserves multiple text parts and mixed content
|
|
226
|
+
merged
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Converts content to array format for merging
|
|
230
|
+
#
|
|
231
|
+
# @param content [String, Array, nil]
|
|
232
|
+
# @return [Array<Hash>] content parts with type and text keys
|
|
233
|
+
def content_to_array(content)
|
|
234
|
+
case content
|
|
235
|
+
when String
|
|
236
|
+
[ { type: "text", text: content } ]
|
|
237
|
+
when Array
|
|
238
|
+
content.map { |part| part.is_a?(String) ? { type: "text", text: part } : part }
|
|
239
|
+
when nil
|
|
240
|
+
[]
|
|
241
|
+
else
|
|
242
|
+
[ content ]
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Simplifies messages for cleaner API requests
|
|
247
|
+
#
|
|
248
|
+
# Converts gem message objects to hashes and simplifies content:
|
|
249
|
+
# - Single text content arrays → strings
|
|
250
|
+
# - Empty content arrays → removed
|
|
251
|
+
#
|
|
252
|
+
# @param messages [Array]
|
|
253
|
+
# @return [Array<Hash>]
|
|
254
|
+
def simplify_messages(messages)
|
|
255
|
+
return messages unless messages.is_a?(Array)
|
|
256
|
+
|
|
257
|
+
messages.map do |msg|
|
|
258
|
+
# Convert to hash if it's a gem object
|
|
259
|
+
simplified = msg.is_a?(Hash) ? msg.dup : gem_to_hash(msg)
|
|
260
|
+
|
|
261
|
+
# Simplify content if it's a single text part
|
|
262
|
+
if simplified[:content].is_a?(Array) && simplified[:content].size == 1
|
|
263
|
+
part = simplified[:content][0]
|
|
264
|
+
if part.is_a?(Hash) && part[:type] == "text" && part.keys.sort == [ :text, :type ]
|
|
265
|
+
simplified[:content] = part[:text]
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Remove empty content arrays
|
|
270
|
+
simplified.delete(:content) if simplified[:content] == []
|
|
271
|
+
|
|
272
|
+
simplified
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Normalizes response_format to OpenAI Chat API format
|
|
277
|
+
#
|
|
278
|
+
# @param format [Hash, Symbol, String]
|
|
279
|
+
# @return [Hash] normalized response format
|
|
280
|
+
def normalize_response_format(format)
|
|
281
|
+
case format
|
|
282
|
+
when Hash
|
|
283
|
+
format_hash = format.deep_symbolize_keys
|
|
284
|
+
|
|
285
|
+
if format_hash[:type] == "json_schema" || format_hash[:type] == :json_schema
|
|
286
|
+
# json_schema format
|
|
287
|
+
{
|
|
288
|
+
type: "json_schema",
|
|
289
|
+
json_schema: {
|
|
290
|
+
name: format_hash[:name] || format_hash[:json_schema]&.dig(:name),
|
|
291
|
+
schema: format_hash[:schema] || format_hash[:json_schema]&.dig(:schema),
|
|
292
|
+
strict: format_hash[:strict] || format_hash[:json_schema]&.dig(:strict)
|
|
293
|
+
}.compact
|
|
294
|
+
}
|
|
295
|
+
elsif format_hash[:type]
|
|
296
|
+
# Other type formats (json_object, text, etc.)
|
|
297
|
+
{ type: format_hash[:type].to_s }
|
|
298
|
+
else
|
|
299
|
+
# Pass through (already properly structured or complex)
|
|
300
|
+
format_hash
|
|
301
|
+
end
|
|
302
|
+
when Symbol, String
|
|
303
|
+
# Simple string type
|
|
304
|
+
{ type: format.to_s }
|
|
305
|
+
else
|
|
306
|
+
format
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Normalizes instructions to developer message format
|
|
311
|
+
#
|
|
312
|
+
# Converts instructions into developer messages with proper content structure.
|
|
313
|
+
# Multiple instructions become content parts in a single developer message
|
|
314
|
+
# rather than separate messages.
|
|
315
|
+
#
|
|
316
|
+
# @param instructions [Array<String>, String]
|
|
317
|
+
# @return [Array<Hash>] developer messages
|
|
318
|
+
def normalize_instructions(instructions)
|
|
319
|
+
instructions_array = Array(instructions)
|
|
320
|
+
|
|
321
|
+
# Convert multiple instructions into content parts for a single developer message
|
|
322
|
+
if instructions_array.size > 1
|
|
323
|
+
content_parts = instructions_array.map do |instruction|
|
|
324
|
+
{ type: "text", text: instruction }
|
|
325
|
+
end
|
|
326
|
+
[ { role: "developer", content: content_parts } ]
|
|
327
|
+
else
|
|
328
|
+
instructions_array.map do |instruction|
|
|
329
|
+
{ role: "developer", content: instruction }
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Cleans up serialized hash for API request
|
|
335
|
+
#
|
|
336
|
+
# Removes default values, simplifies messages, and handles special cases
|
|
337
|
+
# like web_search_options (which requires empty hash to enable).
|
|
338
|
+
#
|
|
339
|
+
# @param hash [Hash] serialized request hash
|
|
340
|
+
# @param defaults [Hash] default values to remove
|
|
341
|
+
# @param gem_object [Object] original gem object for checking values
|
|
342
|
+
# @return [Hash] cleaned request hash
|
|
343
|
+
def cleanup_serialized_request(hash, defaults, gem_object)
|
|
344
|
+
# Remove default values that shouldn't be in the request body
|
|
345
|
+
defaults.each do |key, value|
|
|
346
|
+
hash.delete(key) if hash[key] == value
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Simplify messages for cleaner API requests
|
|
350
|
+
hash[:messages] = simplify_messages(hash[:messages]) if hash[:messages]
|
|
351
|
+
|
|
352
|
+
# Add web_search_options if present (defaults to empty hash to enable feature)
|
|
353
|
+
if gem_object.instance_variable_get(:@data)[:web_search_options]
|
|
354
|
+
hash[:web_search_options] ||= {}
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
hash
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
@@ -4,11 +4,10 @@ require_relative "chat/_types"
|
|
|
4
4
|
module ActiveAgent
|
|
5
5
|
module Providers
|
|
6
6
|
module OpenAI
|
|
7
|
-
# Provider implementation for OpenAI's Chat
|
|
7
|
+
# Provider implementation for OpenAI's Chat Completions API
|
|
8
8
|
#
|
|
9
9
|
# Handles chat-based interactions including streaming responses,
|
|
10
|
-
# function/tool calling, and message management.
|
|
11
|
-
# chat completions endpoint for generating responses.
|
|
10
|
+
# function/tool calling, and message management.
|
|
12
11
|
#
|
|
13
12
|
# @see Base
|
|
14
13
|
# @see https://platform.openai.com/docs/api-reference/chat
|
|
@@ -31,23 +30,32 @@ module ActiveAgent
|
|
|
31
30
|
client.chat.completions
|
|
32
31
|
end
|
|
33
32
|
|
|
34
|
-
#
|
|
33
|
+
# @see BaseProvider#api_response_normalize
|
|
34
|
+
# @param api_response [OpenAI::Models::ChatCompletion]
|
|
35
|
+
# @return [Hash] normalized response hash
|
|
36
|
+
def api_response_normalize(api_response)
|
|
37
|
+
return api_response unless api_response
|
|
38
|
+
|
|
39
|
+
Chat::Transforms.gem_to_hash(api_response)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Processes streaming response chunks from OpenAI's chat API
|
|
35
43
|
#
|
|
36
44
|
# Handles message deltas, content updates, and completion detection.
|
|
37
45
|
# Manages the message stack and broadcasts streaming updates.
|
|
38
46
|
#
|
|
39
|
-
#
|
|
40
|
-
# - `:chunk` -
|
|
41
|
-
# - `:"content.delta"` -
|
|
42
|
-
# - `:"content.done"` -
|
|
43
|
-
# - `:"tool_calls.function.arguments.delta"` -
|
|
44
|
-
# - `:"tool_calls.function.arguments.done"` -
|
|
47
|
+
# Event types handled:
|
|
48
|
+
# - `:chunk` - message content and tool call deltas
|
|
49
|
+
# - `:"content.delta"` - incremental content updates
|
|
50
|
+
# - `:"content.done"` - complete content delivery
|
|
51
|
+
# - `:"tool_calls.function.arguments.delta"` - tool argument deltas
|
|
52
|
+
# - `:"tool_calls.function.arguments.done"` - complete tool arguments
|
|
45
53
|
#
|
|
46
54
|
# @param api_response_event [OpenAI::Helpers::Streaming::ChatChunkEvent]
|
|
47
55
|
# @return [void]
|
|
48
56
|
# @see Base#process_stream_chunk
|
|
49
57
|
def process_stream_chunk(api_response_event)
|
|
50
|
-
instrument("
|
|
58
|
+
instrument("stream_chunk.active_agent")
|
|
51
59
|
|
|
52
60
|
# Called Multiple Times: [Chunk<T>, T]<Content, ToolsCall>
|
|
53
61
|
case api_response_event.type
|
|
@@ -62,11 +70,6 @@ module ActiveAgent
|
|
|
62
70
|
if api_message.delta.content
|
|
63
71
|
broadcast_stream_update(message_stack.last, api_message.delta.content)
|
|
64
72
|
end
|
|
65
|
-
|
|
66
|
-
# If this is the last api_chunk to be processed
|
|
67
|
-
return unless api_message.finish_reason
|
|
68
|
-
|
|
69
|
-
instrument("stream_finished.provider.active_agent", finish_reason: api_message.finish_reason)
|
|
70
73
|
when :"content.delta"
|
|
71
74
|
# Returns the deltas, without context
|
|
72
75
|
# => {type: :"content.delta", delta: "", snapshot: "", parsed: nil}
|
|
@@ -86,42 +89,60 @@ module ActiveAgent
|
|
|
86
89
|
end
|
|
87
90
|
end
|
|
88
91
|
|
|
89
|
-
# Processes function/tool calls from the API response
|
|
92
|
+
# Processes function/tool calls from the API response
|
|
90
93
|
#
|
|
91
94
|
# Executes each tool call and creates tool response messages
|
|
92
95
|
# for the next iteration of the conversation.
|
|
93
96
|
#
|
|
94
|
-
# @param api_function_calls [Array<Hash>] function
|
|
97
|
+
# @param api_function_calls [Array<Hash>] function calls with :type, :id, and :function keys
|
|
95
98
|
# @return [void]
|
|
96
99
|
# @see Base#process_function_calls
|
|
97
100
|
def process_function_calls(api_function_calls)
|
|
98
101
|
api_function_calls.each do |api_function_call|
|
|
99
|
-
content =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
content = instrument("tool_call.active_agent", tool_name: api_function_call.dig(:function, :name)) do
|
|
103
|
+
case api_function_call[:type]
|
|
104
|
+
when "function"
|
|
105
|
+
process_tool_call_function(api_function_call[:function])
|
|
106
|
+
else
|
|
107
|
+
fail "Unexpected Tool Call Type: #{api_function_call[:type]}"
|
|
108
|
+
end
|
|
105
109
|
end
|
|
106
110
|
|
|
107
|
-
message
|
|
111
|
+
# Create tool message using gem's message param class
|
|
112
|
+
message = ::OpenAI::Models::Chat::ChatCompletionToolMessageParam.new(
|
|
113
|
+
role: "tool",
|
|
108
114
|
tool_call_id: api_function_call[:id],
|
|
109
115
|
content: content.to_json
|
|
110
116
|
)
|
|
111
117
|
|
|
112
|
-
|
|
118
|
+
# Serialize and push to message stack
|
|
119
|
+
message_hash = Chat::Transforms.gem_to_hash(message)
|
|
120
|
+
message_stack.push(message_hash)
|
|
113
121
|
end
|
|
114
122
|
end
|
|
115
123
|
|
|
116
124
|
# Extracts messages from the completed API response.
|
|
125
|
+
# Converts OpenAI gem response object to hash for storage.
|
|
117
126
|
#
|
|
118
|
-
# @param api_response [
|
|
127
|
+
# @param api_response [OpenAI::Models::Chat::ChatCompletion]
|
|
128
|
+
# @return [Common::PromptResponse, nil]
|
|
129
|
+
def process_prompt_finished(api_response = nil)
|
|
130
|
+
# Convert gem object to hash so that raw_response["usage"] works
|
|
131
|
+
api_response_hash = api_response ? Chat::Transforms.gem_to_hash(api_response) : nil
|
|
132
|
+
super(api_response_hash)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Extracts messages from completed API response.
|
|
136
|
+
#
|
|
137
|
+
# @param api_response [Hash] converted response hash
|
|
119
138
|
# @return [Array<Hash>, nil] single-element array with message or nil if no message
|
|
120
139
|
# @see Base#process_prompt_finished_extract_messages
|
|
121
140
|
def process_prompt_finished_extract_messages(api_response)
|
|
122
|
-
|
|
141
|
+
return unless api_response
|
|
142
|
+
|
|
143
|
+
api_message = api_response[:choices][0][:message]
|
|
123
144
|
|
|
124
|
-
[ api_message ]
|
|
145
|
+
[ api_message ]
|
|
125
146
|
end
|
|
126
147
|
|
|
127
148
|
# Extracts function calls from the last message in the stack.
|
|
@@ -132,20 +153,20 @@ module ActiveAgent
|
|
|
132
153
|
message_stack.last[:tool_calls]
|
|
133
154
|
end
|
|
134
155
|
|
|
135
|
-
# Merges streaming delta into a message
|
|
156
|
+
# Merges streaming delta into a message
|
|
136
157
|
#
|
|
137
158
|
# Separated from hash_merge_delta to allow Ollama to override role handling.
|
|
138
159
|
#
|
|
139
160
|
# @param message [Hash]
|
|
140
161
|
# @param delta [Hash]
|
|
141
|
-
# @return [Hash]
|
|
162
|
+
# @return [Hash] merged message
|
|
142
163
|
def message_merge_delta(message, delta)
|
|
143
164
|
hash_merge_delta(message, delta)
|
|
144
165
|
end
|
|
145
166
|
|
|
146
167
|
private
|
|
147
168
|
|
|
148
|
-
# Finds an existing message by
|
|
169
|
+
# Finds an existing message by index or creates a new one
|
|
149
170
|
#
|
|
150
171
|
# @param id [Integer]
|
|
151
172
|
# @return [Hash] found or newly created message
|
|
@@ -157,14 +178,14 @@ module ActiveAgent
|
|
|
157
178
|
message_stack.last
|
|
158
179
|
end
|
|
159
180
|
|
|
160
|
-
# Recursively merges delta changes into a hash structure
|
|
181
|
+
# Recursively merges delta changes into a hash structure
|
|
161
182
|
#
|
|
162
|
-
# Handles
|
|
163
|
-
#
|
|
183
|
+
# Handles complex delta merging for OpenAI's streaming API, including
|
|
184
|
+
# arrays with indexed items and string concatenation.
|
|
164
185
|
#
|
|
165
186
|
# @param hash [Hash] target hash to merge into
|
|
166
187
|
# @param delta [Hash] delta changes to apply
|
|
167
|
-
# @return [Hash]
|
|
188
|
+
# @return [Hash] merged hash
|
|
168
189
|
def hash_merge_delta(hash, delta)
|
|
169
190
|
delta.each do |key, value|
|
|
170
191
|
case hash[key]
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "requests/_types"
|
|
4
3
|
require_relative "request"
|
|
5
4
|
|
|
6
5
|
module ActiveAgent
|
|
7
6
|
module Providers
|
|
8
7
|
module OpenAI
|
|
9
8
|
module Embedding
|
|
10
|
-
#
|
|
9
|
+
# ActiveModel type for casting and serializing embedding requests
|
|
11
10
|
class RequestType < ActiveModel::Type::Value
|
|
11
|
+
# Casts value to Request object
|
|
12
|
+
#
|
|
13
|
+
# @param value [Request, Hash, nil]
|
|
14
|
+
# @return [Request, nil]
|
|
15
|
+
# @raise [ArgumentError] when value cannot be cast
|
|
12
16
|
def cast(value)
|
|
13
17
|
case value
|
|
14
18
|
when Request
|
|
@@ -22,6 +26,11 @@ module ActiveAgent
|
|
|
22
26
|
end
|
|
23
27
|
end
|
|
24
28
|
|
|
29
|
+
# Serializes Request to hash for API submission
|
|
30
|
+
#
|
|
31
|
+
# @param value [Request, Hash, nil]
|
|
32
|
+
# @return [Hash, nil]
|
|
33
|
+
# @raise [ArgumentError] when value cannot be serialized
|
|
25
34
|
def serialize(value)
|
|
26
35
|
case value
|
|
27
36
|
when Request
|
|
@@ -35,6 +44,8 @@ module ActiveAgent
|
|
|
35
44
|
end
|
|
36
45
|
end
|
|
37
46
|
|
|
47
|
+
# @param value [Object]
|
|
48
|
+
# @return [Request, nil]
|
|
38
49
|
def deserialize(value)
|
|
39
50
|
cast(value)
|
|
40
51
|
end
|