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.
Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -1
  3. data/lib/active_agent/providers/_base_provider.rb +92 -82
  4. data/lib/active_agent/providers/anthropic/_types.rb +2 -2
  5. data/lib/active_agent/providers/anthropic/request.rb +135 -81
  6. data/lib/active_agent/providers/anthropic/transforms.rb +353 -0
  7. data/lib/active_agent/providers/anthropic_provider.rb +96 -53
  8. data/lib/active_agent/providers/common/messages/_types.rb +37 -1
  9. data/lib/active_agent/providers/common/responses/base.rb +118 -70
  10. data/lib/active_agent/providers/common/usage.rb +385 -0
  11. data/lib/active_agent/providers/concerns/instrumentation.rb +263 -0
  12. data/lib/active_agent/providers/log_subscriber.rb +64 -246
  13. data/lib/active_agent/providers/mock_provider.rb +23 -23
  14. data/lib/active_agent/providers/ollama/chat/request.rb +214 -35
  15. data/lib/active_agent/providers/ollama/chat/transforms.rb +135 -0
  16. data/lib/active_agent/providers/ollama/embedding/request.rb +160 -47
  17. data/lib/active_agent/providers/ollama/embedding/transforms.rb +160 -0
  18. data/lib/active_agent/providers/ollama_provider.rb +0 -1
  19. data/lib/active_agent/providers/open_ai/_base.rb +3 -2
  20. data/lib/active_agent/providers/open_ai/chat/_types.rb +13 -1
  21. data/lib/active_agent/providers/open_ai/chat/request.rb +132 -186
  22. data/lib/active_agent/providers/open_ai/chat/transforms.rb +364 -0
  23. data/lib/active_agent/providers/open_ai/chat_provider.rb +57 -36
  24. data/lib/active_agent/providers/open_ai/embedding/_types.rb +13 -2
  25. data/lib/active_agent/providers/open_ai/embedding/request.rb +38 -70
  26. data/lib/active_agent/providers/open_ai/embedding/transforms.rb +88 -0
  27. data/lib/active_agent/providers/open_ai/responses/_types.rb +1 -7
  28. data/lib/active_agent/providers/open_ai/responses/request.rb +100 -134
  29. data/lib/active_agent/providers/open_ai/responses/transforms.rb +228 -0
  30. data/lib/active_agent/providers/open_ai/responses_provider.rb +77 -30
  31. data/lib/active_agent/providers/open_ai_provider.rb +0 -3
  32. data/lib/active_agent/providers/open_router/_types.rb +27 -1
  33. data/lib/active_agent/providers/open_router/options.rb +49 -1
  34. data/lib/active_agent/providers/open_router/request.rb +232 -66
  35. data/lib/active_agent/providers/open_router/requests/_types.rb +0 -1
  36. data/lib/active_agent/providers/open_router/requests/messages/_types.rb +37 -40
  37. data/lib/active_agent/providers/open_router/requests/messages/content/file.rb +19 -3
  38. data/lib/active_agent/providers/open_router/requests/messages/content/files/details.rb +15 -4
  39. data/lib/active_agent/providers/open_router/requests/plugin.rb +19 -3
  40. data/lib/active_agent/providers/open_router/requests/plugins/pdf_config.rb +30 -8
  41. data/lib/active_agent/providers/open_router/requests/prediction.rb +17 -0
  42. data/lib/active_agent/providers/open_router/requests/provider_preferences/max_price.rb +41 -7
  43. data/lib/active_agent/providers/open_router/requests/provider_preferences.rb +60 -19
  44. data/lib/active_agent/providers/open_router/requests/response_format.rb +30 -2
  45. data/lib/active_agent/providers/open_router/transforms.rb +134 -0
  46. data/lib/active_agent/providers/open_router_provider.rb +9 -0
  47. data/lib/active_agent/version.rb +1 -1
  48. metadata +15 -159
  49. data/lib/active_agent/generation_provider/open_router/types.rb +0 -505
  50. data/lib/active_agent/generation_provider/xai_provider.rb +0 -144
  51. data/lib/active_agent/providers/anthropic/requests/_types.rb +0 -190
  52. data/lib/active_agent/providers/anthropic/requests/container_params.rb +0 -19
  53. data/lib/active_agent/providers/anthropic/requests/content/base.rb +0 -21
  54. data/lib/active_agent/providers/anthropic/requests/content/sources/base.rb +0 -22
  55. data/lib/active_agent/providers/anthropic/requests/context_management_config.rb +0 -18
  56. data/lib/active_agent/providers/anthropic/requests/messages/_types.rb +0 -189
  57. data/lib/active_agent/providers/anthropic/requests/messages/assistant.rb +0 -23
  58. data/lib/active_agent/providers/anthropic/requests/messages/base.rb +0 -63
  59. data/lib/active_agent/providers/anthropic/requests/messages/content/_types.rb +0 -143
  60. data/lib/active_agent/providers/anthropic/requests/messages/content/base.rb +0 -21
  61. data/lib/active_agent/providers/anthropic/requests/messages/content/document.rb +0 -26
  62. data/lib/active_agent/providers/anthropic/requests/messages/content/image.rb +0 -23
  63. data/lib/active_agent/providers/anthropic/requests/messages/content/redacted_thinking.rb +0 -21
  64. data/lib/active_agent/providers/anthropic/requests/messages/content/search_result.rb +0 -27
  65. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/_types.rb +0 -171
  66. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/base.rb +0 -22
  67. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_base64.rb +0 -25
  68. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_file.rb +0 -23
  69. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_text.rb +0 -25
  70. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_url.rb +0 -23
  71. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_base64.rb +0 -27
  72. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_file.rb +0 -23
  73. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_url.rb +0 -23
  74. data/lib/active_agent/providers/anthropic/requests/messages/content/text.rb +0 -22
  75. data/lib/active_agent/providers/anthropic/requests/messages/content/thinking.rb +0 -23
  76. data/lib/active_agent/providers/anthropic/requests/messages/content/tool_result.rb +0 -24
  77. data/lib/active_agent/providers/anthropic/requests/messages/content/tool_use.rb +0 -28
  78. data/lib/active_agent/providers/anthropic/requests/messages/user.rb +0 -21
  79. data/lib/active_agent/providers/anthropic/requests/metadata.rb +0 -18
  80. data/lib/active_agent/providers/anthropic/requests/response_format.rb +0 -22
  81. data/lib/active_agent/providers/anthropic/requests/thinking_config/_types.rb +0 -60
  82. data/lib/active_agent/providers/anthropic/requests/thinking_config/base.rb +0 -20
  83. data/lib/active_agent/providers/anthropic/requests/thinking_config/disabled.rb +0 -16
  84. data/lib/active_agent/providers/anthropic/requests/thinking_config/enabled.rb +0 -20
  85. data/lib/active_agent/providers/anthropic/requests/tool_choice/_types.rb +0 -78
  86. data/lib/active_agent/providers/anthropic/requests/tool_choice/any.rb +0 -17
  87. data/lib/active_agent/providers/anthropic/requests/tool_choice/auto.rb +0 -17
  88. data/lib/active_agent/providers/anthropic/requests/tool_choice/base.rb +0 -20
  89. data/lib/active_agent/providers/anthropic/requests/tool_choice/none.rb +0 -16
  90. data/lib/active_agent/providers/anthropic/requests/tool_choice/tool.rb +0 -20
  91. data/lib/active_agent/providers/ollama/chat/requests/_types.rb +0 -3
  92. data/lib/active_agent/providers/ollama/chat/requests/messages/_types.rb +0 -116
  93. data/lib/active_agent/providers/ollama/chat/requests/messages/assistant.rb +0 -19
  94. data/lib/active_agent/providers/ollama/chat/requests/messages/user.rb +0 -19
  95. data/lib/active_agent/providers/ollama/embedding/requests/_types.rb +0 -83
  96. data/lib/active_agent/providers/ollama/embedding/requests/options.rb +0 -104
  97. data/lib/active_agent/providers/open_ai/chat/requests/_types.rb +0 -229
  98. data/lib/active_agent/providers/open_ai/chat/requests/audio.rb +0 -24
  99. data/lib/active_agent/providers/open_ai/chat/requests/messages/_types.rb +0 -123
  100. data/lib/active_agent/providers/open_ai/chat/requests/messages/assistant.rb +0 -42
  101. data/lib/active_agent/providers/open_ai/chat/requests/messages/base.rb +0 -78
  102. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/_types.rb +0 -133
  103. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/audio.rb +0 -35
  104. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/base.rb +0 -24
  105. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/file.rb +0 -26
  106. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/files/_types.rb +0 -60
  107. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/files/details.rb +0 -41
  108. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/image.rb +0 -37
  109. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/refusal.rb +0 -25
  110. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/text.rb +0 -25
  111. data/lib/active_agent/providers/open_ai/chat/requests/messages/developer.rb +0 -25
  112. data/lib/active_agent/providers/open_ai/chat/requests/messages/function.rb +0 -25
  113. data/lib/active_agent/providers/open_ai/chat/requests/messages/system.rb +0 -25
  114. data/lib/active_agent/providers/open_ai/chat/requests/messages/tool.rb +0 -26
  115. data/lib/active_agent/providers/open_ai/chat/requests/messages/user.rb +0 -32
  116. data/lib/active_agent/providers/open_ai/chat/requests/prediction.rb +0 -46
  117. data/lib/active_agent/providers/open_ai/chat/requests/response_format.rb +0 -53
  118. data/lib/active_agent/providers/open_ai/chat/requests/stream_options.rb +0 -24
  119. data/lib/active_agent/providers/open_ai/chat/requests/tool_choice.rb +0 -26
  120. data/lib/active_agent/providers/open_ai/chat/requests/tools/_types.rb +0 -5
  121. data/lib/active_agent/providers/open_ai/chat/requests/tools/base.rb +0 -22
  122. data/lib/active_agent/providers/open_ai/chat/requests/tools/custom_tool.rb +0 -41
  123. data/lib/active_agent/providers/open_ai/chat/requests/tools/function_tool.rb +0 -51
  124. data/lib/active_agent/providers/open_ai/chat/requests/web_search_options.rb +0 -45
  125. data/lib/active_agent/providers/open_ai/embedding/requests/_types.rb +0 -49
  126. data/lib/active_agent/providers/open_ai/responses/requests/_types.rb +0 -231
  127. data/lib/active_agent/providers/open_ai/responses/requests/conversation.rb +0 -23
  128. data/lib/active_agent/providers/open_ai/responses/requests/inputs/_types.rb +0 -264
  129. data/lib/active_agent/providers/open_ai/responses/requests/inputs/assistant_message.rb +0 -22
  130. data/lib/active_agent/providers/open_ai/responses/requests/inputs/base.rb +0 -89
  131. data/lib/active_agent/providers/open_ai/responses/requests/inputs/code_interpreter_tool_call.rb +0 -30
  132. data/lib/active_agent/providers/open_ai/responses/requests/inputs/computer_tool_call.rb +0 -28
  133. data/lib/active_agent/providers/open_ai/responses/requests/inputs/computer_tool_call_output.rb +0 -33
  134. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/_types.rb +0 -207
  135. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/base.rb +0 -22
  136. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_audio.rb +0 -26
  137. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_file.rb +0 -28
  138. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_image.rb +0 -28
  139. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_text.rb +0 -25
  140. data/lib/active_agent/providers/open_ai/responses/requests/inputs/custom_tool_call.rb +0 -28
  141. data/lib/active_agent/providers/open_ai/responses/requests/inputs/custom_tool_call_output.rb +0 -27
  142. data/lib/active_agent/providers/open_ai/responses/requests/inputs/developer_message.rb +0 -20
  143. data/lib/active_agent/providers/open_ai/responses/requests/inputs/file_search_tool_call.rb +0 -25
  144. data/lib/active_agent/providers/open_ai/responses/requests/inputs/function_call_output.rb +0 -32
  145. data/lib/active_agent/providers/open_ai/responses/requests/inputs/function_tool_call.rb +0 -28
  146. data/lib/active_agent/providers/open_ai/responses/requests/inputs/image_gen_tool_call.rb +0 -27
  147. data/lib/active_agent/providers/open_ai/responses/requests/inputs/input_message.rb +0 -31
  148. data/lib/active_agent/providers/open_ai/responses/requests/inputs/item_reference.rb +0 -23
  149. data/lib/active_agent/providers/open_ai/responses/requests/inputs/local_shell_tool_call.rb +0 -26
  150. data/lib/active_agent/providers/open_ai/responses/requests/inputs/local_shell_tool_call_output.rb +0 -33
  151. data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_approval_request.rb +0 -30
  152. data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_approval_response.rb +0 -28
  153. data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_list_tools.rb +0 -29
  154. data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_tool_call.rb +0 -35
  155. data/lib/active_agent/providers/open_ai/responses/requests/inputs/output_message.rb +0 -35
  156. data/lib/active_agent/providers/open_ai/responses/requests/inputs/reasoning.rb +0 -33
  157. data/lib/active_agent/providers/open_ai/responses/requests/inputs/system_message.rb +0 -20
  158. data/lib/active_agent/providers/open_ai/responses/requests/inputs/tool_call_base.rb +0 -27
  159. data/lib/active_agent/providers/open_ai/responses/requests/inputs/tool_message.rb +0 -23
  160. data/lib/active_agent/providers/open_ai/responses/requests/inputs/user_message.rb +0 -20
  161. data/lib/active_agent/providers/open_ai/responses/requests/inputs/web_search_tool_call.rb +0 -24
  162. data/lib/active_agent/providers/open_ai/responses/requests/prompt_reference.rb +0 -23
  163. data/lib/active_agent/providers/open_ai/responses/requests/reasoning.rb +0 -23
  164. data/lib/active_agent/providers/open_ai/responses/requests/stream_options.rb +0 -20
  165. data/lib/active_agent/providers/open_ai/responses/requests/text/_types.rb +0 -89
  166. data/lib/active_agent/providers/open_ai/responses/requests/text/base.rb +0 -22
  167. data/lib/active_agent/providers/open_ai/responses/requests/text/json_object.rb +0 -20
  168. data/lib/active_agent/providers/open_ai/responses/requests/text/json_schema.rb +0 -48
  169. data/lib/active_agent/providers/open_ai/responses/requests/text/plain.rb +0 -20
  170. data/lib/active_agent/providers/open_ai/responses/requests/text.rb +0 -41
  171. data/lib/active_agent/providers/open_ai/responses/requests/tool_choice.rb +0 -26
  172. data/lib/active_agent/providers/open_ai/responses/requests/tools/_types.rb +0 -112
  173. data/lib/active_agent/providers/open_ai/responses/requests/tools/base.rb +0 -25
  174. data/lib/active_agent/providers/open_ai/responses/requests/tools/code_interpreter_tool.rb +0 -23
  175. data/lib/active_agent/providers/open_ai/responses/requests/tools/computer_tool.rb +0 -27
  176. data/lib/active_agent/providers/open_ai/responses/requests/tools/custom_tool.rb +0 -28
  177. data/lib/active_agent/providers/open_ai/responses/requests/tools/file_search_tool.rb +0 -27
  178. data/lib/active_agent/providers/open_ai/responses/requests/tools/function_tool.rb +0 -29
  179. data/lib/active_agent/providers/open_ai/responses/requests/tools/image_generation_tool.rb +0 -37
  180. data/lib/active_agent/providers/open_ai/responses/requests/tools/local_shell_tool.rb +0 -21
  181. data/lib/active_agent/providers/open_ai/responses/requests/tools/mcp_tool.rb +0 -41
  182. data/lib/active_agent/providers/open_ai/responses/requests/tools/web_search_preview_tool.rb +0 -24
  183. data/lib/active_agent/providers/open_ai/responses/requests/tools/web_search_tool.rb +0 -25
  184. data/lib/active_agent/providers/open_ai/schema.yml +0 -65937
  185. data/lib/active_agent/providers/open_router/requests/message.rb +0 -1
  186. data/lib/active_agent/providers/open_router/requests/messages/assistant.rb +0 -20
  187. data/lib/active_agent/providers/open_router/requests/messages/user.rb +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a2f8f345b5f6a3f9d2717f24542a0dd0a75ae22a7b3601ebe77b9ff8ba3bbc1
