activeagent 0.6.3 → 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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +240 -2
  3. data/README.md +15 -24
  4. data/lib/active_agent/base.rb +389 -39
  5. data/lib/active_agent/concerns/callbacks.rb +251 -0
  6. data/lib/active_agent/concerns/observers.rb +147 -0
  7. data/lib/active_agent/concerns/parameterized.rb +292 -0
  8. data/lib/active_agent/concerns/provider.rb +120 -0
  9. data/lib/active_agent/concerns/queueing.rb +36 -0
  10. data/lib/active_agent/concerns/rescue.rb +64 -0
  11. data/lib/active_agent/concerns/streaming.rb +282 -0
  12. data/lib/active_agent/concerns/tooling.rb +23 -0
  13. data/lib/active_agent/concerns/view.rb +150 -0
  14. data/lib/active_agent/configuration.rb +442 -20
  15. data/lib/active_agent/generation.rb +141 -47
  16. data/lib/active_agent/providers/_base_provider.rb +420 -0
  17. data/lib/active_agent/providers/anthropic/_types.rb +63 -0
  18. data/lib/active_agent/providers/anthropic/options.rb +53 -0
  19. data/lib/active_agent/providers/anthropic/request.rb +163 -0
  20. data/lib/active_agent/providers/anthropic/transforms.rb +353 -0
  21. data/lib/active_agent/providers/anthropic_provider.rb +254 -0
  22. data/lib/active_agent/providers/common/messages/_types.rb +160 -0
  23. data/lib/active_agent/providers/common/messages/assistant.rb +57 -0
  24. data/lib/active_agent/providers/common/messages/base.rb +17 -0
  25. data/lib/active_agent/providers/common/messages/system.rb +20 -0
  26. data/lib/active_agent/providers/common/messages/tool.rb +21 -0
  27. data/lib/active_agent/providers/common/messages/user.rb +20 -0
  28. data/lib/active_agent/providers/common/model.rb +361 -0
  29. data/lib/active_agent/providers/common/response.rb +13 -0
  30. data/lib/active_agent/providers/common/responses/_types.rb +51 -0
  31. data/lib/active_agent/providers/common/responses/base.rb +199 -0
  32. data/lib/active_agent/providers/common/responses/embed.rb +33 -0
  33. data/lib/active_agent/providers/common/responses/format.rb +31 -0
  34. data/lib/active_agent/providers/common/responses/message.rb +3 -0
  35. data/lib/active_agent/providers/common/responses/prompt.rb +42 -0
  36. data/lib/active_agent/providers/common/usage.rb +385 -0
  37. data/lib/active_agent/providers/concerns/exception_handler.rb +72 -0
  38. data/lib/active_agent/providers/concerns/instrumentation.rb +263 -0
  39. data/lib/active_agent/providers/concerns/previewable.rb +150 -0
  40. data/lib/active_agent/providers/log_subscriber.rb +178 -0
  41. data/lib/active_agent/providers/mock/_types.rb +77 -0
  42. data/lib/active_agent/providers/mock/embedding_request.rb +17 -0
  43. data/lib/active_agent/providers/mock/messages/_types.rb +103 -0
  44. data/lib/active_agent/providers/mock/messages/assistant.rb +26 -0
  45. data/lib/active_agent/providers/mock/messages/base.rb +63 -0
  46. data/lib/active_agent/providers/mock/messages/user.rb +18 -0
  47. data/lib/active_agent/providers/mock/options.rb +30 -0
  48. data/lib/active_agent/providers/mock/request.rb +38 -0
  49. data/lib/active_agent/providers/mock_provider.rb +311 -0
  50. data/lib/active_agent/providers/ollama/_types.rb +5 -0
  51. data/lib/active_agent/providers/ollama/chat/_types.rb +44 -0
  52. data/lib/active_agent/providers/ollama/chat/request.rb +249 -0
  53. data/lib/active_agent/providers/ollama/chat/transforms.rb +135 -0
  54. data/lib/active_agent/providers/ollama/embedding/_types.rb +44 -0
  55. data/lib/active_agent/providers/ollama/embedding/request.rb +190 -0
  56. data/lib/active_agent/providers/ollama/embedding/transforms.rb +160 -0
  57. data/lib/active_agent/providers/ollama/options.rb +27 -0
  58. data/lib/active_agent/providers/ollama_provider.rb +94 -0
  59. data/lib/active_agent/providers/open_ai/_base.rb +59 -0
  60. data/lib/active_agent/providers/open_ai/_types.rb +5 -0
  61. data/lib/active_agent/providers/open_ai/chat/_types.rb +56 -0
  62. data/lib/active_agent/providers/open_ai/chat/request.rb +161 -0
  63. data/lib/active_agent/providers/open_ai/chat/transforms.rb +364 -0
  64. data/lib/active_agent/providers/open_ai/chat_provider.rb +219 -0
  65. data/lib/active_agent/providers/open_ai/embedding/_types.rb +56 -0
  66. data/lib/active_agent/providers/open_ai/embedding/request.rb +53 -0
  67. data/lib/active_agent/providers/open_ai/embedding/transforms.rb +88 -0
  68. data/lib/active_agent/providers/open_ai/options.rb +74 -0
  69. data/lib/active_agent/providers/open_ai/responses/_types.rb +44 -0
  70. data/lib/active_agent/providers/open_ai/responses/request.rb +129 -0
  71. data/lib/active_agent/providers/open_ai/responses/transforms.rb +228 -0
  72. data/lib/active_agent/providers/open_ai/responses_provider.rb +200 -0
  73. data/lib/active_agent/providers/open_ai_provider.rb +94 -0
  74. data/lib/active_agent/providers/open_router/_types.rb +71 -0
  75. data/lib/active_agent/providers/open_router/options.rb +141 -0
  76. data/lib/active_agent/providers/open_router/request.rb +249 -0
  77. data/lib/active_agent/providers/open_router/requests/_types.rb +197 -0
  78. data/lib/active_agent/providers/open_router/requests/messages/_types.rb +56 -0
  79. data/lib/active_agent/providers/open_router/requests/messages/content/_types.rb +97 -0
  80. data/lib/active_agent/providers/open_router/requests/messages/content/file.rb +43 -0
  81. data/lib/active_agent/providers/open_router/requests/messages/content/files/_types.rb +61 -0
  82. data/lib/active_agent/providers/open_router/requests/messages/content/files/details.rb +37 -0
  83. data/lib/active_agent/providers/open_router/requests/plugin.rb +41 -0
  84. data/lib/active_agent/providers/open_router/requests/plugins/_types.rb +46 -0
  85. data/lib/active_agent/providers/open_router/requests/plugins/pdf_config.rb +51 -0
  86. data/lib/active_agent/providers/open_router/requests/prediction.rb +34 -0
  87. data/lib/active_agent/providers/open_router/requests/provider_preferences/_types.rb +44 -0
  88. data/lib/active_agent/providers/open_router/requests/provider_preferences/max_price.rb +64 -0
  89. data/lib/active_agent/providers/open_router/requests/provider_preferences.rb +105 -0
  90. data/lib/active_agent/providers/open_router/requests/response_format.rb +77 -0
  91. data/lib/active_agent/providers/open_router/transforms.rb +134 -0
  92. data/lib/active_agent/providers/open_router_provider.rb +62 -0
  93. data/lib/active_agent/providers/openai_provider.rb +2 -0
  94. data/lib/active_agent/providers/openrouter_provider.rb +2 -0
  95. data/lib/active_agent/railtie.rb +8 -6
  96. data/lib/active_agent/schema_generator.rb +333 -166
  97. data/lib/active_agent/version.rb +1 -1
  98. data/lib/active_agent.rb +112 -36
  99. data/lib/generators/active_agent/agent/USAGE +78 -0
  100. data/lib/generators/active_agent/{agent_generator.rb → agent/agent_generator.rb} +14 -4
  101. data/lib/generators/active_agent/install/USAGE +25 -0
  102. data/lib/generators/active_agent/{install_generator.rb → install/install_generator.rb} +1 -19
  103. data/lib/generators/active_agent/templates/agent.rb.tt +7 -3
  104. data/lib/generators/active_agent/templates/application_agent.rb.tt +0 -2
  105. data/lib/generators/erb/agent_generator.rb +31 -16
  106. data/lib/generators/erb/templates/instructions.md.erb.tt +3 -0
  107. data/lib/generators/erb/templates/instructions.md.tt +3 -0
  108. data/lib/generators/erb/templates/instructions.text.tt +1 -0
  109. data/lib/generators/erb/templates/message.md.erb.tt +5 -0
  110. data/lib/generators/erb/templates/schema.json.tt +10 -0
  111. data/lib/generators/test_unit/agent_generator.rb +1 -1
  112. data/lib/generators/test_unit/templates/functional_test.rb.tt +4 -2
  113. metadata +182 -71
  114. data/lib/active_agent/action_prompt/action.rb +0 -13
  115. data/lib/active_agent/action_prompt/base.rb +0 -623
  116. data/lib/active_agent/action_prompt/message.rb +0 -126
  117. data/lib/active_agent/action_prompt/prompt.rb +0 -136
  118. data/lib/active_agent/action_prompt.rb +0 -19
  119. data/lib/active_agent/callbacks.rb +0 -33
  120. data/lib/active_agent/generation_provider/anthropic_provider.rb +0 -163
  121. data/lib/active_agent/generation_provider/base.rb +0 -55
  122. data/lib/active_agent/generation_provider/base_adapter.rb +0 -19
  123. data/lib/active_agent/generation_provider/error_handling.rb +0 -167
  124. data/lib/active_agent/generation_provider/log_subscriber.rb +0 -92
  125. data/lib/active_agent/generation_provider/message_formatting.rb +0 -107
  126. data/lib/active_agent/generation_provider/ollama_provider.rb +0 -66
  127. data/lib/active_agent/generation_provider/open_ai_provider.rb +0 -279
  128. data/lib/active_agent/generation_provider/open_router_provider.rb +0 -385
  129. data/lib/active_agent/generation_provider/parameter_builder.rb +0 -119
  130. data/lib/active_agent/generation_provider/response.rb +0 -75
  131. data/lib/active_agent/generation_provider/responses_adapter.rb +0 -44
  132. data/lib/active_agent/generation_provider/stream_processing.rb +0 -58
  133. data/lib/active_agent/generation_provider/tool_management.rb +0 -142
  134. data/lib/active_agent/generation_provider.rb +0 -67
  135. data/lib/active_agent/log_subscriber.rb +0 -44
  136. data/lib/active_agent/parameterized.rb +0 -75
  137. data/lib/active_agent/prompt_helper.rb +0 -19
  138. data/lib/active_agent/queued_generation.rb +0 -12
  139. data/lib/active_agent/rescuable.rb +0 -34
  140. data/lib/active_agent/sanitizers.rb +0 -40
  141. data/lib/active_agent/streaming.rb +0 -34
  142. data/lib/active_agent/test_case.rb +0 -125
  143. data/lib/generators/USAGE +0 -47
  144. data/lib/generators/active_agent/USAGE +0 -56
  145. data/lib/generators/erb/install_generator.rb +0 -44
  146. data/lib/generators/erb/templates/layout.html.erb.tt +0 -1
  147. data/lib/generators/erb/templates/layout.json.erb.tt +0 -1
  148. data/lib/generators/erb/templates/layout.text.erb.tt +0 -1
  149. data/lib/generators/erb/templates/view.html.erb.tt +0 -5
  150. data/lib/generators/erb/templates/view.json.erb.tt +0 -16
  151. /data/lib/active_agent/{preview.rb → concerns/preview.rb} +0 -0
  152. /data/lib/generators/erb/templates/{view.text.erb.tt → message.text.erb.tt} +0 -0
