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
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module ActiveAgent
6
+ module Providers
7
+ module Ollama
8
+ # Handles transformation and normalization of embedding request parameters
9
+ # for the Ollama Embeddings API
10
+ module Embedding
11
+ # Provides transformation methods for normalizing embedding parameters
12
+ # to Ollama API format with OpenAI gem compatibility
13
+ module Transforms
14
+ class << self
15
+ # Converts gem objects to hash representation
16
+ #
17
+ # @param obj [Object] gem object or primitive value
18
+ # @return [Hash, Object] hash if object supports JSON serialization
19
+ def gem_to_hash(obj)
20
+ if obj.respond_to?(:to_json)
21
+ JSON.parse(obj.to_json, symbolize_names: true)
22
+ else
23
+ obj
24
+ end
25
+ end
26
+
27
+ # Normalizes all embedding request parameters
28
+ #
29
+ # Ollama-specific parameters (options, keep_alive, truncate) are extracted
30
+ # and returned separately from OpenAI-compatible parameters.
31
+ #
32
+ # @param params [Hash] raw request parameters
33
+ # @return [Array<Hash, Hash>] tuple of [openai_params, ollama_params]
34
+ def normalize_params(params)
35
+ params = params.dup
36
+
37
+ # Extract Ollama-specific parameters
38
+ ollama_params = {}
39
+ ollama_params[:options] = params.delete(:options) if params.key?(:options)
40
+ ollama_params[:keep_alive] = params.delete(:keep_alive) if params.key?(:keep_alive)
41
+ ollama_params[:truncate] = params.delete(:truncate) if params.key?(:truncate)
42
+
43
+ # Extract options attributes that can be at top level
44
+ extract_option_attributes(params, ollama_params)
45
+
46
+ # Normalize input - Ollama only accepts strings, not token arrays
47
+ if params[:input]
48
+ params[:input] = normalize_input(params[:input])
49
+ end
50
+
51
+ [ params, ollama_params ]
52
+ end
53
+
54
+ # Normalizes input parameter to Ollama format
55
+ #
56
+ # Ollama only accepts strings or arrays of strings (no token arrays).
57
+ # Converts single string to array internally for consistency.
58
+ #
59
+ # @param input [String, Array<String>]
60
+ # @return [Array<String>] normalized input as array of strings
61
+ # @raise [ArgumentError] if input contains non-string values
62
+ def normalize_input(input)
63
+ case input
64
+ when String
65
+ [ input.presence ].compact
66
+ when Array
67
+ # Validate all elements are strings
68
+ input.each_with_index do |item, index|
69
+ unless item.is_a?(String)
70
+ raise ArgumentError, "Ollama embedding input must contain only strings, got #{item.class} at index #{index}"
71
+ end
72
+ if item.empty?
73
+ raise ArgumentError, "Ollama embedding input cannot contain empty strings at index #{index}"
74
+ end
75
+ end
76
+ input.compact
77
+ when nil
78
+ nil
79
+ else
80
+ raise ArgumentError, "Cannot normalize #{input.class} to Ollama input (expected String or Array)"
81
+ end
82
+ end
83
+
84
+ # Serializes input for API submission
85
+ #
86
+ # Returns single string if array has only one element, otherwise array.
87
+ #
88
+ # @param input [Array<String>, nil]
89
+ # @return [String, Array<String>, nil]
90
+ def serialize_input(input)
91
+ return nil if input.nil?
92
+
93
+ # Return single string if array has only one element
94
+ if input.is_a?(Array) && input.length == 1
95
+ input.first
96
+ else
97
+ input
98
+ end
99
+ end
100
+
101
+ # Cleans up serialized request for API submission
102
+ #
103
+ # Merges OpenAI-compatible params with Ollama-specific params.
104
+ #
105
+ # @param openai_hash [Hash] serialized OpenAI-compatible request
106
+ # @param ollama_params [Hash] Ollama-specific parameters
107
+ # @param defaults [Hash] default values to remove
108
+ # @return [Hash] cleaned and merged request hash
109
+ def cleanup_serialized_request(openai_hash, ollama_params, defaults)
110
+ # Remove nil values
111
+ cleaned = openai_hash.compact
112
+
113
+ # Serialize input (convert single-element array to string)
114
+ if cleaned[:input]
115
+ cleaned[:input] = serialize_input(cleaned[:input])
116
+ end
117
+
118
+ # Merge Ollama-specific params, skip defaults
119
+ ollama_params.each do |key, value|
120
+ next if value.nil?
121
+ next if value.respond_to?(:empty?) && value.empty?
122
+ next if defaults.key?(key) && defaults[key] == value
123
+
124
+ # Serialize options object if present
125
+ cleaned[key] = if value.respond_to?(:serialize)
126
+ value.serialize
127
+ else
128
+ value
129
+ end
130
+ end
131
+
132
+ cleaned
133
+ end
134
+
135
+ private
136
+
137
+ # Extracts option attributes that can be specified at the top level
138
+ #
139
+ # @param params [Hash] request parameters
140
+ # @param ollama_params [Hash] ollama-specific parameters to populate
141
+ def extract_option_attributes(params, ollama_params)
142
+ option_keys = [
143
+ :mirostat, :mirostat_eta, :mirostat_tau, :num_ctx,
144
+ :repeat_last_n, :repeat_penalty, :temperature, :seed,
145
+ :num_predict, :top_k, :top_p, :min_p, :stop
146
+ ]
147
+
148
+ option_keys.each do |key|
149
+ if params.key?(key)
150
+ ollama_params[:options] ||= {}
151
+ ollama_params[:options][key] = params.delete(key)
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -56,7 +56,6 @@ module ActiveAgent
56
56
  # @return [Hash] symbolized API response