4
- data.tar.gz: efe4dc5f9c4d10e4f1454bf274080d42cd88c5af5488195fa98cafd5cbadb812
3
+ metadata.gz: 330c02d2e1ac16e72413f22401dbc6899f652b794d34090b0f499a37b901fbaf
4
+ data.tar.gz: a492f5c88db1f217fe47f9de1477f195c6b150b5dc5ce5791c66fe0f63b0b028
5
5
  SHA512:
6
- metadata.gz: ab6e83518446995be3984dc347524314d831de5650c263e0338f2cff4382cd111196145f2155b206fe651d930054ac3cf166b05cee9aa89fe907fbc4c7d7ed5f
7
- data.tar.gz: bbff91deb2aba1feccb49edbf71664c3569595357fc7d3c1aea9555d95f07c2edb102f24927fe2132eb35ddc5bc2a8b8d0f52eb1b0f40ca9ca308b59263b68a6
6
+ metadata.gz: d8dac6789ba1f3685c24d711aace46ad5b681e9c795a975e61f07ee3c7d9a9bc44c2fd0568fabcabd739df5efd13b222c5fa6e954384bacb8bbcfdaaab0d7182
7
+ data.tar.gz: 8a0f61b2b5a9375c8f1b090bca29af077384079ba008622a1a510d8190acdff034c7f6b8065c1c752a2cee00dabd35d00f017db28954aacc17386fb06be63d50
data/CHANGELOG.md CHANGED
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [1.0.0] - Unreleased
8
+ ## [1.0.0] - 2025-11-21
9
9
 
