rcrewai-rails 0.2.8 → 0.3.1
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/docs/index.html +410 -0
- data/docs/superpowers/specs/2026-06-16-github-pages-landing-design.md +61 -0
- data/lib/rcrewai/rails/agent_builder.rb +5 -7
- data/lib/rcrewai/rails/engine.rb +16 -5
- 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 -3
- data/rcrewai-rails.gemspec +2 -2
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de3fd7383b01dfbcf053637b30537fb9e78d3541b0fdf09d0d7c92f0f132525e
|
|
4
|
+
data.tar.gz: 0f3e47c876aefb405a63abdd5b22ea965afdf438bdc2af46d3154e619d89f42d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8c4bdd914f99b4e2f5bb324e2d5658e088698be20834b0f4186a3e6ef7d574330c0de9cdc112970f937858858738800cf80f97cbc88699c1b7397674ba979a2c
|
|
7
|
+
data.tar.gz: c93205e5e31f6036f796c616ad623711136a6f2ab0f902a149c02737d3ecea902bafc4d564d3f2c36bcf1cfddd754c6bf8a35aefbcc74f3ecabc4165cf767f87
|
|
@@ -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
|
|
data/docs/index.html
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>RcrewAI Rails — AI agent crews for Rails</title>
|
|
7
|
+
<meta name="description" content="Rails engine for building AI agent crews with ActiveRecord persistence, background jobs, generators, and a web dashboard.">
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--crimson: #c4221f;
|
|
11
|
+
--crimson-dark: #9c1714;
|
|
12
|
+
--ink: #1a1a1a;
|
|
13
|
+
--muted: #5c5c64;
|
|
14
|
+
--line: #e5e3df;
|
|
15
|
+
--bg: #fbfaf8;
|
|
16
|
+
--card: #ffffff;
|
|
17
|
+
--code-bg: #1f1d24;
|
|
18
|
+
--code-fg: #e8e6f0;
|
|
19
|
+
--code-line: #2e2b36;
|
|
20
|
+
--radius: 10px;
|
|
21
|
+
--maxw: 960px;
|
|
22
|
+
}
|
|
23
|
+
* { box-sizing: border-box; }
|
|
24
|
+
html { scroll-behavior: smooth; scroll-padding-top: 76px; }
|
|
25
|
+
body {
|
|
26
|
+
margin: 0;
|
|
27
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
28
|
+
color: var(--ink);
|
|
29
|
+
background: var(--bg);
|
|
30
|
+
line-height: 1.6;
|
|
31
|
+
-webkit-font-smoothing: antialiased;
|
|
32
|
+
}
|
|
33
|
+
a { color: var(--crimson); text-decoration: none; }
|
|
34
|
+
a:hover { text-decoration: underline; }
|
|
35
|
+
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }
|
|
36
|
+
|
|
37
|
+
/* Header */
|
|
38
|
+
header {
|
|
39
|
+
position: sticky; top: 0; z-index: 50;
|
|
40
|
+
background: rgba(251,250,248,0.92);
|
|
41
|
+
backdrop-filter: saturate(180%) blur(8px);
|
|
42
|
+
border-bottom: 1px solid var(--line);
|
|
43
|
+
}
|
|
44
|
+
.nav {
|
|
45
|
+
display: flex; align-items: center; gap: 20px;
|
|
46
|
+
height: 60px;
|
|
47
|
+
}
|
|
48
|
+
.brand { font-weight: 700; font-size: 18px; color: var(--ink); display: flex; align-items: center; gap: 8px; }
|
|
49
|
+
.brand .dot { width: 12px; height: 12px; border-radius: 3px; background: var(--crimson); display: inline-block; }
|
|
50
|
+
.nav-links { display: flex; gap: 20px; margin-left: auto; align-items: center; }
|
|
51
|
+
.nav-links a { color: var(--muted); font-size: 14px; font-weight: 500; }
|
|
52
|
+
.nav-links a:hover { color: var(--ink); text-decoration: none; }
|
|
53
|
+
.nav-links .gh { color: var(--ink); font-weight: 600; }
|
|
54
|
+
@media (max-width: 720px) { .nav-links a:not(.gh) { display: none; } }
|
|
55
|
+
|
|
56
|
+
/* Hero */
|
|
57
|
+
.hero { padding: 72px 0 56px; text-align: center; border-bottom: 1px solid var(--line); }
|
|
58
|
+
.hero h1 { font-size: 44px; line-height: 1.1; margin: 0 0 16px; letter-spacing: -0.02em; }
|
|
59
|
+
.hero h1 .accent { color: var(--crimson); }
|
|
60
|
+
.hero p.lead { font-size: 19px; color: var(--muted); max-width: 640px; margin: 0 auto 28px; }
|
|
61
|
+
.install-line {
|
|
62
|
+
display: inline-block; font-family: "SF Mono", Menlo, Consolas, monospace;
|
|
63
|
+
background: var(--code-bg); color: var(--code-fg);
|
|
64
|
+
padding: 12px 18px; border-radius: var(--radius); font-size: 15px;
|
|
65
|
+
margin-bottom: 28px;
|
|
66
|
+
}
|
|
67
|
+
.install-line .prompt { color: #8a86a0; user-select: none; }
|
|
68
|
+
.cta { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
|
|
69
|
+
.btn {
|
|
70
|
+
display: inline-block; padding: 11px 22px; border-radius: var(--radius);
|
|
71
|
+
font-weight: 600; font-size: 15px; border: 1px solid transparent;
|
|
72
|
+
}
|
|
73
|
+
.btn-primary { background: var(--crimson); color: #fff; }
|
|
74
|
+
.btn-primary:hover { background: var(--crimson-dark); text-decoration: none; }
|
|
75
|
+
.btn-secondary { background: var(--card); color: var(--ink); border-color: var(--line); }
|
|
76
|
+
.btn-secondary:hover { border-color: var(--muted); text-decoration: none; }
|
|
77
|
+
|
|
78
|
+
/* Sections */
|
|
79
|
+
section { padding: 56px 0; border-bottom: 1px solid var(--line); }
|
|
80
|
+
section h2 { font-size: 28px; margin: 0 0 8px; letter-spacing: -0.01em; }
|
|
81
|
+
section .sub { color: var(--muted); margin: 0 0 28px; font-size: 16px; }
|
|
82
|
+
h3 { font-size: 18px; margin: 32px 0 10px; }
|
|
83
|
+
h3:first-of-type { margin-top: 0; }
|
|
84
|
+
|
|
85
|
+
/* Feature grid */
|
|
86
|
+
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
|
|
87
|
+
@media (max-width: 760px) { .grid { grid-template-columns: 1fr; } }
|
|
88
|
+
.feature {
|
|
89
|
+
background: var(--card); border: 1px solid var(--line);
|
|
90
|
+
border-radius: var(--radius); padding: 22px;
|
|
91
|
+
}
|
|
92
|
+
.feature h3 { margin: 0 0 6px; font-size: 16px; }
|
|
93
|
+
.feature p { margin: 0; color: var(--muted); font-size: 14.5px; }
|
|
94
|
+
.feature .ic { font-size: 22px; display: block; margin-bottom: 10px; }
|
|
95
|
+
|
|
96
|
+
/* Code */
|
|
97
|
+
pre {
|
|
98
|
+
background: var(--code-bg); color: var(--code-fg);
|
|
99
|
+
border: 1px solid var(--code-line); border-radius: var(--radius);
|
|
100
|
+
padding: 18px 20px; overflow-x: auto; font-size: 14px; line-height: 1.55;
|
|
101
|
+
font-family: "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
102
|
+
margin: 0 0 16px;
|
|
103
|
+
}
|
|
104
|
+
code { font-family: "SF Mono", Menlo, Consolas, monospace; }
|
|
105
|
+
p code, li code, td code {
|
|
106
|
+
background: #efece6; color: var(--crimson-dark);
|
|
107
|
+
padding: 2px 6px; border-radius: 5px; font-size: 13.5px;
|
|
108
|
+
}
|
|
109
|
+
.caption { font-size: 13px; color: var(--muted); margin: -8px 0 22px; }
|
|
110
|
+
|
|
111
|
+
/* Lists */
|
|
112
|
+
ul.clean { padding-left: 20px; }
|
|
113
|
+
ul.clean li { margin-bottom: 6px; }
|
|
114
|
+
|
|
115
|
+
/* Table */
|
|
116
|
+
table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
117
|
+
th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--line); }
|
|
118
|
+
th { color: var(--muted); font-weight: 600; font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
119
|
+
td.method { font-family: "SF Mono", Menlo, Consolas, monospace; }
|
|
120
|
+
.verb { display: inline-block; min-width: 46px; font-weight: 700; }
|
|
121
|
+
.verb.get { color: #1f7a3d; }
|
|
122
|
+
.verb.post { color: var(--crimson); }
|
|
123
|
+
|
|
124
|
+
/* Footer */
|
|
125
|
+
footer { padding: 40px 0; text-align: center; color: var(--muted); font-size: 14px; }
|
|
126
|
+
footer a { color: var(--muted); text-decoration: underline; }
|
|
127
|
+
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 28px; }
|
|
128
|
+
@media (max-width: 760px) { .two-col { grid-template-columns: 1fr; } }
|
|
129
|
+
</style>
|
|
130
|
+
</head>
|
|
131
|
+
<body>
|
|
132
|
+
|
|
133
|
+
<header>
|
|
134
|
+
<div class="wrap nav">
|
|
135
|
+
<span class="brand"><span class="dot"></span>RcrewAI Rails</span>
|
|
136
|
+
<nav class="nav-links">
|
|
137
|
+
<a href="#features">Features</a>
|
|
138
|
+
<a href="#install">Install</a>
|
|
139
|
+
<a href="#usage">Usage</a>
|
|
140
|
+
<a href="#tools">Tools</a>
|
|
141
|
+
<a href="#api">API</a>
|
|
142
|
+
<a class="gh" href="https://github.com/gkosmo/rcrewai-rails">GitHub →</a>
|
|
143
|
+
</nav>
|
|
144
|
+
</div>
|
|
145
|
+
</header>
|
|
146
|
+
|
|
147
|
+
<section class="hero" style="border-top:none;">
|
|
148
|
+
<div class="wrap">
|
|
149
|
+
<h1>Build AI agent crews,<br><span class="accent">the Rails way.</span></h1>
|
|
150
|
+
<p class="lead">
|
|
151
|
+
A Rails engine for orchestrating AI agents — with ActiveRecord persistence,
|
|
152
|
+
background-job execution, generators, and a built-in web dashboard.
|
|
153
|
+
</p>
|
|
154
|
+
<div class="install-line"><span class="prompt">gem </span>'rcrewai-rails'</div>
|
|
155
|
+
<div class="cta">
|
|
156
|
+
<a class="btn btn-primary" href="https://github.com/gkosmo/rcrewai-rails">View on GitHub</a>
|
|
157
|
+
<a class="btn btn-secondary" href="https://rubygems.org/gems/rcrewai-rails">RubyGems</a>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</section>
|
|
161
|
+
|
|
162
|
+
<section id="features">
|
|
163
|
+
<div class="wrap">
|
|
164
|
+
<h2>Features</h2>
|
|
165
|
+
<p class="sub">Everything you need to run collaborative AI crews inside a Rails app.</p>
|
|
166
|
+
<div class="grid">
|
|
167
|
+
<div class="feature">
|
|
168
|
+
<span class="ic">🗄️</span>
|
|
169
|
+
<h3>ActiveRecord Integration</h3>
|
|
170
|
+
<p>Persist crews, agents, tasks, and executions directly in your database.</p>
|
|
171
|
+
</div>
|
|
172
|
+
<div class="feature">
|
|
173
|
+
<span class="ic">⚙️</span>
|
|
174
|
+
<h3>Background Jobs</h3>
|
|
175
|
+
<p>Runs through ActiveJob — works with Sidekiq, Resque, Delayed Job, or any adapter.</p>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="feature">
|
|
178
|
+
<span class="ic">🏗️</span>
|
|
179
|
+
<h3>Rails Generators</h3>
|
|
180
|
+
<p>Scaffold new crews and agents from the command line in seconds.</p>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="feature">
|
|
183
|
+
<span class="ic">📊</span>
|
|
184
|
+
<h3>Web Dashboard</h3>
|
|
185
|
+
<p>Monitor executions, view logs, and manage crews through a built-in UI.</p>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="feature">
|
|
188
|
+
<span class="ic">🤖</span>
|
|
189
|
+
<h3>Multi-LLM Support</h3>
|
|
190
|
+
<p>OpenAI GPT, Anthropic Claude, Google Gemini, Azure OpenAI, and Ollama.</p>
|
|
191
|
+
</div>
|
|
192
|
+
<div class="feature">
|
|
193
|
+
<span class="ic">🧰</span>
|
|
194
|
+
<h3>Rails-Specific Tools</h3>
|
|
195
|
+
<p>Pre-built tools for ActiveRecord, ActionMailer, the Rails cache, and more.</p>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
</section>
|
|
200
|
+
|
|
201
|
+
<section id="install">
|
|
202
|
+
<div class="wrap">
|
|
203
|
+
<h2>Installation</h2>
|
|
204
|
+
<p class="sub">Add the gem, run the installer, and migrate.</p>
|
|
205
|
+
|
|
206
|
+
<h3>1. Add to your Gemfile</h3>
|
|
207
|
+
<pre>gem 'rcrewai-rails'</pre>
|
|
208
|
+
<pre>$ bundle install</pre>
|
|
209
|
+
|
|
210
|
+
<h3>2. Run the install generator</h3>
|
|
211
|
+
<pre>$ rails generate rcrew_a_i:rails:install
|
|
212
|
+
$ rails db:migrate</pre>
|
|
213
|
+
<p class="caption">This creates the database migrations, adds a configuration initializer, and mounts the engine routes.</p>
|
|
214
|
+
|
|
215
|
+
<h3>Manual route setup</h3>
|
|
216
|
+
<p>If you prefer to mount the engine yourself, add this to <code>config/routes.rb</code>:</p>
|
|
217
|
+
<pre>Rails.application.routes.draw do
|
|
218
|
+
mount RcrewAI::Rails::Engine => '/rcrewai'
|
|
219
|
+
# Your other routes...
|
|
220
|
+
end</pre>
|
|
221
|
+
<p class="caption">The web UI is then available at <code>/rcrewai</code> and the API at <code>/rcrewai/api/v1/</code>.</p>
|
|
222
|
+
</div>
|
|
223
|
+
</section>
|
|
224
|
+
|
|
225
|
+
<section id="config">
|
|
226
|
+
<div class="wrap">
|
|
227
|
+
<h2>Configuration</h2>
|
|
228
|
+
<p class="sub">Configure the engine and the underlying RcrewAI gem in an initializer.</p>
|
|
229
|
+
<pre>RcrewAI::Rails.configure do |config|
|
|
230
|
+
# ActiveJob queue for background processing
|
|
231
|
+
config.job_queue_name = "default"
|
|
232
|
+
|
|
233
|
+
# Enable/disable web UI
|
|
234
|
+
config.enable_web_ui = true
|
|
235
|
+
|
|
236
|
+
# Use async execution by default
|
|
237
|
+
config.async_execution = true
|
|
238
|
+
|
|
239
|
+
# Default LLM settings
|
|
240
|
+
config.default_llm_provider = "openai"
|
|
241
|
+
config.default_llm_model = "gpt-4"
|
|
242
|
+
|
|
243
|
+
# Logging
|
|
244
|
+
config.enable_logging = true
|
|
245
|
+
config.log_level = :info
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Configure the base RcrewAI gem
|
|
249
|
+
RcrewAI.configure do |config|
|
|
250
|
+
config.openai_api_key = ENV["OPENAI_API_KEY"]
|
|
251
|
+
# Add other LLM provider keys as needed
|
|
252
|
+
end</pre>
|
|
253
|
+
</div>
|
|
254
|
+
</section>
|
|
255
|
+
|
|
256
|
+
<section id="usage">
|
|
257
|
+
<div class="wrap">
|
|
258
|
+
<h2>Usage</h2>
|
|
259
|
+
<p class="sub">Generate a crew, or define one programmatically.</p>
|
|
260
|
+
|
|
261
|
+
<h3>Generate a crew</h3>
|
|
262
|
+
<pre>$ rails generate rcrewai:rails:crew research_team sequential \
|
|
263
|
+
--agents researcher analyst writer \
|
|
264
|
+
--description "Research team for market analysis"</pre>
|
|
265
|
+
<p class="caption">Creates a crew class in <code>app/crews/research_team_crew.rb</code>.</p>
|
|
266
|
+
|
|
267
|
+
<h3>Define a crew programmatically</h3>
|
|
268
|
+
<pre>class ResearchCrew
|
|
269
|
+
include RcrewAI::Rails::CrewBuilder
|
|
270
|
+
|
|
271
|
+
crew_name "research_team"
|
|
272
|
+
crew_description "AI-powered research team"
|
|
273
|
+
process_type :sequential
|
|
274
|
+
memory_enabled true
|
|
275
|
+
|
|
276
|
+
def setup_agents
|
|
277
|
+
@researcher = create_agent("researcher",
|
|
278
|
+
role: "Senior Research Analyst",
|
|
279
|
+
goal: "Uncover insights and trends",
|
|
280
|
+
backstory: "Expert researcher with years of experience"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
@writer = create_agent("writer",
|
|
284
|
+
role: "Content Writer",
|
|
285
|
+
goal: "Create compelling reports",
|
|
286
|
+
backstory: "Skilled writer specializing in technical content"
|
|
287
|
+
)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def setup_tasks
|
|
291
|
+
@research_task = create_task("Research latest AI trends",
|
|
292
|
+
expected_output: "Comprehensive research report",
|
|
293
|
+
position: 1
|
|
294
|
+
)
|
|
295
|
+
assign_agent_to_task(@researcher, @research_task)
|
|
296
|
+
|
|
297
|
+
@writing_task = create_task("Write executive summary",
|
|
298
|
+
expected_output: "2-page executive summary",
|
|
299
|
+
position: 2
|
|
300
|
+
)
|
|
301
|
+
assign_agent_to_task(@writer, @writing_task)
|
|
302
|
+
add_task_dependency(@writing_task, @research_task)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Execute the crew
|
|
307
|
+
crew = ResearchCrew.new
|
|
308
|
+
execution = crew.execute(topic: "AI in Healthcare")</pre>
|
|
309
|
+
|
|
310
|
+
<h3>Running executions</h3>
|
|
311
|
+
<p>Executions run through ActiveJob by default, using whatever adapter your app is configured with:</p>
|
|
312
|
+
<pre># Async execution (default)
|
|
313
|
+
crew.execute_async(inputs)
|
|
314
|
+
|
|
315
|
+
# Sync execution
|
|
316
|
+
crew.execute_sync(inputs)
|
|
317
|
+
|
|
318
|
+
# Custom job options
|
|
319
|
+
CrewExecutionJob.set(wait: 5.minutes).perform_later(crew, inputs)</pre>
|
|
320
|
+
</div>
|
|
321
|
+
</section>
|
|
322
|
+
|
|
323
|
+
<section id="tools">
|
|
324
|
+
<div class="wrap">
|
|
325
|
+
<h2>Rails Tools</h2>
|
|
326
|
+
<p class="sub">Give your agents safe, scoped access to your Rails app.</p>
|
|
327
|
+
<p>Bundled tools include <code>ActiveRecordTool</code>, <code>ActionMailerTool</code>,
|
|
328
|
+
<code>RailsCacheTool</code>, <code>ActiveStorageTool</code>, and <code>RailsLoggerTool</code> —
|
|
329
|
+
each with allow-lists so agents only touch what you permit.</p>
|
|
330
|
+
<pre>class DataAnalystAgent
|
|
331
|
+
include RcrewAI::Rails::AgentBuilder
|
|
332
|
+
|
|
333
|
+
agent_role "Data Analyst"
|
|
334
|
+
agent_goal "Analyze application data"
|
|
335
|
+
|
|
336
|
+
tools [
|
|
337
|
+
RcrewAI::Rails::Tools::ActiveRecordTool.new(
|
|
338
|
+
model_class: User,
|
|
339
|
+
allowed_methods: [:count, :where, :pluck]
|
|
340
|
+
),
|
|
341
|
+
RcrewAI::Rails::Tools::RailsCacheTool.new,
|
|
342
|
+
RcrewAI::Rails::Tools::ActionMailerTool.new(
|
|
343
|
+
mailer_class: ReportMailer,
|
|
344
|
+
allowed_methods: [:send_report]
|
|
345
|
+
)
|
|
346
|
+
]
|
|
347
|
+
end</pre>
|
|
348
|
+
</div>
|
|
349
|
+
</section>
|
|
350
|
+
|
|
351
|
+
<section id="dashboard">
|
|
352
|
+
<div class="wrap">
|
|
353
|
+
<div class="two-col">
|
|
354
|
+
<div>
|
|
355
|
+
<h2>Web Dashboard</h2>
|
|
356
|
+
<p class="sub">Mounted at <code>/rcrewai</code>.</p>
|
|
357
|
+
<ul class="clean">
|
|
358
|
+
<li>View all crews and their configurations</li>
|
|
359
|
+
<li>Monitor execution status and logs</li>
|
|
360
|
+
<li>Start new executions</li>
|
|
361
|
+
<li>Browse execution history and results</li>
|
|
362
|
+
</ul>
|
|
363
|
+
</div>
|
|
364
|
+
<div>
|
|
365
|
+
<h2>Models</h2>
|
|
366
|
+
<p class="sub">Persisted as ActiveRecord.</p>
|
|
367
|
+
<ul class="clean">
|
|
368
|
+
<li><code>RcrewAI::Rails::Crew</code></li>
|
|
369
|
+
<li><code>RcrewAI::Rails::Agent</code></li>
|
|
370
|
+
<li><code>RcrewAI::Rails::Task</code></li>
|
|
371
|
+
<li><code>RcrewAI::Rails::Execution</code></li>
|
|
372
|
+
<li><code>RcrewAI::Rails::ExecutionLog</code></li>
|
|
373
|
+
</ul>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
</section>
|
|
378
|
+
|
|
379
|
+
<section id="api">
|
|
380
|
+
<div class="wrap">
|
|
381
|
+
<h2>API Endpoints</h2>
|
|
382
|
+
<p class="sub">JSON API served under <code>/rcrewai/api/v1/</code>.</p>
|
|
383
|
+
<table>
|
|
384
|
+
<thead><tr><th style="width:90px;">Method</th><th>Path</th></tr></thead>
|
|
385
|
+
<tbody>
|
|
386
|
+
<tr><td><span class="verb get">GET</span></td><td class="method">/crews</td></tr>
|
|
387
|
+
<tr><td><span class="verb get">GET</span></td><td class="method">/crews/:id</td></tr>
|
|
388
|
+
<tr><td><span class="verb post">POST</span></td><td class="method">/crews/:id/execute</td></tr>
|
|
389
|
+
<tr><td><span class="verb get">GET</span></td><td class="method">/executions</td></tr>
|
|
390
|
+
<tr><td><span class="verb get">GET</span></td><td class="method">/executions/:id</td></tr>
|
|
391
|
+
<tr><td><span class="verb get">GET</span></td><td class="method">/executions/:id/status</td></tr>
|
|
392
|
+
<tr><td><span class="verb get">GET</span></td><td class="method">/executions/:id/logs</td></tr>
|
|
393
|
+
</tbody>
|
|
394
|
+
</table>
|
|
395
|
+
</div>
|
|
396
|
+
</section>
|
|
397
|
+
|
|
398
|
+
<footer>
|
|
399
|
+
<div class="wrap">
|
|
400
|
+
<p>
|
|
401
|
+
<a href="https://github.com/gkosmo/rcrewai-rails">GitHub</a> ·
|
|
402
|
+
<a href="https://rubygems.org/gems/rcrewai-rails">RubyGems</a> ·
|
|
403
|
+
<a href="https://github.com/gkosmo/rcrewai-rails/issues">Issues</a>
|
|
404
|
+
</p>
|
|
405
|
+
<p>Released under the MIT License.</p>
|
|
406
|
+
</div>
|
|
407
|
+
</footer>
|
|
408
|
+
|
|
409
|
+
</body>
|
|
410
|
+
</html>
|