57
57
  # @raise [OpenAI::Errors::APIConnectionError] when Ollama server unreachable
58
58
  def api_embed_execute(parameters)
59
- instrument("embeddings_request.provider.active_agent")
60
59
  client.embeddings.create(**parameters).as_json.deep_symbolize_keys
61
60
 
62
61
  rescue ::OpenAI::Errors::APIConnectionError => exception
@@ -49,8 +49,9 @@ module ActiveAgent
49
49
  name = api_function_call[:name]
50
50
  kwargs = JSON.parse(api_function_call[:arguments], symbolize_names: true) if api_function_call[:arguments]
51
51
 
52
- instrument("tool_execution.provider.active_agent", tool_name: name)
53
- tools_function.call(name, **kwargs)
52
+ instrument("tool_call.active_agent", tool_name: name) do
53
+ tools_function.call(name, **kwargs)
54
+ end
54
55
  end
55
56
  end
56
57
  end
@@ -6,8 +6,13 @@ module ActiveAgent
6
6
  module Providers
7
7
  module OpenAI
8
8
  module Chat
9
- # Type for Request model
9
+ # ActiveModel type for casting and serializing chat requests
10
10
  class RequestType < ActiveModel::Type::Value
11
+ # Casts value to Request object
12
+ #
13
+ # @param value [Request, Hash, nil]
14
+ # @return [Request, nil]
15
+ # @raise [ArgumentError] when value cannot be cast
11
16
  def cast(value)
12
17
  case value
13
18
  when Request
@@ -21,6 +26,11 @@ module ActiveAgent
21
26
  end
22
27
  end
23
28
 
29
+ # Serializes Request to hash for API submission
30
+ #
31
+ # @param value [Request, Hash, nil]
32
+ # @return [Hash, nil]
33
+ # @raise [ArgumentError] when value cannot be serialized
24
34
  def serialize(value)
25
35
  case value
26
36
  when Request
@@ -34,6 +44,8 @@ module ActiveAgent
34
44
  end
35
45
  end
36
46
 
47
+ # @param value [Object]
48
+ # @return [Request, nil]
37
49
  def deserialize(value)
38
50
  cast(value)
39
51
  end
@@ -1,212 +1,158 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_agent/providers/common/model"
4
- require_relative "requests/_types"
3
+ require "delegate"
4
+ require "json"
5
+ require_relative "transforms"
5
6
 
6
7
  module ActiveAgent
7
8
  module Providers
8
9
  module OpenAI
9
10
  module Chat
