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,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Load all message classes
4
+ require_relative "base"
5
+ require_relative "user"
6
+ require_relative "assistant"
7
+
8
+ module ActiveAgent
9
+ module Providers
10
+ module Mock
11
+ module Messages
12
+ # Type for Messages array
13
+ class MessagesType < ActiveModel::Type::Value
14
+ def initialize
15
+ super
16
+ @message_type = MessageType.new
17
+ end
18
+
19
+ def cast(value)
20
+ case value
21
+ when Array
22
+ value.map { |v| @message_type.cast(v) }
23
+ when nil
24
+ nil
25
+ else
26
+ raise ArgumentError, "Cannot cast #{value.class} to Messages array"
27
+ end
28
+ end
29
+
30
+ def serialize(value)
31
+ case value
32
+ when Array
33
+ grouped = []
34
+
35
+ value.each do |message|
36
+ if grouped.empty? || grouped.last.role != message.role
37
+ grouped << message.deep_dup
38
+ else
39
+ grouped.last.content += message.content.deep_dup
40
+ end
41
+ end
42
+
43
+ grouped.map { |v| @message_type.serialize(v) }
44
+ when nil
45
+ nil
46
+ else
47
+ raise ArgumentError, "Cannot serialize #{value.class}"
48
+ end
49
+ end
50
+
51
+ def deserialize(value)
52
+ cast(value)
53
+ end
54
+ end
55
+
56
+ # Type for individual Message
57
+ class MessageType < ActiveModel::Type::Value
58
+ def cast(value)
59
+ case value
60
+ when Base
61
+ value
62
+ when String
63
+ User.new(content: value)
64
+ when Hash
65
+ hash = value.deep_symbolize_keys
66
+ role = hash[:role]&.to_sym
67
+
68
+ case role
69
+ when :user, nil
70
+ User.new(**hash)
71
+ when :assistant
72
+ Assistant.new(**hash)
73
+ else
74
+ raise ArgumentError, "Unknown message role: #{role}"
75
+ end
76
+ when nil
77
+ nil
78
+ else
79
+ raise ArgumentError, "Cannot cast #{value.class} to Message"
80
+ end
81
+ end
82
+
83
+ def serialize(value)
84
+ case value
85
+ when Base
86
+ value.serialize
87
+ when Hash
88
+ value
89
+ when nil
90
+ nil
91
+ else
92
+ raise ArgumentError, "Cannot serialize #{value.class}"
93
+ end
94
+ end
95
+
96
+ def deserialize(value)
97
+ cast(value)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module ActiveAgent
6
+ module Providers
7
+ module Mock
8
+ module Messages
9
+ # Assistant message for Mock provider.
10
+ #
11
+ # Drops extra fields that are part of the API response but not
12
+ # part of the message structure (usage, id, model, stop_reason, type, etc).
13
+ class Assistant < Base
14
+ attribute :role, :string, as: "assistant"
15
+ attribute :content # Can be string or array of content blocks
16
+ attribute :name, :string
17
+
18
+ validates :content, presence: true
19
+
20
+ # Drop API response fields that aren't part of the message
21
+ drop_attributes :usage, :id, :model, :stop_reason, :stop_sequence, :type
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_agent/providers/common/model"
4
+
5
+ module ActiveAgent
6
+ module Providers
7
+ module Mock
8
+ module Messages
9
+ # Base class for Mock messages.
10
+ class Base < Common::BaseModel
11
+ attribute :role, :string
12
+ attribute :content
13
+
14
+ validates :role, presence: true
15
+
16
+ # Define content setter methods for different content types
17
+ %i[text image document].each do |content_type|
18
+ define_method(:"#{content_type}=") do |value|
19
+ # For mock provider, we keep content simple
20
+ # If it's text, just set content to the text value
21
+ if content_type == :text
22
+ self.content = value
23
+ else
24
+ # For image/document, MockProvider doesn't support these, so ignore
25
+ # (or could raise an error)
26
+ end
27
+ end
28
+ end
29
+
30
+ # Converts to common format.
31
+ #
32
+ # @return [Hash] message in canonical format with role and text content
33
+ def to_common
34
+ {
35
+ role: role,
36
+ content: extract_text_content,
37
+ name: nil
38
+ }
39
+ end
40
+
41
+ private
42
+
43
+ # Extracts text content from the content structure.
44
+ #
45
+ # @return [String] extracted text content
46
+ def extract_text_content
47
+ case content
48
+ when String
49
+ content
50
+ when Array
51
+ # Join all text blocks
52
+ content.select { |block| block.is_a?(Hash) && block[:type] == "text" }
53
+ .map { |block| block[:text] }
54
+ .join("\n")
55
+ else
56
+ content.to_s
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module ActiveAgent
6
+ module Providers
7
+ module Mock
8
+ module Messages
9
+ # User message for Mock provider.
10
+ class User < Base
11
+ attribute :role, :string, as: "user"
12
+
13
+ validates :content, presence: true
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_agent/providers/common/model"
4
+
5
+ module ActiveAgent
6
+ module Providers
7
+ module Mock
8
+ # Configuration options for the Mock provider.
9
+ #
10
+ # This provider doesn't make real API calls, so most options are unused.
11
+ # Included for consistency with other providers.
12
+ class Options < Common::BaseModel
13
+ attribute :base_url, :string, default: "https://mock.example.com"
14
+ attribute :api_key, :string, default: "mock-api-key"
15
+
16
+ # Common Interface Compatibility
17
+ alias_attribute :access_token, :api_key
18
+
19
+ def initialize(kwargs = {})
20
+ kwargs = kwargs.deep_symbolize_keys if kwargs.respond_to?(:deep_symbolize_keys)
21
+ super(**deep_compact(kwargs))
22
+ end
23
+
24
+ def extra_headers
25
+ {}
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_agent/providers/common/model"
4
+
5
+ require_relative "messages/_types"
6
+
7
+ module ActiveAgent
8
+ module Providers
9
+ module Mock
10
+ # Request model for Mock provider.
11
+ #
12
+ # Simplified request model that accepts messages and basic parameters.
13
+ class Request < Common::BaseModel
14
+ # Required parameters
15
+ attribute :model, :string, default: "mock-model"
16
+ attribute :messages, Messages::MessagesType.new
17
+
18
+ # Optional parameters
19
+ attribute :instructions # System instructions
20
+ attribute :temperature, :float
21
+ attribute :max_tokens, :integer
22
+ attribute :stream, :boolean, default: false
23
+ attribute :tools # Array of tool definitions
24
+ attribute :tool_choice # Tool choice configuration
25
+
26
+ # Common Format Compatibility
27
+ def message=(value)
28
+ self.messages ||= []
29
+ self.messages << value
30
+ end
31
+
32
+ def response_format
33
+ { type: "text" }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,311 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "_base_provider"
4
+ require_relative "mock/_types"
5
+
6
+ module ActiveAgent
7
+ module Providers
8
+ # Mock provider for testing purposes.
9
+ #
10
+ # This provider doesn't make real API calls. Instead, it returns the last
11
+ # message content converted to pig latin for prompts, and random data for embeddings.
12
+ # Useful for testing without incurring API costs or requiring network access.
13
+ #
14
+ # @example Basic usage
15
+ # provider = ActiveAgent::Providers::MockProvider.new(...)
16
+ # result = provider.prompt
17
+ #
18
+ # @see BaseProvider
19
+ class MockProvider < BaseProvider
20
+ # Returns the embedding request type for Mock.
21
+ #
22
+ # @return [ActiveModel::Type::Value] The Mock embedding request type
23
+ def self.embed_request_type
24
+ Mock::EmbeddingRequestType.new
25
+ end
26
+
27
+ # Returns a mock client (just returns self since we don't need a real client).
28
+ #
29
+ # @return [MockProvider] Returns self
30
+ def client
31
+ self
32
+ end
33
+
34
+ protected
35
+
36
+ # Executes a mock prompt request.
37
+ #
38
+ # Extracts the last user message, converts it to pig latin, and returns
39
+ # a mock response structure. Handles both streaming and non-streaming.
40
+ #
41
+ # @param parameters [Hash] The prompt request parameters
42
+ # @return [Hash] A mock API response structure
43
+ def api_prompt_execute(parameters)
44
+ # Extract the last message content
45
+ last_message = [ request.instructions, parameters[:messages]&.last&.dig(:content) ].compact.join(" ")
46
+ content = extract_message_content(last_message)
47
+
48
+ # Convert to pig latin
49
+ pig_latin_content = to_pig_latin(content)
50
+
51
+ if parameters[:stream]
52
+ # For streaming, call the stream proc with chunks
53
+ stream_proc = parameters[:stream]
54
+ simulate_streaming(pig_latin_content, stream_proc)
55
+ nil
56
+ else
57
+ # Return a complete response
58
+ {
59
+ "id" => "mock-#{SecureRandom.hex(8)}",
60
+ "type" => "message",
61
+ "role" => "assistant",
62
+ "content" => [
63
+ {
64
+ "type" => "text",
65
+ "text" => pig_latin_content
66
+ }
67
+ ],
68
+ "model" => parameters[:model] || "mock-model",
69
+ "stop_reason" => "end_turn",
70
+ "usage" => {
71
+ "input_tokens" => content.length,
72
+ "output_tokens" => pig_latin_content.length
73
+ }
74
+ }
75
+ end
76
+ end
77
+
78
+ # Executes a mock embedding request.
79
+ #
80
+ # Returns random embedding vectors for testing purposes.
81
+ #
82
+ # @param parameters [Hash] The embedding request parameters
83
+ # @return [Hash] A mock embedding response structure with symbol keys
84
+ def api_embed_execute(parameters)
85
+ input = parameters[:input]
86
+ inputs = input.is_a?(Array) ? input : [ input ]
87
+ dimensions = parameters[:dimensions] || 1536
88
+
89
+ {
90
+ "object" => "list",
91
+ "data" => inputs.map.with_index do |text, index|
92
+ {
93
+ "object" => "embedding",
94
+ "index" => index,
95
+ "embedding" => generate_random_embedding(dimensions)
96
+ }
97
+ end,
98
+ "model" => parameters[:model] || "mock-embedding-model",
99
+ "usage" => {
100
+ "prompt_tokens" => inputs.sum { |text| text.to_s.length },
101
+ "total_tokens" => inputs.sum { |text| text.to_s.length }
102
+ }
103
+ }.deep_symbolize_keys
104
+ end
105
+
106
+ # Processes streaming response chunks.
107
+ #
108
+ # Handles mock streaming chunks, similar to real provider implementations.
109
+ #
110
+ # @param api_response_chunk [Hash] The streaming response chunk
111
+ # @return [void]
112
+ def process_stream_chunk(api_response_chunk)
113
+ chunk_type = api_response_chunk[:type]&.to_sym
114
+
115
+ instrument("stream_chunk.active_agent", chunk_type: chunk_type)
116
+
117
+ broadcast_stream_open
118
+
119
+ case chunk_type
120
+ when :message_start
121
+ api_message = api_response_chunk[:message]
122
+ message_stack.push(api_message)
123
+ broadcast_stream_update(message_stack.last)
124
+
125
+ when :content_block_start
126
+ api_content = api_response_chunk[:content_block]
127
+ message_stack.last[:content] ||= []
128
+ message_stack.last[:content].push(api_content)
129
+ broadcast_stream_update(message_stack.last, api_content[:text])
130
+
131
+ when :content_block_delta
132
+ index = api_response_chunk[:index]
133
+ content = message_stack.last[:content][index]
134
+ delta = api_response_chunk[:delta]
135
+
136
+ if delta[:type] == "text_delta"
137
+ content[:text] ||= ""
138
+ content[:text] += delta[:text]
139
+ broadcast_stream_update(message_stack.last, delta[:text])
140
+ end
141
+
142
+ when :message_delta
143
+ delta = api_response_chunk[:delta]
144
+ message_stack.last[:stop_reason] = delta[:stop_reason] if delta[:stop_reason]
145
+
146
+ when :message_stop
147
+ # Stream complete
148
+ end
149
+ end
150
+
151
+ # Extracts messages from API response.
152
+ #
153
+ # @param api_response [Hash] The API response
154
+ # @return [Array<Hash>] Array of message hashes
155
+ def process_prompt_finished_extract_messages(api_response)
156
+ return nil if api_response.nil? # Streaming case
157
+ [ api_response ]
158
+ end
159
+
160
+ # Extracts function calls from API response.
161
+ #
162
+ # Mock provider doesn't support tool calling by default.
163
+ #
164
+ # @return [nil]
165
+ def process_prompt_finished_extract_function_calls
166
+ nil
167
+ end
168
+
169
+ private
170
+
171
+ # Extracts text content from a message.
172
+ #
173
+ # @param message [Hash, String, nil] The message to extract from
174
+ # @return [String] The extracted text content
175
+ def extract_message_content(message)
176
+ return "" if message.nil?
177
+
178
+ case message
179
+ when String
180
+ message
181
+ when Hash
182
+ if message[:content].is_a?(String)
183
+ message[:content]
184
+ elsif message[:content].is_a?(Array)
185
+ message[:content]
186
+ .select { |block| block.is_a?(Hash) && block[:type] == "text" }
187
+ .map { |block| block[:text] }
188
+ .join(" ")
189
+ else
190
+ message[:content].to_s
191
+ end
192
+ else
193
+ message.to_s
194
+ end
195
+ end
196
+
197
+ # Converts text to pig latin.
198
+ #
199
+ # Simple pig latin conversion:
200
+ # - Words starting with vowels: add "way" to the end
201
+ # - Words starting with consonants: move consonants to end and add "ay"
202
+ # - Preserves punctuation and capitalization
203
+ #
204
+ # @param text [String] The text to convert
205
+ # @return [String] The text in pig latin
206
+ def to_pig_latin(text)
207
+ return "" if text.nil? || text.empty?
208
+
209
+ words = text.split(/\b/)
210
+
211
+ words.map do |word|
212
+ # Skip non-word characters (spaces, punctuation, etc.)
213
+ next word unless word.match?(/\w/)
214
+
215
+ # Check if word starts with a vowel
216
+ if word.match?(/^[aeiouAEIOU]/)
217
+ "#{word}way"
218
+ else
219
+ # Find the first vowel
220
+ match = word.match(/^([^aeiouAEIOU]+)(.*)/)
221
+ if match
222
+ consonants = match[1]
223
+ rest = match[2]
224
+
225
+ # Preserve capitalization
226
+ if word[0] == word[0].upcase && rest.length > 0
227
+ "#{rest[0].upcase}#{rest[1..-1]}#{consonants.downcase}ay"
228
+ else
229
+ "#{rest}#{consonants}ay"
230
+ end
231
+ else
232
+ # No vowels found, just add "ay"
233
+ "#{word}ay"
234
+ end
235
+ end
236
+ end.join
237
+ end
238
+
239
+ # Simulates streaming by sending chunks.
240
+ #
241
+ # @param content [String] The full content to stream
242
+ # @param stream_proc [Proc] The streaming callback
243
+ # @return [void]
244
+ def simulate_streaming(content, stream_proc)
245
+ message_id = "mock-#{SecureRandom.hex(8)}"
246
+
247
+ # Send message_start
248
+ stream_proc.call({
249
+ type: :message_start,
250
+ message: {
251
+ id: message_id,
252
+ type: "message",
253
+ role: "assistant",
254
+ content: [],
255
+ model: "mock-model"
256
+ }
257
+ })
258
+
259
+ # Send content_block_start
260
+ stream_proc.call({
261
+ type: :content_block_start,
262
+ index: 0,
263
+ content_block: {
264
+ type: "text",
265
+ text: ""
266
+ }
267
+ })
268
+
269
+ # Send content in chunks (simulate word-by-word streaming)
270
+ words = content.split(" ")
271
+ words.each_with_index do |word, i|
272
+ text_chunk = i == 0 ? word : " #{word}"
273
+ stream_proc.call({
274
+ type: :content_block_delta,
275
+ index: 0,
276
+ delta: {
277
+ type: "text_delta",
278
+ text: text_chunk
279
+ }
280
+ })
281
+ end
282
+
283
+ # Send message_delta with stop_reason
284
+ stream_proc.call({
285
+ type: :message_delta,
286
+ delta: {
287
+ stop_reason: "end_turn"
288
+ }
289
+ })
290
+
291
+ # Send message_stop
292
+ stream_proc.call({
293
+ type: :message_stop
294
+ })
295
+ end
296
+
297
+ # Generates a random embedding vector.
298
+ #
299
+ # @param dimensions [Integer] The number of dimensions for the embedding
300
+ # @return [Array<Float>] A random normalized vector
301
+ def generate_random_embedding(dimensions)
302
+ # Generate random values between -1 and 1
303
+ vector = Array.new(dimensions) { rand * 2 - 1 }
304
+
305
+ # Normalize the vector to unit length (common for embeddings)
306
+ magnitude = Math.sqrt(vector.sum { |v| v ** 2 })
307
+ vector.map { |v| v / magnitude }
308
+ end
309
+ end
310
+ end
311
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "options"
4
+ require_relative "chat/_types"
5
+ require_relative "embedding/_types"
@@ -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 Chat
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