10
10
  Major refactor with breaking changes. Complete provider rewrite. New modular architecture.
11
11
 
@@ -148,6 +148,35 @@ response = MyAgent.embed(inputs: ["Text 1", "Text 2"]).embed_now
148
148
  vectors = response.data.map { |d| d[:embedding] }
149
149
  ```
150
150
 
151
+ **Normalized Usage Statistics**
152
+ ```ruby
153
+ response = MyAgent.prompt("Hello").generate_now
154
+
155
+ # Works across all providers
156
+ response.usage.input_tokens
157
+ response.usage.output_tokens
158
+ response.usage.total_tokens
159
+
160
+ # Provider-specific fields when available
161
+ response.usage.cached_tokens # OpenAI, Anthropic
162
+ response.usage.reasoning_tokens # OpenAI o1 models
163
+ response.usage.service_tier # Anthropic
164
+ ```
165
+
166
+ **Enhanced Instrumentation for APM Integration**
167
+ - Unified event structure: `prompt.active_agent` and `embed.active_agent` (top-level) plus `prompt.provider.active_agent` and `embed.provider.active_agent` (per-API-call)
168
+ - Event payloads include comprehensive data for monitoring tools (New Relic, DataDog, etc.):
169
+ - Request parameters: `model`, `temperature`, `max_tokens`, `top_p`, `stream`, `message_count`, `has_tools`
170
+ - Usage data: `input_tokens`, `output_tokens`, `total_tokens`, `cached_tokens`, `reasoning_tokens`, `audio_tokens`, `cache_creation_tokens` (critical for cost tracking)
171
+ - Response metadata: `finish_reason`, `response_model`, `response_id`, `embedding_count`
172
+ - Top-level events report cumulative usage across all API calls in multi-turn conversations
173
+ - Provider-level events report per-call usage for granular tracking
174
+
175
+ **Multi-Turn Usage Tracking**
176
+ - `response.usage` now returns cumulative token counts across all API calls during tool calling
177
+ - New `response.usages` array contains individual usage objects from each API call
178
+ - `Usage` objects support addition: `usage1 + usage2` for combining statistics
179
+
151
180
  **Provider Enhancements**
152
181
  - OpenAI Responses API: `api: :responses` or `api: :chat`
153
182
  - Anthropic JSON object mode with automatic extraction
@@ -195,6 +224,7 @@ vectors = response.data.map { |d| d[:embedding] }
195
224
  - Template rendering without blocks
196
225
  - Schema generator key symbolization
197
226
  - Rails 8.0 and 8.1 compatibility
227
+ - Usage extraction across OpenAI/Anthropic response formats
198
228
 
199
229
  ### Removed
200
230
 
@@ -2,21 +2,21 @@ require "active_support/delegation"
2
2
 
3
3
  require_relative "common/response"
4
4
  require_relative "concerns/exception_handler"
5
+ require_relative "concerns/instrumentation"
5
6
  require_relative "concerns/previewable"
6
7
 
7
- # Maps provider types to their gem dependencies.
8
8
  # @private
9
9
  GEM_LOADERS = {
10
10
  anthropic: [ "anthropic", "~> 1.12", "anthropic" ],
11
11
  openai: [ "openai", "~> 0.34", "openai" ]
12
12
  }
13
13
 
14
- # Loads and requires a provider's gem dependency.
14
+ # Requires a provider's gem dependency.
15
15
  #
16
16
  # @param type [Symbol] provider type (:anthropic, :openai)
17
- # @param file_name [String] provider file path for error context
17
+ # @param file_name [String] for error context
18
18
  # @return [void]
19
- # @raise [LoadError] when the required gem is not available
19
+ # @raise [LoadError] when required gem is not installed
20
20
  def require_gem!(type, file_name)
21
21
  gem_name, requirement, package_name = GEM_LOADERS.fetch(type)
22
22
  provider_name = file_name.split("/").last.delete_suffix(".rb").camelize
@@ -31,9 +31,8 @@ end
31
31
 
32
32
  module ActiveAgent
33
33
  module Providers
34
- # Base class for LLM provider integrations.
34
+ # Orchestrates LLM provider API requests, streaming, and multi-turn tool calling.
35
35
  #
36
- # Orchestrates API requests, streaming responses, and multi-turn tool calling.
37
36
  # Each provider (OpenAI, Anthropic, etc.) subclasses this to implement
38
37
  # provider-specific API interactions.
39
38
  #
@@ -44,6 +43,7 @@ module ActiveAgent
44
43
  extend ActiveSupport::Delegation
45
44
 
46
45
  include ExceptionHandler
46
+ include Instrumentation
47
47
  include Previewable
48
48
 
49
49
  class ProvidersError < StandardError; end
@@ -51,34 +51,35 @@ module ActiveAgent
51
51
  attr_internal :options, :context, :trace_id, # Setup
52
52
  :request, :message_stack, # Runtime
53
53
  :stream_broadcaster, :streaming, # Callback (Streams)
54
- :tools_function # Callback (Tools)
54
+ :tools_function, # Callback (Tools)
55
+ :usage_stack # Usage Tracking
55
56
 
56
- # @return [String] provider name extracted from class name (e.g., "Anthropic", "OpenAI")
57
+ # @return [String] e.g., "Anthropic", "OpenAI"
57
58
  def self.service_name
58
59
  name.split("::").last.delete_suffix("Provider")
59
60
  end
60
61
 
61
- # @return [String] module-qualified provider name (e.g., "Anthropic", "OpenAI::Chat")
62
+ # @return [String] e.g., "Anthropic", "OpenAI::Chat"
62
63
  def self.tag_name
63
64
  name.delete_prefix("ActiveAgent::Providers::").delete_suffix("Provider")
64
65
  end
65
66
 
66
- # @return [Module] provider's namespace module (e.g., ActiveAgent::Providers::OpenAI)
67
+ # @return [Module] e.g., ActiveAgent::Providers::OpenAI
67
68
  def self.namespace
68
69
  "#{name.deconstantize}::#{service_name}".safe_constantize
69
70
  end
70
71
 
71
- # @return [Class] provider's options class
72
+ # @return [Class]
72
73
  def self.options_klass
73
74
  namespace::Options
74
75
  end
75
76
 
76
- # @return [ActiveModel::Type::Value] provider-specific request type for prompt casting/serialization
77
+ # @return [ActiveModel::Type::Value] for prompt casting/serialization
77
78
  def self.prompt_request_type
78
79
  namespace::RequestType.new
79
80
  end
80
81
 
81
- # @return [ActiveModel::Type::Value] provider-specific request type for embedding casting/serialization
82
+ # @return [ActiveModel::Type::Value] for embedding casting/serialization
82
83
  # @raise [NotImplementedError] when provider doesn't support embeddings
83
84
  def self.embed_request_type
84
85
  fail(NotImplementedError)
@@ -86,12 +87,10 @@ module ActiveAgent
86
87
 
87
88
  delegate :service_name, :tag_name, :namespace, :options_klass, :prompt_request_type, :embed_request_type, to: :class
88
89
 
89
- # Initializes a provider instance.
90
- #
91
90
  # @param kwargs [Hash] configuration and callbacks
92
91
  # @option kwargs [Symbol] :service validates against provider's service name
93
- # @option kwargs [Proc] :stream_broadcaster callback for streaming events (:open, :update, :close)
94
- # @option kwargs [Proc] :tools_function callback to execute tool/function calls
92
+ # @option kwargs [Proc] :stream_broadcaster for streaming events (:open, :update, :close)
93
+ # @option kwargs [Proc] :tools_function to execute tool/function calls
95
94
  # @raise [RuntimeError] when service name doesn't match provider
96
95
  def initialize(kwargs = {})
97
96
  assert_service!(kwargs.delete(:service))
@@ -107,21 +106,10 @@ module ActiveAgent
107
106
  self.options = options_klass.new(kwargs.extract!(*options_klass.keys))
108
107
  self.context = kwargs
109
108
  self.message_stack = []
109
+ self.usage_stack = []
110
110
  end
111
111
 
112
- # Executes a prompt request with error handling and instrumentation.
113
- #
114
- # @return [ActiveAgent::Providers::Common::PromptResponse]
115
- def prompt
116
- instrument("prompt_start.provider.active_agent") do
117
- self.request = prompt_request_type.cast(context.except(:trace_id))
118
- resolve_prompt
119
- end
120
- end
121
-
122
- # Generates a preview of the prompt without executing the API call.
123
- #
124
- # Casts context into a request object and renders it as markdown for inspection.
112
+ # Generates prompt preview without executing the API call.
125
113
  #
126
114
  # @return [String] markdown-formatted preview
127
115
  def preview
@@ -129,15 +117,31 @@ module ActiveAgent
129
117
  preview_prompt
130
118
  end
131
119
 
132
- # Executes an embedding request with error handling and instrumentation.
120
+ # Executes prompt request with error handling and instrumentation.
133
121
  #
134
- # Converts text into vector representations for semantic search and similarity operations.
122
+ # @return [ActiveAgent::Providers::Common::PromptResponse]
123
+ def prompt
124
+ self.request = prompt_request_type.cast(context.except(:trace_id))
125
+
126
+ instrument("prompt.active_agent") do |payload|
127
+ response = resolve_prompt
128
+ instrumentation_prompt_payload(payload, request, response)
129
+
130
+ response
131
+ end
132
+ end
133
+
134
+ # Executes embedding request with error handling and instrumentation.
135
135
  #
136
136
  # @return [ActiveAgent::Providers::Common::EmbedResponse]
137
137
  def embed
138
- instrument("embed_start.provider.active_agent") do
139
- self.request = embed_request_type.cast(context.except(:trace_id))
140
- resolve_embed
138
+ self.request = embed_request_type.cast(context.except(:trace_id))
139
+
140
+ instrument("embed.active_agent") do |payload|
141
+ response = resolve_embed
142
+ instrumentation_embed_payload(payload, request, response)
143
+
144
+ response
141
145
  end
142
146
  end
143
147
 
@@ -149,8 +153,6 @@ module ActiveAgent
149
153
  fail "Unexpected Service Name: #{name} != #{service_name}" if name && name != service_name
150
154
  end
151
155
 
152
- # Instruments an event for logging and metrics.
153
- #
154
156
  # @param name [String]
155
157
  # @param payload [Hash]
156
158
  # @yield block to instrument
@@ -160,34 +162,42 @@ module ActiveAgent
160
162
  ActiveSupport::Notifications.instrument(name, full_payload, &block)
161
163
  end
162
164
 
163
- # Orchestrates the complete prompt request lifecycle.
165
+ # Orchestrates complete prompt request lifecycle.
164
166
  #
165
- # Prepares request, executes API call, processes response, and handles
166
- # recursive tool/function calling until completion.
167
+ # Handles recursive tool/function calling until completion.
167
168
  #
168
169
  # @return [ActiveAgent::Providers::Common::PromptResponse]
169
170
  def resolve_prompt
170
- request = prepare_prompt_request
171
-
172
- instrument("request_prepared.provider.active_agent", message_count: request.messages.size)
173
-
174
- # @todo Validate Request
175
- api_parameters = api_request_build(request, prompt_request_type)
176
- api_response = instrument("api_call.provider.active_agent", streaming: api_parameters[:stream].present?) do
177
- with_exception_handling { api_prompt_execute(api_parameters) }
171
+ api_parameters = api_request_build(prepare_prompt_request, prompt_request_type)
172
+ api_response = instrument("prompt.provider.active_agent") do |payload|
173
+ raw_response = with_exception_handling { api_prompt_execute(api_parameters) }
174
+
175
+ # Instrumentation Context Building
176
+ # Normalize response for instrumentation (providers may return gem objects)
177
+ normalized_response = api_response_normalize(raw_response)
178
+ common_response = Common::PromptResponse.new(raw_response: normalized_response)
179
+ instrumentation_prompt_payload(payload, self.request, common_response)
180
+ usage_stack.push(common_response.usage) if common_response&.usage
181
+
182
+ raw_response
178
183
  end
179
184
 
180
- process_prompt_finished(api_response.as_json&.deep_symbolize_keys)
185
+ process_prompt_finished(api_response)
181
186
  end
182
187
 
183
- # Orchestrates the complete embedding request lifecycle.
188
+ # Orchestrates complete embedding request lifecycle.
184
189
  #
185
190
  # @return [ActiveAgent::Providers::Common::EmbedResponse]
186
191
  def resolve_embed
187
- # @todo Validate Request
188
- api_parameters = api_request_build(request, embed_request_type)
189
- api_response = instrument("embed_call.provider.active_agent") do
190
- with_exception_handling { api_embed_execute(api_parameters) }
192
+ api_parameters = api_request_build(self.request, embed_request_type)
193
+ api_response = instrument("embed.provider.active_agent") do |payload|
194
+ raw_response = with_exception_handling { api_embed_execute(api_parameters) }
195
+
196
+ # Instrumentation Context Building
197
+ common_response = Common::EmbedResponse.new(raw_response:)
198
+ instrumentation_embed_payload(payload, self.request, common_response)
199
+
200
+ raw_response
191
201
  end
192
202
 
193
203
  process_embed_finished(api_response)
@@ -195,7 +205,7 @@ module ActiveAgent
195
205
 
196
206
  # Prepares request for next iteration in multi-turn conversation.
197
207
  #
198
- # Appends accumulated messages from message stack and resets buffer for next cycle.
208
+ # Appends accumulated messages and resets buffer for next cycle.
199
209
  #
200
210
  # @return [Request]
201
211
  def prepare_prompt_request
@@ -205,11 +215,9 @@ module ActiveAgent
205
215
  self.request
206
216
  end
207
217
 
208
- # Builds API request parameters from request object.
209
- #
210
218
  # @param request [Request]
211
- # @param request_type [ActiveModel::Type::Value] type for serialization
212
- # @return [Hash]
219
+ # @param request_type [ActiveModel::Type::Value] for serialization
220
+ # @return [Hash] API request parameters
213
221
  def api_request_build(request, request_type)
214
222
  parameters = request_type.serialize(request)
215
223
  parameters[:stream] = process_stream if request.try(:stream)
@@ -221,7 +229,7 @@ module ActiveAgent
221
229
  parameters
222
230
  end
223
231
 
224
- # @return [Proc] invoked for each response chunk
232
+ # @return [Proc] for each response chunk
225
233
  def process_stream
226
234
  proc do |api_response_chunk|
227
235
  process_stream_chunk(api_response_chunk)
@@ -231,12 +239,10 @@ module ActiveAgent
231
239
  # Executes prompt request against provider's API.
232
240
  #
233
241
  # @abstract
234
- # @param request_parameters [Hash]
242
+ # @param parameters [Hash]
235
243
  # @return [Object] provider-specific API response
236
244
  # @raise [NotImplementedError]
237
245
  def api_prompt_execute(parameters)
238
- instrument("api_request.provider.active_agent", model: parameters[:model], streaming: !!parameters[:stream])
239
-
240
246
  unless parameters[:stream]
241
247
  api_prompt_executer.create(**parameters)
242
248
  else
@@ -257,6 +263,18 @@ module ActiveAgent
257
263
  fail NotImplementedError, "Subclass expected to implement"
258
264
  end
259
265
 
266
+ # Normalizes API response for instrumentation.
267
+ #
268
+ # Providers that return gem objects (like Anthropic::Models::Message) should
269
+ # override this to convert to a hash so usage data can be extracted.
270
+ # By default, returns the response as-is (for providers returning hashes).
271
+ #
272
+ # @param api_response [Object] provider-specific API response
273
+ # @return [Hash, Object] normalized response (preferably hash)
274
+ def api_response_normalize(api_response)
275
+ api_response
276
+ end
277
+
260
278
  # Executes embedding request against provider's API.
261
279
  #
262
280
  # @abstract
@@ -278,21 +296,21 @@ module ActiveAgent
278
296
 
279
297
  # Broadcasts stream open event.
280
298
  #
281
- # Fires once per request cycle even during multi-turn tool calling.
299
+ # Fires once per request cycle, even during multi-turn tool calling.
282
300
  #
283
301
  # @return [void]
284
302
  def broadcast_stream_open
285
303
  return if streaming
286
304
  self.streaming = true
287
305
 
288
- instrument("stream_open.provider.active_agent")
306
+ instrument("stream_open.active_agent")
289
307
  stream_broadcaster.call(nil, nil, :open)
290
308
  end
291
309
 
292
310
  # Broadcasts stream update with message content delta.
293
311
  #
294
312
  # @param message [Hash, Object]
295
- # @param delta [String, nil] incremental content chunk
313
+ # @param delta [String, nil]
296
314
  # @return [void]
297
315
  def broadcast_stream_update(message, delta = nil)
298
316
  stream_broadcaster.call(message, delta, :update)
@@ -300,35 +318,31 @@ module ActiveAgent
300
318
 
301
319
  # Broadcasts stream close event.
302
320
  #
303
- # Fires once per request cycle even during multi-turn tool calling.
321
+ # Fires once per request cycle, even during multi-turn tool calling.
304
322
  #
305
323
  # @return [void]
306
324
  def broadcast_stream_close
307
325
  return unless streaming
308
326
  self.streaming = false
309
327
 
310
- instrument("stream_close.provider.active_agent")
328
+ instrument("stream_close.active_agent")
311
329
  stream_broadcaster.call(message_stack.last, nil, :close)
312
330
  end
313
331
 
314
332
  # Processes completed API response and handles tool calling recursion.
315
333
  #
316
- # Extracts messages and function calls from the response. If tools were invoked,
317
- # executes them and recursively continues the prompt until completion.
334
+ # Extracts messages and function calls. If tools were invoked,
335
+ # executes them and recursively continues until completion.
318
336
  #
319
337
  # @param api_response [Object, nil] provider-specific response
320
338
  # @return [Common::PromptResponse, nil]
321
339
  def process_prompt_finished(api_response = nil)
322
340
  if (api_messages = process_prompt_finished_extract_messages(api_response))
323
- instrument("messages_extracted.provider.active_agent", message_count: api_messages.size)
324
341
  message_stack.push(*api_messages)
325
342
  end
326
343
 
327
344
  if (tool_calls = process_prompt_finished_extract_function_calls)&.any?
328
- instrument("tool_calls_processing.provider.active_agent", tool_count: tool_calls.size)
329
345
  process_function_calls(tool_calls)
330
-
331
- instrument("multi_turn_continue.provider.active_agent")
332
346
  resolve_prompt
333
347
  else
334
348
 
@@ -337,27 +351,25 @@ module ActiveAgent
337
351
  # as they continue to work.
338
352
  broadcast_stream_close
339
353
 
340
- instrument("prompt_complete.provider.active_agent", message_count: message_stack.size)
341
-
342
354
  # To convert the messages into common format we first need to merge the current
343
355
  # stack and then cast them to the provider type, so we can cast them out to common.
344
356
  messages = prompt_request_type.cast(
345
357
  messages: [ *request.messages, *message_stack ]
346
358
  ).messages
347
359
 
360
+ # Create response object with usage_stack array for multi-turn cumulative tracking.
348
361
  # This will returned as it closes up the recursive stack
349
362
  Common::PromptResponse.new(
350
363
  context:,
364
+ format: request.response_format,
365
+ messages:,
351
366
  raw_request: prompt_request_type.serialize(request),
352
367
  raw_response: api_response,
353
- messages:,
354
- format: request.response_format
368
+ usages: usage_stack
355
369
  )
356
370
  end
357
371
  end
358
372
 
359
- # Extracts messages from API response.
360
- #
361
373
  # @abstract
362
374
  # @param api_response [Object]
363
375
  # @return [Array<Message>, nil]
@@ -366,8 +378,6 @@ module ActiveAgent
366
378
  fail NotImplementedError, "Subclass expected to implement"
367
379
  end
368
380
 
369
- # Extracts tool/function calls from API response.
370
- #
371
381
  # @abstract
372
382
  # @return [Array<Hash>, nil]
373
383
  # @raise [NotImplementedError]
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "requests/_types"
4
-
5
3
  require_relative "options"
6
4
  require_relative "request"
7
5
 
@@ -11,6 +9,8 @@ module ActiveAgent
11
9
  # ActiveModel type for casting and serializing Anthropic Request objects.
12
10
  #
13
11
  # Handles conversion between Hash, Request, and serialized formats for API calls.
12
+ # The Request class now delegates to the official Anthropic gem model, eliminating
13
+ # the need for maintaining nested type definitions.
14
14
  class RequestType < ActiveModel::Type::Value
15
15
  # Casts input to Request object.
16
16
  #