10
- class Request < Common::BaseModel
11
- # Messages array (required)
12
- attribute :messages, Requests::Messages::MessagesType.new
13
-
14
- # Model ID (required)
15
- attribute :model, :string
16
-
17
- # Audio output parameters
18
- attribute :audio, Requests::AudioType.new
19
-
20
- # Frequency penalty
21
- attribute :frequency_penalty, :float, default: 0
22
-
23
- # Deprecated: function_call (use tool_choice instead)
24
- attribute :function_call # String or object
25
-
26
- # Deprecated: functions (use tools instead)
27
- attribute :functions # Array of function objects
28
-
29
- # Logit bias
30
- attribute :logit_bias # Hash of token_id => bias_value
31
-
32
- # Log probabilities
33
- attribute :logprobs, :boolean, default: false
34
-
35
- # Max completion tokens
36
- attribute :max_completion_tokens, :integer
37
-
38
- # Deprecated: max_tokens (use max_completion_tokens)
39
- attribute :max_tokens, :integer
40
-
41
- # Metadata
42
- attribute :metadata # Hash of key-value pairs
43
-
44
- # Modalities
45
- attribute :modalities, default: -> { [ "text" ] } # Array of strings
46
-
47
- # Number of completions
48
- attribute :n, :integer, default: 1
49
-
50
- # Parallel tool calls
51
- attribute :parallel_tool_calls, :boolean, default: true
52
-
53
- # Prediction configuration
54
- attribute :prediction, Requests::PredictionType.new
55
-
56
- # Presence penalty
57
- attribute :presence_penalty, :float, default: 0
58
-
59
- # Prompt cache key
60
- attribute :prompt_cache_key, :string
61
-
62
- # Reasoning effort (for reasoning models)
63
- attribute :reasoning_effort, :string
64
-
65
- # Response format
66
- attribute :response_format, Requests::ResponseFormatType.new
67
-
68
- # Safety identifier
69
- attribute :safety_identifier, :string
70
-
71
- # Deprecated: seed
72
- attribute :seed, :integer
73
-
74
- # Service tier
75
- attribute :service_tier, :string, default: "auto"
76
-
77
- # Stop sequences
78
- attribute :stop # String, array, or null
79
-
80
- # Storage
81
- attribute :store, :boolean, default: false
82
-
83
- # Streaming
84
- attribute :stream, :boolean, default: false
85
- attribute :stream_options, Requests::StreamOptionsType.new
86
-
87
- # Temperature sampling
88
- attribute :temperature, :float, default: 1
89
-
90
- # Tool choice
91
- attribute :tool_choice, Requests::ToolChoiceType.new
92
-
93
- # Tools array
94
- attribute :tools # Array of tool objects
95
-
96
- # Top logprobs
97
- attribute :top_logprobs, :integer
98
-
99
- # Top P sampling
100
- attribute :top_p, :float, default: 1
101
-
102
- # Deprecated: user (use safety_identifier or prompt_cache_key)
103
- attribute :user, :string
104
-
105
- # Verbosity (for reasoning models)
106
- attribute :verbosity, :string
107
-
108
- # Web search options
109
- attribute :web_search_options, Requests::WebSearchOptionsType.new
110
-
111
- # Validations
112
- validates :model, :messages, presence: true
113
-
114
- validates :frequency_penalty, numericality: { greater_than_or_equal_to: -2.0, less_than_or_equal_to: 2.0 }, allow_nil: true
115
- validates :presence_penalty, numericality: { greater_than_or_equal_to: -2.0, less_than_or_equal_to: 2.0 }, allow_nil: true
116
- validates :temperature, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 2 }, allow_nil: true
117
- validates :top_p, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }, allow_nil: true
118
- validates :top_logprobs, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 20 }, allow_nil: true
119
- validates :n, numericality: { greater_than: 0 }, allow_nil: true
120
- validates :max_completion_tokens, numericality: { greater_than: 0 }, allow_nil: true
121
- validates :max_tokens, numericality: { greater_than: 0 }, allow_nil: true
122
-
123
- validates :service_tier, inclusion: { in: %w[auto default flex priority] }, allow_nil: true
124
- validates :reasoning_effort, inclusion: { in: %w[minimal low medium high] }, allow_nil: true
125
- validates :verbosity, inclusion: { in: %w[low medium high] }, allow_nil: true
126
- validates :modalities, inclusion: { in: %w[text audio] }, allow_nil: true
127
-
128
- # Custom validations
129
- validate :validate_metadata_format
130
- validate :validate_logit_bias_format
131
- validate :validate_stop_sequences
132
-
133
- def serialize
134
- super.tap do |hash|
135
- # Can be an empty hash, to enable the feature
136
- hash[:web_search_options] ||= {} if web_search_options
137
- end
11
+ # Wraps OpenAI gem's CompletionCreateParams with normalization
12
+ #
13
+ # Delegates to OpenAI::Models::Chat::CompletionCreateParams while providing
14
+ # parameter normalization and shorthand format support via the Transforms module.
15
+ #
16
+ # All OpenAI Chat API fields are available via delegation:
17
+ # model, messages, temperature, max_tokens, max_completion_tokens, top_p,
18
+ # frequency_penalty, presence_penalty, tools, tool_choice, response_format,
19
+ # stream_options, audio, prediction, metadata, modalities, service_tier, store,
20
+ # parallel_tool_calls, reasoning_effort, verbosity, stop, seed, logit_bias,
21
+ # logprobs, top_logprobs, prompt_cache_key, safety_identifier, user,
22
+ # web_search_options, function_call, functions
23
+ #
24
+ # @example Basic usage
25
+ # request = Request.new(
26
+ # model: "gpt-4o",
27
+ # messages: [{role: "user", content: "Hello"}]
28
+ # )
29
+ #
30
+ # @example String message normalization
31
+ # Request.new(model: "gpt-4o", messages: "Hello")
32
+ # # Normalized to: [{role: "user", content: "Hello"}]
33
+ #
34
+ # @example Instructions support
35
+ # Request.new(
36
+ # model: "gpt-4o",
37
+ # messages: [{role: "user", content: "Hi"}],
38
+ # instructions: ["You are helpful", "Be concise"]
39
+ # )
40
+ # # instructions converted to developer messages and prepended
41
+ class Request < SimpleDelegator
42
+ # Default parameter values applied during initialization
43
+ DEFAULTS = {
44
+ frequency_penalty: 0,
45
+ logprobs: false,
46
+ modalities: [ "text" ],
47
+ n: 1,
48
+ parallel_tool_calls: true,
49
+ presence_penalty: 0,
50
+ service_tier: "auto",
51
+ store: false,
52
+ stream: false,
53
+ temperature: 1,
54
+ top_p: 1
55
+ }.freeze
56
+
57
+ # @return [Boolean, nil]
58
+ attr_reader :stream
59
+
60
+ # Creates a new chat completion request
61
+ #
62
+ # @param params [Hash] request parameters
63
+ # @option params [String] :model required model identifier
64
+ # @option params [Array, String, Hash] :messages required conversation messages
65
+ # @option params [Array<String>, String] :instructions system/developer prompts
66
+ # @option params [Hash, String, Symbol] :response_format
67
+ # @option params [Float] :temperature (1) sampling temperature 0-2
68
+ # @option params [Integer] :max_tokens maximum tokens to generate
69
+ # @option params [Array] :tools available tool definitions
70
+ # @raise [ArgumentError] when parameters are invalid
71
+ def initialize(**params)
72
+ # Step 1: Extract stream flag
73
+ @stream = params[:stream]
74
+
75
+ # Step 2: Apply defaults
76
+ params = apply_defaults(params)
77
+
78
+ # Step 3: Normalize all parameters (instructions, messages, response_format)
79
+ params = Chat::Transforms.normalize_params(params)
80
+
81
+ # Step 4: Create gem model - this validates all parameters!
82
+ gem_model = ::OpenAI::Models::Chat::CompletionCreateParams.new(**params)
83
+
84
+ # Step 5: Delegate all method calls to gem model
85
+ super(gem_model)
86
+ rescue ArgumentError => e
87
+ # Re-raise with more context
88
+ raise ArgumentError, "Invalid OpenAI Chat request parameters: #{e.message}"
138
89
  end