@@ -0,0 +1,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "json"
5
+ require_relative "transforms"
6
+
7
+ module ActiveAgent
8
+ module Providers
9
+ module Ollama
10
+ module Chat
11
+ # Wraps OpenAI gem's CompletionCreateParams with Ollama-specific extensions
12
+ #
13
+ # Delegates to OpenAI::Models::Chat::CompletionCreateParams for OpenAI-compatible
14
+ # parameters while adding support for Ollama-specific features like format,
15
+ # options, keep_alive, and raw mode.
16
+ #
17
+ # Ollama-specific parameters:
18
+ # - format: Return response in JSON or as a JSON schema (String "json" or Hash)
19
+ # - options: Additional model parameters (Hash of key-value pairs)
20
+ # - keep_alive: Controls how long model stays in memory (String duration or Integer seconds)
21
+ # - raw: If true, no formatting applied to prompt (Boolean)
22
+ #
23
+ # @example Basic usage
24
+ # request = Request.new(
25
+ # model: "llama2",
26
+ # messages: [{role: "user", content: "Hello"}]
27
+ # )
28
+ #
29
+ # @example With Ollama-specific features
30
+ # request = Request.new(
31
+ # model: "llama2",
32
+ # messages: [{role: "user", content: "Hello"}],
33
+ # format: "json",
34
+ # options: {temperature: 0.7, num_predict: 100},
35
+ # keep_alive: "5m"
36
+ # )
37
+ class Request < SimpleDelegator
38
+ # Default parameter values
39
+ DEFAULTS = {
40
+ frequency_penalty: 0,
41
+ logprobs: false,
42
+ n: 1,
43
+ parallel_tool_calls: true,
44
+ presence_penalty: 0,
45
+ temperature: 1,
46
+ top_p: 1,
47
+ raw: false,
48
+ keep_alive: "5m"
49
+ }.freeze
50
+
51
+ # @return [Boolean, nil]
52
+ attr_reader :stream
53
+
54
+ # @return [Hash] Ollama-specific parameters
55
+ attr_reader :ollama_params
56
+
57
+ # Creates a new Ollama request
58
+ #
59
+ # @param params [Hash] request parameters
60
+ # @option params [String] :model model identifier (required)
61
+ # @option params [Array, String, Hash] :messages required conversation messages
62
+ # @option params [String, Hash] :format JSON format ("json" or schema object)
63
+ # @option params [Hash] :options model-specific options
64
+ # @option params [String, Integer] :keep_alive memory duration
65
+ # @option params [Boolean] :raw raw prompt mode
66
+ # @raise [ArgumentError] when parameters are invalid
67
+ def initialize(**params)
68
+ # Step 1: Extract stream flag
69
+ @stream = params[:stream]
70
+
71
+ # Step 2: Apply defaults
72
+ params = apply_defaults(params)
73
+
74
+ # Step 3: Normalize parameters and split into OpenAI vs Ollama-specific
75
+ openai_params, @ollama_params = Transforms.normalize_params(params)
76
+
77
+ # Step 4: Validate Ollama-specific parameters
78
+ validate_format(@ollama_params[:format]) if @ollama_params[:format]
79
+ validate_options(@ollama_params[:options]) if @ollama_params[:options]
80
+
81
+ # Step 5: Create gem model with OpenAI-compatible params
82
+ gem_model = ::OpenAI::Models::Chat::CompletionCreateParams.new(**openai_params)
83
+
84
+ # Step 6: Delegate to the gem model
85
+ super(gem_model)
86
+ rescue ArgumentError => e
87
+ raise ArgumentError, "Invalid Ollama Chat request parameters: #{e.message}"
88
+ end
89
+
90
+ # Serializes request for API submission
91
+ #
92
+ # Merges OpenAI-compatible parameters with Ollama-specific extensions.
93
+ #
94
+ # @return [Hash] cleaned request hash
95
+ def serialize
96
+ # Get OpenAI params from gem model
97
+ openai_hash = Transforms.gem_to_hash(__getobj__)
98
+
99
+ # Merge with Ollama-specific params
100
+ Transforms.cleanup_serialized_request(openai_hash, @ollama_params, DEFAULTS, __getobj__)
101
+ end
102
+
103
+ # @return [Array<Hash>, nil]
104
+ def messages
105
+ __getobj__.instance_variable_get(:@data)[:messages]
106
+ end
107
+
108
+ # Sets messages with normalization
109
+ #
110
+ # Merges new messages with existing ones for compatibility.
111
+ #
112
+ # @param value [Array, String, Hash]
113
+ # @return [void]
114
+ def messages=(value)
115
+ normalized_value = Transforms.normalize_messages(value)
116
+ current_messages = messages || []
117
+
118
+ # Merge behavior for Ollama compatibility
119
+ merged = current_messages | Array(normalized_value)
120
+ __getobj__.instance_variable_get(:@data)[:messages] = merged
121
+ end
122
+
123
+ # Alias for messages (common format compatibility)
124
+ #
125
+ # @return [Array<Hash>, nil]
126
+ def message
127
+ messages
128
+ end
129
+
130
+ # @param value [Array, String, Hash]
131
+ # @return [void]
132
+ def message=(value)
133
+ self.messages = value
134
+ end
135
+
136
+ # Sets instructions as developer messages
137
+ #
138
+ # Prepends developer messages to the messages array.
139
+ #
140
+ # @param values [Array<String>, String]
141
+ # @return [void]
142
+ def instructions=(*values)
143
+ instructions_messages = Transforms.normalize_instructions(values.flatten)
144
+ current_messages = messages || []
145
+ self.messages = instructions_messages + current_messages
146
+ end
147
+
148
+ # Accessor for Ollama format parameter
149
+ #
150
+ # @return [String, Hash, nil]
151
+ def format
152
+ @ollama_params[:format]
153
+ end
154
+
155
+ # Sets format parameter
156
+ #
157
+ # @param value [String, Hash]
158
+ # @return [void]
159
+ def format=(value)
160
+ validate_format(value)
161
+ @ollama_params[:format] = value
162
+ end
163
+
164
+ # Accessor for Ollama options parameter
165
+ #
166
+ # @return [Hash, nil]
167
+ def options
168
+ @ollama_params[:options]
169
+ end
170
+
171
+ # Sets options parameter
172
+ #
173
+ # @param value [Hash]
174
+ # @return [void]
175
+ def options=(value)
176
+ validate_options(value)
177
+ @ollama_params[:options] = value
178
+ end
179
+
180
+ # Accessor for keep_alive parameter
181
+ #
182
+ # @return [String, Integer, nil]
183
+ def keep_alive
184
+ @ollama_params[:keep_alive] || DEFAULTS[:keep_alive]
185
+ end
186
+
187
+ # Sets keep_alive parameter
188
+ #
189
+ # @param value [String, Integer]
190
+ # @return [void]
191
+ def keep_alive=(value)
192
+ @ollama_params[:keep_alive] = value
193
+ end
194
+
195
+ # Accessor for raw parameter
196
+ #
197
+ # @return [Boolean]
198
+ def raw
199
+ @ollama_params[:raw] || DEFAULTS[:raw]
200
+ end
201
+
202
+ # Sets raw parameter
203
+ #
204
+ # @param value [Boolean]
205
+ # @return [void]
206
+ def raw=(value)
207
+ @ollama_params[:raw] = value
208
+ end
209
+
210
+ private
211
+
212
+ # @api private
213
+ # @param params [Hash]
214
+ # @return [Hash]
215
+ def apply_defaults(params)
216
+ # Apply defaults
217
+ DEFAULTS.each do |key, value|
218
+ params[key] = value unless params.key?(key)
219
+ end
220
+
221
+ params
222
+ end
223
+
224
+ # @api private
225
+ # @param format [String, Hash, nil]
226
+ # @raise [ArgumentError]
227
+ def validate_format(format)
228
+ return if format.nil?
229
+ return if format == "json"
230
+ return if format.is_a?(Hash) # JSON schema object
231
+
232
+ raise ArgumentError, "format must be 'json' or a JSON schema object"
233
+ end
234
+
235
+ # @api private
236
+ # @param options [Hash, nil]
237
+ # @raise [ArgumentError]
238
+ def validate_options(options)
239
+ return if options.nil?
240
+
241
+ unless options.is_a?(Hash)
242
+ raise ArgumentError, "options must be a hash of model parameters"
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
4
+ require_relative "../../open_ai/chat/transforms"
5
+
6
+ module ActiveAgent
7
+ module Providers
8
+ module Ollama
9
+ module Chat
10
+ # Provides transformation methods for normalizing Ollama parameters
11
+ # to work with OpenAI gem's native format plus Ollama extensions
12
+ #
13
+ # Leverages OpenAI::Chat::Transforms for base message normalization while
14
+ # adding handling for Ollama-specific parameters like format, options,
15
+ # keep_alive, and raw mode.
16
+ module Transforms
17
+ class << self
18
+ # Converts gem model object to hash via JSON round-trip
19
+ #
20
+ # @param gem_object [Object]
21
+ # @return [Hash] with symbolized keys
22
+ def gem_to_hash(gem_object)
23
+ OpenAI::Chat::Transforms.gem_to_hash(gem_object)
24
+ end
25
+
26
+ # Normalizes all request parameters for Ollama API
27
+ #
28
+ # Handles both OpenAI-compatible parameters and Ollama-specific extensions.
29
+ # Ollama-specific params (format, options, keep_alive, raw) are
30
+ # extracted and returned separately for manual serialization.
31
+ #
32
+ # @param params [Hash]
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[:format] = params.delete(:format) if params.key?(:format)
40
+ ollama_params[:options] = params.delete(:options) if params.key?(:options)
41
+ ollama_params[:keep_alive] = params.delete(:keep_alive) if params.key?(:keep_alive)
42
+ ollama_params[:raw] = params.delete(:raw) if params.key?(:raw)
43
+
44
+ # Use OpenAI transforms for the base parameters
45
+ openai_params = OpenAI::Chat::Transforms.normalize_params(params)
46
+
47
+ [ openai_params, ollama_params ]
48
+ end
49
+
50
+ # Normalizes messages using OpenAI transforms
51
+ #
52
+ # @param messages [Array, String, Hash, nil]
53
+ # @return [Array<OpenAI::Models::Chat::ChatCompletionMessageParam>, nil]
54
+ def normalize_messages(messages)
55
+ OpenAI::Chat::Transforms.normalize_messages(messages)
56
+ end
57
+
58
+ # Normalizes instructions using OpenAI transforms
59
+ #
60
+ # @param instructions [Array<String>, String]
61
+ # @return [Array<OpenAI::Models::Chat::ChatCompletionMessageParam>]
62
+ def normalize_instructions(instructions)
63
+ OpenAI::Chat::Transforms.normalize_instructions(instructions)
64
+ end
65
+
66
+ # Cleans up serialized request for API submission
67
+ #
68
+ # Merges OpenAI-compatible params with Ollama-specific params.
69
+ # Also groups consecutive same-role messages as required by Ollama.
70
+ #
71
+ # @param openai_hash [Hash] serialized OpenAI request
72
+ # @param ollama_params [Hash] Ollama-specific parameters
73
+ # @param defaults [Hash] default values to remove
74
+ # @param gem_object [Object] original gem object
75
+ # @return [Hash] cleaned and merged request hash
76
+ def cleanup_serialized_request(openai_hash, ollama_params, defaults, gem_object)
77
+ # Start with OpenAI cleanup
78
+ cleaned = OpenAI::Chat::Transforms.cleanup_serialized_request(openai_hash, defaults, gem_object)
79
+
80
+ # Group consecutive same-role messages for Ollama
81
+ if cleaned[:messages]
82
+ cleaned[:messages] = group_same_role_messages(cleaned[:messages])
83
+ end
84
+
85
+ # Merge Ollama-specific params, but skip default values
86
+ ollama_params.each do |key, value|
87
+ # Skip if value is nil, empty, or matches the default
88
+ next if value.nil?
89
+ next if value.respond_to?(:empty?) && value.empty?
90
+ next if defaults.key?(key) && defaults[key] == value
91
+
92
+ cleaned[key] = value
93
+ end
94
+
95
+ cleaned
96
+ end
97
+
98
+ # Groups consecutive same-role messages for Ollama
99
+ #
100
+ # Ollama requires consecutive messages with the same role to be merged
101
+ # by concatenating their content.
102
+ #
103
+ # @param messages [Array<Hash>] array of message hashes
104
+ # @return [Array<Hash>] grouped messages
105
+ def group_same_role_messages(messages)
106
+ return [] if messages.nil? || messages.empty?
107
+
108
+ grouped = []
109
+ messages.each do |message|
110
+ if grouped.empty? || grouped.last[:role] != message[:role]
111
+ grouped << message.deep_dup
112
+ else
113
+ # Concatenate content for same-role messages
114
+ last_content = grouped.last[:content]
115
+ new_content = message[:content]
116
+
117
+ grouped.last[:content] = if last_content.is_a?(Array) && new_content.is_a?(Array)
118
+ last_content + new_content
119
+ elsif last_content.is_a?(String) && new_content.is_a?(String)
120
+ last_content + new_content
121
+ else
122
+ # Mix of types, convert to array
123
+ Array(last_content) + Array(new_content)
124
+ end
125
+ end
126
+ end
127
+
128
+ grouped
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "request"
4
+
5
+ module ActiveAgent
6
+ module Providers
7
+ module Ollama
8
+ module Embedding
9
+ # Type for Request model
10
+ class RequestType < ActiveModel::Type::Value
11
+ def cast(value)
12
+ case value
13
+ when Request
14
+ value
15
+ when Hash
16
+ Request.new(**value.deep_symbolize_keys)
17
+ when nil
18
+ nil
19
+ else
20
+ raise ArgumentError, "Cannot cast #{value.class} to Request"
21
+ end
22
+ end
23
+
24
+ def serialize(value)
25
+ case value
26
+ when Request
27
+ value.serialize
28
+ when Hash
29
+ value
30
+ when nil
31
+ nil
32
+ else
33
+ raise ArgumentError, "Cannot serialize #{value.class}"
34
+ end
35
+ end
36
+
37
+ def deserialize(value)
38
+ cast(value)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "json"
5
+ require_relative "transforms"
6
+
7
+ module ActiveAgent
8
+ module Providers
9
+ module Ollama
10
+ module Embedding
11
+ # Wraps OpenAI gem's EmbeddingCreateParams with Ollama-specific extensions
12
+ #
13
+ # Delegates to OpenAI::Models::EmbeddingCreateParams for OpenAI-compatible
14
+ # parameters while adding support for Ollama-specific features like options,
15
+ # keep_alive, and truncate.
16
+ #
17
+ # Ollama-specific parameters:
18
+ # - options: Additional model parameters (Hash with temperature, seed, etc.)
19
+ # - keep_alive: Controls how long model stays in memory (String duration)
20
+ # - truncate: Truncates input to fit context length (Boolean)
21
+ #
22
+ # @example Basic usage
23
+ # request = Request.new(
24
+ # model: "llama2",
25
+ # input: "Hello world"
26
+ # )
27
+ #
28
+ # @example With Ollama-specific features
29
+ # request = Request.new(
30
+ # model: "llama2",
31
+ # input: ["Hello", "World"],
32
+ # options: {temperature: 0.7, seed: 42},
33
+ # keep_alive: "10m",
34
+ # truncate: false
35
+ # )
36
+ #
37
+ # @example With delegated option attributes
38
+ # request = Request.new(
39
+ # model: "llama2",
40
+ # input: "Hello",
41
+ # temperature: 0.7, # Automatically goes to options
42
+ # seed: 42 # Automatically goes to options
43
+ # )
44
+ class Request < SimpleDelegator
45
+ # Default parameter values
46
+ DEFAULTS = {
47
+ truncate: true,
48
+ keep_alive: "5m"
49
+ }.freeze
50
+
51
+ # @return [Hash] Ollama-specific parameters
52
+ attr_reader :ollama_params
53
+
54
+ # Creates a new Ollama embedding request
55
+ #
56
+ # @param params [Hash] request parameters
57
+ # @option params [String] :model model identifier (required)
58
+ # @option params [String, Array<String>] :input text to embed (required)
59
+ # @option params [Hash] :options model-specific options
60
+ # @option params [String] :keep_alive memory duration
61
+ # @option params [Boolean] :truncate truncate to context length
62
+ # @option params [Float] :temperature delegated to options
63
+ # @option params [Integer] :seed delegated to options
64
+ # @raise [ArgumentError] when parameters are invalid
65
+ def initialize(**params)
66
+ # Step 1: Apply defaults
67
+ params = apply_defaults(params)
68
+
69
+ # Step 2: Validate presence of required params before normalization
70
+ raise ArgumentError, "model is required" unless params[:model]
71
+ raise ArgumentError, "input is required" unless params[:input]
72
+
73
+ # Step 3: Normalize parameters and split into OpenAI vs Ollama-specific
74
+ openai_params, @ollama_params = Transforms.normalize_params(params)
75
+
76
+ # Step 4: Create gem model with OpenAI-compatible params
77
+ gem_model = ::OpenAI::Models::EmbeddingCreateParams.new(**openai_params)
78
+
79
+ # Step 5: Delegate to the gem model
80
+ super(gem_model)
81
+ rescue ArgumentError => e
82
+ raise ArgumentError, "Invalid Ollama Embedding request parameters: #{e.message}"
83
+ end
84
+
85
+ # Serializes request for API submission
86
+ #
87
+ # Merges OpenAI-compatible parameters with Ollama-specific extensions.
88
+ #
89
+ # @return [Hash] cleaned request hash
90
+ def serialize
91
+ # Get OpenAI params from gem model
92
+ openai_hash = Transforms.gem_to_hash(__getobj__)
93
+
94
+ # Merge with Ollama-specific params
95
+ Transforms.cleanup_serialized_request(openai_hash, @ollama_params, DEFAULTS)
96
+ end
97
+
98
+ # Accessor for input parameter
99
+ #
100
+ # @return [Array<String>, nil]
101
+ def input
102
+ __getobj__.instance_variable_get(:@data)[:input]
103
+ end
104
+
105
+ # Sets input with normalization
106
+ #
107
+ # @param value [String, Array<String>]
108
+ # @return [void]
109
+ def input=(value)
110
+ normalized_value = Transforms.normalize_input(value)
111
+ __getobj__.instance_variable_get(:@data)[:input] = normalized_value
112
+ end
113
+
114
+ # Accessor for Ollama options parameter
115
+ #
116
+ # @return [Hash, nil]
117
+ def options
118
+ @ollama_params[:options]
119
+ end
120
+
121
+ # Sets options parameter
122
+ #
123
+ # @param value [Hash]
124
+ # @return [void]
125
+ def options=(value)
126
+ @ollama_params[:options] = value
127
+ end
128
+
129
+ # Accessor for keep_alive parameter
130
+ #
131
+ # @return [String, nil]
132
+ def keep_alive
133
+ @ollama_params[:keep_alive] || DEFAULTS[:keep_alive]
134
+ end
135
+
136
+ # Sets keep_alive parameter
137
+ #
138
+ # @param value [String]
139
+ # @return [void]
140
+ def keep_alive=(value)
141
+ @ollama_params[:keep_alive] = value
142
+ end
143
+
144
+ # Accessor for truncate parameter
145
+ #
146
+ # @return [Boolean]
147
+ def truncate
148
+ @ollama_params.fetch(:truncate, DEFAULTS[:truncate])
149
+ end
150
+
151
+ # Sets truncate parameter
152
+ #
153
+ # @param value [Boolean]
154
+ # @return [void]
155
+ def truncate=(value)
156
+ @ollama_params[:truncate] = value
157
+ end
158
+
159
+ # Delegated option attribute accessors
160
+ # These allow setting option values at the top level
161
+ %i[mirostat mirostat_eta mirostat_tau num_ctx repeat_last_n
162
+ repeat_penalty temperature seed num_predict top_k top_p min_p stop].each do |attr|
163
+ define_method(attr) do
164
+ options&.dig(attr)
165
+ end
166
+
167
+ define_method(:"#{attr}=") do |value|
168
+ @ollama_params[:options] ||= {}
169
+ @ollama_params[:options][attr] = value
170
+ end
171
+ end
172
+
173
+ private
174
+
175
+ # @api private
176
+ # @param params [Hash]
177
+ # @return [Hash]
178
+ def apply_defaults(params)
179
+ # Apply defaults
180
+ DEFAULTS.each do |key, value|
181
+ params[key] = value unless params.key?(key)
182
+ end
183
+
184
+ params
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end