activeagent 0.6.0rc1 → 0.6.0rc4
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.
- checksums.yaml +4 -4
- data/lib/active_agent/action_prompt/base.rb +37 -14
- data/lib/active_agent/action_prompt/message.rb +17 -1
- data/lib/active_agent/action_prompt/prompt.rb +17 -7
- data/lib/active_agent/callbacks.rb +0 -15
- data/lib/active_agent/generation_provider/anthropic_provider.rb +8 -4
- data/lib/active_agent/generation_provider/base.rb +1 -0
- data/lib/active_agent/generation_provider/error_handling.rb +3 -2
- data/lib/active_agent/generation_provider/message_formatting.rb +2 -2
- data/lib/active_agent/generation_provider/open_ai_provider.rb +43 -13
- data/lib/active_agent/generation_provider/open_router_provider.rb +86 -44
- data/lib/active_agent/generation_provider/parameter_builder.rb +1 -1
- data/lib/active_agent/generation_provider/response.rb +38 -8
- data/lib/active_agent/railtie.rb +7 -1
- data/lib/active_agent/streaming.rb +34 -0
- data/lib/active_agent/version.rb +1 -1
- data/lib/active_agent.rb +1 -0
- data/lib/generators/erb/install_generator.rb +0 -1
- metadata +128 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 805b4d2cce3173788f3bb281e40973d8782891937b5220654b7dad53a9f701ec
|
4
|
+
data.tar.gz: 968f534f94d4e32541f1d87e21854add0f4bf8c9e851ba75f7599291a0b384b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec13c2dd2d8deb4fd4adb1a6f302de6a7bb5057fad1cd45f27fdecc6b8bea7b5496f3078e7a1f40168de9b4efaa8e4a2abf1cf1bd2865fa929912c58adce561c
|
7
|
+
data.tar.gz: a564772159ac558c8350d770977fc9b7b509c814435a748cac61af89bd87dc196614b45cc9f33db7bce8951568f659730bfe4cf95fd274f145ae00bb78510af6
|
@@ -2,6 +2,8 @@ require "active_agent/collector"
|
|
2
2
|
require "active_support/core_ext/string/inflections"
|
3
3
|
require "active_support/core_ext/hash/except"
|
4
4
|
require "active_support/core_ext/module/anonymous"
|
5
|
+
require "active_agent/action_prompt/message"
|
6
|
+
require "active_agent/action_prompt/action"
|
5
7
|
|
6
8
|
# require "active_agent/log_subscriber"
|
7
9
|
require "active_agent/rescuable"
|
@@ -10,6 +12,7 @@ module ActiveAgent
|
|
10
12
|
class Base < AbstractController::Base
|
11
13
|
include Callbacks
|
12
14
|
include GenerationProvider
|
15
|
+
include Streaming
|
13
16
|
include QueuedGeneration
|
14
17
|
include Rescuable
|
15
18
|
include Parameterized
|
@@ -104,7 +107,6 @@ module ActiveAgent
|
|
104
107
|
# Define how the agent should generate content
|
105
108
|
def generate_with(provider, **options)
|
106
109
|
self.generation_provider = provider
|
107
|
-
|
108
110
|
if options.has_key?(:instructions) || (self.options || {}).empty?
|
109
111
|
# Either instructions explicitly provided, or no inherited options exist
|
110
112
|
self.options = (self.options || {}).merge(options)
|
@@ -218,7 +220,8 @@ module ActiveAgent
|
|
218
220
|
def handle_response(response)
|
219
221
|
return response unless response.message.requested_actions.present?
|
220
222
|
|
221
|
-
#
|
223
|
+
# The assistant message with tool_calls is already added by update_context in the provider
|
224
|
+
# Now perform the requested actions which will add tool response messages
|
222
225
|
perform_actions(requested_actions: response.message.requested_actions)
|
223
226
|
|
224
227
|
# Continue generation with updated context
|
@@ -242,22 +245,36 @@ module ActiveAgent
|
|
242
245
|
end
|
243
246
|
|
244
247
|
def perform_action(action)
|
245
|
-
|
246
|
-
|
247
|
-
original_params =
|
248
|
+
# Save the current messages to preserve conversation history
|
249
|
+
original_messages = context.messages.dup
|
250
|
+
original_params = context.params || {}
|
251
|
+
|
248
252
|
if action.params.is_a?(Hash)
|
249
253
|
self.params = original_params.merge(action.params)
|
250
254
|
else
|
251
255
|
self.params = original_params
|
252
256
|
end
|
257
|
+
|
258
|
+
# Save the current prompt_was_called state and reset it so the action can render
|
259
|
+
original_prompt_was_called = @_prompt_was_called
|
260
|
+
@_prompt_was_called = false
|
261
|
+
|
262
|
+
# Process the action, which will render the view and populate context
|
253
263
|
process(action.name)
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
context.message.
|
258
|
-
|
259
|
-
|
260
|
-
|
264
|
+
|
265
|
+
# The action should have called prompt which populates context.message
|
266
|
+
# Create a tool message from the rendered response
|
267
|
+
tool_message = context.message.dup
|
268
|
+
tool_message.role = :tool
|
269
|
+
tool_message.action_id = action.id
|
270
|
+
tool_message.action_name = action.name
|
271
|
+
tool_message.generation_id = action.id
|
272
|
+
|
273
|
+
# Restore the messages with the new tool message
|
274
|
+
context.messages = original_messages + [ tool_message ]
|
275
|
+
|
276
|
+
# Restore the prompt_was_called state
|
277
|
+
@_prompt_was_called = original_prompt_was_called
|
261
278
|
end
|
262
279
|
|
263
280
|
def initialize # :nodoc:
|
@@ -373,6 +390,10 @@ module ActiveAgent
|
|
373
390
|
if headers[:message].present? && headers[:message].is_a?(ActiveAgent::ActionPrompt::Message)
|
374
391
|
headers[:body] = headers[:message].content
|
375
392
|
headers[:role] = headers[:message].role
|
393
|
+
elsif headers[:message].present? && headers[:message].is_a?(Array)
|
394
|
+
# Handle array of multipart content like [{type: "text", text: "..."}, {type: "file", file: {...}}]
|
395
|
+
headers[:body] = headers[:message]
|
396
|
+
headers[:role] = :user
|
376
397
|
elsif headers[:message].present? && headers[:message].is_a?(String)
|
377
398
|
headers[:body] = headers[:message]
|
378
399
|
headers[:role] = :user
|
@@ -394,7 +415,6 @@ module ActiveAgent
|
|
394
415
|
ActiveAgent::ActionPrompt::Message.new(content: headers[:body], content_type: "input_text")
|
395
416
|
]
|
396
417
|
end
|
397
|
-
|
398
418
|
headers
|
399
419
|
end
|
400
420
|
|
@@ -421,7 +441,10 @@ module ActiveAgent
|
|
421
441
|
# Extract runtime options from prompt_options (exclude instructions as it has special template logic)
|
422
442
|
runtime_options = prompt_options.slice(
|
423
443
|
:model, :temperature, :max_tokens, :stream, :top_p, :frequency_penalty,
|
424
|
-
:presence_penalty, :response_format, :seed, :stop, :tools_choice, :
|
444
|
+
:presence_penalty, :response_format, :seed, :stop, :tools_choice, :plugins,
|
445
|
+
|
446
|
+
# OpenRouter Provider Settings
|
447
|
+
:data_collection, :require_parameters, :only, :ignore, :quantizations, :sort, :max_price
|
425
448
|
)
|
426
449
|
# Handle explicit options parameter
|
427
450
|
explicit_options = prompt_options[:options] || {}
|
@@ -27,7 +27,7 @@ module ActiveAgent
|
|
27
27
|
@metadata = attributes[:metadata] || {}
|
28
28
|
@charset = attributes[:charset] || "UTF-8"
|
29
29
|
@content = attributes[:content] || ""
|
30
|
-
@content_type = attributes
|
30
|
+
@content_type = detect_content_type(attributes)
|
31
31
|
@role = attributes[:role] || :user
|
32
32
|
@raw_actions = attributes[:raw_actions]
|
33
33
|
@requested_actions = attributes[:requested_actions] || []
|
@@ -85,6 +85,22 @@ module ActiveAgent
|
|
85
85
|
|
86
86
|
private
|
87
87
|
|
88
|
+
def detect_content_type(attributes)
|
89
|
+
# If content_type is explicitly provided, use it
|
90
|
+
return attributes[:content_type] if attributes[:content_type]
|
91
|
+
|
92
|
+
# If content is an array with multipart/mixed content, set appropriate type
|
93
|
+
if attributes[:content].is_a?(Array)
|
94
|
+
# Check if it contains multimodal content (text, image_url, file, etc.)
|
95
|
+
has_multimodal = attributes[:content].any? do |item|
|
96
|
+
item.is_a?(Hash) && (item[:type] || item["type"])
|
97
|
+
end
|
98
|
+
has_multimodal ? "multipart/mixed" : "array"
|
99
|
+
else
|
100
|
+
"text/plain"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
88
104
|
def validate_role
|
89
105
|
unless VALID_ROLES.include?(role.to_s)
|
90
106
|
raise ArgumentError, "Invalid role: #{role}. Valid roles are: #{VALID_ROLES.join(", ")}"
|
@@ -30,26 +30,36 @@ module ActiveAgent
|
|
30
30
|
@action_name = attributes.fetch(:action_name, nil)
|
31
31
|
@mcp_servers = attributes.fetch(:mcp_servers, [])
|
32
32
|
set_message if attributes[:message].is_a?(String) || @body.is_a?(String) && @message&.content
|
33
|
-
|
33
|
+
# Ensure we have a system message with instructions at the start
|
34
|
+
if @messages.empty? || @messages[0].role != :system
|
35
|
+
@messages.unshift(instructions_message)
|
36
|
+
elsif @instructions.present?
|
37
|
+
@messages[0] = instructions_message
|
38
|
+
end
|
34
39
|
end
|
35
40
|
|
36
41
|
def multimodal?
|
37
|
-
@multimodal ||= @message&.content.is_a?(Array) || @messages.any? { |m| m
|
42
|
+
@multimodal ||= @message&.content.is_a?(Array) || @messages.any? { |m| m&.content.is_a?(Array) }
|
38
43
|
end
|
39
44
|
|
40
45
|
def messages=(messages)
|
41
46
|
@messages = messages
|
42
|
-
|
47
|
+
# Only add system message if we have instructions and don't already have a system message
|
48
|
+
if @instructions.present? && (@messages.empty? || @messages.first&.role != :system)
|
49
|
+
set_messages
|
50
|
+
end
|
43
51
|
end
|
44
52
|
|
45
53
|
def instructions=(instructions)
|
46
|
-
|
54
|
+
# Store the instructions even if blank (will use empty string)
|
55
|
+
@instructions = instructions || ""
|
47
56
|
|
48
|
-
|
57
|
+
# Update or add the system message
|
49
58
|
if @messages[0].present? && @messages[0].role == :system
|
50
59
|
@messages[0] = instructions_message
|
51
|
-
|
52
|
-
|
60
|
+
elsif @messages.empty? || @messages[0].role != :system
|
61
|
+
# Only add system message if we don't have one at the start
|
62
|
+
@messages.unshift(instructions_message)
|
53
63
|
end
|
54
64
|
end
|
55
65
|
|
@@ -7,7 +7,6 @@ module ActiveAgent
|
|
7
7
|
included do
|
8
8
|
include ActiveSupport::Callbacks
|
9
9
|
define_callbacks :generation, skip_after_callbacks_if_terminated: true
|
10
|
-
define_callbacks :stream, skip_after_callbacks_if_terminated: true
|
11
10
|
end
|
12
11
|
|
13
12
|
module ClassMethods
|
@@ -20,20 +19,6 @@ module ActiveAgent
|
|
20
19
|
end
|
21
20
|
end
|
22
21
|
end
|
23
|
-
|
24
|
-
# Defines a callback for handling streaming responses during generation
|
25
|
-
def on_stream(*names, &blk)
|
26
|
-
_insert_callbacks(names, blk) do |name, options|
|
27
|
-
set_callback(:stream, :before, name, options)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Helper method to run stream callbacks
|
33
|
-
def run_stream_callbacks(message, delta = nil, stop = false)
|
34
|
-
run_callbacks(:stream) do
|
35
|
-
yield(message, delta, stop) if block_given?
|
36
|
-
end
|
37
22
|
end
|
38
23
|
end
|
39
24
|
end
|
@@ -36,9 +36,12 @@ module ActiveAgent
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def chat_prompt(parameters: prompt_parameters)
|
39
|
-
|
39
|
+
if prompt.options[:stream] || config["stream"]
|
40
|
+
parameters[:stream] = provider_stream
|
41
|
+
@streaming_request_params = parameters
|
42
|
+
end
|
40
43
|
|
41
|
-
chat_response(@client.messages(parameters: parameters))
|
44
|
+
chat_response(@client.messages(parameters: parameters), parameters)
|
42
45
|
end
|
43
46
|
|
44
47
|
protected
|
@@ -120,7 +123,7 @@ module ActiveAgent
|
|
120
123
|
end
|
121
124
|
end
|
122
125
|
|
123
|
-
def chat_response(response)
|
126
|
+
def chat_response(response, request_params = nil)
|
124
127
|
return @response if prompt.options[:stream]
|
125
128
|
|
126
129
|
content = response["content"].first["text"]
|
@@ -137,7 +140,8 @@ module ActiveAgent
|
|
137
140
|
@response = ActiveAgent::GenerationProvider::Response.new(
|
138
141
|
prompt: prompt,
|
139
142
|
message: message,
|
140
|
-
raw_response: response
|
143
|
+
raw_response: response,
|
144
|
+
raw_request: request_params
|
141
145
|
)
|
142
146
|
end
|
143
147
|
|
@@ -46,7 +46,6 @@ module ActiveAgent
|
|
46
46
|
|
47
47
|
def handle_generation_error(error)
|
48
48
|
error_message = format_error_message(error)
|
49
|
-
|
50
49
|
# Create new error with original backtrace preserved
|
51
50
|
new_error = ActiveAgent::GenerationProvider::Base::GenerationProviderError.new(error_message)
|
52
51
|
new_error.set_backtrace(error.backtrace) if error.respond_to?(:backtrace)
|
@@ -61,7 +60,9 @@ module ActiveAgent
|
|
61
60
|
end
|
62
61
|
|
63
62
|
def format_error_message(error)
|
64
|
-
message = if error.respond_to?(:
|
63
|
+
message = if error.respond_to?(:response)
|
64
|
+
error.response[:body]
|
65
|
+
elsif error.respond_to?(:message)
|
65
66
|
error.message
|
66
67
|
elsif error.respond_to?(:to_s)
|
67
68
|
error.to_s
|
@@ -94,12 +94,12 @@ module ActiveAgent
|
|
94
94
|
def format_single_tool_call(action)
|
95
95
|
# Default tool call format (OpenAI style)
|
96
96
|
{
|
97
|
+
id: action.id,
|
97
98
|
type: "function",
|
98
99
|
function: {
|
99
100
|
name: action.name,
|
100
101
|
arguments: action.params.is_a?(String) ? action.params : action.params.to_json
|
101
|
-
}
|
102
|
-
id: action.id
|
102
|
+
}
|
103
103
|
}
|
104
104
|
end
|
105
105
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
begin
|
2
|
-
gem "ruby-openai", "
|
2
|
+
gem "ruby-openai", ">= 8.1.0"
|
3
3
|
require "openai"
|
4
4
|
rescue LoadError
|
5
5
|
raise LoadError, "The 'ruby-openai' gem is required for OpenAIProvider. Please add it to your Gemfile and run `bundle install`."
|
@@ -25,7 +25,13 @@ module ActiveAgent
|
|
25
25
|
@access_token ||= config["api_key"] || config["access_token"] || OpenAI.configuration.access_token || ENV["OPENAI_ACCESS_TOKEN"]
|
26
26
|
@organization_id = config["organization_id"] || OpenAI.configuration.organization_id || ENV["OPENAI_ORGANIZATION_ID"]
|
27
27
|
@admin_token = config["admin_token"] || OpenAI.configuration.admin_token || ENV["OPENAI_ADMIN_TOKEN"]
|
28
|
-
@client = OpenAI::Client.new(
|
28
|
+
@client = OpenAI::Client.new(
|
29
|
+
access_token: @access_token,
|
30
|
+
uri_base: @host,
|
31
|
+
organization_id: @organization_id,
|
32
|
+
admin_token: @admin_token,
|
33
|
+
log_errors: Rails.env.development?
|
34
|
+
)
|
29
35
|
|
30
36
|
@model_name = config["model"] || "gpt-4o-mini"
|
31
37
|
end
|
@@ -63,7 +69,12 @@ module ActiveAgent
|
|
63
69
|
elsif chunk.dig("choices", 0, "delta", "tool_calls") && chunk.dig("choices", 0, "delta", "role")
|
64
70
|
message = handle_message(chunk.dig("choices", 0, "delta"))
|
65
71
|
prompt.messages << message
|
66
|
-
@response = ActiveAgent::GenerationProvider::Response.new(
|
72
|
+
@response = ActiveAgent::GenerationProvider::Response.new(
|
73
|
+
prompt:,
|
74
|
+
message:,
|
75
|
+
raw_response: chunk,
|
76
|
+
raw_request: @streaming_request_params
|
77
|
+
)
|
67
78
|
end
|
68
79
|
|
69
80
|
if chunk.dig("choices", 0, "finish_reason")
|
@@ -86,7 +97,7 @@ module ActiveAgent
|
|
86
97
|
# The format_tools method comes from ToolManagement module
|
87
98
|
# The provider_messages method comes from MessageFormatting module
|
88
99
|
|
89
|
-
def chat_response(response)
|
100
|
+
def chat_response(response, request_params = nil)
|
90
101
|
return @response if prompt.options[:stream]
|
91
102
|
message_json = response.dig("choices", 0, "message")
|
92
103
|
message_json["id"] = response.dig("id") if message_json["id"].blank?
|
@@ -94,10 +105,15 @@ module ActiveAgent
|
|
94
105
|
|
95
106
|
update_context(prompt: prompt, message: message, response: response)
|
96
107
|
|
97
|
-
@response = ActiveAgent::GenerationProvider::Response.new(
|
108
|
+
@response = ActiveAgent::GenerationProvider::Response.new(
|
109
|
+
prompt: prompt,
|
110
|
+
message: message,
|
111
|
+
raw_response: response,
|
112
|
+
raw_request: request_params
|
113
|
+
)
|
98
114
|
end
|
99
115
|
|
100
|
-
def responses_response(response)
|
116
|
+
def responses_response(response, request_params = nil)
|
101
117
|
message_json = response["output"].find { |output_item| output_item["type"] == "message" }
|
102
118
|
message_json["id"] = response.dig("id") if message_json["id"].blank?
|
103
119
|
|
@@ -110,7 +126,12 @@ module ActiveAgent
|
|
110
126
|
content_type: prompt.output_schema.present? ? "application/json" : "text/plain",
|
111
127
|
)
|
112
128
|
|
113
|
-
@response = ActiveAgent::GenerationProvider::Response.new(
|
129
|
+
@response = ActiveAgent::GenerationProvider::Response.new(
|
130
|
+
prompt: prompt,
|
131
|
+
message: message,
|
132
|
+
raw_response: response,
|
133
|
+
raw_request: request_params
|
134
|
+
)
|
114
135
|
end
|
115
136
|
|
116
137
|
def handle_message(message_json)
|
@@ -127,13 +148,16 @@ module ActiveAgent
|
|
127
148
|
# handle_actions is now provided by ToolManagement module
|
128
149
|
|
129
150
|
def chat_prompt(parameters: prompt_parameters)
|
130
|
-
|
131
|
-
|
151
|
+
if prompt.options[:stream] || config["stream"]
|
152
|
+
parameters[:stream] = provider_stream
|
153
|
+
@streaming_request_params = parameters
|
154
|
+
end
|
155
|
+
chat_response(@client.chat(parameters: parameters), parameters)
|
132
156
|
end
|
133
157
|
|
134
158
|
def responses_prompt(parameters: responses_parameters)
|
135
159
|
# parameters[:stream] = provider_stream if prompt.options[:stream] || config["stream"]
|
136
|
-
responses_response(@client.responses.create(parameters: parameters))
|
160
|
+
responses_response(@client.responses.create(parameters: parameters), parameters)
|
137
161
|
end
|
138
162
|
|
139
163
|
def responses_parameters(model: @prompt.options[:model] || @model_name, messages: @prompt.messages, temperature: @prompt.options[:temperature] || @config["temperature"] || 0.7, tools: @prompt.actions, structured_output: @prompt.output_schema)
|
@@ -152,14 +176,20 @@ module ActiveAgent
|
|
152
176
|
}
|
153
177
|
end
|
154
178
|
|
155
|
-
def embeddings_response(response)
|
179
|
+
def embeddings_response(response, request_params = nil)
|
156
180
|
message = ActiveAgent::ActionPrompt::Message.new(content: response.dig("data", 0, "embedding"), role: "assistant")
|
157
181
|
|
158
|
-
@response = ActiveAgent::GenerationProvider::Response.new(
|
182
|
+
@response = ActiveAgent::GenerationProvider::Response.new(
|
183
|
+
prompt: prompt,
|
184
|
+
message: message,
|
185
|
+
raw_response: response,
|
186
|
+
raw_request: request_params
|
187
|
+
)
|
159
188
|
end
|
160
189
|
|
161
190
|
def embeddings_prompt(parameters:)
|
162
|
-
|
191
|
+
params = embeddings_parameters
|
192
|
+
embeddings_response(@client.embeddings(parameters: params), params)
|
163
193
|
end
|
164
194
|
end
|
165
195
|
end
|
@@ -4,31 +4,6 @@ require_relative "open_ai_provider"
|
|
4
4
|
module ActiveAgent
|
5
5
|
module GenerationProvider
|
6
6
|
class OpenRouterProvider < OpenAIProvider
|
7
|
-
# Vision-capable models on OpenRouter
|
8
|
-
VISION_MODELS = [
|
9
|
-
"openai/gpt-4-vision-preview",
|
10
|
-
"openai/gpt-4o",
|
11
|
-
"openai/gpt-4o-mini",
|
12
|
-
"anthropic/claude-3-5-sonnet",
|
13
|
-
"anthropic/claude-3-opus",
|
14
|
-
"anthropic/claude-3-sonnet",
|
15
|
-
"anthropic/claude-3-haiku",
|
16
|
-
"google/gemini-pro-1.5",
|
17
|
-
"google/gemini-pro-vision"
|
18
|
-
].freeze
|
19
|
-
|
20
|
-
# Models that support structured output
|
21
|
-
STRUCTURED_OUTPUT_MODELS = [
|
22
|
-
"openai/gpt-4o",
|
23
|
-
"openai/gpt-4o-2024-08-06",
|
24
|
-
"openai/gpt-4o-mini",
|
25
|
-
"openai/gpt-4o-mini-2024-07-18",
|
26
|
-
"openai/gpt-4-turbo",
|
27
|
-
"openai/gpt-4-turbo-2024-04-09",
|
28
|
-
"openai/gpt-3.5-turbo-0125",
|
29
|
-
"openai/gpt-3.5-turbo-1106"
|
30
|
-
].freeze
|
31
|
-
|
32
7
|
def initialize(config)
|
33
8
|
@config = config
|
34
9
|
@access_token = config["api_key"] || config["access_token"] ||
|
@@ -48,12 +23,22 @@ module ActiveAgent
|
|
48
23
|
# Data collection preference (allow, deny, or specific provider list)
|
49
24
|
@data_collection = config["data_collection"] || @provider_preferences["data_collection"] || "allow"
|
50
25
|
|
26
|
+
# Require parameters preference (defaults to false)
|
27
|
+
@require_parameters = config["require_parameters"] || @provider_preferences["require_parameters"] || false
|
28
|
+
|
29
|
+
# Additional OpenRouter provider routing options
|
30
|
+
@only_providers = config["only"] || @provider_preferences["only"]
|
31
|
+
@ignore_providers = config["ignore"] || @provider_preferences["ignore"]
|
32
|
+
@quantizations = config["quantizations"] || @provider_preferences["quantizations"]
|
33
|
+
@sort_preference = config["sort"] || @provider_preferences["sort"]
|
34
|
+
@max_price = config["max_price"] || @provider_preferences["max_price"]
|
35
|
+
|
51
36
|
# Initialize OpenAI client with OpenRouter base URL
|
52
37
|
@client = OpenAI::Client.new(
|
53
38
|
uri_base: "https://openrouter.ai/api/v1",
|
54
39
|
access_token: @access_token,
|
55
|
-
log_errors:
|
56
|
-
|
40
|
+
log_errors: Rails.env.development?,
|
41
|
+
extra_headers: openrouter_headers
|
57
42
|
)
|
58
43
|
end
|
59
44
|
|
@@ -69,15 +54,6 @@ module ActiveAgent
|
|
69
54
|
handle_openrouter_error(e)
|
70
55
|
end
|
71
56
|
|
72
|
-
# Helper methods for checking model capabilities
|
73
|
-
def supports_vision?(model = @model_name)
|
74
|
-
VISION_MODELS.include?(model)
|
75
|
-
end
|
76
|
-
|
77
|
-
def supports_structured_output?(model = @model_name)
|
78
|
-
STRUCTURED_OUTPUT_MODELS.include?(model)
|
79
|
-
end
|
80
|
-
|
81
57
|
protected
|
82
58
|
|
83
59
|
def build_provider_parameters
|
@@ -88,6 +64,32 @@ module ActiveAgent
|
|
88
64
|
add_openrouter_params(params)
|
89
65
|
end
|
90
66
|
|
67
|
+
def format_content_item(item)
|
68
|
+
# Handle OpenRouter-specific content formats
|
69
|
+
if item.is_a?(Hash)
|
70
|
+
case item[:type] || item["type"]
|
71
|
+
when "file"
|
72
|
+
# Convert file type to image_url for OpenRouter PDF support
|
73
|
+
file_data = item.dig(:file, :file_data) || item.dig("file", "file_data")
|
74
|
+
if file_data
|
75
|
+
{
|
76
|
+
type: "image_url",
|
77
|
+
image_url: {
|
78
|
+
url: file_data
|
79
|
+
}
|
80
|
+
}
|
81
|
+
else
|
82
|
+
item
|
83
|
+
end
|
84
|
+
else
|
85
|
+
# Use default formatting for other types
|
86
|
+
super
|
87
|
+
end
|
88
|
+
else
|
89
|
+
super
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
91
93
|
private
|
92
94
|
|
93
95
|
def default_app_name
|
@@ -152,19 +154,34 @@ module ActiveAgent
|
|
152
154
|
parameters[:transforms] = @transforms if @transforms.present?
|
153
155
|
|
154
156
|
# Add provider preferences (always include if we have data_collection or other settings)
|
155
|
-
# Check both configured and runtime data_collection values
|
157
|
+
# Check both configured and runtime data_collection/require_parameters values
|
156
158
|
runtime_data_collection = prompt&.options&.key?(:data_collection)
|
157
|
-
|
159
|
+
runtime_require_parameters = prompt&.options&.key?(:require_parameters)
|
160
|
+
runtime_provider_options = prompt&.options&.keys&.any? { |k| [ :only, :ignore, :quantizations, :sort, :max_price ].include?(k) }
|
161
|
+
|
162
|
+
if @provider_preferences.present? || @data_collection != "allow" || @require_parameters != false ||
|
163
|
+
@only_providers.present? || @ignore_providers.present? || @quantizations.present? ||
|
164
|
+
@sort_preference.present? || @max_price.present? ||
|
165
|
+
runtime_data_collection || runtime_require_parameters || runtime_provider_options
|
158
166
|
parameters[:provider] = build_provider_preferences
|
159
167
|
end
|
160
168
|
|
169
|
+
# Add plugins (e.g., for PDF processing)
|
170
|
+
|
171
|
+
parameters[:plugins] = prompt.options[:plugins] if prompt.options[:plugins].present?
|
172
|
+
parameters[:models] = prompt.options[:fallback_models] if prompt.options[:enable_fallbacks] && prompt.options[:fallback_models].present?
|
161
173
|
parameters
|
162
174
|
end
|
163
175
|
|
164
176
|
def build_provider_preferences
|
165
177
|
prefs = {}
|
166
178
|
prefs[:order] = @provider_preferences["order"] if @provider_preferences["order"]
|
167
|
-
|
179
|
+
|
180
|
+
# Require parameters can be overridden at runtime
|
181
|
+
require_parameters = prompt.options[:require_parameters] if prompt&.options&.key?(:require_parameters)
|
182
|
+
require_parameters = @require_parameters if require_parameters.nil?
|
183
|
+
prefs[:require_parameters] = require_parameters if require_parameters != false
|
184
|
+
|
168
185
|
prefs[:allow_fallbacks] = @enable_fallbacks
|
169
186
|
|
170
187
|
# Data collection can be:
|
@@ -176,6 +193,27 @@ module ActiveAgent
|
|
176
193
|
data_collection ||= @data_collection
|
177
194
|
prefs[:data_collection] = data_collection
|
178
195
|
|
196
|
+
# Additional OpenRouter provider routing options - check runtime overrides first
|
197
|
+
only_providers = prompt.options[:only] if prompt&.options&.key?(:only)
|
198
|
+
only_providers ||= @only_providers
|
199
|
+
prefs[:only] = only_providers if only_providers.present?
|
200
|
+
|
201
|
+
ignore_providers = prompt.options[:ignore] if prompt&.options&.key?(:ignore)
|
202
|
+
ignore_providers ||= @ignore_providers
|
203
|
+
prefs[:ignore] = ignore_providers if ignore_providers.present?
|
204
|
+
|
205
|
+
quantizations = prompt.options[:quantizations] if prompt&.options&.key?(:quantizations)
|
206
|
+
quantizations ||= @quantizations
|
207
|
+
prefs[:quantizations] = quantizations if quantizations.present?
|
208
|
+
|
209
|
+
sort_preference = prompt.options[:sort] if prompt&.options&.key?(:sort)
|
210
|
+
sort_preference ||= @sort_preference
|
211
|
+
prefs[:sort] = sort_preference if sort_preference.present?
|
212
|
+
|
213
|
+
max_price = prompt.options[:max_price] if prompt&.options&.key?(:max_price)
|
214
|
+
max_price ||= @max_price
|
215
|
+
prefs[:max_price] = max_price if max_price.present?
|
216
|
+
|
179
217
|
prefs.compact
|
180
218
|
end
|
181
219
|
|
@@ -190,9 +228,15 @@ module ActiveAgent
|
|
190
228
|
params[:transforms] = @transforms if @transforms.present?
|
191
229
|
|
192
230
|
# Add provider configuration (always include if we have data_collection or other settings)
|
193
|
-
# Check both configured and runtime data_collection values
|
231
|
+
# Check both configured and runtime data_collection/require_parameters values
|
194
232
|
runtime_data_collection = prompt&.options&.key?(:data_collection)
|
195
|
-
|
233
|
+
runtime_require_parameters = prompt&.options&.key?(:require_parameters)
|
234
|
+
runtime_provider_options = prompt&.options&.keys&.any? { |k| [ :only, :ignore, :quantizations, :sort, :max_price ].include?(k) }
|
235
|
+
|
236
|
+
if @provider_preferences.present? || @data_collection != "allow" || @require_parameters != false ||
|
237
|
+
@only_providers.present? || @ignore_providers.present? || @quantizations.present? ||
|
238
|
+
@sort_preference.present? || @max_price.present? ||
|
239
|
+
runtime_data_collection || runtime_require_parameters || runtime_provider_options
|
196
240
|
params[:provider] = build_provider_preferences
|
197
241
|
end
|
198
242
|
|
@@ -208,7 +252,6 @@ module ActiveAgent
|
|
208
252
|
parameters[:stream] = provider_stream if prompt.options[:stream] || config["stream"]
|
209
253
|
|
210
254
|
response = @client.chat(parameters: parameters)
|
211
|
-
|
212
255
|
# Log if fallback was used
|
213
256
|
if response.respond_to?(:headers) && response.headers["x-model"] != @model_name
|
214
257
|
Rails.logger.info "[OpenRouter] Fallback model used: #{response.headers['x-model']}" if defined?(Rails)
|
@@ -229,7 +272,6 @@ module ActiveAgent
|
|
229
272
|
message = handle_message(message_json) if message_json
|
230
273
|
|
231
274
|
update_context(prompt: prompt, message: message, response: response) if message
|
232
|
-
|
233
275
|
# Create response with OpenRouter metadata
|
234
276
|
@response = ActiveAgent::GenerationProvider::Response.new(
|
235
277
|
prompt: prompt,
|
@@ -315,7 +357,7 @@ module ActiveAgent
|
|
315
357
|
handle_timeout_error(error)
|
316
358
|
else
|
317
359
|
# Fall back to parent error handling
|
318
|
-
|
360
|
+
raise GenerationProviderError, error, error.backtrace
|
319
361
|
end
|
320
362
|
end
|
321
363
|
|
@@ -46,7 +46,7 @@ module ActiveAgent
|
|
46
46
|
options = {}
|
47
47
|
|
48
48
|
# Common options that map directly
|
49
|
-
[ :stream, :top_p, :frequency_penalty, :presence_penalty, :seed, :stop, :user ].each do |key|
|
49
|
+
[ :stream, :top_p, :frequency_penalty, :presence_penalty, :seed, :stop, :user, :plugins ].each do |key|
|
50
50
|
options[key] = @prompt.options[key] if @prompt.options.key?(key)
|
51
51
|
end
|
52
52
|
|
@@ -3,13 +3,14 @@
|
|
3
3
|
module ActiveAgent
|
4
4
|
module GenerationProvider
|
5
5
|
class Response
|
6
|
-
attr_reader :message, :prompt, :raw_response
|
6
|
+
attr_reader :message, :prompt, :raw_response, :raw_request
|
7
7
|
attr_accessor :metadata
|
8
8
|
|
9
|
-
def initialize(prompt:, message: nil, raw_response: nil, metadata: nil)
|
9
|
+
def initialize(prompt:, message: nil, raw_response: nil, raw_request: nil, metadata: nil)
|
10
10
|
@prompt = prompt
|
11
11
|
@message = message || prompt.message
|
12
12
|
@raw_response = raw_response
|
13
|
+
@raw_request = sanitize_request(raw_request)
|
13
14
|
@metadata = metadata || {}
|
14
15
|
end
|
15
16
|
|
@@ -17,14 +18,9 @@ module ActiveAgent
|
|
17
18
|
def usage
|
18
19
|
return nil unless @raw_response
|
19
20
|
|
20
|
-
#
|
21
|
+
# Most providers store usage in the same format
|
21
22
|
if @raw_response.is_a?(Hash) && @raw_response["usage"]
|
22
23
|
@raw_response["usage"]
|
23
|
-
# Anthropic format
|
24
|
-
elsif @raw_response.is_a?(Hash) && @raw_response["usage"]
|
25
|
-
@raw_response["usage"]
|
26
|
-
else
|
27
|
-
nil
|
28
24
|
end
|
29
25
|
end
|
30
26
|
|
@@ -40,6 +36,40 @@ module ActiveAgent
|
|
40
36
|
def total_tokens
|
41
37
|
usage&.dig("total_tokens")
|
42
38
|
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def sanitize_request(request)
|
43
|
+
return nil if request.nil?
|
44
|
+
return request unless request.is_a?(Hash)
|
45
|
+
|
46
|
+
# Deep clone the request to avoid modifying the original
|
47
|
+
sanitized = request.deep_dup
|
48
|
+
|
49
|
+
# Sanitize any string values in the request
|
50
|
+
sanitize_hash_values(sanitized)
|
51
|
+
end
|
52
|
+
|
53
|
+
def sanitize_hash_values(hash)
|
54
|
+
hash.each do |key, value|
|
55
|
+
case value
|
56
|
+
when String
|
57
|
+
# Use ActiveAgent's sanitize_credentials to replace sensitive data
|
58
|
+
hash[key] = ActiveAgent.sanitize_credentials(value)
|
59
|
+
when Hash
|
60
|
+
sanitize_hash_values(value)
|
61
|
+
when Array
|
62
|
+
value.each_with_index do |item, index|
|
63
|
+
if item.is_a?(String)
|
64
|
+
value[index] = ActiveAgent.sanitize_credentials(item)
|
65
|
+
elsif item.is_a?(Hash)
|
66
|
+
sanitize_hash_values(item)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
hash
|
72
|
+
end
|
43
73
|
end
|
44
74
|
end
|
45
75
|
end
|
data/lib/active_agent/railtie.rb
CHANGED
@@ -68,7 +68,13 @@ module ActiveAgent
|
|
68
68
|
|
69
69
|
initializer "active_agent.compile_config_methods" do
|
70
70
|
ActiveSupport.on_load(:active_agent) do
|
71
|
-
config.compile_methods! if config.respond_to?(:compile_methods!)
|
71
|
+
config.compile_methods! if config.class.respond_to?(:compile_methods!)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
initializer "active_agent.inflections" do
|
76
|
+
ActiveSupport::Inflector.inflections do |inflect|
|
77
|
+
inflect.acronym "AI"
|
72
78
|
end
|
73
79
|
end
|
74
80
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveAgent
|
4
|
+
module Streaming
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class StreamChunk < Data.define(:delta, :stop)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :stream_chunk
|
11
|
+
|
12
|
+
included do
|
13
|
+
include ActiveSupport::Callbacks
|
14
|
+
define_callbacks :stream, skip_after_callbacks_if_terminated: true
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# Defines a callback for handling streaming responses during generation
|
19
|
+
def on_stream(*names, &blk)
|
20
|
+
_insert_callbacks(names, blk) do |name, options|
|
21
|
+
set_callback(:stream, :before, name, options)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Helper method to run stream callbacks
|
27
|
+
def run_stream_callbacks(message, delta = nil, stop = false)
|
28
|
+
@stream_chunk = StreamChunk.new(delta, stop)
|
29
|
+
run_callbacks(:stream) do
|
30
|
+
yield(message, delta, stop) if block_given?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/active_agent/version.rb
CHANGED
data/lib/active_agent.rb
CHANGED
@@ -9,7 +9,6 @@ module Erb # :nodoc:
|
|
9
9
|
def create_agent_layouts
|
10
10
|
if behavior == :invoke
|
11
11
|
formats.each do |format|
|
12
|
-
puts format
|
13
12
|
layout_path = File.join("app/views/layouts", filename_with_extensions("agent", format))
|
14
13
|
template filename_with_extensions(:layout, format), layout_path unless File.exist?(layout_path)
|
15
14
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activeagent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.0rc4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Bowen
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
version: '7.2'
|
19
19
|
- - "<="
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
21
|
+
version: '9.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -28,7 +28,7 @@ dependencies:
|
|
28
28
|
version: '7.2'
|
29
29
|
- - "<="
|
30
30
|
- !ruby/object:Gem::Version
|
31
|
-
version:
|
31
|
+
version: '9.0'
|
32
32
|
- !ruby/object:Gem::Dependency
|
33
33
|
name: actionview
|
34
34
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,7 +38,7 @@ dependencies:
|
|
38
38
|
version: '7.2'
|
39
39
|
- - "<="
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version:
|
41
|
+
version: '9.0'
|
42
42
|
type: :runtime
|
43
43
|
prerelease: false
|
44
44
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -48,7 +48,7 @@ dependencies:
|
|
48
48
|
version: '7.2'
|
49
49
|
- - "<="
|
50
50
|
- !ruby/object:Gem::Version
|
51
|
-
version:
|
51
|
+
version: '9.0'
|
52
52
|
- !ruby/object:Gem::Dependency
|
53
53
|
name: activesupport
|
54
54
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,7 +58,7 @@ dependencies:
|
|
58
58
|
version: '7.2'
|
59
59
|
- - "<="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: '9.0'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -68,7 +68,7 @@ dependencies:
|
|
68
68
|
version: '7.2'
|
69
69
|
- - "<="
|
70
70
|
- !ruby/object:Gem::Version
|
71
|
-
version:
|
71
|
+
version: '9.0'
|
72
72
|
- !ruby/object:Gem::Dependency
|
73
73
|
name: activemodel
|
74
74
|
requirement: !ruby/object:Gem::Requirement
|
@@ -78,7 +78,7 @@ dependencies:
|
|
78
78
|
version: '7.2'
|
79
79
|
- - "<="
|
80
80
|
- !ruby/object:Gem::Version
|
81
|
-
version:
|
81
|
+
version: '9.0'
|
82
82
|
type: :runtime
|
83
83
|
prerelease: false
|
84
84
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -88,7 +88,7 @@ dependencies:
|
|
88
88
|
version: '7.2'
|
89
89
|
- - "<="
|
90
90
|
- !ruby/object:Gem::Version
|
91
|
-
version:
|
91
|
+
version: '9.0'
|
92
92
|
- !ruby/object:Gem::Dependency
|
93
93
|
name: activejob
|
94
94
|
requirement: !ruby/object:Gem::Requirement
|
@@ -98,7 +98,7 @@ dependencies:
|
|
98
98
|
version: '7.2'
|
99
99
|
- - "<="
|
100
100
|
- !ruby/object:Gem::Version
|
101
|
-
version:
|
101
|
+
version: '9.0'
|
102
102
|
type: :runtime
|
103
103
|
prerelease: false
|
104
104
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -108,35 +108,147 @@ dependencies:
|
|
108
108
|
version: '7.2'
|
109
109
|
- - "<="
|
110
110
|
- !ruby/object:Gem::Version
|
111
|
-
version:
|
111
|
+
version: '9.0'
|
112
112
|
- !ruby/object:Gem::Dependency
|
113
113
|
name: jbuilder
|
114
114
|
requirement: !ruby/object:Gem::Requirement
|
115
115
|
requirements:
|
116
116
|
- - "~>"
|
117
117
|
- !ruby/object:Gem::Version
|
118
|
-
version: 2.14
|
118
|
+
version: '2.14'
|
119
119
|
type: :development
|
120
120
|
prerelease: false
|
121
121
|
version_requirements: !ruby/object:Gem::Requirement
|
122
122
|
requirements:
|
123
123
|
- - "~>"
|
124
124
|
- !ruby/object:Gem::Version
|
125
|
-
version: 2.14
|
125
|
+
version: '2.14'
|
126
126
|
- !ruby/object:Gem::Dependency
|
127
127
|
name: rails
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
- !ruby/object:Gem::Dependency
|
141
|
+
name: ruby-openai
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: 8.1.0
|
147
|
+
type: :development
|
148
|
+
prerelease: false
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: 8.1.0
|
154
|
+
- !ruby/object:Gem::Dependency
|
155
|
+
name: ruby-anthropic
|
128
156
|
requirement: !ruby/object:Gem::Requirement
|
129
157
|
requirements:
|
130
158
|
- - "~>"
|
131
159
|
- !ruby/object:Gem::Version
|
132
|
-
version:
|
160
|
+
version: 0.4.2
|
133
161
|
type: :development
|
134
162
|
prerelease: false
|
135
163
|
version_requirements: !ruby/object:Gem::Requirement
|
136
164
|
requirements:
|
137
165
|
- - "~>"
|
138
166
|
- !ruby/object:Gem::Version
|
139
|
-
version:
|
167
|
+
version: 0.4.2
|
168
|
+
- !ruby/object:Gem::Dependency
|
169
|
+
name: standard
|
170
|
+
requirement: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
type: :development
|
176
|
+
prerelease: false
|
177
|
+
version_requirements: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - ">="
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
- !ruby/object:Gem::Dependency
|
183
|
+
name: rubocop-rails-omakase
|
184
|
+
requirement: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: '0'
|
189
|
+
type: :development
|
190
|
+
prerelease: false
|
191
|
+
version_requirements: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
193
|
+
- - ">="
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '0'
|
196
|
+
- !ruby/object:Gem::Dependency
|
197
|
+
name: puma
|
198
|
+
requirement: !ruby/object:Gem::Requirement
|
199
|
+
requirements:
|
200
|
+
- - ">="
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '0'
|
203
|
+
type: :development
|
204
|
+
prerelease: false
|
205
|
+
version_requirements: !ruby/object:Gem::Requirement
|
206
|
+
requirements:
|
207
|
+
- - ">="
|
208
|
+
- !ruby/object:Gem::Version
|
209
|
+
version: '0'
|
210
|
+
- !ruby/object:Gem::Dependency
|
211
|
+
name: sqlite3
|
212
|
+
requirement: !ruby/object:Gem::Requirement
|
213
|
+
requirements:
|
214
|
+
- - ">="
|
215
|
+
- !ruby/object:Gem::Version
|
216
|
+
version: '0'
|
217
|
+
type: :development
|
218
|
+
prerelease: false
|
219
|
+
version_requirements: !ruby/object:Gem::Requirement
|
220
|
+
requirements:
|
221
|
+
- - ">="
|
222
|
+
- !ruby/object:Gem::Version
|
223
|
+
version: '0'
|
224
|
+
- !ruby/object:Gem::Dependency
|
225
|
+
name: vcr
|
226
|
+
requirement: !ruby/object:Gem::Requirement
|
227
|
+
requirements:
|
228
|
+
- - ">="
|
229
|
+
- !ruby/object:Gem::Version
|
230
|
+
version: '0'
|
231
|
+
type: :development
|
232
|
+
prerelease: false
|
233
|
+
version_requirements: !ruby/object:Gem::Requirement
|
234
|
+
requirements:
|
235
|
+
- - ">="
|
236
|
+
- !ruby/object:Gem::Version
|
237
|
+
version: '0'
|
238
|
+
- !ruby/object:Gem::Dependency
|
239
|
+
name: webmock
|
240
|
+
requirement: !ruby/object:Gem::Requirement
|
241
|
+
requirements:
|
242
|
+
- - ">="
|
243
|
+
- !ruby/object:Gem::Version
|
244
|
+
version: '0'
|
245
|
+
type: :development
|
246
|
+
prerelease: false
|
247
|
+
version_requirements: !ruby/object:Gem::Requirement
|
248
|
+
requirements:
|
249
|
+
- - ">="
|
250
|
+
- !ruby/object:Gem::Version
|
251
|
+
version: '0'
|
140
252
|
description: The only agent-oriented AI framework designed for Rails, where Agents
|
141
253
|
are Controllers. Build AI features with less complexity using the MVC conventions
|
142
254
|
you love.
|
@@ -186,6 +298,7 @@ files:
|
|
186
298
|
- lib/active_agent/rescuable.rb
|
187
299
|
- lib/active_agent/sanitizers.rb
|
188
300
|
- lib/active_agent/service.rb
|
301
|
+
- lib/active_agent/streaming.rb
|
189
302
|
- lib/active_agent/test_case.rb
|
190
303
|
- lib/active_agent/version.rb
|
191
304
|
- lib/activeagent.rb
|