139
90
 
140
- # Common Format Compatability
141
- def instructions=(*values)
142
- self.messages ||= []
91
+ # Serializes request for API call
92
+ #
93
+ # Uses gem's JSON serialization, removes default values for minimal
94
+ # request body, and simplifies messages where possible.
95
+ #
96
+ # @return [Hash] cleaned request hash
97
+ def serialize
98
+ # Use gem's JSON serialization (handles all nested objects)
99
+ hash = Chat::Transforms.gem_to_hash(__getobj__)
143
100
 
144
- values.flatten.reverse.each do |value|
145
- self.messages.unshift({ role: "developer", content: value })
146
- end
101
+ # Cleanup and simplify for API request
102
+ Chat::Transforms.cleanup_serialized_request(hash, DEFAULTS, __getobj__)
147
103
  end
148
104
 
149
- # Common Format Compatability
150
- alias_attribute :message, :messages
105
+ # @return [Array<Hash>, nil]
106
+ def messages
107
+ __getobj__.instance_variable_get(:@data)[:messages]
108
+ end
151
109
 
152
- # Common Format Compatability
110
+ # Sets messages with normalization
111
+ #
112
+ # @param value [Array, String, Hash]
113
+ # @return [void]
153
114
  def messages=(value)
154
- case value
155
- when Array
156
- super((messages || []) | value)
157
- else
158
- super((messages || []) | [ value ])
159
- end
115
+ normalized_value = Chat::Transforms.normalize_messages(value)
116
+ __getobj__.instance_variable_get(:@data)[:messages] = normalized_value
160
117
  end
