rcrewai-rails 0.2.8 → 0.3.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.
- checksums.yaml +4 -4
- data/app/jobs/rcrewai/rails/crew_execution_job.rb +39 -29
- data/app/jobs/rcrewai/rails/task_execution_job.rb +14 -6
- data/app/models/rcrewai/rails/agent.rb +2 -4
- data/app/models/rcrewai/rails/crew.rb +2 -7
- data/app/models/rcrewai/rails/task.rb +9 -4
- data/lib/generators/rcrew_a_i/rails/crew/crew_generator.rb +42 -0
- data/lib/generators/rcrew_a_i/rails/crew/templates/agent.rb.erb +45 -0
- data/lib/generators/rcrew_a_i/rails/crew/templates/crew.rb.erb +72 -0
- data/lib/generators/rcrew_a_i/rails/install/install_generator.rb +40 -0
- data/lib/generators/rcrew_a_i/rails/install/templates/create_rcrewai_tables.rb +109 -0
- data/lib/generators/rcrew_a_i/rails/install/templates/rcrewai.rb +53 -0
- data/lib/generators/rcrewai/rails/install/install_generator.rb +20 -15
- data/lib/rcrewai/rails/agent_builder.rb +5 -7
- data/lib/rcrewai/rails/engine.rb +4 -7
- data/lib/rcrewai/rails/tools/action_mailer_tool.rb +39 -26
- data/lib/rcrewai/rails/tools/active_record_tool.rb +40 -30
- data/lib/rcrewai/rails/tools/active_storage_tool.rb +35 -33
- data/lib/rcrewai/rails/tools/rails_cache_tool.rb +29 -30
- data/lib/rcrewai/rails/tools/rails_logger_tool.rb +24 -23
- data/lib/rcrewai/rails/version.rb +1 -4
- data/rcrewai-rails.gemspec +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d2402d751d591a36e40134763d02f9c45d3b9fe3f3e38bfbcda988f08764269
|
|
4
|
+
data.tar.gz: d1e38e88b3effbba297ea931df56a7499912d8c2f7c7d4dd02b1b9c4e71fba1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bdaa9583e0b28908b9b44ae811dcb9c48e3fa5e25f929028c09e26911abc825f2867245b83a62112676c8b431cd83c7185320d43c65ee683b5e7d415bb9a6529
|
|
7
|
+
data.tar.gz: 425ebe18b1e13d9963887e5906f47574ade7c882347d4086ff1f9b32b9814424379f6e9193248e7febc433649ff63624008bcc9434473653e4d345be21f2729d
|
|
@@ -13,52 +13,63 @@ module RcrewAI
|
|
|
13
13
|
|
|
14
14
|
begin
|
|
15
15
|
execution.start!
|
|
16
|
-
execution.log("info", "Starting crew execution", { crew_id: crew.id })
|
|
16
|
+
execution.log("info", "Starting crew execution", { crew_id: crew.id, inputs: inputs })
|
|
17
17
|
|
|
18
|
-
# Convert Rails models to RcrewAI objects
|
|
19
18
|
rcrew = crew.to_rcrew
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
|
|
20
|
+
result = rcrew.execute(stream: stream_sink_for(execution))
|
|
21
|
+
|
|
24
22
|
execution.complete!(result)
|
|
25
|
-
execution.log("info", "Crew execution completed
|
|
26
|
-
|
|
27
|
-
# Trigger callbacks if configured
|
|
23
|
+
execution.log("info", "Crew execution completed", { result: result })
|
|
24
|
+
|
|
28
25
|
notify_completion(crew, execution, result)
|
|
29
|
-
|
|
26
|
+
|
|
30
27
|
result
|
|
31
28
|
rescue => e
|
|
32
29
|
execution.fail!(e)
|
|
33
|
-
execution.log("error", "Crew execution failed", {
|
|
30
|
+
execution.log("error", "Crew execution failed", {
|
|
34
31
|
error: e.message,
|
|
35
|
-
backtrace: e.backtrace
|
|
32
|
+
backtrace: e.backtrace&.first(5)
|
|
36
33
|
})
|
|
37
|
-
|
|
38
|
-
# Re-raise for ActiveJob retry mechanism
|
|
34
|
+
|
|
39
35
|
raise
|
|
40
36
|
end
|
|
41
37
|
end
|
|
42
38
|
|
|
43
39
|
private
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
# Build a stream sink that translates rcrewai events into ExecutionLog rows.
|
|
42
|
+
# Note: the gem currently builds the sink but does not yet thread it down
|
|
43
|
+
# to per-agent execution, so this is wired up for forward-compatibility.
|
|
44
|
+
def stream_sink_for(execution)
|
|
45
|
+
lambda do |event|
|
|
46
|
+
case event
|
|
47
|
+
when RCrewAI::Events::IterationStart
|
|
48
|
+
execution.log("debug", "Iteration #{event.iteration_index} start", { agent: event.agent })
|
|
49
|
+
when RCrewAI::Events::IterationEnd
|
|
50
|
+
execution.log("debug", "Iteration end", { agent: event.agent, finish_reason: event.finish_reason })
|
|
51
|
+
when RCrewAI::Events::ToolCallStart
|
|
52
|
+
execution.log("info", "Tool call: #{event.tool}", { args: event.args, agent: event.agent })
|
|
53
|
+
when RCrewAI::Events::ToolCallResult
|
|
54
|
+
execution.log("info", "Tool result: #{event.tool}", { duration_ms: event.duration_ms, agent: event.agent })
|
|
55
|
+
when RCrewAI::Events::ToolCallError
|
|
56
|
+
execution.log("error", "Tool error: #{event.tool}", { error: event.error, agent: event.agent })
|
|
57
|
+
when RCrewAI::Events::Usage
|
|
58
|
+
execution.log("debug", "Usage", {
|
|
59
|
+
prompt_tokens: event.prompt_tokens,
|
|
60
|
+
completion_tokens: event.completion_tokens,
|
|
61
|
+
total_tokens: event.total_tokens,
|
|
62
|
+
cost_usd: event.cost_usd,
|
|
63
|
+
agent: event.agent
|
|
64
|
+
})
|
|
65
|
+
when RCrewAI::Events::Error
|
|
66
|
+
execution.log("error", "Crew error", { error: event.error, agent: event.agent })
|
|
67
|
+
end
|
|
53
68
|
end
|
|
54
|
-
|
|
55
|
-
# Execute the crew
|
|
56
|
-
rcrew.kickoff(inputs)
|
|
57
69
|
end
|
|
58
70
|
|
|
59
71
|
def notify_completion(crew, execution, result)
|
|
60
|
-
|
|
61
|
-
if crew.notification_webhook_url.present?
|
|
72
|
+
if crew.respond_to?(:notification_webhook_url) && crew.notification_webhook_url.present?
|
|
62
73
|
NotificationJob.perform_later(
|
|
63
74
|
crew.notification_webhook_url,
|
|
64
75
|
{
|
|
@@ -70,7 +81,6 @@ module RcrewAI
|
|
|
70
81
|
)
|
|
71
82
|
end
|
|
72
83
|
|
|
73
|
-
# Trigger Rails events
|
|
74
84
|
ActiveSupport::Notifications.instrument("crew_execution.completed", {
|
|
75
85
|
crew: crew,
|
|
76
86
|
execution: execution,
|
|
@@ -79,4 +89,4 @@ module RcrewAI
|
|
|
79
89
|
end
|
|
80
90
|
end
|
|
81
91
|
end
|
|
82
|
-
end
|
|
92
|
+
end
|
|
@@ -17,17 +17,25 @@ module RcrewAI
|
|
|
17
17
|
rcrew_task = task.to_rcrew_task
|
|
18
18
|
rcrew_agent = agent.to_rcrew_agent
|
|
19
19
|
|
|
20
|
-
# Execute the task
|
|
21
|
-
|
|
20
|
+
# Execute the task. Agent#execute_task returns a hash:
|
|
21
|
+
# { content:, tool_calls_history:, usage:, iterations:, finish_reason: }
|
|
22
|
+
# `inputs` is recorded by the caller and made available as task context
|
|
23
|
+
# via the Task#context column; the gem does not accept it as a kwarg.
|
|
24
|
+
result = rcrew_agent.execute_task(rcrew_task)
|
|
25
|
+
content = result.is_a?(Hash) ? result[:content].to_s : result.to_s
|
|
22
26
|
|
|
23
27
|
execution_log[:completed_at] = Time.current
|
|
24
28
|
execution_log[:status] = "completed"
|
|
25
|
-
execution_log[:result] =
|
|
29
|
+
execution_log[:result] = content
|
|
30
|
+
if result.is_a?(Hash)
|
|
31
|
+
execution_log[:usage] = result[:usage]
|
|
32
|
+
execution_log[:tool_calls] = result[:tool_calls_history]
|
|
33
|
+
execution_log[:iterations] = result[:iterations]
|
|
34
|
+
execution_log[:finish_reason] = result[:finish_reason]
|
|
35
|
+
end
|
|
26
36
|
|
|
27
37
|
# Save result if configured
|
|
28
|
-
if task.output_file.present?
|
|
29
|
-
save_output_to_file(task.output_file, result)
|
|
30
|
-
end
|
|
38
|
+
save_output_to_file(task.output_file, content) if task.output_file.present?
|
|
31
39
|
|
|
32
40
|
# Log success
|
|
33
41
|
::Rails.logger.info "Task #{task.id} completed successfully by agent #{agent.id}"
|
|
@@ -17,16 +17,14 @@ module RcrewAI
|
|
|
17
17
|
|
|
18
18
|
def to_rcrew_agent
|
|
19
19
|
RCrewAI::Agent.new(
|
|
20
|
+
name: name,
|
|
20
21
|
role: role,
|
|
21
22
|
goal: goal,
|
|
22
23
|
backstory: backstory,
|
|
23
|
-
memory: memory_enabled,
|
|
24
24
|
verbose: verbose,
|
|
25
25
|
allow_delegation: allow_delegation,
|
|
26
26
|
tools: instantiated_tools,
|
|
27
|
-
|
|
28
|
-
max_rpm: max_rpm,
|
|
29
|
-
llm: llm_config
|
|
27
|
+
max_iterations: max_iterations
|
|
30
28
|
)
|
|
31
29
|
end
|
|
32
30
|
|
|
@@ -18,14 +18,9 @@ module RcrewAI
|
|
|
18
18
|
|
|
19
19
|
def to_rcrew
|
|
20
20
|
crew = RCrewAI::Crew.new(
|
|
21
|
-
name
|
|
22
|
-
description: description,
|
|
21
|
+
name,
|
|
23
22
|
process: process_type.to_sym,
|
|
24
|
-
verbose: verbose
|
|
25
|
-
memory: memory_enabled,
|
|
26
|
-
cache: cache_enabled,
|
|
27
|
-
max_rpm: max_rpm,
|
|
28
|
-
manager_llm: manager_llm
|
|
23
|
+
verbose: verbose
|
|
29
24
|
)
|
|
30
25
|
|
|
31
26
|
agents.each do |agent|
|
|
@@ -20,14 +20,12 @@ module RcrewAI
|
|
|
20
20
|
|
|
21
21
|
def to_rcrew_task
|
|
22
22
|
RCrewAI::Task.new(
|
|
23
|
+
name: rcrew_task_name,
|
|
23
24
|
description: description,
|
|
24
25
|
expected_output: expected_output,
|
|
25
26
|
agent: agent&.to_rcrew_agent,
|
|
26
27
|
context: context,
|
|
27
|
-
|
|
28
|
-
output_json: output_json,
|
|
29
|
-
output_pydantic: output_pydantic,
|
|
30
|
-
output_file: output_file,
|
|
28
|
+
async: async_execution,
|
|
31
29
|
tools: instantiated_tools,
|
|
32
30
|
callback: callback_method
|
|
33
31
|
)
|
|
@@ -54,6 +52,13 @@ module RcrewAI
|
|
|
54
52
|
|
|
55
53
|
private
|
|
56
54
|
|
|
55
|
+
def rcrew_task_name
|
|
56
|
+
return "task_#{id}" if id
|
|
57
|
+
return description.to_s.parameterize.first(40).presence || "task" if description.present?
|
|
58
|
+
|
|
59
|
+
"task"
|
|
60
|
+
end
|
|
61
|
+
|
|
57
62
|
def callback_method
|
|
58
63
|
return nil unless callback_class.present? && callback_method_name.present?
|
|
59
64
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
|
|
3
|
+
module RcrewAI
|
|
4
|
+
module Rails
|
|
5
|
+
module Generators
|
|
6
|
+
class CrewGenerator < ::Rails::Generators::NamedBase
|
|
7
|
+
source_root File.expand_path("templates", __dir__)
|
|
8
|
+
|
|
9
|
+
argument :process_type, type: :string, default: "sequential", banner: "sequential|hierarchical"
|
|
10
|
+
|
|
11
|
+
class_option :agents, type: :array, default: [], desc: "List of agents to create"
|
|
12
|
+
class_option :description, type: :string, desc: "Crew description"
|
|
13
|
+
|
|
14
|
+
def create_crew_file
|
|
15
|
+
template "crew.rb.erb", "app/crews/#{file_name}_crew.rb"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create_agent_files
|
|
19
|
+
options[:agents].each do |agent_name|
|
|
20
|
+
@agent_name = agent_name
|
|
21
|
+
template "agent.rb.erb", "app/crews/agents/#{agent_name.underscore}_agent.rb"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def display_next_steps
|
|
26
|
+
say "\n✅ Created #{class_name}Crew!", :green
|
|
27
|
+
say "\nNext steps:", :yellow
|
|
28
|
+
say " 1. Configure your crew in app/crews/#{file_name}_crew.rb"
|
|
29
|
+
say " 2. Define tasks for your crew"
|
|
30
|
+
say " 3. Run your crew with: #{class_name}Crew.new.execute"
|
|
31
|
+
say "\n"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def crew_description
|
|
37
|
+
options[:description] || "#{class_name} crew for AI task orchestration"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
class <%= @agent_name.camelize %>Agent
|
|
2
|
+
include RcrewAI::Rails::AgentBuilder
|
|
3
|
+
|
|
4
|
+
def initialize
|
|
5
|
+
@agent = RcrewAI::Agent.new(
|
|
6
|
+
role: role,
|
|
7
|
+
goal: goal,
|
|
8
|
+
backstory: backstory,
|
|
9
|
+
memory: true,
|
|
10
|
+
verbose: true,
|
|
11
|
+
allow_delegation: false,
|
|
12
|
+
tools: tools,
|
|
13
|
+
max_iter: 25,
|
|
14
|
+
max_rpm: 10
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def role
|
|
19
|
+
"<%= @agent_name.humanize %> Specialist"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def goal
|
|
23
|
+
"Perform <%= @agent_name.humanize.downcase %> tasks efficiently and accurately"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def backstory
|
|
27
|
+
<<~BACKSTORY
|
|
28
|
+
You are a highly skilled <%= @agent_name.humanize.downcase %> specialist with extensive experience.
|
|
29
|
+
Your expertise allows you to handle complex <%= @agent_name.humanize.downcase %> challenges effectively.
|
|
30
|
+
BACKSTORY
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def tools
|
|
34
|
+
[
|
|
35
|
+
# Add your tools here
|
|
36
|
+
# RcrewAI::Tools::WebSearch.new,
|
|
37
|
+
# RcrewAI::Tools::FileReader.new,
|
|
38
|
+
# CustomTool.new
|
|
39
|
+
]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_agent
|
|
43
|
+
@agent
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
class <%= class_name %>Crew
|
|
2
|
+
include RcrewAI::Rails::CrewBuilder
|
|
3
|
+
|
|
4
|
+
def initialize
|
|
5
|
+
@crew = RcrewAI::Rails::Crew.create!(
|
|
6
|
+
name: "<%= file_name %>",
|
|
7
|
+
description: "<%= crew_description %>",
|
|
8
|
+
process_type: "<%= process_type %>",
|
|
9
|
+
verbose: true,
|
|
10
|
+
memory_enabled: false,
|
|
11
|
+
cache_enabled: true
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
setup_agents
|
|
15
|
+
setup_tasks
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def execute(inputs = {})
|
|
19
|
+
# Execute asynchronously via ActiveJob
|
|
20
|
+
@crew.execute_async(inputs)
|
|
21
|
+
|
|
22
|
+
# Or execute synchronously:
|
|
23
|
+
# @crew.execute_sync(inputs)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def setup_agents
|
|
29
|
+
# Define your agents here
|
|
30
|
+
# Example:
|
|
31
|
+
# @researcher = @crew.agents.create!(
|
|
32
|
+
# name: "researcher",
|
|
33
|
+
# role: "Senior Research Analyst",
|
|
34
|
+
# goal: "Uncover cutting-edge developments in AI and data science",
|
|
35
|
+
# backstory: "You are an expert researcher with years of experience",
|
|
36
|
+
# verbose: true,
|
|
37
|
+
# allow_delegation: false
|
|
38
|
+
# )
|
|
39
|
+
<% options[:agents].each do |agent_name| %>
|
|
40
|
+
|
|
41
|
+
@<%= agent_name.underscore %> = @crew.agents.create!(
|
|
42
|
+
name: "<%= agent_name.underscore %>",
|
|
43
|
+
role: "<%= agent_name.humanize %> Agent",
|
|
44
|
+
goal: "Perform <%= agent_name.humanize.downcase %> tasks",
|
|
45
|
+
backstory: "You are an expert <%= agent_name.humanize.downcase %> agent",
|
|
46
|
+
verbose: true,
|
|
47
|
+
allow_delegation: false
|
|
48
|
+
)
|
|
49
|
+
<% end %>
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def setup_tasks
|
|
53
|
+
# Define your tasks here
|
|
54
|
+
# Example:
|
|
55
|
+
# @research_task = @crew.tasks.create!(
|
|
56
|
+
# description: "Research the latest AI trends",
|
|
57
|
+
# expected_output: "A comprehensive report on AI trends",
|
|
58
|
+
# position: 1
|
|
59
|
+
# )
|
|
60
|
+
# @research_task.agents << @researcher
|
|
61
|
+
|
|
62
|
+
# Task dependencies can be set like:
|
|
63
|
+
# @analysis_task.add_dependency(@research_task)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Add any helper methods here
|
|
67
|
+
|
|
68
|
+
# Example callback for task completion:
|
|
69
|
+
# def task_completed(output)
|
|
70
|
+
# Rails.logger.info "Task completed with output: #{output}"
|
|
71
|
+
# end
|
|
72
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
require "rails/generators/migration"
|
|
3
|
+
|
|
4
|
+
module RcrewAI
|
|
5
|
+
module Rails
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
8
|
+
include ::Rails::Generators::Migration
|
|
9
|
+
|
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
|
11
|
+
|
|
12
|
+
def self.next_migration_number(dirname)
|
|
13
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create_migration_file
|
|
17
|
+
migration_template "create_rcrewai_tables.rb", "db/migrate/create_rcrewai_tables.rb", migration_class_name: "CreateRcrewaiTables"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create_initializer
|
|
21
|
+
template "rcrewai.rb", "config/initializers/rcrewai.rb"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add_routes
|
|
25
|
+
route "mount RcrewAI::Rails::Engine => '/rcrewai'"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def display_post_install_message
|
|
29
|
+
say "\n✅ RcrewAI Rails has been installed!", :green
|
|
30
|
+
say "\nNext steps:", :yellow
|
|
31
|
+
say " 1. Run migrations: rails db:migrate"
|
|
32
|
+
say " 2. Configure your settings in config/initializers/rcrewai.rb"
|
|
33
|
+
say " 3. Set your LLM API keys in environment variables"
|
|
34
|
+
say " 4. Visit /rcrewai for the web UI (if enabled)"
|
|
35
|
+
say "\n"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
class CreateRcrewaiTables < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :rcrewai_crews do |t|
|
|
4
|
+
t.string :name, null: false
|
|
5
|
+
t.text :description
|
|
6
|
+
t.string :process_type, default: "sequential"
|
|
7
|
+
t.boolean :verbose, default: false
|
|
8
|
+
t.boolean :memory_enabled, default: false
|
|
9
|
+
t.boolean :cache_enabled, default: false
|
|
10
|
+
t.integer :max_rpm
|
|
11
|
+
t.string :manager_llm
|
|
12
|
+
t.text :config
|
|
13
|
+
t.text :memory
|
|
14
|
+
t.boolean :active, default: true
|
|
15
|
+
|
|
16
|
+
t.timestamps
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
add_index :rcrewai_crews, :name
|
|
20
|
+
add_index :rcrewai_crews, :active
|
|
21
|
+
|
|
22
|
+
create_table :rcrewai_agents do |t|
|
|
23
|
+
t.references :crew, null: false, foreign_key: { to_table: :rcrewai_crews }
|
|
24
|
+
t.string :name, null: false
|
|
25
|
+
t.string :role, null: false
|
|
26
|
+
t.text :goal
|
|
27
|
+
t.text :backstory
|
|
28
|
+
t.boolean :memory_enabled, default: false
|
|
29
|
+
t.boolean :verbose, default: false
|
|
30
|
+
t.boolean :allow_delegation, default: false
|
|
31
|
+
t.text :tools
|
|
32
|
+
t.integer :max_iterations, default: 25
|
|
33
|
+
t.integer :max_rpm
|
|
34
|
+
t.text :llm_config
|
|
35
|
+
t.boolean :active, default: true
|
|
36
|
+
|
|
37
|
+
t.timestamps
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
add_index :rcrewai_agents, :name
|
|
41
|
+
|
|
42
|
+
create_table :rcrewai_tasks do |t|
|
|
43
|
+
t.references :crew, null: false, foreign_key: { to_table: :rcrewai_crews }
|
|
44
|
+
t.text :description, null: false
|
|
45
|
+
t.text :expected_output, null: false
|
|
46
|
+
t.boolean :async_execution, default: false
|
|
47
|
+
t.text :context
|
|
48
|
+
t.text :output_json
|
|
49
|
+
t.text :output_pydantic
|
|
50
|
+
t.string :output_file
|
|
51
|
+
t.text :tools
|
|
52
|
+
t.string :callback_class
|
|
53
|
+
t.string :callback_method_name
|
|
54
|
+
t.integer :position
|
|
55
|
+
|
|
56
|
+
t.timestamps
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
add_index :rcrewai_tasks, :position
|
|
60
|
+
|
|
61
|
+
create_table :rcrewai_task_assignments do |t|
|
|
62
|
+
t.references :task, null: false, foreign_key: { to_table: :rcrewai_tasks }
|
|
63
|
+
t.references :agent, null: false, foreign_key: { to_table: :rcrewai_agents }
|
|
64
|
+
|
|
65
|
+
t.timestamps
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
add_index :rcrewai_task_assignments, [:task_id, :agent_id], unique: true
|
|
69
|
+
|
|
70
|
+
create_table :rcrewai_task_dependencies do |t|
|
|
71
|
+
t.references :task, null: false, foreign_key: { to_table: :rcrewai_tasks }
|
|
72
|
+
t.references :dependency, null: false, foreign_key: { to_table: :rcrewai_tasks }
|
|
73
|
+
|
|
74
|
+
t.timestamps
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
add_index :rcrewai_task_dependencies, [:task_id, :dependency_id], unique: true
|
|
78
|
+
|
|
79
|
+
create_table :rcrewai_executions do |t|
|
|
80
|
+
t.references :crew, null: false, foreign_key: { to_table: :rcrewai_crews }
|
|
81
|
+
t.string :status, null: false
|
|
82
|
+
t.text :inputs
|
|
83
|
+
t.text :output
|
|
84
|
+
t.string :error_message
|
|
85
|
+
t.text :error_details
|
|
86
|
+
t.datetime :started_at
|
|
87
|
+
t.datetime :completed_at
|
|
88
|
+
t.integer :duration_seconds
|
|
89
|
+
|
|
90
|
+
t.timestamps
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
add_index :rcrewai_executions, :status
|
|
94
|
+
add_index :rcrewai_executions, :created_at
|
|
95
|
+
|
|
96
|
+
create_table :rcrewai_execution_logs do |t|
|
|
97
|
+
t.references :execution, null: false, foreign_key: { to_table: :rcrewai_executions }
|
|
98
|
+
t.string :level, null: false
|
|
99
|
+
t.text :message, null: false
|
|
100
|
+
t.text :details
|
|
101
|
+
t.datetime :timestamp, null: false
|
|
102
|
+
|
|
103
|
+
t.timestamps
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
add_index :rcrewai_execution_logs, :level
|
|
107
|
+
add_index :rcrewai_execution_logs, :timestamp
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
RcrewAI::Rails.configure do |config|
|
|
2
|
+
# ActiveJob queue name for background processing
|
|
3
|
+
# Default: "default"
|
|
4
|
+
config.job_queue_name = ENV.fetch("RCREWAI_QUEUE", "default")
|
|
5
|
+
|
|
6
|
+
# Enable web UI for monitoring crews
|
|
7
|
+
# Default: true
|
|
8
|
+
config.enable_web_ui = ENV.fetch("RCREWAI_WEB_UI", "true") == "true"
|
|
9
|
+
|
|
10
|
+
# Use ActiveJob for async execution
|
|
11
|
+
# Default: true
|
|
12
|
+
config.async_execution = ENV.fetch("RCREWAI_ASYNC", "true") == "true"
|
|
13
|
+
|
|
14
|
+
# Persistence backend (currently only :active_record supported)
|
|
15
|
+
# Default: :active_record
|
|
16
|
+
config.persistence_backend = :active_record
|
|
17
|
+
|
|
18
|
+
# Default LLM provider
|
|
19
|
+
# Options: "openai", "anthropic", "cohere", "groq", etc.
|
|
20
|
+
config.default_llm_provider = ENV.fetch("RCREWAI_LLM_PROVIDER", "openai")
|
|
21
|
+
|
|
22
|
+
# Default LLM model
|
|
23
|
+
config.default_llm_model = ENV.fetch("RCREWAI_LLM_MODEL", "gpt-4")
|
|
24
|
+
|
|
25
|
+
# Maximum retries for failed tasks
|
|
26
|
+
# Default: 3
|
|
27
|
+
config.max_retries = 3
|
|
28
|
+
|
|
29
|
+
# Timeout for crew execution (in seconds)
|
|
30
|
+
# Default: 300 (5 minutes)
|
|
31
|
+
config.timeout = 300
|
|
32
|
+
|
|
33
|
+
# Enable logging
|
|
34
|
+
# Default: true
|
|
35
|
+
config.enable_logging = true
|
|
36
|
+
|
|
37
|
+
# Log level
|
|
38
|
+
# Options: :debug, :info, :warn, :error
|
|
39
|
+
# Default: :info
|
|
40
|
+
config.log_level = :info
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Configure RcrewAI base gem if needed
|
|
44
|
+
RcrewAI.configure do |config|
|
|
45
|
+
# Set your API keys here or in environment variables
|
|
46
|
+
# config.openai_api_key = ENV["OPENAI_API_KEY"]
|
|
47
|
+
# config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
|
|
48
|
+
# config.groq_api_key = ENV["GROQ_API_KEY"]
|
|
49
|
+
|
|
50
|
+
# Configure other RcrewAI settings
|
|
51
|
+
# config.default_model = "gpt-4"
|
|
52
|
+
# config.temperature = 0.7
|
|
53
|
+
end
|
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
require "rails/generators"
|
|
2
|
+
require "rails/generators/migration"
|
|
2
3
|
|
|
3
4
|
module RcrewAI
|
|
4
5
|
module Rails
|
|
5
6
|
module Generators
|
|
6
7
|
class InstallGenerator < ::Rails::Generators::Base
|
|
8
|
+
include ::Rails::Generators::Migration
|
|
9
|
+
|
|
7
10
|
source_root File.expand_path("templates", __dir__)
|
|
8
11
|
|
|
12
|
+
def self.next_migration_number(dirname)
|
|
13
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create_migration_file
|
|
17
|
+
migration_template "create_rcrewai_tables.rb", "db/migrate/create_rcrewai_tables.rb", migration_class_name: "CreateRcrewaiTables"
|
|
18
|
+
end
|
|
19
|
+
|
|
9
20
|
def create_initializer
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# Rails specific configuration
|
|
17
|
-
config.job_queue_name = ENV.fetch("RCREWAI_QUEUE", "default")
|
|
18
|
-
config.enable_web_ui = ENV.fetch("RCREWAI_WEB_UI", "true") == "true"
|
|
19
|
-
config.async_execution = ENV.fetch("RCREWAI_ASYNC", "true") == "true"
|
|
20
|
-
end
|
|
21
|
-
RUBY
|
|
21
|
+
template "rcrewai.rb", "config/initializers/rcrewai.rb"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add_routes
|
|
25
|
+
route "mount RcrewAI::Rails::Engine => '/rcrewai'"
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
def display_post_install_message
|
|
25
29
|
say "\n✅ RcrewAI Rails has been installed!", :green
|
|
26
30
|
say "\nNext steps:", :yellow
|
|
27
|
-
say " 1.
|
|
28
|
-
say " 2.
|
|
29
|
-
say " 3.
|
|
31
|
+
say " 1. Run migrations: rails db:migrate"
|
|
32
|
+
say " 2. Configure your settings in config/initializers/rcrewai.rb"
|
|
33
|
+
say " 3. Set your LLM API keys in environment variables"
|
|
34
|
+
say " 4. Visit /rcrewai for the web UI (if enabled)"
|
|
30
35
|
say "\n"
|
|
31
36
|
end
|
|
32
37
|
end
|
|
@@ -62,7 +62,6 @@ module RcrewAI
|
|
|
62
62
|
def initialize(attributes = {})
|
|
63
63
|
@attributes = attributes
|
|
64
64
|
@agent = build_agent
|
|
65
|
-
setup_tools
|
|
66
65
|
configure_agent
|
|
67
66
|
end
|
|
68
67
|
|
|
@@ -78,20 +77,19 @@ module RcrewAI
|
|
|
78
77
|
|
|
79
78
|
def build_agent
|
|
80
79
|
RCrewAI::Agent.new(
|
|
80
|
+
name: @attributes[:name] || default_agent_name,
|
|
81
81
|
role: @attributes[:role] || self.class.agent_role,
|
|
82
82
|
goal: @attributes[:goal] || self.class.agent_goal,
|
|
83
83
|
backstory: @attributes[:backstory] || self.class.agent_backstory,
|
|
84
|
-
|
|
84
|
+
tools: instantiate_tools,
|
|
85
85
|
verbose: verbose?,
|
|
86
86
|
allow_delegation: @attributes[:allow_delegation] || self.class.allow_delegation,
|
|
87
|
-
|
|
88
|
-
llm: @attributes[:llm_config] || self.class.llm_config
|
|
87
|
+
max_iterations: @attributes[:max_iterations] || self.class.max_iterations
|
|
89
88
|
)
|
|
90
89
|
end
|
|
91
90
|
|
|
92
|
-
def
|
|
93
|
-
|
|
94
|
-
@agent.tools = tools if tools.any?
|
|
91
|
+
def default_agent_name
|
|
92
|
+
self.class.name.to_s.split("::").last&.underscore.presence || "agent"
|
|
95
93
|
end
|
|
96
94
|
|
|
97
95
|
def instantiate_tools
|
data/lib/rcrewai/rails/engine.rb
CHANGED
|
@@ -19,11 +19,6 @@ module RcrewAI
|
|
|
19
19
|
#{config.root}/lib
|
|
20
20
|
]
|
|
21
21
|
|
|
22
|
-
# Tell Zeitwerk to ignore generator files - they aren't meant to be autoloaded
|
|
23
|
-
initializer "rcrewai_rails.zeitwerk" do |app|
|
|
24
|
-
::Rails.autoloaders.main.ignore("#{root}/lib/generators")
|
|
25
|
-
end
|
|
26
|
-
|
|
27
22
|
config.generators do |g|
|
|
28
23
|
g.test_framework :rspec
|
|
29
24
|
g.fixture_replacement :factory_bot
|
|
@@ -31,8 +26,10 @@ module RcrewAI
|
|
|
31
26
|
end
|
|
32
27
|
|
|
33
28
|
initializer "rcrewai_rails.assets" do |app|
|
|
34
|
-
app.config.
|
|
35
|
-
|
|
29
|
+
if app.config.respond_to?(:assets)
|
|
30
|
+
app.config.assets.paths << root.join("app/assets/stylesheets")
|
|
31
|
+
app.config.assets.paths << root.join("app/assets/javascripts")
|
|
32
|
+
end
|
|
36
33
|
end
|
|
37
34
|
|
|
38
35
|
|
|
@@ -2,35 +2,46 @@ module RcrewAI
|
|
|
2
2
|
module Rails
|
|
3
3
|
module Tools
|
|
4
4
|
class ActionMailerTool < RCrewAI::Tools::Base
|
|
5
|
+
tool_name "action_mailer_send"
|
|
6
|
+
description "Send emails using a Rails Action Mailer. Choose the mailer class, the mailer method, and the delivery strategy."
|
|
7
|
+
|
|
8
|
+
param :mailer_method, type: :string, required: true,
|
|
9
|
+
description: "Name of the mailer method to invoke (e.g. 'welcome_email')."
|
|
10
|
+
param :mailer, type: :string, required: false,
|
|
11
|
+
description: "Mailer class name with or without the 'Mailer' suffix (e.g. 'User' or 'UserMailer'). Defaults to the mailer configured on the tool."
|
|
12
|
+
param :params, type: :object, required: false,
|
|
13
|
+
description: "Keyword arguments to pass to the mailer method."
|
|
14
|
+
param :deliver_method, type: :enum, required: false,
|
|
15
|
+
values: %w[deliver_now deliver_later deliver_later_at],
|
|
16
|
+
description: "How to send: deliver_now, deliver_later, or deliver_later_at."
|
|
17
|
+
param :at, type: :string, required: false,
|
|
18
|
+
description: "ISO8601 timestamp for deliver_later_at. Required when deliver_method is deliver_later_at."
|
|
19
|
+
|
|
5
20
|
def initialize(mailer_class: nil, allowed_methods: [])
|
|
21
|
+
super()
|
|
6
22
|
@mailer_class = mailer_class
|
|
7
|
-
@allowed_methods = allowed_methods
|
|
8
|
-
super(
|
|
9
|
-
name: "Action Mailer Tool",
|
|
10
|
-
description: "Send emails using Rails Action Mailer"
|
|
11
|
-
)
|
|
23
|
+
@allowed_methods = allowed_methods.map(&:to_sym)
|
|
12
24
|
end
|
|
13
25
|
|
|
14
|
-
def execute(mailer_method, params
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
mail =
|
|
21
|
-
|
|
22
|
-
# Deliver based on specified method
|
|
26
|
+
def execute(mailer_method:, mailer: nil, params: {}, deliver_method: "deliver_later", at: nil)
|
|
27
|
+
method_sym = mailer_method.to_sym
|
|
28
|
+
validate_mailer_method!(method_sym)
|
|
29
|
+
|
|
30
|
+
mailer_klass = resolve_mailer_class(mailer)
|
|
31
|
+
mailer_params = (params || {}).transform_keys(&:to_sym)
|
|
32
|
+
mail = mailer_klass.public_send(method_sym, **mailer_params)
|
|
33
|
+
|
|
23
34
|
case deliver_method.to_sym
|
|
24
35
|
when :deliver_now
|
|
25
36
|
mail.deliver_now
|
|
26
|
-
{ status: "sent", method:
|
|
37
|
+
{ status: "sent", method: method_sym, delivered_at: Time.current }
|
|
27
38
|
when :deliver_later
|
|
28
39
|
job = mail.deliver_later
|
|
29
|
-
{ status: "queued", method:
|
|
40
|
+
{ status: "queued", method: method_sym, job_id: job.job_id }
|
|
30
41
|
when :deliver_later_at
|
|
31
|
-
delivery_time =
|
|
42
|
+
delivery_time = at.present? ? Time.iso8601(at) : 1.hour.from_now
|
|
32
43
|
job = mail.deliver_later(wait_until: delivery_time)
|
|
33
|
-
{ status: "scheduled", method:
|
|
44
|
+
{ status: "scheduled", method: method_sym, job_id: job.job_id, scheduled_for: delivery_time }
|
|
34
45
|
else
|
|
35
46
|
{ error: "Unknown delivery method: #{deliver_method}" }
|
|
36
47
|
end
|
|
@@ -41,15 +52,17 @@ module RcrewAI
|
|
|
41
52
|
private
|
|
42
53
|
|
|
43
54
|
def validate_mailer_method!(method)
|
|
44
|
-
if @allowed_methods.
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
return if @allowed_methods.empty? || @allowed_methods.include?(method)
|
|
56
|
+
|
|
57
|
+
raise ArgumentError, "Mailer method '#{method}' is not allowed"
|
|
47
58
|
end
|
|
48
59
|
|
|
49
|
-
def
|
|
50
|
-
return @mailer_class if
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
def resolve_mailer_class(mailer_name)
|
|
61
|
+
return @mailer_class if mailer_name.nil? && @mailer_class
|
|
62
|
+
raise ArgumentError, "No mailer specified" if mailer_name.nil?
|
|
63
|
+
|
|
64
|
+
mailer_name = mailer_name.to_s
|
|
65
|
+
mailer_name = "#{mailer_name.camelize}Mailer" unless mailer_name.end_with?("Mailer")
|
|
53
66
|
mailer_name.constantize
|
|
54
67
|
rescue NameError
|
|
55
68
|
raise ArgumentError, "Mailer class '#{mailer_name}' not found"
|
|
@@ -57,4 +70,4 @@ module RcrewAI
|
|
|
57
70
|
end
|
|
58
71
|
end
|
|
59
72
|
end
|
|
60
|
-
end
|
|
73
|
+
end
|
|
@@ -2,40 +2,50 @@ module RcrewAI
|
|
|
2
2
|
module Rails
|
|
3
3
|
module Tools
|
|
4
4
|
class ActiveRecordTool < RCrewAI::Tools::Base
|
|
5
|
-
|
|
5
|
+
tool_name "active_record_query"
|
|
6
|
+
description "Query the Rails database using ActiveRecord. Supports find, find_by, where, count, pluck, exists?, first, last, and all."
|
|
7
|
+
|
|
8
|
+
param :query_type, type: :enum, required: true,
|
|
9
|
+
values: %w[find find_by where count pluck exists? first last all],
|
|
10
|
+
description: "ActiveRecord query method to invoke."
|
|
11
|
+
param :model, type: :string, required: false,
|
|
12
|
+
description: "Model class name (e.g. 'User'). Defaults to the model configured on the tool."
|
|
13
|
+
param :conditions, type: :object, required: false,
|
|
14
|
+
description: "Query conditions as a hash. For :find, include :id. For :pluck, include :field."
|
|
15
|
+
|
|
16
|
+
def initialize(model_class: nil, allowed_methods: %i[find where count])
|
|
17
|
+
super()
|
|
6
18
|
@model_class = model_class
|
|
7
|
-
@allowed_methods = allowed_methods
|
|
8
|
-
super(
|
|
9
|
-
name: "ActiveRecord Query Tool",
|
|
10
|
-
description: "Query Rails database using ActiveRecord"
|
|
11
|
-
)
|
|
19
|
+
@allowed_methods = allowed_methods.map(&:to_sym)
|
|
12
20
|
end
|
|
13
21
|
|
|
14
|
-
def execute(query_type, conditions
|
|
22
|
+
def execute(query_type:, model: nil, conditions: {})
|
|
23
|
+
query_type = query_type.to_sym
|
|
15
24
|
validate_query_type!(query_type)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
|
|
26
|
+
model_klass = resolve_model_class(model)
|
|
27
|
+
conds = (conditions || {}).transform_keys(&:to_sym)
|
|
28
|
+
|
|
29
|
+
case query_type
|
|
20
30
|
when :find
|
|
21
|
-
|
|
31
|
+
model_klass.find(conds[:id])
|
|
22
32
|
when :find_by
|
|
23
|
-
|
|
33
|
+
model_klass.find_by(conds)
|
|
24
34
|
when :where
|
|
25
|
-
|
|
35
|
+
model_klass.where(conds)
|
|
26
36
|
when :count
|
|
27
|
-
|
|
37
|
+
conds.empty? ? model_klass.count : model_klass.where(conds).count
|
|
28
38
|
when :pluck
|
|
29
|
-
field =
|
|
30
|
-
|
|
39
|
+
field = conds.delete(:field)
|
|
40
|
+
model_klass.where(conds).pluck(field)
|
|
31
41
|
when :exists?
|
|
32
|
-
|
|
42
|
+
model_klass.exists?(conds)
|
|
33
43
|
when :first
|
|
34
|
-
|
|
44
|
+
model_klass.where(conds).first
|
|
35
45
|
when :last
|
|
36
|
-
|
|
46
|
+
model_klass.where(conds).last
|
|
37
47
|
when :all
|
|
38
|
-
|
|
48
|
+
model_klass.where(conds).to_a
|
|
39
49
|
else
|
|
40
50
|
raise ArgumentError, "Unsupported query type: #{query_type}"
|
|
41
51
|
end
|
|
@@ -48,20 +58,20 @@ module RcrewAI
|
|
|
48
58
|
private
|
|
49
59
|
|
|
50
60
|
def validate_query_type!(query_type)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
return if @allowed_methods.include?(query_type)
|
|
62
|
+
|
|
63
|
+
raise ArgumentError, "Query type '#{query_type}' is not allowed"
|
|
54
64
|
end
|
|
55
65
|
|
|
56
|
-
def
|
|
57
|
-
return @model_class if
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
model_name.constantize
|
|
66
|
+
def resolve_model_class(model_name)
|
|
67
|
+
return @model_class if model_name.nil? && @model_class
|
|
68
|
+
raise ArgumentError, "No model specified" if model_name.nil?
|
|
69
|
+
|
|
70
|
+
model_name.to_s.camelize.constantize
|
|
61
71
|
rescue NameError
|
|
62
72
|
raise ArgumentError, "Model class '#{model_name}' not found"
|
|
63
73
|
end
|
|
64
74
|
end
|
|
65
75
|
end
|
|
66
76
|
end
|
|
67
|
-
end
|
|
77
|
+
end
|
|
@@ -2,30 +2,32 @@ module RcrewAI
|
|
|
2
2
|
module Rails
|
|
3
3
|
module Tools
|
|
4
4
|
class ActiveStorageTool < RCrewAI::Tools::Base
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
tool_name "active_storage"
|
|
6
|
+
description "Manage Rails Active Storage attachments: attach, download, analyze, delete, or build a URL."
|
|
7
|
+
|
|
8
|
+
param :operation, type: :enum, required: true,
|
|
9
|
+
values: %w[attach download analyze delete url],
|
|
10
|
+
description: "Active Storage operation to perform."
|
|
11
|
+
param :params, type: :object, required: false,
|
|
12
|
+
description: "Operation parameters (model, id, attachment_name, blob_id, signed_id, key, file, filename, content_type, expires_in)."
|
|
13
|
+
|
|
14
|
+
def initialize(allowed_operations: %i[attach download analyze])
|
|
15
|
+
super()
|
|
16
|
+
@allowed_operations = allowed_operations.map(&:to_sym)
|
|
11
17
|
end
|
|
12
18
|
|
|
13
|
-
def execute(operation
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
when :
|
|
20
|
-
|
|
21
|
-
when :analyze
|
|
22
|
-
|
|
23
|
-
when :
|
|
24
|
-
|
|
25
|
-
when :url
|
|
26
|
-
get_url(params)
|
|
27
|
-
else
|
|
28
|
-
{ error: "Unknown operation: #{operation}" }
|
|
19
|
+
def execute(operation:, params: {})
|
|
20
|
+
op = operation.to_sym
|
|
21
|
+
validate_operation!(op)
|
|
22
|
+
opts = (params || {}).transform_keys(&:to_sym)
|
|
23
|
+
|
|
24
|
+
case op
|
|
25
|
+
when :attach then attach_file(opts)
|
|
26
|
+
when :download then download_file(opts)
|
|
27
|
+
when :analyze then analyze_file(opts)
|
|
28
|
+
when :delete then delete_file(opts)
|
|
29
|
+
when :url then get_url(opts)
|
|
30
|
+
else { error: "Unknown operation: #{operation}" }
|
|
29
31
|
end
|
|
30
32
|
rescue => e
|
|
31
33
|
{ error: "Active Storage operation failed", message: e.message }
|
|
@@ -34,31 +36,31 @@ module RcrewAI
|
|
|
34
36
|
private
|
|
35
37
|
|
|
36
38
|
def validate_operation!(operation)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
return if @allowed_operations.include?(operation)
|
|
40
|
+
|
|
41
|
+
raise ArgumentError, "Operation '#{operation}' is not allowed"
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
def attach_file(params)
|
|
43
45
|
record = find_record(params[:model], params[:id])
|
|
44
46
|
attachment_name = params[:attachment_name] || :file
|
|
45
|
-
|
|
47
|
+
|
|
46
48
|
file = params[:file] || params[:io]
|
|
47
49
|
filename = params[:filename] || "attachment"
|
|
48
50
|
content_type = params[:content_type] || "application/octet-stream"
|
|
49
|
-
|
|
51
|
+
|
|
50
52
|
record.send(attachment_name).attach(
|
|
51
53
|
io: file,
|
|
52
54
|
filename: filename,
|
|
53
55
|
content_type: content_type
|
|
54
56
|
)
|
|
55
|
-
|
|
57
|
+
|
|
56
58
|
{ attached: true, filename: filename, record_id: record.id }
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
def download_file(params)
|
|
60
62
|
blob = find_blob(params)
|
|
61
|
-
|
|
63
|
+
|
|
62
64
|
{
|
|
63
65
|
filename: blob.filename.to_s,
|
|
64
66
|
content_type: blob.content_type,
|
|
@@ -70,7 +72,7 @@ module RcrewAI
|
|
|
70
72
|
def analyze_file(params)
|
|
71
73
|
blob = find_blob(params)
|
|
72
74
|
blob.analyze unless blob.analyzed?
|
|
73
|
-
|
|
75
|
+
|
|
74
76
|
{
|
|
75
77
|
filename: blob.filename.to_s,
|
|
76
78
|
content_type: blob.content_type,
|
|
@@ -84,14 +86,14 @@ module RcrewAI
|
|
|
84
86
|
blob = find_blob(params)
|
|
85
87
|
filename = blob.filename.to_s
|
|
86
88
|
blob.purge
|
|
87
|
-
|
|
89
|
+
|
|
88
90
|
{ deleted: true, filename: filename }
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
def get_url(params)
|
|
92
94
|
blob = find_blob(params)
|
|
93
95
|
expires_in = params[:expires_in] || 5.minutes
|
|
94
|
-
|
|
96
|
+
|
|
95
97
|
{
|
|
96
98
|
url: ::Rails.application.routes.url_helpers.rails_blob_url(blob, expires_in: expires_in),
|
|
97
99
|
expires_at: expires_in.from_now
|
|
@@ -119,4 +121,4 @@ module RcrewAI
|
|
|
119
121
|
end
|
|
120
122
|
end
|
|
121
123
|
end
|
|
122
|
-
end
|
|
124
|
+
end
|
|
@@ -2,29 +2,31 @@ module RcrewAI
|
|
|
2
2
|
module Rails
|
|
3
3
|
module Tools
|
|
4
4
|
class RailsCacheTool < RCrewAI::Tools::Base
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
tool_name "rails_cache"
|
|
6
|
+
description "Read from and write to the Rails cache. Supports read, write, delete, exist?, fetch, and clear."
|
|
7
|
+
|
|
8
|
+
param :action, type: :enum, required: true,
|
|
9
|
+
values: %w[read write delete exist? fetch clear],
|
|
10
|
+
description: "Cache action to perform."
|
|
11
|
+
param :key, type: :string, required: false,
|
|
12
|
+
description: "Cache key. Required for every action except clear."
|
|
13
|
+
param :value, type: :string, required: false,
|
|
14
|
+
description: "Value to store (used by write and fetch fallback)."
|
|
15
|
+
param :options, type: :object, required: false,
|
|
16
|
+
description: "Cache options hash (e.g. expires_in)."
|
|
11
17
|
|
|
12
|
-
def execute(action
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
when :
|
|
19
|
-
|
|
20
|
-
when :exist?
|
|
21
|
-
|
|
22
|
-
when :
|
|
23
|
-
|
|
24
|
-
when :clear
|
|
25
|
-
clear_cache
|
|
26
|
-
else
|
|
27
|
-
{ error: "Unknown action: #{action}" }
|
|
18
|
+
def execute(action:, key: nil, value: nil, options: {})
|
|
19
|
+
act = action.to_sym
|
|
20
|
+
opts = (options || {}).transform_keys(&:to_sym)
|
|
21
|
+
|
|
22
|
+
case act
|
|
23
|
+
when :read then read_cache(key)
|
|
24
|
+
when :write then write_cache(key, value, opts)
|
|
25
|
+
when :delete then delete_cache(key)
|
|
26
|
+
when :exist? then cache_exists?(key)
|
|
27
|
+
when :fetch then fetch_cache(key, value, opts)
|
|
28
|
+
when :clear then clear_cache
|
|
29
|
+
else { error: "Unknown action: #{action}" }
|
|
28
30
|
end
|
|
29
31
|
rescue => e
|
|
30
32
|
{ error: "Cache operation failed", message: e.message }
|
|
@@ -37,7 +39,7 @@ module RcrewAI
|
|
|
37
39
|
{ key: key, value: value, exists: !value.nil? }
|
|
38
40
|
end
|
|
39
41
|
|
|
40
|
-
def write_cache(key, value, options
|
|
42
|
+
def write_cache(key, value, options)
|
|
41
43
|
success = ::Rails.cache.write(key, value, options)
|
|
42
44
|
{ key: key, written: success }
|
|
43
45
|
end
|
|
@@ -48,14 +50,11 @@ module RcrewAI
|
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
def cache_exists?(key)
|
|
51
|
-
exists
|
|
52
|
-
{ key: key, exists: exists }
|
|
53
|
+
{ key: key, exists: ::Rails.cache.exist?(key) }
|
|
53
54
|
end
|
|
54
55
|
|
|
55
|
-
def fetch_cache(key, options
|
|
56
|
-
value = ::Rails.cache.fetch(key, options)
|
|
57
|
-
yield if block_given?
|
|
58
|
-
end
|
|
56
|
+
def fetch_cache(key, fallback, options)
|
|
57
|
+
value = ::Rails.cache.fetch(key, options) { fallback }
|
|
59
58
|
{ key: key, value: value }
|
|
60
59
|
end
|
|
61
60
|
|
|
@@ -66,4 +65,4 @@ module RcrewAI
|
|
|
66
65
|
end
|
|
67
66
|
end
|
|
68
67
|
end
|
|
69
|
-
end
|
|
68
|
+
end
|
|
@@ -2,40 +2,41 @@ module RcrewAI
|
|
|
2
2
|
module Rails
|
|
3
3
|
module Tools
|
|
4
4
|
class RailsLoggerTool < RCrewAI::Tools::Base
|
|
5
|
+
tool_name "rails_logger"
|
|
6
|
+
description "Log a message to the Rails logger at the given level and emit an ActiveSupport notification."
|
|
7
|
+
|
|
8
|
+
param :level, type: :enum, required: true,
|
|
9
|
+
values: %w[debug info warn error fatal],
|
|
10
|
+
description: "Log level."
|
|
11
|
+
param :message, type: :string, required: true,
|
|
12
|
+
description: "Message to log."
|
|
13
|
+
param :metadata, type: :object, required: false,
|
|
14
|
+
description: "Additional metadata appended as JSON to the log line."
|
|
15
|
+
|
|
5
16
|
def initialize(tag: "RcrewAI")
|
|
17
|
+
super()
|
|
6
18
|
@tag = tag
|
|
7
|
-
super(
|
|
8
|
-
name: "Rails Logger Tool",
|
|
9
|
-
description: "Log messages to Rails logger"
|
|
10
|
-
)
|
|
11
19
|
end
|
|
12
20
|
|
|
13
|
-
def execute(level
|
|
21
|
+
def execute(level:, message:, metadata: {})
|
|
14
22
|
logger = ::Rails.logger
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
meta = metadata || {}
|
|
24
|
+
formatted_message = format_message(message, meta)
|
|
25
|
+
|
|
19
26
|
case level.to_sym
|
|
20
|
-
when :debug
|
|
21
|
-
|
|
22
|
-
when :
|
|
23
|
-
|
|
24
|
-
when :
|
|
25
|
-
logger.warn(formatted_message)
|
|
26
|
-
when :error
|
|
27
|
-
logger.error(formatted_message)
|
|
28
|
-
when :fatal
|
|
29
|
-
logger.fatal(formatted_message)
|
|
27
|
+
when :debug then logger.debug(formatted_message)
|
|
28
|
+
when :info then logger.info(formatted_message)
|
|
29
|
+
when :warn then logger.warn(formatted_message)
|
|
30
|
+
when :error then logger.error(formatted_message)
|
|
31
|
+
when :fatal then logger.fatal(formatted_message)
|
|
30
32
|
else
|
|
31
33
|
return { error: "Unknown log level: #{level}" }
|
|
32
34
|
end
|
|
33
35
|
|
|
34
|
-
# Also instrument for monitoring
|
|
35
36
|
ActiveSupport::Notifications.instrument("log.rcrewai", {
|
|
36
37
|
level: level,
|
|
37
38
|
message: message,
|
|
38
|
-
metadata:
|
|
39
|
+
metadata: meta,
|
|
39
40
|
tag: @tag
|
|
40
41
|
})
|
|
41
42
|
|
|
@@ -48,10 +49,10 @@ module RcrewAI
|
|
|
48
49
|
|
|
49
50
|
def format_message(message, metadata)
|
|
50
51
|
return "[#{@tag}] #{message}" if metadata.empty?
|
|
51
|
-
|
|
52
|
+
|
|
52
53
|
"[#{@tag}] #{message} | #{metadata.to_json}"
|
|
53
54
|
end
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
end
|
|
57
|
-
end
|
|
58
|
+
end
|
data/rcrewai-rails.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rcrewai-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- gkosmo
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.
|
|
18
|
+
version: '0.3'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.
|
|
25
|
+
version: '0.3'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: rails
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -238,6 +238,12 @@ files:
|
|
|
238
238
|
- app/views/rcrewai/rails/tools/show.html.erb
|
|
239
239
|
- config/routes.rb
|
|
240
240
|
- db/migrate/001_add_agent_to_tasks.rb
|
|
241
|
+
- lib/generators/rcrew_a_i/rails/crew/crew_generator.rb
|
|
242
|
+
- lib/generators/rcrew_a_i/rails/crew/templates/agent.rb.erb
|
|
243
|
+
- lib/generators/rcrew_a_i/rails/crew/templates/crew.rb.erb
|
|
244
|
+
- lib/generators/rcrew_a_i/rails/install/install_generator.rb
|
|
245
|
+
- lib/generators/rcrew_a_i/rails/install/templates/create_rcrewai_tables.rb
|
|
246
|
+
- lib/generators/rcrew_a_i/rails/install/templates/rcrewai.rb
|
|
241
247
|
- lib/generators/rcrewai/rails/crew/crew_generator.rb
|
|
242
248
|
- lib/generators/rcrewai/rails/crew/templates/agent.rb.erb
|
|
243
249
|
- lib/generators/rcrewai/rails/crew/templates/crew.rb.erb
|