rcrewai 0.1.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 +7 -0
- data/CHANGELOG.md +108 -0
- data/LICENSE +21 -0
- data/README.md +328 -0
- data/Rakefile +130 -0
- data/bin/rcrewai +7 -0
- data/docs/_config.yml +59 -0
- data/docs/_layouts/api.html +16 -0
- data/docs/_layouts/default.html +78 -0
- data/docs/_layouts/example.html +24 -0
- data/docs/_layouts/tutorial.html +33 -0
- data/docs/api/configuration.md +327 -0
- data/docs/api/crew.md +345 -0
- data/docs/api/index.md +41 -0
- data/docs/api/tools.md +412 -0
- data/docs/assets/css/style.css +416 -0
- data/docs/examples/human-in-the-loop.md +382 -0
- data/docs/examples/index.md +78 -0
- data/docs/examples/production-ready-crew.md +485 -0
- data/docs/examples/simple-research-crew.md +297 -0
- data/docs/index.md +353 -0
- data/docs/tutorials/getting-started.md +341 -0
- data/examples/async_execution_example.rb +294 -0
- data/examples/hierarchical_crew_example.rb +193 -0
- data/examples/human_in_the_loop_example.rb +233 -0
- data/lib/rcrewai/agent.rb +636 -0
- data/lib/rcrewai/async_executor.rb +248 -0
- data/lib/rcrewai/cli.rb +39 -0
- data/lib/rcrewai/configuration.rb +100 -0
- data/lib/rcrewai/crew.rb +292 -0
- data/lib/rcrewai/human_input.rb +520 -0
- data/lib/rcrewai/llm_client.rb +41 -0
- data/lib/rcrewai/llm_clients/anthropic.rb +127 -0
- data/lib/rcrewai/llm_clients/azure.rb +158 -0
- data/lib/rcrewai/llm_clients/base.rb +82 -0
- data/lib/rcrewai/llm_clients/google.rb +158 -0
- data/lib/rcrewai/llm_clients/ollama.rb +199 -0
- data/lib/rcrewai/llm_clients/openai.rb +124 -0
- data/lib/rcrewai/memory.rb +194 -0
- data/lib/rcrewai/process.rb +421 -0
- data/lib/rcrewai/task.rb +376 -0
- data/lib/rcrewai/tools/base.rb +82 -0
- data/lib/rcrewai/tools/code_executor.rb +333 -0
- data/lib/rcrewai/tools/email_sender.rb +210 -0
- data/lib/rcrewai/tools/file_reader.rb +111 -0
- data/lib/rcrewai/tools/file_writer.rb +115 -0
- data/lib/rcrewai/tools/pdf_processor.rb +342 -0
- data/lib/rcrewai/tools/sql_database.rb +226 -0
- data/lib/rcrewai/tools/web_search.rb +131 -0
- data/lib/rcrewai/version.rb +5 -0
- data/lib/rcrewai.rb +36 -0
- data/rcrewai.gemspec +54 -0
- metadata +365 -0
@@ -0,0 +1,421 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RCrewAI
|
4
|
+
module Process
|
5
|
+
class Base
|
6
|
+
attr_reader :crew, :logger
|
7
|
+
|
8
|
+
def initialize(crew)
|
9
|
+
@crew = crew
|
10
|
+
@logger = Logger.new($stdout)
|
11
|
+
@logger.level = crew.verbose ? Logger::DEBUG : Logger::INFO
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute
|
15
|
+
raise NotImplementedError, "Subclasses must implement #execute method"
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def log_execution_start
|
21
|
+
@logger.info "Starting #{self.class.name.split('::').last.downcase} process execution"
|
22
|
+
@logger.info "Crew: #{crew.name} with #{crew.agents.length} agents, #{crew.tasks.length} tasks"
|
23
|
+
end
|
24
|
+
|
25
|
+
def log_execution_end(results)
|
26
|
+
completed_tasks = results.count { |r| r[:status] == :completed }
|
27
|
+
@logger.info "Process execution completed: #{completed_tasks}/#{crew.tasks.length} tasks successful"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Sequential < Base
|
32
|
+
def execute
|
33
|
+
log_execution_start
|
34
|
+
results = []
|
35
|
+
|
36
|
+
crew.tasks.each do |task|
|
37
|
+
@logger.info "Executing task: #{task.name}"
|
38
|
+
begin
|
39
|
+
result = task.execute
|
40
|
+
results << { task: task, result: result, status: :completed }
|
41
|
+
rescue => e
|
42
|
+
@logger.error "Task #{task.name} failed: #{e.message}"
|
43
|
+
results << { task: task, result: e.message, status: :failed }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
log_execution_end(results)
|
48
|
+
results
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Hierarchical < Base
|
53
|
+
attr_reader :manager_agent, :hierarchy
|
54
|
+
|
55
|
+
def initialize(crew)
|
56
|
+
super
|
57
|
+
@manager_agent = find_or_create_manager
|
58
|
+
@hierarchy = build_hierarchy
|
59
|
+
validate_hierarchy!
|
60
|
+
end
|
61
|
+
|
62
|
+
def execute
|
63
|
+
log_execution_start
|
64
|
+
@logger.info "Hierarchical execution with manager: #{manager_agent.name}"
|
65
|
+
|
66
|
+
# Manager coordinates all execution
|
67
|
+
execution_plan = create_execution_plan
|
68
|
+
results = execute_with_manager(execution_plan)
|
69
|
+
|
70
|
+
log_execution_end(results)
|
71
|
+
results
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def find_or_create_manager
|
77
|
+
# Look for an agent marked as manager
|
78
|
+
manager = crew.agents.find { |agent| agent.is_manager? }
|
79
|
+
|
80
|
+
# If no explicit manager, find agent with delegation capabilities
|
81
|
+
manager ||= crew.agents.find { |agent| agent.allow_delegation }
|
82
|
+
|
83
|
+
# Create a default manager if none found
|
84
|
+
if manager.nil?
|
85
|
+
@logger.warn "No manager agent found, creating default manager"
|
86
|
+
manager = create_default_manager
|
87
|
+
crew.add_agent(manager)
|
88
|
+
end
|
89
|
+
|
90
|
+
manager
|
91
|
+
end
|
92
|
+
|
93
|
+
def create_default_manager
|
94
|
+
RCrewAI::Agent.new(
|
95
|
+
name: "crew_manager",
|
96
|
+
role: "Crew Manager",
|
97
|
+
goal: "Coordinate team efforts and delegate tasks effectively",
|
98
|
+
backstory: "You are an experienced project manager who coordinates team efforts, delegates tasks appropriately, and ensures deliverables meet requirements.",
|
99
|
+
allow_delegation: true,
|
100
|
+
manager: true,
|
101
|
+
verbose: crew.verbose
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
def build_hierarchy
|
106
|
+
hierarchy = {
|
107
|
+
manager: manager_agent,
|
108
|
+
subordinates: crew.agents.reject { |agent| agent == manager_agent },
|
109
|
+
task_assignments: {},
|
110
|
+
delegation_chains: []
|
111
|
+
}
|
112
|
+
|
113
|
+
# Assign tasks to appropriate agents
|
114
|
+
crew.tasks.each do |task|
|
115
|
+
if task.agent && task.agent != manager_agent
|
116
|
+
hierarchy[:task_assignments][task] = task.agent
|
117
|
+
else
|
118
|
+
# Manager will delegate this task
|
119
|
+
best_agent = find_best_agent_for_task(task, hierarchy[:subordinates])
|
120
|
+
hierarchy[:task_assignments][task] = best_agent
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
hierarchy
|
125
|
+
end
|
126
|
+
|
127
|
+
def find_best_agent_for_task(task, available_agents)
|
128
|
+
# Simple heuristic: match task keywords with agent role/goal
|
129
|
+
task_keywords = extract_keywords(task.description.downcase)
|
130
|
+
|
131
|
+
best_agent = available_agents.max_by do |agent|
|
132
|
+
agent_keywords = extract_keywords("#{agent.role} #{agent.goal}".downcase)
|
133
|
+
common_keywords = (task_keywords & agent_keywords).length
|
134
|
+
# Boost score for agents with relevant tools
|
135
|
+
tool_bonus = agent.tools.any? ? 0.5 : 0
|
136
|
+
common_keywords + tool_bonus
|
137
|
+
end
|
138
|
+
|
139
|
+
best_agent || available_agents.first
|
140
|
+
end
|
141
|
+
|
142
|
+
def extract_keywords(text)
|
143
|
+
stopwords = %w[the a an and or but in on at to for of with by is are was were be been being have has had do does did will would could should]
|
144
|
+
text.split(/\W+/).reject { |w| w.length < 3 || stopwords.include?(w) }
|
145
|
+
end
|
146
|
+
|
147
|
+
def validate_hierarchy!
|
148
|
+
raise ProcessError, "No manager agent available" unless manager_agent
|
149
|
+
raise ProcessError, "No subordinate agents available" if hierarchy[:subordinates].empty?
|
150
|
+
raise ProcessError, "No tasks to execute" if crew.tasks.empty?
|
151
|
+
end
|
152
|
+
|
153
|
+
def create_execution_plan
|
154
|
+
plan = {
|
155
|
+
phases: [],
|
156
|
+
total_tasks: crew.tasks.length,
|
157
|
+
dependencies: build_dependency_graph
|
158
|
+
}
|
159
|
+
|
160
|
+
# Group tasks by dependencies
|
161
|
+
phases = organize_tasks_by_dependencies
|
162
|
+
plan[:phases] = phases
|
163
|
+
|
164
|
+
@logger.debug "Execution plan: #{phases.length} phases"
|
165
|
+
phases.each_with_index do |phase, i|
|
166
|
+
@logger.debug " Phase #{i + 1}: #{phase.map(&:name).join(', ')}"
|
167
|
+
end
|
168
|
+
|
169
|
+
plan
|
170
|
+
end
|
171
|
+
|
172
|
+
def build_dependency_graph
|
173
|
+
graph = {}
|
174
|
+
crew.tasks.each do |task|
|
175
|
+
graph[task] = task.context || []
|
176
|
+
end
|
177
|
+
graph
|
178
|
+
end
|
179
|
+
|
180
|
+
def organize_tasks_by_dependencies
|
181
|
+
phases = []
|
182
|
+
remaining_tasks = crew.tasks.dup
|
183
|
+
completed_tasks = Set.new
|
184
|
+
|
185
|
+
while remaining_tasks.any?
|
186
|
+
# Find tasks with no unmet dependencies
|
187
|
+
ready_tasks = remaining_tasks.select do |task|
|
188
|
+
dependencies = task.context || []
|
189
|
+
dependencies.all? { |dep| completed_tasks.include?(dep) }
|
190
|
+
end
|
191
|
+
|
192
|
+
if ready_tasks.empty?
|
193
|
+
# Circular dependency or other issue
|
194
|
+
@logger.warn "Circular dependency detected, executing remaining tasks in order"
|
195
|
+
phases << remaining_tasks
|
196
|
+
break
|
197
|
+
end
|
198
|
+
|
199
|
+
phases << ready_tasks
|
200
|
+
remaining_tasks -= ready_tasks
|
201
|
+
completed_tasks.merge(ready_tasks)
|
202
|
+
end
|
203
|
+
|
204
|
+
phases
|
205
|
+
end
|
206
|
+
|
207
|
+
def execute_with_manager(plan)
|
208
|
+
results = []
|
209
|
+
|
210
|
+
plan[:phases].each_with_index do |phase_tasks, phase_index|
|
211
|
+
@logger.info "Executing phase #{phase_index + 1}: #{phase_tasks.length} tasks"
|
212
|
+
|
213
|
+
# Manager delegates tasks in this phase
|
214
|
+
phase_results = execute_phase(phase_tasks, phase_index + 1)
|
215
|
+
results.concat(phase_results)
|
216
|
+
|
217
|
+
# Check if we should continue to next phase
|
218
|
+
if phase_results.any? { |r| r[:status] == :failed }
|
219
|
+
failed_tasks = phase_results.select { |r| r[:status] == :failed }
|
220
|
+
@logger.warn "Phase #{phase_index + 1} had #{failed_tasks.length} failures"
|
221
|
+
|
222
|
+
# Manager decides whether to continue
|
223
|
+
if should_abort_execution?(failed_tasks, phase_index + 1, plan)
|
224
|
+
@logger.error "Manager decided to abort execution due to critical failures"
|
225
|
+
break
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
results
|
231
|
+
end
|
232
|
+
|
233
|
+
def execute_phase(tasks, phase_number)
|
234
|
+
phase_results = []
|
235
|
+
|
236
|
+
# Manager creates delegation plan for this phase
|
237
|
+
delegation_plan = create_delegation_plan(tasks, phase_number)
|
238
|
+
|
239
|
+
# Execute delegated tasks
|
240
|
+
tasks.each do |task|
|
241
|
+
assigned_agent = hierarchy[:task_assignments][task]
|
242
|
+
|
243
|
+
@logger.info "Manager delegating '#{task.name}' to #{assigned_agent.name}"
|
244
|
+
|
245
|
+
begin
|
246
|
+
# Manager provides delegation context
|
247
|
+
delegation_context = create_delegation_context(task, assigned_agent, delegation_plan)
|
248
|
+
|
249
|
+
# Execute task with delegation
|
250
|
+
result = execute_delegated_task(task, assigned_agent, delegation_context)
|
251
|
+
|
252
|
+
phase_results << {
|
253
|
+
task: task,
|
254
|
+
result: result,
|
255
|
+
status: :completed,
|
256
|
+
assigned_agent: assigned_agent,
|
257
|
+
phase: phase_number
|
258
|
+
}
|
259
|
+
|
260
|
+
@logger.info "Task '#{task.name}' completed successfully"
|
261
|
+
|
262
|
+
rescue => e
|
263
|
+
@logger.error "Delegated task '#{task.name}' failed: #{e.message}"
|
264
|
+
|
265
|
+
phase_results << {
|
266
|
+
task: task,
|
267
|
+
result: e.message,
|
268
|
+
status: :failed,
|
269
|
+
assigned_agent: assigned_agent,
|
270
|
+
phase: phase_number,
|
271
|
+
error: e
|
272
|
+
}
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
phase_results
|
277
|
+
end
|
278
|
+
|
279
|
+
def create_delegation_plan(tasks, phase_number)
|
280
|
+
{
|
281
|
+
phase: phase_number,
|
282
|
+
tasks: tasks.map(&:name),
|
283
|
+
priorities: assign_task_priorities(tasks),
|
284
|
+
coordination_notes: generate_coordination_notes(tasks)
|
285
|
+
}
|
286
|
+
end
|
287
|
+
|
288
|
+
def assign_task_priorities(tasks)
|
289
|
+
# Simple priority assignment based on dependencies and complexity
|
290
|
+
priorities = {}
|
291
|
+
|
292
|
+
tasks.each do |task|
|
293
|
+
priority = :normal
|
294
|
+
|
295
|
+
# High priority if other tasks depend on this one
|
296
|
+
if crew.tasks.any? { |t| t.context&.include?(task) }
|
297
|
+
priority = :high
|
298
|
+
end
|
299
|
+
|
300
|
+
# Low priority if task is optional or has many dependencies
|
301
|
+
if task.context&.length.to_i > 2
|
302
|
+
priority = :low
|
303
|
+
end
|
304
|
+
|
305
|
+
priorities[task] = priority
|
306
|
+
end
|
307
|
+
|
308
|
+
priorities
|
309
|
+
end
|
310
|
+
|
311
|
+
def generate_coordination_notes(tasks)
|
312
|
+
notes = []
|
313
|
+
|
314
|
+
if tasks.length > 1
|
315
|
+
notes << "Multiple tasks in this phase - coordinate timing if needed"
|
316
|
+
end
|
317
|
+
|
318
|
+
if tasks.any? { |t| t.context&.any? }
|
319
|
+
notes << "Some tasks depend on previous results - ensure context is available"
|
320
|
+
end
|
321
|
+
|
322
|
+
if tasks.any? { |t| t.tools&.any? }
|
323
|
+
notes << "Tasks require external tools - monitor for failures"
|
324
|
+
end
|
325
|
+
|
326
|
+
notes.join(". ") + "."
|
327
|
+
end
|
328
|
+
|
329
|
+
def create_delegation_context(task, assigned_agent, delegation_plan)
|
330
|
+
{
|
331
|
+
delegation_source: "Manager: #{manager_agent.name}",
|
332
|
+
assignment_reason: generate_assignment_reason(task, assigned_agent),
|
333
|
+
phase_context: delegation_plan,
|
334
|
+
expectations: generate_task_expectations(task),
|
335
|
+
escalation_notes: "Contact manager if issues arise or guidance needed"
|
336
|
+
}
|
337
|
+
end
|
338
|
+
|
339
|
+
def generate_assignment_reason(task, agent)
|
340
|
+
"Assigned to #{agent.name} based on role '#{agent.role}' and expertise alignment with task requirements"
|
341
|
+
end
|
342
|
+
|
343
|
+
def generate_task_expectations(task)
|
344
|
+
expectations = []
|
345
|
+
expectations << "Expected output: #{task.expected_output}" if task.expected_output
|
346
|
+
expectations << "Quality: Professional and thorough"
|
347
|
+
expectations << "Communication: Report progress and any blockers"
|
348
|
+
expectations.join(". ") + "."
|
349
|
+
end
|
350
|
+
|
351
|
+
def execute_delegated_task(task, agent, delegation_context)
|
352
|
+
# Enhance task with delegation context
|
353
|
+
enhanced_task = task.dup
|
354
|
+
enhanced_task.instance_variable_set(:@delegation_context, delegation_context)
|
355
|
+
|
356
|
+
# Define method to access delegation context
|
357
|
+
def enhanced_task.delegation_context
|
358
|
+
@delegation_context
|
359
|
+
end
|
360
|
+
|
361
|
+
# Execute the task
|
362
|
+
agent.execute_task(enhanced_task)
|
363
|
+
end
|
364
|
+
|
365
|
+
def should_abort_execution?(failed_tasks, phase_number, plan)
|
366
|
+
# Abort if more than 50% of critical tasks failed
|
367
|
+
critical_failures = failed_tasks.count { |r| r[:task].context&.any? || r[:task].expected_output }
|
368
|
+
|
369
|
+
if critical_failures > (failed_tasks.length * 0.5)
|
370
|
+
@logger.error "Too many critical task failures (#{critical_failures}/#{failed_tasks.length})"
|
371
|
+
return true
|
372
|
+
end
|
373
|
+
|
374
|
+
# Abort if we're early in execution and having major issues
|
375
|
+
if phase_number <= 2 && failed_tasks.length > 1
|
376
|
+
@logger.error "Multiple failures in early phases indicate systemic issues"
|
377
|
+
return true
|
378
|
+
end
|
379
|
+
|
380
|
+
false
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
class Consensual < Base
|
385
|
+
def execute
|
386
|
+
log_execution_start
|
387
|
+
@logger.info "Consensual execution - agents collaborate on decisions"
|
388
|
+
|
389
|
+
# For now, implement as enhanced sequential with collaboration
|
390
|
+
# Full consensual process would involve agent voting/discussion
|
391
|
+
results = []
|
392
|
+
|
393
|
+
crew.tasks.each do |task|
|
394
|
+
@logger.info "Collaborative execution of task: #{task.name}"
|
395
|
+
|
396
|
+
# Simple consensus: let multiple agents provide input
|
397
|
+
consensus_result = execute_with_consensus(task)
|
398
|
+
results << consensus_result
|
399
|
+
end
|
400
|
+
|
401
|
+
log_execution_end(results)
|
402
|
+
results
|
403
|
+
end
|
404
|
+
|
405
|
+
private
|
406
|
+
|
407
|
+
def execute_with_consensus(task)
|
408
|
+
# For now, just execute normally
|
409
|
+
# Future: implement actual consensus mechanisms
|
410
|
+
begin
|
411
|
+
result = task.execute
|
412
|
+
{ task: task, result: result, status: :completed }
|
413
|
+
rescue => e
|
414
|
+
{ task: task, result: e.message, status: :failed }
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
class ProcessError < RCrewAI::Error; end
|
420
|
+
end
|
421
|
+
end
|