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
@@ -1,66 +1,245 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../open_ai/chat/request"
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 Ollama
9
10
  module Chat
10
- # Ollama uses the same request structure as OpenAI's chat completion API
11
- # This class exists to allow for Ollama-specific customizations.
12
- class Request < OpenAI::Chat::Request
13
- # Messages array (required)
14
- attribute :messages, Requests::Messages::MessagesType.new
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
15
50
 
16
- # Ollama-specific parameters
51
+ # @return [Boolean, nil]
52
+ attr_reader :stream
17
53
 
18
- # Format: return response in json or as a JSON schema
19
- # Can be "json" or a JSON schema object
20
- attribute :format
54
+ # @return [Hash] Ollama-specific parameters
55
+ attr_reader :ollama_params
21
56
 
22
- # Options: additional model parameters (temperature, num_predict, etc.)
23
- # Hash of key-value pairs for model-specific options
24
- attribute :options
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]
25
70
 
26
- # Keep alive: controls how long the model stays loaded in memory
27
- # String duration (e.g., "5m", "1h") or integer in seconds
28
- # Default: "5m"
29
- attribute :keep_alive
71
+ # Step 2: Apply defaults
72
+ params = apply_defaults(params)
30
73
 
31
- # Raw: if true, no formatting will be applied to the prompt
32
- # You may use this if you are specifying a full templated prompt
33
- attribute :raw, :boolean, default: false
74
+ # Step 3: Normalize parameters and split into OpenAI vs Ollama-specific
75
+ openai_params, @ollama_params = Transforms.normalize_params(params)
34
76
 
35
- # Validations for Ollama-specific parameters
36
- validate :validate_format
37
- validate :validate_options_format
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]
38
80
 
39
- # Common Format Compatability
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]
40
114
  def messages=(value)
41
- case value
42
- when Array
43
- super((messages || []) | value)
44
- else
45
- super((messages || []) | [ value ])
46
- end
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
47
208
  end
48
209
 
49
210
  private
50
211
 
51
- def validate_format
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)
52
228
  return if format.nil?
53
229
  return if format == "json"
54
230
  return if format.is_a?(Hash) # JSON schema object
55
231
 
56
- errors.add(:format, "must be 'json' or a JSON schema object")
232
+ raise ArgumentError, "format must be 'json' or a JSON schema object"
57
233
  end
58
234
 
59
- def validate_options_format
235
+ # @api private
236
+ # @param options [Hash, nil]
237
+ # @raise [ArgumentError]
238
+ def validate_options(options)
60
239
  return if options.nil?
61
240
 
62
241
  unless options.is_a?(Hash)
63
- errors.add(:options, "must be a hash of model parameters")
242
+ raise ArgumentError, "options must be a hash of model parameters"
64
243
  end
65
244
  end
66
245
  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
@@ -1,74 +1,187 @@
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 Ollama
9
10
  module Embedding
10
- class Request < Common::BaseModel
11
- # Model name to generate embeddings from (required)
12
- attribute :model, :string
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
13
84
 
14
- # Input text or list of text to generate embeddings for (required)
15
- # Can be a string or array of strings
16
- attribute :input, Requests::InputType.new
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
17
97
 
18
- # Truncates the end of each input to fit within context length (optional)
19
- # Returns error if false and context length is exceeded
20
- # Defaults to true
21
- attribute :truncate, :boolean, default: true
98
+ # Accessor for input parameter
99
+ #
100
+ # @return [Array<String>, nil]
101
+ def input
102
+ __getobj__.instance_variable_get(:@data)[:input]
103
+ end
22
104
 
23
- # Additional model parameters listed in the documentation for the Modelfile (optional)
24
- # such as temperature
25
- attribute :options, Requests::OptionsType.new
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
26
113
 
27
- # Controls how long the model will stay loaded into memory following the request (optional)
28
- # Default: 5m
29
- attribute :keep_alive, :string
114
+ # Accessor for Ollama options parameter
115
+ #
116
+ # @return [Hash, nil]
117
+ def options
118
+ @ollama_params[:options]
119
+ end
30
120
 
31
- # Validations
32
- validates :model, :input, presence: true
121
+ # Sets options parameter
122
+ #
123
+ # @param value [Hash]
124
+ # @return [void]
125
+ def options=(value)
126
+ @ollama_params[:options] = value
127
+ end
33
128
 
34
- # Custom validations
35
- validate :validate_input_format
36
- validate :validate_input_not_empty
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
37
135
 
38
- # To merge over global prompt/model options over
39
- delegate_attributes :mirostat, :mirostat_eta, :mirostat_tau, :num_ctx, :repeat_last_n, :repeat_penalty,
40
- :temperature, :seed, :num_predict, :top_k, :top_p, :min_p,
41
- to: :options
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
42
143
 
43
- private
144
+ # Accessor for truncate parameter
145
+ #
146
+ # @return [Boolean]
147
+ def truncate
148
+ @ollama_params.fetch(:truncate, DEFAULTS[:truncate])
149
+ end
44
150
 
45
- def validate_input_format
46
- return if input.nil?
151
+ # Sets truncate parameter
152
+ #
153
+ # @param value [Boolean]
154
+ # @return [void]
155
+ def truncate=(value)
156
+ @ollama_params[:truncate] = value
157
+ end
47
158
 
48
- unless input.is_a?(Array)
49
- errors.add(:input, "must be stored as an array internally")
50
- return
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)
51
165
  end
52
166
 
53
- # Validate array contents - Ollama only accepts strings
54
- input.each_with_index do |item, index|
55
- unless item.is_a?(String)
56
- errors.add(:input, "array elements must be strings at index #{index}")
57
- next
58
- end
59
-
60
- if item.empty?
61
- errors.add(:input, "cannot contain empty strings at index #{index}")
62
- end
167
+ define_method(:"#{attr}=") do |value|
168
+ @ollama_params[:options] ||= {}
169
+ @ollama_params[:options][attr] = value
63
170
  end
64
171
  end
65
172
 
66
- def validate_input_not_empty
67
- return if input.nil?
173
+ private
68
174
 
69
- if input.is_a?(Array) && input.empty?
70
- errors.add(:input, "cannot be an empty array")
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)
71
182
  end
183
+
184
+ params
72
185
  end
73
186
  end
74
187
  end