161
118
 
162
- private
163
-
164
- def validate_metadata_format
165
- return if metadata.nil?
166
-
167
- unless metadata.is_a?(Hash)
168
- errors.add(:metadata, "must be a hash")
169
- return
170
- end
171
-
172
- metadata.each do |key, value|
173
- if key.to_s.length > 64
174
- errors.add(:metadata, "keys must be 64 characters or less")
175
- end
176
- if value.to_s.length > 512
177
- errors.add(:metadata, "values must be 512 characters or less")
178
- end
179
- end
180
-
181
- if metadata.size > 16
182
- errors.add(:metadata, "must have 16 key-value pairs or less")
183
- end
119
+ # Alias for messages (common format compatibility)
120
+ #
121
+ # @return [Array<Hash>, nil]
122
+ def message
123
+ messages
184
124
  end
185
125
 
186
- def validate_logit_bias_format
187
- return if logit_bias.nil?
188
-
189
- unless logit_bias.is_a?(Hash)
190
- errors.add(:logit_bias, "must be a hash")
191
- return
192
- end
126
+ # @param value [Array, String, Hash]
127
+ # @return [void]
128
+ def message=(value)
129
+ self.messages = value
130
+ end
193
131
 
194
- logit_bias.each do |token_id, bias|
195
- unless bias.is_a?(Numeric) && bias >= -100 && bias <= 100
196
- errors.add(:logit_bias, "bias values must be between -100 and 100")
197
- end
198
- end
132
+ # Sets instructions as developer messages
133
+ #
134
+ # Prepends developer messages to the messages array for common format compatibility.
135
+ #
136
+ # @param values [Array<String>, String]
137
+ # @return [void]
138
+ def instructions=(*values)
139
+ instructions_messages = Chat::Transforms.normalize_instructions(values.flatten)
140
+ current_messages = messages || []
141
+ self.messages = instructions_messages + current_messages
199
142
  end
200
143
 
201
- def validate_stop_sequences
202
- return if stop.nil?
203
- return if stop.is_a?(String)
144
+ private
204
145
 
205
- if stop.is_a?(Array)
206
- errors.add(:stop, "can have at most 4 sequences") if stop.length > 4
207
- else
208
- errors.add(:stop, "must be a string, array, or null")
146
+ # @api private
147
+ # @param params [Hash]
148
+ # @return [Hash]
149
+ def apply_defaults(params)
150
+ # Only apply defaults for keys that aren't present
151
+ DEFAULTS.each do |key, value|
152
+ params[key] = value unless params.key?(key)
209
153
  end
154
+
155
+ params
210
156
  end
211
157
  end
212
158
  end