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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +108 -0
  3. data/LICENSE +21 -0
  4. data/README.md +328 -0
  5. data/Rakefile +130 -0
  6. data/bin/rcrewai +7 -0
  7. data/docs/_config.yml +59 -0
  8. data/docs/_layouts/api.html +16 -0
  9. data/docs/_layouts/default.html +78 -0
  10. data/docs/_layouts/example.html +24 -0
  11. data/docs/_layouts/tutorial.html +33 -0
  12. data/docs/api/configuration.md +327 -0
  13. data/docs/api/crew.md +345 -0
  14. data/docs/api/index.md +41 -0
  15. data/docs/api/tools.md +412 -0
  16. data/docs/assets/css/style.css +416 -0
  17. data/docs/examples/human-in-the-loop.md +382 -0
  18. data/docs/examples/index.md +78 -0
  19. data/docs/examples/production-ready-crew.md +485 -0
  20. data/docs/examples/simple-research-crew.md +297 -0
  21. data/docs/index.md +353 -0
  22. data/docs/tutorials/getting-started.md +341 -0
  23. data/examples/async_execution_example.rb +294 -0
  24. data/examples/hierarchical_crew_example.rb +193 -0
  25. data/examples/human_in_the_loop_example.rb +233 -0
  26. data/lib/rcrewai/agent.rb +636 -0
  27. data/lib/rcrewai/async_executor.rb +248 -0
  28. data/lib/rcrewai/cli.rb +39 -0
  29. data/lib/rcrewai/configuration.rb +100 -0
  30. data/lib/rcrewai/crew.rb +292 -0
  31. data/lib/rcrewai/human_input.rb +520 -0
  32. data/lib/rcrewai/llm_client.rb +41 -0
  33. data/lib/rcrewai/llm_clients/anthropic.rb +127 -0
  34. data/lib/rcrewai/llm_clients/azure.rb +158 -0
  35. data/lib/rcrewai/llm_clients/base.rb +82 -0
  36. data/lib/rcrewai/llm_clients/google.rb +158 -0
  37. data/lib/rcrewai/llm_clients/ollama.rb +199 -0
  38. data/lib/rcrewai/llm_clients/openai.rb +124 -0
  39. data/lib/rcrewai/memory.rb +194 -0
  40. data/lib/rcrewai/process.rb +421 -0
  41. data/lib/rcrewai/task.rb +376 -0
  42. data/lib/rcrewai/tools/base.rb +82 -0
  43. data/lib/rcrewai/tools/code_executor.rb +333 -0
  44. data/lib/rcrewai/tools/email_sender.rb +210 -0
  45. data/lib/rcrewai/tools/file_reader.rb +111 -0
  46. data/lib/rcrewai/tools/file_writer.rb +115 -0
  47. data/lib/rcrewai/tools/pdf_processor.rb +342 -0
  48. data/lib/rcrewai/tools/sql_database.rb +226 -0
  49. data/lib/rcrewai/tools/web_search.rb +131 -0
  50. data/lib/rcrewai/version.rb +5 -0
  51. data/lib/rcrewai.rb +36 -0
  52. data/rcrewai.gemspec +54 -0
  53. 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