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,263 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAgent
|
|
4
|
+
module Providers
|
|
5
|
+
# Builds instrumentation event payloads for ActiveSupport::Notifications.
|
|
6
|
+
#
|
|
7
|
+
# Extracts request parameters and response metadata for monitoring, debugging,
|
|
8
|
+
# and APM integration (New Relic, DataDog, etc.).
|
|
9
|
+
#
|
|
10
|
+
# == Event Payloads
|
|
11
|
+
#
|
|
12
|
+
# Top-Level Events (overall request lifecycle):
|
|
13
|
+
#
|
|
14
|
+
# prompt.active_agent::
|
|
15
|
+
# Initial: `{ model:, temperature:, max_tokens:, message_count:, has_tools:, stream: }`
|
|
16
|
+
# Final: `{ usage: { input_tokens:, output_tokens:, total_tokens: }, finish_reason:, response_model:, response_id: }`
|
|
17
|
+
# Note: Usage is cumulative across all API calls in multi-turn conversations
|
|
18
|
+
#
|
|
19
|
+
# embed.active_agent::
|
|
20
|
+
# Initial: `{ model:, input_size:, encoding_format:, dimensions: }`
|
|
21
|
+
# Final: `{ usage: { input_tokens:, total_tokens: }, embedding_count:, response_model:, response_id: }`
|
|
22
|
+
#
|
|
23
|
+
# Provider-Level Events (per API call):
|
|
24
|
+
#
|
|
25
|
+
# prompt.provider.active_agent::
|
|
26
|
+
# Initial: `{ model:, temperature:, max_tokens:, message_count:, has_tools:, stream: }`
|
|
27
|
+
# Final: `{ usage: { input_tokens:, output_tokens:, total_tokens: }, finish_reason:, response_model:, response_id: }`
|
|
28
|
+
# Note: Usage is per individual API call
|
|
29
|
+
#
|
|
30
|
+
# embed.provider.active_agent::
|
|
31
|
+
# Initial: `{ model:, input_size:, encoding_format:, dimensions: }`
|
|
32
|
+
# Final: `{ usage: { input_tokens:, total_tokens: }, embedding_count:, response_model:, response_id: }`
|
|
33
|
+
module Instrumentation
|
|
34
|
+
extend ActiveSupport::Concern
|
|
35
|
+
|
|
36
|
+
# Builds and merges payload data for prompt instrumentation events.
|
|
37
|
+
#
|
|
38
|
+
# Populates both request parameters and response metadata for top-level and
|
|
39
|
+
# provider-level events. Usage data (tokens) is CRITICAL for APM cost tracking
|
|
40
|
+
# and performance monitoring.
|
|
41
|
+
#
|
|
42
|
+
# @param payload [Hash] instrumentation payload to merge into
|
|
43
|
+
# @param request [Request] request object with parameters
|
|
44
|
+
# @param response [Common::PromptResponse] completed response with normalized data
|
|
45
|
+
# @return [void]
|
|
46
|
+
def instrumentation_prompt_payload(payload, request, response)
|
|
47
|
+
# message_count: prefer the request/input messages (pre-call), fall back to
|
|
48
|
+
# response messages only if the request doesn't expose messages. New Relic
|
|
49
|
+
# expects parameters[:messages] to be the request messages and computes
|
|
50
|
+
# total message counts by adding response choices to that count.
|
|
51
|
+
message_count = safe_access(request, :messages)&.size
|
|
52
|
+
message_count = safe_access(response, :messages)&.size if message_count.nil?
|
|
53
|
+
|
|
54
|
+
payload.merge!(trace_id: trace_id, message_count: message_count || 0, stream: !!safe_access(request, :stream))
|
|
55
|
+
|
|
56
|
+
# Common parameters: prefer response-normalized values, then request
|
|
57
|
+
payload[:model] = safe_access(response, :model) || safe_access(request, :model)
|
|
58
|
+
payload[:temperature] = safe_access(request, :temperature)
|
|
59
|
+
payload[:max_tokens] = safe_access(request, :max_tokens)
|
|
60
|
+
payload[:top_p] = safe_access(request, :top_p)
|
|
61
|
+
|
|
62
|
+
# Tools / instructions
|
|
63
|
+
if (tools_val = safe_access(request, :tools))
|
|
64
|
+
payload[:has_tools] = tools_val.respond_to?(:present?) ? tools_val.present? : !!tools_val
|
|
65
|
+
payload[:tool_count] = tools_val&.size || 0
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if (instr_val = safe_access(request, :instructions))
|
|
69
|
+
payload[:has_instructions] = instr_val.respond_to?(:present?) ? instr_val.present? : !!instr_val
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Usage (normalized)
|
|
73
|
+
if response.usage
|
|
74
|
+
usage = response.usage
|
|
75
|
+
payload[:usage] = {
|
|
76
|
+
input_tokens: usage.input_tokens,
|
|
77
|
+
output_tokens: usage.output_tokens,
|
|
78
|
+
total_tokens: usage.total_tokens
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
payload[:usage][:cached_tokens] = usage.cached_tokens if usage.cached_tokens
|
|
82
|
+
payload[:usage][:cache_creation_tokens] = usage.cache_creation_tokens if usage.cache_creation_tokens
|
|
83
|
+
payload[:usage][:reasoning_tokens] = usage.reasoning_tokens if usage.reasoning_tokens
|
|
84
|
+
payload[:usage][:audio_tokens] = usage.audio_tokens if usage.audio_tokens
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Response metadata
|
|
88
|
+
payload[:finish_reason] = safe_access(response, :finish_reason) || response.finish_reason
|
|
89
|
+
payload[:response_model] = safe_access(response, :model) || response.model
|
|
90
|
+
payload[:response_id] = safe_access(response, :id) || response.id
|
|
91
|
+
|
|
92
|
+
# Build messages list: prefer request messages; if unavailable use prior
|
|
93
|
+
# response messages (all but the final generated message).
|
|
94
|
+
if (req_msgs = safe_access(request, :messages)).is_a?(Array)
|
|
95
|
+
payload[:messages] = req_msgs.map { |m| extract_message_hash(m, false) }
|
|
96
|
+
else
|
|
97
|
+
prior = safe_access(response, :messages)
|
|
98
|
+
prior = prior[0...-1] if prior.is_a?(Array) && prior.size > 1
|
|
99
|
+
if prior.is_a?(Array) && prior.any?
|
|
100
|
+
payload[:messages] = prior.map { |m| extract_message_hash(m, false) }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Build a parameters hash that mirrors what New Relic's OpenAI
|
|
105
|
+
# instrumentation expects. This makes it easy for APM adapters to
|
|
106
|
+
# map our provider payload to their LLM event constructors.
|
|
107
|
+
parameters = {}
|
|
108
|
+
parameters[:model] = payload[:model] if payload[:model]
|
|
109
|
+
parameters[:max_tokens] = payload[:max_tokens] if payload[:max_tokens]
|
|
110
|
+
parameters[:temperature] = payload[:temperature] if payload[:temperature]
|
|
111
|
+
parameters[:top_p] = payload[:top_p] if payload[:top_p]
|
|
112
|
+
parameters[:stream] = payload[:stream]
|
|
113
|
+
parameters[:messages] = payload[:messages] if payload[:messages]
|
|
114
|
+
|
|
115
|
+
# Include tools/instructions where available — New Relic ignores unknown keys,
|
|
116
|
+
# but having them here makes the parameter shape closer to OpenAI's.
|
|
117
|
+
parameters[:tools] = begin request.tools rescue nil end if begin request.tools rescue nil end
|
|
118
|
+
parameters[:instructions] = begin request.instructions rescue nil end if begin request.instructions rescue nil end
|
|
119
|
+
|
|
120
|
+
payload[:parameters] = parameters
|
|
121
|
+
|
|
122
|
+
# Attach raw response (provider-specific) so downstream APM integrations
|
|
123
|
+
# can inspect the provider response if needed. Use the normalized raw_response
|
|
124
|
+
# available on the Common::Response when possible.
|
|
125
|
+
begin
|
|
126
|
+
payload[:response_raw] = response.raw_response if response.respond_to?(:raw_response) && response.raw_response
|
|
127
|
+
rescue StandardError
|
|
128
|
+
# ignore
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
# Safely attempt to call a method or lookup a key on an object. We avoid
|
|
135
|
+
# probing with `respond_to?` to prevent ActiveModel attribute casting side
|
|
136
|
+
# effects; instead we attempt the call and rescue failures.
|
|
137
|
+
def safe_access(obj, name)
|
|
138
|
+
return nil if obj.nil?
|
|
139
|
+
|
|
140
|
+
begin
|
|
141
|
+
return obj.public_send(name)
|
|
142
|
+
rescue StandardError
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
begin
|
|
146
|
+
return obj[name]
|
|
147
|
+
rescue StandardError
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
begin
|
|
151
|
+
return obj[name.to_s]
|
|
152
|
+
rescue StandardError
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
nil
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# NOTE: message access is handled via `safe_access(obj, :messages)` to
|
|
159
|
+
# avoid duplicating guarded lookup logic.
|
|
160
|
+
|
|
161
|
+
# Extract a simple hash from a provider message object or hash-like value.
|
|
162
|
+
def extract_message_hash(msg, is_response = false)
|
|
163
|
+
role = begin
|
|
164
|
+
if msg.respond_to?(:[])
|
|
165
|
+
begin msg[:role] rescue (begin msg["role"] rescue nil end) end
|
|
166
|
+
elsif msg.respond_to?(:role)
|
|
167
|
+
msg.role
|
|
168
|
+
elsif msg.respond_to?(:type)
|
|
169
|
+
msg.type
|
|
170
|
+
end
|
|
171
|
+
rescue StandardError
|
|
172
|
+
begin msg.role rescue msg.type rescue nil end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
content = begin
|
|
176
|
+
if msg.respond_to?(:[])
|
|
177
|
+
begin msg[:content] rescue (begin msg["content"] rescue nil end) end
|
|
178
|
+
elsif msg.respond_to?(:content)
|
|
179
|
+
msg.content
|
|
180
|
+
elsif msg.respond_to?(:text)
|
|
181
|
+
msg.text
|
|
182
|
+
elsif msg.respond_to?(:to_h)
|
|
183
|
+
begin msg.to_h[:content] rescue (begin msg.to_h["content"] rescue nil end) end
|
|
184
|
+
elsif msg.respond_to?(:to_s)
|
|
185
|
+
msg.to_s
|
|
186
|
+
end
|
|
187
|
+
rescue StandardError
|
|
188
|
+
begin msg.to_s rescue nil end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
{ role: role, content: content, is_response: is_response }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Builds and merges payload data for embed instrumentation events.
|
|
195
|
+
#
|
|
196
|
+
# Embeddings typically only report input tokens (no output tokens).
|
|
197
|
+
#
|
|
198
|
+
# @param payload [Hash] instrumentation payload to merge into
|
|
199
|
+
# @param request [Request] request object with parameters
|
|
200
|
+
# @param response [Common::EmbedResponse] completed response with normalized data
|
|
201
|
+
# @return [void]
|
|
202
|
+
def instrumentation_embed_payload(payload, request, response)
|
|
203
|
+
# Add request parameters
|
|
204
|
+
payload[:trace_id] = trace_id
|
|
205
|
+
payload[:model] = request.model if request.respond_to?(:model)
|
|
206
|
+
|
|
207
|
+
# Add input size if available
|
|
208
|
+
if request.respond_to?(:input)
|
|
209
|
+
begin
|
|
210
|
+
input = request.input
|
|
211
|
+
if input.is_a?(String)
|
|
212
|
+
payload[:input_size] = 1
|
|
213
|
+
elsif input.is_a?(Array)
|
|
214
|
+
payload[:input_size] = input.size
|
|
215
|
+
end
|
|
216
|
+
rescue # OpenAI throws errors this for some reason when you try to look at the input.
|
|
217
|
+
payload[:input_size] = request[:input].size
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Expose embedding input content similarly to message content.
|
|
222
|
+
# Use guarded access to avoid provider-specific errors.
|
|
223
|
+
begin
|
|
224
|
+
if (emb_input = safe_access(request, :input))
|
|
225
|
+
# Keep the raw input (string or array) in the payload so APM adapters
|
|
226
|
+
# can inspect it. This matches how we include message content.
|
|
227
|
+
payload[:input] = emb_input
|
|
228
|
+
end
|
|
229
|
+
rescue StandardError
|
|
230
|
+
# ignore
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Add encoding format if available (OpenAI)
|
|
234
|
+
payload[:encoding_format] = request.encoding_format if request.respond_to?(:encoding_format)
|
|
235
|
+
|
|
236
|
+
# Add dimensions if available (OpenAI)
|
|
237
|
+
payload[:dimensions] = request.dimensions if request.respond_to?(:dimensions)
|
|
238
|
+
|
|
239
|
+
# Add response data
|
|
240
|
+
payload[:embedding_count] = response.data&.size || 0
|
|
241
|
+
|
|
242
|
+
# Add usage data if available (CRITICAL for APM integration)
|
|
243
|
+
# Embeddings typically only have input tokens
|
|
244
|
+
if response.usage
|
|
245
|
+
payload[:usage] = {
|
|
246
|
+
input_tokens: response.usage.input_tokens,
|
|
247
|
+
total_tokens: response.usage.total_tokens
|
|
248
|
+
}
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Add response metadata directly from response object
|
|
252
|
+
payload[:response_model] = response.model
|
|
253
|
+
payload[:response_id] = response.id
|
|
254
|
+
|
|
255
|
+
# Build a parameters hash for embeddings to match New Relic's shape.
|
|
256
|
+
emb_params = {}
|
|
257
|
+
emb_params[:model] = payload[:model] if payload[:model]
|
|
258
|
+
emb_params[:input] = payload[:input] if payload.key?(:input)
|
|
259
|
+
payload[:parameters] = emb_params unless emb_params.empty?
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
@@ -86,7 +86,13 @@ module ActiveAgent
|
|
|
86
86
|
"### Message #{index} (#{role.capitalize})\n#{content}"
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
# Renders
|
|
89
|
+
# Renders tools section for preview.
|
|
90
|
+
#
|
|
91
|
+
# Handles multiple tool formats:
|
|
92
|
+
# - Common format: {name: "...", description: "...", parameters: {...}}
|
|
93
|
+
# - Anthropic format: {name: "...", description: "...", input_schema: {...}}
|
|
94
|
+
# - Chat API format: {type: "function", function: {name: "...", description: "...", parameters: {...}}}
|
|
95
|
+
# - Responses API format: {type: "function", name: "...", description: "...", parameters: {...}}
|
|
90
96
|
#
|
|
91
97
|
# @param tools [Array<Hash>]
|
|
92
98
|
# @return [String]
|
|
@@ -96,17 +102,45 @@ module ActiveAgent
|
|
|
96
102
|
content = +"## Tools\n\n"
|
|
97
103
|
|
|
98
104
|
tools.each_with_index do |tool, index|
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
# Extract name and description from different formats
|
|
106
|
+
tool_name, tool_description, tool_params = extract_tool_details(tool)
|
|
107
|
+
|
|
108
|
+
content << "### #{tool_name || "Tool #{index + 1}"}\n"
|
|
109
|
+
content << "**Description:** #{tool_description || 'No description'}\n\n"
|
|
101
110
|
|
|
102
|
-
if
|
|
103
|
-
content << "**Parameters:**\n```json\n#{JSON.pretty_generate(
|
|
111
|
+
if tool_params
|
|
112
|
+
content << "**Parameters:**\n```json\n#{JSON.pretty_generate(tool_params)}\n```\n\n"
|
|
104
113
|
end
|
|
105
114
|
end
|
|
106
115
|
|
|
107
116
|
content.chomp
|
|
108
117
|
end
|
|
109
118
|
|
|
119
|
+
# Extracts tool details from different formats.
|
|
120
|
+
#
|
|
121
|
+
# @param tool [Hash]
|
|
122
|
+
# @return [Array<String, String, Hash>] [name, description, parameters]
|
|
123
|
+
def extract_tool_details(tool)
|
|
124
|
+
tool_hash = tool.is_a?(Hash) ? tool : {}
|
|
125
|
+
|
|
126
|
+
# Chat API nested format: {type: "function", function: {...}}
|
|
127
|
+
if tool_hash[:type] == "function" && tool_hash[:function]
|
|
128
|
+
func = tool_hash[:function]
|
|
129
|
+
return [
|
|
130
|
+
func[:name],
|
|
131
|
+
func[:description],
|
|
132
|
+
func[:parameters] || func[:input_schema]
|
|
133
|
+
]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Flat formats (common, Anthropic, Responses)
|
|
137
|
+
[
|
|
138
|
+
tool_hash[:name],
|
|
139
|
+
tool_hash[:description],
|
|
140
|
+
tool_hash[:parameters] || tool_hash[:input_schema]
|
|
141
|
+
]
|
|
142
|
+
end
|
|
143
|
+
|
|
110
144
|
# Extracts text content from various message formats.
|
|
111
145
|
#
|
|
112
146
|
# Handles string messages, hash messages with :content key, and
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAgent
|
|
4
|
+
module Providers
|
|
5
|
+
# Provides unified logic for clearing tool_choice after tool execution.
|
|
6
|
+
#
|
|
7
|
+
# When a tool_choice is set to "required" or to a specific tool name,
|
|
8
|
+
# it forces the model to use that tool. After the tool is executed,
|
|
9
|
+
# we need to clear the tool_choice to prevent infinite loops where
|
|
10
|
+
# the model keeps calling the same tool repeatedly.
|
|
11
|
+
#
|
|
12
|
+
# Each provider implements:
|
|
13
|
+
# - `extract_used_function_names`: Returns array of tool names that have been called
|
|
14
|
+
# - `tool_choice_forces_required?`: Returns true if tool_choice forces any tool use
|
|
15
|
+
# - `tool_choice_forces_specific?`: Returns [true, name] if tool_choice forces specific tool
|
|
16
|
+
module ToolChoiceClearing
|
|
17
|
+
extend ActiveSupport::Concern
|
|
18
|
+
|
|
19
|
+
# @api private
|
|
20
|
+
def prepare_prompt_request_tools
|
|
21
|
+
return unless request.tool_choice
|
|
22
|
+
|
|
23
|
+
functions_used = extract_used_function_names
|
|
24
|
+
|
|
25
|
+
# Clear if forcing required and any tool was used
|
|
26
|
+
if tool_choice_forces_required? && functions_used.any?
|
|
27
|
+
request.tool_choice = nil
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Clear if forcing specific tool and that tool was used
|
|
32
|
+
forces_specific, tool_name = tool_choice_forces_specific?
|
|
33
|
+
if forces_specific && tool_name && functions_used.include?(tool_name)
|
|
34
|
+
request.tool_choice = nil
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Extracts the list of function names that have been called.
|
|
41
|
+
#
|
|
42
|
+
# @return [Array<String>] function names
|
|
43
|
+
def extract_used_function_names
|
|
44
|
+
raise NotImplementedError, "#{self.class} must implement #extract_used_function_names"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns true if tool_choice forces any tool to be used (e.g., "required", "any").
|
|
48
|
+
#
|
|
49
|
+
# @return [Boolean]
|
|
50
|
+
def tool_choice_forces_required?
|
|
51
|
+
raise NotImplementedError, "#{self.class} must implement #tool_choice_forces_required?"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Returns [true, tool_name] if tool_choice forces a specific tool, [false, nil] otherwise.
|
|
55
|
+
#
|
|
56
|
+
# @return [Array<Boolean, String|nil>]
|
|
57
|
+
def tool_choice_forces_specific?
|
|
58
|
+
raise NotImplementedError, "#{self.class} must implement #tool_choice_forces_specific?"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|