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
|
@@ -0,0 +1,444 @@
|
|
|
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
|
+
# tools normalization, and response_format conversion. This is the main entry point
|
|
28
|
+
# for parameter 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 tools from common format to Chat API format
|
|
45
|
+
params[:tools] = normalize_tools(params[:tools]) if params[:tools]
|
|
46
|
+
|
|
47
|
+
# Normalize tool_choice from common format
|
|
48
|
+
params[:tool_choice] = normalize_tool_choice(params[:tool_choice]) if params[:tool_choice]
|
|
49
|
+
|
|
50
|
+
# Normalize response_format if present
|
|
51
|
+
params[:response_format] = normalize_response_format(params[:response_format]) if params[:response_format]
|
|
52
|
+
|
|
53
|
+
params
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Normalizes messages to OpenAI Chat API format using gem message classes
|
|
57
|
+
#
|
|
58
|
+
# Handles various input formats:
|
|
59
|
+
# - `"text"` → UserMessageParam
|
|
60
|
+
# - `[{role: "user", content: "..."}]` → array of message params
|
|
61
|
+
# - Merges consecutive same-role messages into single message
|
|
62
|
+
#
|
|
63
|
+
# @param messages [Array, String, Hash, nil]
|
|
64
|
+
# @return [Array<OpenAI::Models::Chat::ChatCompletionMessageParam>, nil]
|
|
65
|
+
def normalize_messages(messages)
|
|
66
|
+
case messages
|
|
67
|
+
when String
|
|
68
|
+
[ create_message_param("user", messages) ]
|
|
69
|
+
when Hash
|
|
70
|
+
[ normalize_message(messages) ]
|
|
71
|
+
when Array
|
|
72
|
+
grouped = []
|
|
73
|
+
|
|
74
|
+
messages.each do |msg|
|
|
75
|
+
normalized = normalize_message(msg)
|
|
76
|
+
|
|
77
|
+
# Don't merge tool messages - each needs its own tool_call_id
|
|
78
|
+
if grouped.empty? || grouped.last.role != normalized.role || normalized.role.to_s == "tool"
|
|
79
|
+
grouped << normalized
|
|
80
|
+
else
|
|
81
|
+
# Merge consecutive same-role messages
|
|
82
|
+
merged_content = merge_content(grouped.last.content, normalized.content)
|
|
83
|
+
grouped[-1] = create_message_param(grouped.last.role, merged_content)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
grouped
|
|
88
|
+
when nil
|
|
89
|
+
nil
|
|
90
|
+
else
|
|
91
|
+
raise ArgumentError, "Cannot normalize #{messages.class} to messages array"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Normalizes a single message to proper gem message param class
|
|
96
|
+
#
|
|
97
|
+
# Handles shorthand formats:
|
|
98
|
+
# - `"text"` → user message
|
|
99
|
+
# - `{text: "..."}` → user message
|
|
100
|
+
# - `{role: "system", text: "..."}` → system message
|
|
101
|
+
# - `{image: "url"}` → user message with image content part
|
|
102
|
+
# - `{text: "...", image: "url"}` → user message with text and image parts
|
|
103
|
+
#
|
|
104
|
+
# @param message [String, Hash, OpenAI::Models::Chat::ChatCompletionMessageParam]
|
|
105
|
+
# @return [OpenAI::Models::Chat::ChatCompletionMessageParam]
|
|
106
|
+
def normalize_message(message)
|
|
107
|
+
case message
|
|
108
|
+
when String
|
|
109
|
+
create_message_param("user", message)
|
|
110
|
+
when ::OpenAI::Models::Chat::ChatCompletionMessageParam
|
|
111
|
+
# Already a gem message param - pass through
|
|
112
|
+
message
|
|
113
|
+
when Hash
|
|
114
|
+
msg_hash = message.deep_symbolize_keys
|
|
115
|
+
role = msg_hash[:role]&.to_s || "user"
|
|
116
|
+
|
|
117
|
+
# Handle shorthand formats
|
|
118
|
+
content = if msg_hash.key?(:content)
|
|
119
|
+
# Standard format with explicit content
|
|
120
|
+
msg_hash[:content]
|
|
121
|
+
elsif msg_hash.key?(:text) && msg_hash.key?(:image)
|
|
122
|
+
# Shorthand with both text and image: { text: "...", image: "url" }
|
|
123
|
+
[
|
|
124
|
+
{ type: "text", text: msg_hash[:text] },
|
|
125
|
+
{ type: "image_url", image_url: { url: msg_hash[:image] } }
|
|
126
|
+
]
|
|
127
|
+
elsif msg_hash.key?(:image)
|
|
128
|
+
# Shorthand with only image: { image: "url" }
|
|
129
|
+
# Text comes from adjacent prompt arguments
|
|
130
|
+
[ { type: "image_url", image_url: { url: msg_hash[:image] } } ]
|
|
131
|
+
elsif msg_hash.key?(:text)
|
|
132
|
+
# Shorthand: { text: "..." } or { role: "...", text: "..." }
|
|
133
|
+
msg_hash[:text]
|
|
134
|
+
else
|
|
135
|
+
# No content specified
|
|
136
|
+
nil
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Create appropriate message param based on role and content
|
|
140
|
+
extra_params = msg_hash.except(:role, :content, :text, :image)
|
|
141
|
+
create_message_param(role, content, extra_params)
|
|
142
|
+
else
|
|
143
|
+
raise ArgumentError, "Cannot normalize #{message.class} to message"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Creates the appropriate gem message param class for the given role
|
|
148
|
+
#
|
|
149
|
+
# @param role [String] message role (developer, system, user, assistant, tool, function)
|
|
150
|
+
# @param content [String, Array, Hash, nil]
|
|
151
|
+
# @param extra_params [Hash] additional parameters (tool_call_id, name, etc.)
|
|
152
|
+
# @return [OpenAI::Models::Chat::ChatCompletionMessageParam]
|
|
153
|
+
# @raise [ArgumentError] when role is unknown
|
|
154
|
+
def create_message_param(role, content, extra_params = {})
|
|
155
|
+
params = { role: role }
|
|
156
|
+
params[:content] = normalize_content(content) if content
|
|
157
|
+
params.merge!(extra_params)
|
|
158
|
+
|
|
159
|
+
case role.to_s
|
|
160
|
+
when "developer"
|
|
161
|
+
::OpenAI::Models::Chat::ChatCompletionDeveloperMessageParam.new(**params)
|
|
162
|
+
when "system"
|
|
163
|
+
::OpenAI::Models::Chat::ChatCompletionSystemMessageParam.new(**params)
|
|
164
|
+
when "user"
|
|
165
|
+
::OpenAI::Models::Chat::ChatCompletionUserMessageParam.new(**params)
|
|
166
|
+
when "assistant"
|
|
167
|
+
::OpenAI::Models::Chat::ChatCompletionAssistantMessageParam.new(**params)
|
|
168
|
+
when "tool"
|
|
169
|
+
::OpenAI::Models::Chat::ChatCompletionToolMessageParam.new(**params)
|
|
170
|
+
when "function"
|
|
171
|
+
::OpenAI::Models::Chat::ChatCompletionFunctionMessageParam.new(**params)
|
|
172
|
+
else
|
|
173
|
+
raise ArgumentError, "Unknown message role: #{role}"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Normalizes message content to Chat API format
|
|
178
|
+
#
|
|
179
|
+
# @param content [String, Array, Hash, nil]
|
|
180
|
+
# @return [String, Array, nil]
|
|
181
|
+
# @raise [ArgumentError] when content type is invalid
|
|
182
|
+
def normalize_content(content)
|
|
183
|
+
case content
|
|
184
|
+
when String
|
|
185
|
+
content
|
|
186
|
+
when Array
|
|
187
|
+
content.map { |part| normalize_content_part(part) }
|
|
188
|
+
when Hash
|
|
189
|
+
# Single content part as hash - wrap in array
|
|
190
|
+
[ normalize_content_part(content) ]
|
|
191
|
+
when nil
|
|
192
|
+
nil
|
|
193
|
+
else
|
|
194
|
+
raise ArgumentError, "Cannot normalize #{content.class} to content"
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Normalizes a single content part
|
|
199
|
+
#
|
|
200
|
+
# Converts strings to proper content part format with type and text keys.
|
|
201
|
+
#
|
|
202
|
+
# @param part [Hash, String]
|
|
203
|
+
# @return [Hash] content part with symbolized keys
|
|
204
|
+
# @raise [ArgumentError] when part type is invalid
|
|
205
|
+
def normalize_content_part(part)
|
|
206
|
+
case part
|
|
207
|
+
when Hash
|
|
208
|
+
part.deep_symbolize_keys
|
|
209
|
+
when String
|
|
210
|
+
{ type: "text", text: part }
|
|
211
|
+
else
|
|
212
|
+
raise ArgumentError, "Cannot normalize #{part.class} to content part"
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Merges two content values for consecutive same-role messages
|
|
217
|
+
#
|
|
218
|
+
# Preserves multiple text parts and mixed content as array structure
|
|
219
|
+
# rather than concatenating strings.
|
|
220
|
+
#
|
|
221
|
+
# @param content1 [String, Array, nil]
|
|
222
|
+
# @param content2 [String, Array, nil]
|
|
223
|
+
# @return [Array] merged content parts
|
|
224
|
+
def merge_content(content1, content2)
|
|
225
|
+
# Convert to arrays for consistent handling
|
|
226
|
+
arr1 = content_to_array(content1)
|
|
227
|
+
arr2 = content_to_array(content2)
|
|
228
|
+
|
|
229
|
+
merged = arr1 + arr2
|
|
230
|
+
|
|
231
|
+
# Keep as array of content parts - don't simplify to string
|
|
232
|
+
# This preserves multiple text parts and mixed content
|
|
233
|
+
merged
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Converts content to array format for merging
|
|
237
|
+
#
|
|
238
|
+
# @param content [String, Array, nil]
|
|
239
|
+
# @return [Array<Hash>] content parts with type and text keys
|
|
240
|
+
def content_to_array(content)
|
|
241
|
+
case content
|
|
242
|
+
when String
|
|
243
|
+
[ { type: "text", text: content } ]
|
|
244
|
+
when Array
|
|
245
|
+
content.map { |part| part.is_a?(String) ? { type: "text", text: part } : part }
|
|
246
|
+
when nil
|
|
247
|
+
[]
|
|
248
|
+
else
|
|
249
|
+
[ content ]
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Simplifies messages for cleaner API requests
|
|
254
|
+
#
|
|
255
|
+
# Converts gem message objects to hashes and simplifies content:
|
|
256
|
+
# - Single text content arrays → strings
|
|
257
|
+
# - Empty content arrays → removed
|
|
258
|
+
#
|
|
259
|
+
# @param messages [Array]
|
|
260
|
+
# @return [Array<Hash>]
|
|
261
|
+
def simplify_messages(messages)
|
|
262
|
+
return messages unless messages.is_a?(Array)
|
|
263
|
+
|
|
264
|
+
messages.map do |msg|
|
|
265
|
+
# Convert to hash if it's a gem object
|
|
266
|
+
simplified = msg.is_a?(Hash) ? msg.dup : gem_to_hash(msg)
|
|
267
|
+
|
|
268
|
+
# Simplify content if it's a single text part
|
|
269
|
+
if simplified[:content].is_a?(Array) && simplified[:content].size == 1
|
|
270
|
+
part = simplified[:content][0]
|
|
271
|
+
if part.is_a?(Hash) && part[:type] == "text" && part.keys.sort == [ :text, :type ]
|
|
272
|
+
simplified[:content] = part[:text]
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Remove empty content arrays
|
|
277
|
+
simplified.delete(:content) if simplified[:content] == []
|
|
278
|
+
|
|
279
|
+
simplified
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Normalizes response_format to OpenAI Chat API format
|
|
284
|
+
#
|
|
285
|
+
# @param format [Hash, Symbol, String]
|
|
286
|
+
# @return [Hash] normalized response format
|
|
287
|
+
def normalize_response_format(format)
|
|
288
|
+
case format
|
|
289
|
+
when Hash
|
|
290
|
+
format_hash = format.deep_symbolize_keys
|
|
291
|
+
|
|
292
|
+
if format_hash[:type] == "json_schema" || format_hash[:type] == :json_schema
|
|
293
|
+
# json_schema format
|
|
294
|
+
{
|
|
295
|
+
type: "json_schema",
|
|
296
|
+
json_schema: {
|
|
297
|
+
name: format_hash[:name] || format_hash[:json_schema]&.dig(:name),
|
|
298
|
+
schema: format_hash[:schema] || format_hash[:json_schema]&.dig(:schema),
|
|
299
|
+
strict: format_hash[:strict] || format_hash[:json_schema]&.dig(:strict)
|
|
300
|
+
}.compact
|
|
301
|
+
}
|
|
302
|
+
elsif format_hash[:type]
|
|
303
|
+
# Other type formats (json_object, text, etc.)
|
|
304
|
+
{ type: format_hash[:type].to_s }
|
|
305
|
+
else
|
|
306
|
+
# Pass through (already properly structured or complex)
|
|
307
|
+
format_hash
|
|
308
|
+
end
|
|
309
|
+
when Symbol, String
|
|
310
|
+
# Simple string type
|
|
311
|
+
{ type: format.to_s }
|
|
312
|
+
else
|
|
313
|
+
format
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Normalizes tools from common format to OpenAI Chat API format.
|
|
318
|
+
#
|
|
319
|
+
# Accepts tools in multiple formats:
|
|
320
|
+
# - Common format: `{name: "...", description: "...", parameters: {...}}`
|
|
321
|
+
# - Common format alt: `{name: "...", description: "...", input_schema: {...}}`
|
|
322
|
+
# - Nested format: `{type: "function", function: {name: "...", parameters: {...}}}`
|
|
323
|
+
#
|
|
324
|
+
# Always outputs nested Chat API format: `{type: "function", function: {...}}`
|
|
325
|
+
#
|
|
326
|
+
# @param tools [Array<Hash>]
|
|
327
|
+
# @return [Array<Hash>]
|
|
328
|
+
def normalize_tools(tools)
|
|
329
|
+
return tools unless tools.is_a?(Array)
|
|
330
|
+
|
|
331
|
+
tools.map do |tool|
|
|
332
|
+
tool_hash = tool.is_a?(Hash) ? tool.deep_symbolize_keys : tool
|
|
333
|
+
|
|
334
|
+
# Already in nested format - return as is
|
|
335
|
+
if tool_hash[:type] == "function" && tool_hash[:function]
|
|
336
|
+
tool_hash
|
|
337
|
+
# Common format - convert to nested format
|
|
338
|
+
elsif tool_hash[:name]
|
|
339
|
+
{
|
|
340
|
+
type: "function",
|
|
341
|
+
function: {
|
|
342
|
+
name: tool_hash[:name],
|
|
343
|
+
description: tool_hash[:description],
|
|
344
|
+
parameters: tool_hash[:parameters] || tool_hash[:input_schema]
|
|
345
|
+
}.compact
|
|
346
|
+
}
|
|
347
|
+
else
|
|
348
|
+
tool_hash
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Normalizes tool_choice from common format to OpenAI Chat API format.
|
|
354
|
+
#
|
|
355
|
+
# Accepts:
|
|
356
|
+
# - "auto" (common) → "auto" (passthrough)
|
|
357
|
+
# - "required" (common) → "required" (passthrough)
|
|
358
|
+
# - `{name: "..."}` (common) → `{type: "function", function: {name: "..."}}`
|
|
359
|
+
# - Already nested format → passthrough
|
|
360
|
+
#
|
|
361
|
+
# @param tool_choice [String, Hash, Symbol]
|
|
362
|
+
# @return [String, Hash, Symbol]
|
|
363
|
+
def normalize_tool_choice(tool_choice)
|
|
364
|
+
case tool_choice
|
|
365
|
+
when "auto", :auto, "required", :required
|
|
366
|
+
# Passthrough - Chat API accepts these directly
|
|
367
|
+
tool_choice.to_s
|
|
368
|
+
when Hash
|
|
369
|
+
tool_choice_hash = tool_choice.deep_symbolize_keys
|
|
370
|
+
|
|
371
|
+
# Already in nested format with type and function keys
|
|
372
|
+
if tool_choice_hash[:type] == "function" && tool_choice_hash[:function]
|
|
373
|
+
tool_choice_hash
|
|
374
|
+
# Common format with just name - convert to nested format
|
|
375
|
+
elsif tool_choice_hash[:name]
|
|
376
|
+
{
|
|
377
|
+
type: "function",
|
|
378
|
+
function: {
|
|
379
|
+
name: tool_choice_hash[:name]
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else
|
|
383
|
+
tool_choice_hash
|
|
384
|
+
end
|
|
385
|
+
else
|
|
386
|
+
tool_choice
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Normalizes instructions to developer message format
|
|
391
|
+
#
|
|
392
|
+
# Converts instructions into developer messages with proper content structure.
|
|
393
|
+
# Multiple instructions become content parts in a single developer message
|
|
394
|
+
# rather than separate messages.
|
|
395
|
+
#
|
|
396
|
+
# @param instructions [Array<String>, String]
|
|
397
|
+
# @return [Array<Hash>] developer messages
|
|
398
|
+
def normalize_instructions(instructions)
|
|
399
|
+
instructions_array = Array(instructions)
|
|
400
|
+
|
|
401
|
+
# Convert multiple instructions into content parts for a single developer message
|
|
402
|
+
if instructions_array.size > 1
|
|
403
|
+
content_parts = instructions_array.map do |instruction|
|
|
404
|
+
{ type: "text", text: instruction }
|
|
405
|
+
end
|
|
406
|
+
[ { role: "developer", content: content_parts } ]
|
|
407
|
+
else
|
|
408
|
+
instructions_array.map do |instruction|
|
|
409
|
+
{ role: "developer", content: instruction }
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Cleans up serialized hash for API request
|
|
415
|
+
#
|
|
416
|
+
# Removes default values, simplifies messages, and handles special cases
|
|
417
|
+
# like web_search_options (which requires empty hash to enable).
|
|
418
|
+
#
|
|
419
|
+
# @param hash [Hash] serialized request hash
|
|
420
|
+
# @param defaults [Hash] default values to remove
|
|
421
|
+
# @param gem_object [Object] original gem object for checking values
|
|
422
|
+
# @return [Hash] cleaned request hash
|
|
423
|
+
def cleanup_serialized_request(hash, defaults, gem_object)
|
|
424
|
+
# Remove default values that shouldn't be in the request body
|
|
425
|
+
defaults.each do |key, value|
|
|
426
|
+
hash.delete(key) if hash[key] == value
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Simplify messages for cleaner API requests
|
|
430
|
+
hash[:messages] = simplify_messages(hash[:messages]) if hash[:messages]
|
|
431
|
+
|
|
432
|
+
# Add web_search_options if present (defaults to empty hash to enable feature)
|
|
433
|
+
if gem_object.instance_variable_get(:@data)[:web_search_options]
|
|
434
|
+
hash[:web_search_options] ||= {}
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
hash
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
end
|
|
@@ -4,15 +4,16 @@ 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
|
|
15
14
|
class ChatProvider < Base
|
|
15
|
+
include ToolChoiceClearing
|
|
16
|
+
|
|
16
17
|
# @return [Class] the options class for this provider
|
|
17
18
|
def self.options_klass
|
|
18
19
|
Options
|
|
@@ -31,23 +32,68 @@ module ActiveAgent
|
|
|
31
32
|
client.chat.completions
|
|
32
33
|
end
|
|
33
34
|
|
|
34
|
-
#
|
|
35
|
+
# @see BaseProvider#prepare_prompt_request
|
|
36
|
+
# @return [Request]
|
|
37
|
+
def prepare_prompt_request
|
|
38
|
+
prepare_prompt_request_tools
|
|
39
|
+
super
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Extracts function names from Chat API tool_calls in assistant messages.
|
|
43
|
+
#
|
|
44
|
+
# @return [Array<String>]
|
|
45
|
+
def extract_used_function_names
|
|
46
|
+
message_stack
|
|
47
|
+
.select { |msg| msg[:role] == "assistant" && msg[:tool_calls] }
|
|
48
|
+
.flat_map { |msg| msg[:tool_calls] }
|
|
49
|
+
.map { |tc| tc.dig(:function, :name) }
|
|
50
|
+
.compact
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns true if tool_choice == "required".
|
|
54
|
+
#
|
|
55
|
+
# @return [Boolean]
|
|
56
|
+
def tool_choice_forces_required?
|
|
57
|
+
request.tool_choice == "required"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns [true, name] if tool_choice is a hash with nested function name.
|
|
61
|
+
#
|
|
62
|
+
# @return [Array<Boolean, String|nil>]
|
|
63
|
+
def tool_choice_forces_specific?
|
|
64
|
+
if request.tool_choice.is_a?(Hash)
|
|
65
|
+
[ true, request.tool_choice.dig(:function, :name) ]
|
|
66
|
+
else
|
|
67
|
+
[ false, nil ]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @see BaseProvider#api_response_normalize
|
|
72
|
+
# @param api_response [OpenAI::Models::ChatCompletion]
|
|
73
|
+
# @return [Hash] normalized response hash
|
|
74
|
+
def api_response_normalize(api_response)
|
|
75
|
+
return api_response unless api_response
|
|
76
|
+
|
|
77
|
+
Chat::Transforms.gem_to_hash(api_response)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Processes streaming response chunks from OpenAI's chat API
|
|
35
81
|
#
|
|
36
82
|
# Handles message deltas, content updates, and completion detection.
|
|
37
83
|
# Manages the message stack and broadcasts streaming updates.
|
|
38
84
|
#
|
|
39
|
-
#
|
|
40
|
-
# - `:chunk` -
|
|
41
|
-
# - `:"content.delta"` -
|
|
42
|
-
# - `:"content.done"` -
|
|
43
|
-
# - `:"tool_calls.function.arguments.delta"` -
|
|
44
|
-
# - `:"tool_calls.function.arguments.done"` -
|
|
85
|
+
# Event types handled:
|
|
86
|
+
# - `:chunk` - message content and tool call deltas
|
|
87
|
+
# - `:"content.delta"` - incremental content updates
|
|
88
|
+
# - `:"content.done"` - complete content delivery
|
|
89
|
+
# - `:"tool_calls.function.arguments.delta"` - tool argument deltas
|
|
90
|
+
# - `:"tool_calls.function.arguments.done"` - complete tool arguments
|
|
45
91
|
#
|
|
46
92
|
# @param api_response_event [OpenAI::Helpers::Streaming::ChatChunkEvent]
|
|
47
93
|
# @return [void]
|
|
48
94
|
# @see Base#process_stream_chunk
|
|
49
95
|
def process_stream_chunk(api_response_event)
|
|
50
|
-
instrument("
|
|
96
|
+
instrument("stream_chunk.active_agent")
|
|
51
97
|
|
|
52
98
|
# Called Multiple Times: [Chunk<T>, T]<Content, ToolsCall>
|
|
53
99
|
case api_response_event.type
|
|
@@ -62,11 +108,6 @@ module ActiveAgent
|
|
|
62
108
|
if api_message.delta.content
|
|
63
109
|
broadcast_stream_update(message_stack.last, api_message.delta.content)
|
|
64
110
|
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
111
|
when :"content.delta"
|
|
71
112
|
# Returns the deltas, without context
|
|
72
113
|
# => {type: :"content.delta", delta: "", snapshot: "", parsed: nil}
|
|
@@ -86,42 +127,60 @@ module ActiveAgent
|
|
|
86
127
|
end
|
|
87
128
|
end
|
|
88
129
|
|
|
89
|
-
# Processes function/tool calls from the API response
|
|
130
|
+
# Processes function/tool calls from the API response
|
|
90
131
|
#
|
|
91
132
|
# Executes each tool call and creates tool response messages
|
|
92
133
|
# for the next iteration of the conversation.
|
|
93
134
|
#
|
|
94
|
-
# @param api_function_calls [Array<Hash>] function
|
|
135
|
+
# @param api_function_calls [Array<Hash>] function calls with :type, :id, and :function keys
|
|
95
136
|
# @return [void]
|
|
96
137
|
# @see Base#process_function_calls
|
|
97
138
|
def process_function_calls(api_function_calls)
|
|
98
139
|
api_function_calls.each do |api_function_call|
|
|
99
|
-
content =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
140
|
+
content = instrument("tool_call.active_agent", tool_name: api_function_call.dig(:function, :name)) do
|
|
141
|
+
case api_function_call[:type]
|
|
142
|
+
when "function"
|
|
143
|
+
process_tool_call_function(api_function_call[:function])
|
|
144
|
+
else
|
|
145
|
+
fail "Unexpected Tool Call Type: #{api_function_call[:type]}"
|
|
146
|
+
end
|
|
105
147
|
end
|
|
106
148
|
|
|
107
|
-
message
|
|
149
|
+
# Create tool message using gem's message param class
|
|
150
|
+
message = ::OpenAI::Models::Chat::ChatCompletionToolMessageParam.new(
|
|
151
|
+
role: "tool",
|
|
108
152
|
tool_call_id: api_function_call[:id],
|
|
109
153
|
content: content.to_json
|
|
110
154
|
)
|
|
111
155
|
|
|
112
|
-
|
|
156
|
+
# Serialize and push to message stack
|
|
157
|
+
message_hash = Chat::Transforms.gem_to_hash(message)
|
|
158
|
+
message_stack.push(message_hash)
|
|
113
159
|
end
|
|
114
160
|
end
|
|
115
161
|
|
|
116
162
|
# Extracts messages from the completed API response.
|
|
163
|
+
# Converts OpenAI gem response object to hash for storage.
|
|
164
|
+
#
|
|
165
|
+
# @param api_response [OpenAI::Models::Chat::ChatCompletion]
|
|
166
|
+
# @return [Common::PromptResponse, nil]
|
|
167
|
+
def process_prompt_finished(api_response = nil)
|
|
168
|
+
# Convert gem object to hash so that raw_response["usage"] works
|
|
169
|
+
api_response_hash = api_response ? Chat::Transforms.gem_to_hash(api_response) : nil
|
|
170
|
+
super(api_response_hash)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Extracts messages from completed API response.
|
|
117
174
|
#
|
|
118
|
-
# @param api_response [Hash]
|
|
175
|
+
# @param api_response [Hash] converted response hash
|
|
119
176
|
# @return [Array<Hash>, nil] single-element array with message or nil if no message
|
|
120
177
|
# @see Base#process_prompt_finished_extract_messages
|
|
121
178
|
def process_prompt_finished_extract_messages(api_response)
|
|
122
|
-
|
|
179
|
+
return unless api_response
|
|
180
|
+
|
|
181
|
+
api_message = api_response[:choices][0][:message]
|
|
123
182
|
|
|
124
|
-
[ api_message ]
|
|
183
|
+
[ api_message ]
|
|
125
184
|
end
|
|
126
185
|
|
|
127
186
|
# Extracts function calls from the last message in the stack.
|
|
@@ -132,20 +191,20 @@ module ActiveAgent
|
|
|
132
191
|
message_stack.last[:tool_calls]
|
|
133
192
|
end
|
|
134
193
|
|
|
135
|
-
# Merges streaming delta into a message
|
|
194
|
+
# Merges streaming delta into a message
|
|
136
195
|
#
|
|
137
196
|
# Separated from hash_merge_delta to allow Ollama to override role handling.
|
|
138
197
|
#
|
|
139
198
|
# @param message [Hash]
|
|
140
199
|
# @param delta [Hash]
|
|
141
|
-
# @return [Hash]
|
|
200
|
+
# @return [Hash] merged message
|
|
142
201
|
def message_merge_delta(message, delta)
|
|
143
202
|
hash_merge_delta(message, delta)
|
|
144
203
|
end
|
|
145
204
|
|
|
146
205
|
private
|
|
147
206
|
|
|
148
|
-
# Finds an existing message by
|
|
207
|
+
# Finds an existing message by index or creates a new one
|
|
149
208
|
#
|
|
150
209
|
# @param id [Integer]
|
|
151
210
|
# @return [Hash] found or newly created message
|
|
@@ -157,14 +216,14 @@ module ActiveAgent
|
|
|
157
216
|
message_stack.last
|
|
158
217
|
end
|
|
159
218
|
|
|
160
|
-
# Recursively merges delta changes into a hash structure
|
|
219
|
+
# Recursively merges delta changes into a hash structure
|
|
161
220
|
#
|
|
162
|
-
# Handles
|
|
163
|
-
#
|
|
221
|
+
# Handles complex delta merging for OpenAI's streaming API, including
|
|
222
|
+
# arrays with indexed items and string concatenation.
|
|
164
223
|
#
|
|
165
224
|
# @param hash [Hash] target hash to merge into
|
|
166
225
|
# @param delta [Hash] delta changes to apply
|
|
167
|
-
# @return [Hash]
|
|
226
|
+
# @return [Hash] merged hash
|
|
168
227
|
def hash_merge_delta(hash, delta)
|
|
169
228
|
delta.each do |key, value|
|
|
170
229
|
case hash[key]
|