rcrewai 0.2.1 → 0.4.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +21 -0
  3. data/.rubocop_todo.yml +99 -0
  4. data/CHANGELOG.md +64 -1
  5. data/README.md +170 -2
  6. data/ROADMAP.md +84 -0
  7. data/Rakefile +53 -53
  8. data/bin/rcrewai +3 -3
  9. data/docs/mcp.md +109 -0
  10. data/docs/superpowers/plans/2026-05-11-llm-modernization.md +2753 -0
  11. data/docs/superpowers/specs/2026-05-11-llm-modernization-design.md +479 -0
  12. data/docs/upgrading-to-0.3.md +163 -0
  13. data/examples/async_execution_example.rb +82 -81
  14. data/examples/hierarchical_crew_example.rb +68 -72
  15. data/examples/human_in_the_loop_example.rb +73 -74
  16. data/examples/mcp_example.rb +48 -0
  17. data/examples/native_tools_example.rb +64 -0
  18. data/examples/streaming_example.rb +56 -0
  19. data/lib/rcrewai/agent.rb +181 -286
  20. data/lib/rcrewai/async_executor.rb +43 -43
  21. data/lib/rcrewai/cli.rb +11 -11
  22. data/lib/rcrewai/configuration.rb +34 -9
  23. data/lib/rcrewai/crew.rb +134 -39
  24. data/lib/rcrewai/events.rb +30 -0
  25. data/lib/rcrewai/flow/state.rb +47 -0
  26. data/lib/rcrewai/flow/state_store.rb +50 -0
  27. data/lib/rcrewai/flow.rb +243 -0
  28. data/lib/rcrewai/human_input.rb +104 -114
  29. data/lib/rcrewai/knowledge/base.rb +52 -0
  30. data/lib/rcrewai/knowledge/chunker.rb +31 -0
  31. data/lib/rcrewai/knowledge/embedder.rb +48 -0
  32. data/lib/rcrewai/knowledge/sources.rb +83 -0
  33. data/lib/rcrewai/knowledge/store.rb +58 -0
  34. data/lib/rcrewai/knowledge.rb +13 -0
  35. data/lib/rcrewai/legacy_react_runner.rb +172 -0
  36. data/lib/rcrewai/llm_client.rb +24 -1
  37. data/lib/rcrewai/llm_clients/anthropic.rb +174 -54
  38. data/lib/rcrewai/llm_clients/azure.rb +23 -128
  39. data/lib/rcrewai/llm_clients/base.rb +11 -7
  40. data/lib/rcrewai/llm_clients/google.rb +159 -95
  41. data/lib/rcrewai/llm_clients/ollama.rb +150 -106
  42. data/lib/rcrewai/llm_clients/openai.rb +140 -63
  43. data/lib/rcrewai/mcp/client.rb +101 -0
  44. data/lib/rcrewai/mcp/tool_adapter.rb +59 -0
  45. data/lib/rcrewai/mcp/transport/http.rb +53 -0
  46. data/lib/rcrewai/mcp/transport/stdio.rb +55 -0
  47. data/lib/rcrewai/mcp.rb +8 -0
  48. data/lib/rcrewai/memory.rb +45 -37
  49. data/lib/rcrewai/output_schema.rb +79 -0
  50. data/lib/rcrewai/planning.rb +65 -0
  51. data/lib/rcrewai/pricing.rb +34 -0
  52. data/lib/rcrewai/process.rb +86 -95
  53. data/lib/rcrewai/provider_schema.rb +38 -0
  54. data/lib/rcrewai/sse_parser.rb +55 -0
  55. data/lib/rcrewai/task.rb +145 -66
  56. data/lib/rcrewai/tool_runner.rb +132 -0
  57. data/lib/rcrewai/tool_schema.rb +97 -0
  58. data/lib/rcrewai/tools/base.rb +98 -37
  59. data/lib/rcrewai/tools/code_executor.rb +71 -74
  60. data/lib/rcrewai/tools/email_sender.rb +70 -78
  61. data/lib/rcrewai/tools/file_reader.rb +38 -30
  62. data/lib/rcrewai/tools/file_writer.rb +40 -38
  63. data/lib/rcrewai/tools/pdf_processor.rb +115 -130
  64. data/lib/rcrewai/tools/sql_database.rb +58 -55
  65. data/lib/rcrewai/tools/web_search.rb +26 -25
  66. data/lib/rcrewai/version.rb +2 -2
  67. data/lib/rcrewai.rb +20 -10
  68. data/rcrewai.gemspec +39 -39
  69. metadata +77 -47
data/lib/rcrewai/agent.rb CHANGED
@@ -4,13 +4,17 @@ require 'logger'
4
4
  require_relative 'llm_client'
5
5
  require_relative 'memory'
6
6
  require_relative 'tools/base'
7
+ require_relative 'tool_runner'
8
+ require_relative 'legacy_react_runner'
7
9
  require_relative 'human_input'
8
10
 
9
11
  module RCrewAI
10
12
  class Agent
11
13
  include HumanInteractionExtensions
12
- attr_reader :name, :role, :goal, :backstory, :tools, :memory, :llm_client
14
+ attr_reader :name, :role, :goal, :backstory, :tools, :memory, :llm_client, :knowledge
13
15
  attr_accessor :verbose, :allow_delegation, :max_iterations, :max_execution_time, :manager
16
+ # Set by the crew so agents see shared knowledge in addition to their own.
17
+ attr_writer :crew_knowledge
14
18
 
15
19
  def initialize(name:, role:, goal:, backstory: nil, tools: [], **options)
16
20
  @name = name
@@ -20,7 +24,7 @@ module RCrewAI
20
24
  @tools = tools
21
25
  @verbose = options.fetch(:verbose, false)
22
26
  @allow_delegation = options.fetch(:allow_delegation, false)
23
- @manager = options.fetch(:manager, false) # New manager flag
27
+ @manager = options.fetch(:manager, false) # New manager flag
24
28
  @max_iterations = options.fetch(:max_iterations, 10)
25
29
  @max_execution_time = options.fetch(:max_execution_time, 300) # 5 minutes
26
30
  @human_input_enabled = options.fetch(:human_input, false)
@@ -29,39 +33,50 @@ module RCrewAI
29
33
  @logger = Logger.new($stdout)
30
34
  @logger.level = verbose ? Logger::DEBUG : Logger::INFO
31
35
  @memory = Memory.new
32
- @llm_client = LLMClient.for_provider
33
- @subordinates = [] # For manager agents
36
+ @llm_client = build_llm_client(options[:llm])
37
+ @knowledge = build_knowledge(options[:knowledge], options[:knowledge_sources])
38
+ @subordinates = [] # For manager agents
34
39
  end
35
40
 
36
- def execute_task(task)
41
+ def execute_task(task, stream: nil, **opts)
37
42
  @logger.info "Agent #{name} starting task: #{task.name}"
38
43
  start_time = Time.now
39
44
 
40
45
  begin
41
- # Build context for the agent
42
- context = build_context(task)
43
-
44
- # Execute task with reasoning loop
45
- result = reasoning_loop(task, context)
46
-
46
+ initial_messages = build_initial_messages(task)
47
+ sink = stream || ->(_) {}
48
+
49
+ runner_class = pick_runner_class
50
+ @logger.info "[rcrewai] agent=#{name} runner=#{runner_class.name.split('::').last}"
51
+
52
+ runner = runner_class.new(
53
+ agent: self, llm: @llm_client, tools: @tools,
54
+ max_iterations: opts.fetch(:max_iterations, max_iterations),
55
+ event_sink: sink
56
+ )
57
+
58
+ runner_result = runner.run(messages: initial_messages)
47
59
  execution_time = Time.now - start_time
48
60
  @logger.info "Task completed in #{execution_time.round(2)}s"
49
-
50
- # Store in memory
51
- memory.add_execution(task, result, execution_time)
52
-
53
- task.result = result
54
- result
55
61
 
56
- rescue => e
62
+ result_string = runner_result[:content].to_s
63
+ memory.add_execution(task, result_string, execution_time)
64
+ task.result = result_string
65
+
66
+ build_task_result(task, runner_result)
67
+ rescue StandardError => e
57
68
  @logger.error "Task execution failed: #{e.message}"
58
69
  task.result = "Task failed: #{e.message}"
59
70
  raise AgentError, "Agent #{name} failed to execute task: #{e.message}"
60
71
  end
61
72
  end
62
73
 
74
+ def require_approval_for_tools?
75
+ @require_approval_for_tools && @human_input_enabled
76
+ end
77
+
63
78
  def available_tools_description
64
- return "No tools available." if tools.empty?
79
+ return 'No tools available.' if tools.empty?
65
80
 
66
81
  tools.map do |tool|
67
82
  "- #{tool.name}: #{tool.description}"
@@ -82,23 +97,21 @@ module RCrewAI
82
97
  end
83
98
 
84
99
  @logger.debug "Using tool: #{tool_name} with params: #{params}"
85
-
100
+
86
101
  begin
87
102
  result = tool.execute(**params)
88
-
103
+
89
104
  # Store tool usage in memory
90
105
  memory.add_tool_usage(tool_name, params, result)
91
-
106
+
92
107
  result
93
- rescue => e
108
+ rescue StandardError => e
94
109
  @logger.error "Tool execution failed: #{e.message}"
95
-
110
+
96
111
  # Offer human intervention if tool fails and human input is enabled
97
- if @human_input_enabled
98
- handle_tool_failure(tool_name, params, e)
99
- else
100
- raise e
101
- end
112
+ raise e unless @human_input_enabled
113
+
114
+ handle_tool_failure(tool_name, params, e)
102
115
  end
103
116
  end
104
117
 
@@ -109,32 +122,31 @@ module RCrewAI
109
122
 
110
123
  def add_subordinate(agent)
111
124
  return unless is_manager?
125
+
112
126
  @subordinates << agent unless @subordinates.include?(agent)
113
127
  end
114
128
 
115
- def subordinates
116
- @subordinates
117
- end
129
+ attr_reader :subordinates
118
130
 
119
131
  def delegate_task(task, target_agent)
120
132
  return unless is_manager?
121
133
  return unless @subordinates.include?(target_agent) || allow_delegation
122
134
 
123
135
  @logger.info "Manager #{name} delegating task '#{task.name}' to #{target_agent.name}"
124
-
136
+
125
137
  # Create delegation context
126
138
  delegation_prompt = build_delegation_prompt(task, target_agent)
127
-
139
+
128
140
  # Use LLM to create proper delegation
129
141
  response = llm_client.chat(
130
142
  messages: [{ role: 'user', content: delegation_prompt }],
131
143
  temperature: 0.2,
132
144
  max_tokens: 1000
133
145
  )
134
-
146
+
135
147
  delegation_instructions = response[:content]
136
148
  @logger.debug "Delegation instructions: #{delegation_instructions}"
137
-
149
+
138
150
  # Execute delegated task
139
151
  target_agent.execute_delegated_task(task, delegation_instructions, self)
140
152
  end
@@ -142,21 +154,21 @@ module RCrewAI
142
154
  def execute_delegated_task(task, delegation_instructions, manager_agent)
143
155
  @logger.info "Receiving delegation from manager #{manager_agent.name}"
144
156
  @logger.debug "Delegation instructions: #{delegation_instructions}"
145
-
157
+
146
158
  # Store delegation context in task
147
159
  original_description = task.description
148
160
  enhanced_description = "#{original_description}\n\nDelegation Instructions from #{manager_agent.name}:\n#{delegation_instructions}"
149
-
161
+
150
162
  # Temporarily modify task
151
163
  task.instance_variable_set(:@description, enhanced_description)
152
164
  task.instance_variable_set(:@manager, manager_agent)
153
-
165
+
154
166
  begin
155
167
  result = execute_task(task)
156
-
168
+
157
169
  # Report back to manager
158
170
  report_to_manager(task, result, manager_agent)
159
-
171
+
160
172
  result
161
173
  ensure
162
174
  # Restore original task description
@@ -185,6 +197,21 @@ module RCrewAI
185
197
 
186
198
  private
187
199
 
200
+ # Resolves the +llm:+ option into an LLM client. See LLMClient.resolve.
201
+ def build_llm_client(llm)
202
+ LLMClient.resolve(llm)
203
+ end
204
+
205
+ # Accepts a pre-built Knowledge::Base via +knowledge:+ or an array of
206
+ # sources via +knowledge_sources:+ (wrapped in a Base). Returns nil if
207
+ # neither is given.
208
+ def build_knowledge(knowledge, sources)
209
+ return knowledge if knowledge
210
+ return nil if sources.nil? || sources.empty?
211
+
212
+ Knowledge::Base.new(sources: sources)
213
+ end
214
+
188
215
  def build_context(task)
189
216
  context = {
190
217
  agent_role: role,
@@ -198,240 +225,109 @@ module RCrewAI
198
225
  }
199
226
 
200
227
  # Add delegation capabilities if allowed
201
- if allow_delegation
202
- context[:delegation_note] = "You can delegate subtasks to other agents if needed."
203
- end
228
+ context[:delegation_note] = 'You can delegate subtasks to other agents if needed.' if allow_delegation
204
229
 
205
230
  context
206
231
  end
207
232
 
208
- def reasoning_loop(task, context)
209
- iteration = 0
210
- start_time = Time.now
211
-
212
- loop do
213
- iteration += 1
214
- current_time = Time.now
215
-
216
- # Check limits
217
- if iteration > max_iterations
218
- @logger.warn "Max iterations (#{max_iterations}) reached"
219
- break
220
- end
221
-
222
- if current_time - start_time > max_execution_time
223
- @logger.warn "Max execution time (#{max_execution_time}s) reached"
224
- break
225
- end
226
-
227
- # Human review of reasoning at key points
228
- if @human_input_enabled && (iteration == 1 || iteration % 3 == 0)
229
- review_result = request_reasoning_review(task, context, iteration)
230
- if review_result && review_result[:feedback]
231
- context[:human_guidance] = review_result[:feedback]
232
- @logger.info "Incorporating human guidance into reasoning"
233
- end
234
- end
235
-
236
- # Generate reasoning prompt
237
- prompt = build_reasoning_prompt(context, iteration)
238
-
239
- # Get LLM response
240
- @logger.debug "Iteration #{iteration}: Sending prompt to LLM"
241
- response = llm_client.chat(
242
- messages: [{ role: 'user', content: prompt }],
243
- temperature: 0.1,
244
- max_tokens: 2000
245
- )
246
-
247
- reasoning = response[:content]
248
- @logger.debug "LLM Response: #{reasoning[0..200]}..." if verbose
249
-
250
- # Parse and execute actions
251
- action_result = parse_and_execute_actions(reasoning, task)
252
-
253
- # Check if task is complete
254
- if task_complete?(reasoning, action_result)
255
- final_result = extract_final_result(reasoning, action_result)
256
-
257
- # Human approval of final result if required
258
- if @require_approval_for_final_answer && @human_input_enabled
259
- final_result = request_final_answer_approval(final_result)
260
- end
261
-
262
- @logger.info "Task completed successfully in #{iteration} iterations"
263
- return final_result
264
- end
265
-
266
- # Update context with new information
267
- context[:previous_reasoning] = reasoning
268
- context[:previous_result] = action_result
269
- context[:iteration] = iteration
270
- end
271
-
272
- # If we exit the loop without completion, return best effort result
273
- final_result = extract_final_result(context[:previous_reasoning], context[:previous_result]) ||
274
- "Task execution reached limits without clear completion"
275
-
276
- # Human approval even for incomplete results if required
277
- if @require_approval_for_final_answer && @human_input_enabled
278
- final_result = request_final_answer_approval(final_result)
233
+ def build_initial_messages(task)
234
+ ctx = build_context(task)
235
+ system = +"You are #{ctx[:agent_role]}. Goal: #{ctx[:agent_goal]}."
236
+ system << " #{ctx[:agent_backstory]}" if ctx[:agent_backstory]
237
+ if @tools.any?
238
+ system << "\nAvailable Tools:\n#{ctx[:available_tools]}"
239
+ system << "\nYou may call tools by name when needed."
279
240
  end
280
-
281
- final_result
282
- end
241
+ system << "\n#{ctx[:delegation_note]}" if ctx[:delegation_note]
283
242
 
284
- def build_reasoning_prompt(context, iteration)
285
- prompt = <<~PROMPT
286
- You are #{context[:agent_role]}.
287
-
288
- Your goal: #{context[:agent_goal]}
289
-
290
- Background: #{context[:agent_backstory]}
291
-
292
- Current Task: #{context[:task_description]}
293
- Expected Output: #{context[:task_expected_output]}
294
-
295
- Available Tools:
296
- #{context[:available_tools]}
297
-
298
- #{context[:delegation_note] if context[:delegation_note]}
299
-
300
- #{build_context_section(context)}
301
-
302
- This is iteration #{iteration}. Think step by step about how to approach this task.
303
-
304
- You can:
305
- 1. Use tools by writing: USE_TOOL[tool_name](param1=value1, param2=value2)
306
- 2. Provide your final answer when ready: FINAL_ANSWER[your complete response]
307
- 3. Continue reasoning if you need more information
308
-
309
- What is your next step?
310
- PROMPT
243
+ user = +"Task: #{task.description}"
244
+ user << "\nExpected Output: #{task.expected_output}" if task.expected_output
245
+ user << "\nAdditional Context:\n#{ctx[:context_data]}" if ctx[:context_data] && !ctx[:context_data].to_s.empty?
311
246
 
312
- prompt
313
- end
247
+ knowledge = retrieve_knowledge(task)
248
+ user << "\n\nRelevant Knowledge:\n#{knowledge}" unless knowledge.empty?
314
249
 
315
- def build_context_section(context)
316
- sections = []
317
-
318
- if context[:context_data] && !context[:context_data].empty?
319
- sections << "Additional Context:\n#{context[:context_data]}"
320
- end
321
-
322
- if context[:previous_executions] && !context[:previous_executions].empty?
323
- sections << "Previous Similar Tasks:\n#{context[:previous_executions]}"
324
- end
325
-
326
- if context[:human_guidance]
327
- sections << "Human Guidance:\n#{context[:human_guidance]}"
328
- end
329
-
330
- if context[:previous_reasoning]
331
- sections << "Previous Reasoning:\n#{context[:previous_reasoning]}"
332
- end
333
-
334
- if context[:previous_result]
335
- sections << "Previous Action Result:\n#{context[:previous_result]}"
336
- end
337
-
338
- sections.join("\n\n")
250
+ [
251
+ { role: 'system', content: system },
252
+ { role: 'user', content: user }
253
+ ]
339
254
  end
340
255
 
341
- def parse_and_execute_actions(reasoning, task)
342
- results = []
343
-
344
- # Look for tool usage patterns
345
- tool_matches = reasoning.scan(/USE_TOOL\[(\w+)\]\(([^)]*)\)/)
346
- tool_matches.each do |tool_name, params_str|
347
- begin
348
- params = parse_tool_params(params_str)
349
- result = use_tool(tool_name, **params)
350
- results << "Tool #{tool_name} result: #{result}"
351
- rescue => e
352
- results << "Tool #{tool_name} failed: #{e.message}"
353
- @logger.error "Tool execution failed: #{e.message}"
354
- end
355
- end
356
-
357
- results.join("\n")
256
+ # Retrieves knowledge chunks relevant to the task from the agent's own
257
+ # knowledge base and/or the crew-level base injected via #knowledge=.
258
+ def retrieve_knowledge(task)
259
+ bases = [@knowledge, @crew_knowledge].compact
260
+ return '' if bases.empty?
261
+
262
+ chunks = bases.flat_map { |kb| kb.search(task.description, k: 3) }
263
+ chunks.uniq.join("\n---\n")
264
+ rescue StandardError => e
265
+ @logger.warn("Knowledge retrieval failed: #{e.message}")
266
+ ''
358
267
  end
359
268
 
360
- def parse_tool_params(params_str)
361
- params = {}
362
- return params if params_str.strip.empty?
363
-
364
- param_pairs = params_str.split(',').map(&:strip)
365
- param_pairs.each do |pair|
366
- key, value = pair.split('=', 2).map(&:strip)
367
- if key && value
368
- # Remove quotes if present
369
- value = value.gsub(/^["']|["']$/, '')
370
- params[key.to_sym] = value
371
- end
372
- end
373
-
374
- params
269
+ def build_task_result(task, runner_result)
270
+ {
271
+ task: task.name,
272
+ agent: name,
273
+ content: runner_result[:content],
274
+ tool_calls_history: runner_result[:tool_calls_history] || [],
275
+ usage: runner_result[:usage] || {},
276
+ iterations: runner_result[:iterations],
277
+ finish_reason: runner_result[:finish_reason]
278
+ }
375
279
  end
376
280
 
377
- def task_complete?(reasoning, action_result)
378
- reasoning.include?('FINAL_ANSWER[') ||
379
- reasoning.downcase.include?('task complete') ||
380
- reasoning.downcase.include?('finished')
281
+ def pick_runner_class
282
+ schemas_ok = @tools.empty? || @tools.all? { |t| t.respond_to?(:json_schema) && t.json_schema }
283
+ native = @llm_client.respond_to?(:supports_native_tools?) &&
284
+ safe_supports_native?(@llm_client)
285
+ schemas_ok && native ? ToolRunner : LegacyReactRunner
381
286
  end
382
287
 
383
- def extract_final_result(reasoning, action_result)
384
- # Look for FINAL_ANSWER pattern
385
- if match = reasoning.match(/FINAL_ANSWER\[(.*?)\]$/m)
386
- return match[1].strip
387
- end
388
-
389
- # Otherwise try to extract meaningful result from reasoning
390
- lines = reasoning.split("\n").map(&:strip).reject(&:empty?)
391
- final_lines = lines.last(3).join(" ")
392
-
393
- return final_lines if final_lines.length > 20
394
-
395
- # Fallback to action result
396
- action_result
288
+ def safe_supports_native?(llm)
289
+ model = llm.respond_to?(:config) && llm.config.respond_to?(:model) ? llm.config.model : nil
290
+ llm.supports_native_tools?(model: model)
291
+ rescue StandardError
292
+ false
397
293
  end
398
294
 
399
295
  # Manager-specific private methods
400
296
  def build_delegation_prompt(task, target_agent)
401
297
  <<~PROMPT
402
298
  You are #{name}, a #{role}.
403
-
299
+
404
300
  You need to delegate the following task to #{target_agent.name} (#{target_agent.role}):
405
-
301
+
406
302
  Task: #{task.name}
407
303
  Description: #{task.description}
408
304
  Expected Output: #{task.expected_output || 'Not specified'}
409
-
305
+
410
306
  Target Agent Profile:
411
307
  - Role: #{target_agent.role}
412
308
  - Goal: #{target_agent.goal}
413
309
  - Available Tools: #{target_agent.available_tools_description}
414
-
310
+
415
311
  Create clear, specific delegation instructions that:
416
312
  1. Explain why this agent is the right choice for this task
417
313
  2. Provide any additional context or requirements
418
314
  3. Set clear expectations for deliverables
419
315
  4. Include any coordination notes with other team members
420
-
316
+
421
317
  Keep instructions concise but comprehensive.
422
318
  PROMPT
423
319
  end
424
320
 
425
321
  def report_to_manager(task, result, manager_agent)
426
322
  @logger.info "Reporting task completion to manager #{manager_agent.name}"
427
-
323
+
428
324
  # Store the delegation result in manager's memory
429
325
  manager_agent.memory.add_execution(
430
- task,
326
+ task,
431
327
  "Delegated to #{name}: #{result}",
432
328
  task.execution_time || 0
433
329
  )
434
-
330
+
435
331
  # Could enhance with formal reporting mechanism
436
332
  end
437
333
 
@@ -440,34 +336,33 @@ module RCrewAI
440
336
  message = "Agent #{name} wants to use tool '#{tool_name}'"
441
337
  context = "Parameters: #{params.inspect}"
442
338
  consequences = "This will execute the #{tool_name} tool with the specified parameters."
443
-
444
- request_human_approval(message,
445
- context: context,
446
- consequences: consequences,
447
- timeout: 60
448
- )
339
+
340
+ request_human_approval(message,
341
+ context: context,
342
+ consequences: consequences,
343
+ timeout: 60)
449
344
  end
450
345
 
451
346
  def handle_tool_failure(tool_name, params, error)
452
- @logger.warn "Requesting human intervention for tool failure"
453
-
347
+ @logger.warn 'Requesting human intervention for tool failure'
348
+
454
349
  choices = [
455
- "Retry with same parameters",
456
- "Retry with different parameters",
457
- "Skip this tool and continue",
458
- "Abort task execution"
350
+ 'Retry with same parameters',
351
+ 'Retry with different parameters',
352
+ 'Skip this tool and continue',
353
+ 'Abort task execution'
459
354
  ]
460
-
355
+
461
356
  choice_result = request_human_choice(
462
357
  "Tool '#{tool_name}' failed with error: #{error.message}. How should I proceed?",
463
358
  choices,
464
359
  timeout: 120
465
360
  )
466
-
361
+
467
362
  case choice_result[:choice_index]
468
363
  when 0
469
364
  # Retry with same parameters
470
- @logger.info "Human requested retry with same parameters"
365
+ @logger.info 'Human requested retry with same parameters'
471
366
  tool = tools.find { |t| t.name == tool_name }
472
367
  tool.execute(**params)
473
368
  when 1
@@ -475,11 +370,11 @@ module RCrewAI
475
370
  new_params_result = request_human_input(
476
371
  "Please provide new parameters for #{tool_name} (JSON format):",
477
372
  type: :json,
478
- help_text: "Enter parameters as JSON, e.g. {\"param1\": \"value1\"}"
373
+ help_text: 'Enter parameters as JSON, e.g. {"param1": "value1"}'
479
374
  )
480
-
375
+
481
376
  if new_params_result[:valid]
482
- @logger.info "Human provided new parameters, retrying tool"
377
+ @logger.info 'Human provided new parameters, retrying tool'
483
378
  tool = tools.find { |t| t.name == tool_name }
484
379
  tool.execute(**new_params_result[:processed_input])
485
380
  else
@@ -487,43 +382,43 @@ module RCrewAI
487
382
  end
488
383
  when 2
489
384
  # Skip tool
490
- @logger.info "Human requested to skip failed tool"
491
- "Tool execution skipped by human intervention"
385
+ @logger.info 'Human requested to skip failed tool'
386
+ 'Tool execution skipped by human intervention'
492
387
  else
493
388
  # Abort
494
- @logger.error "Human requested task abortion due to tool failure"
389
+ @logger.error 'Human requested task abortion due to tool failure'
495
390
  raise AgentError, "Task aborted by human due to tool failure: #{error.message}"
496
391
  end
497
392
  end
498
393
 
499
394
  def request_final_answer_approval(proposed_answer)
500
395
  return proposed_answer unless @require_approval_for_final_answer && @human_input_enabled
501
-
396
+
502
397
  review_result = request_human_review(
503
398
  proposed_answer,
504
- review_criteria: ["Accuracy", "Completeness", "Clarity", "Relevance"],
399
+ review_criteria: %w[Accuracy Completeness Clarity Relevance],
505
400
  timeout: 180
506
401
  )
507
-
402
+
508
403
  if review_result[:approved]
509
- @logger.info "Final answer approved by human"
404
+ @logger.info 'Final answer approved by human'
510
405
  proposed_answer
511
406
  else
512
- @logger.info "Human provided feedback on final answer"
513
-
407
+ @logger.info 'Human provided feedback on final answer'
408
+
514
409
  if review_result[:suggested_changes].any?
515
410
  @logger.info "Suggested changes: #{review_result[:suggested_changes].join('; ')}"
516
-
411
+
517
412
  # Ask human what to do with the feedback
518
413
  choice_result = request_human_choice(
519
- "How should I handle your feedback?",
414
+ 'How should I handle your feedback?',
520
415
  [
521
- "Revise the answer based on feedback",
522
- "Use the answer as-is despite feedback",
523
- "Let me provide a completely new answer"
416
+ 'Revise the answer based on feedback',
417
+ 'Use the answer as-is despite feedback',
418
+ 'Let me provide a completely new answer'
524
419
  ]
525
420
  )
526
-
421
+
527
422
  case choice_result[:choice_index]
528
423
  when 0
529
424
  # Revise based on feedback
@@ -535,8 +430,8 @@ module RCrewAI
535
430
  when 2
536
431
  # Get new answer from human
537
432
  new_answer_result = request_human_input(
538
- "Please provide the final answer:",
539
- help_text: "Provide the complete answer for this task"
433
+ 'Please provide the final answer:',
434
+ help_text: 'Provide the complete answer for this task'
540
435
  )
541
436
  new_answer_result[:input] || proposed_answer
542
437
  else
@@ -550,29 +445,29 @@ module RCrewAI
550
445
  end
551
446
 
552
447
  def revise_answer_with_feedback(feedback_context)
553
- @logger.info "Revising answer based on human feedback"
554
-
448
+ @logger.info 'Revising answer based on human feedback'
449
+
555
450
  revision_prompt = <<~PROMPT
556
451
  You are #{name}, a #{role}.
557
-
452
+
558
453
  You need to revise your previous answer based on human feedback.
559
-
454
+
560
455
  #{feedback_context}
561
-
456
+
562
457
  Please provide a revised answer that addresses the feedback while maintaining accuracy and completeness.
563
-
458
+
564
459
  Revised answer:
565
460
  PROMPT
566
-
461
+
567
462
  response = llm_client.chat(
568
463
  messages: [{ role: 'user', content: revision_prompt }],
569
464
  temperature: 0.2,
570
465
  max_tokens: 1000
571
466
  )
572
-
467
+
573
468
  revised_answer = response[:content]
574
469
  @logger.debug "Revised answer based on feedback: #{revised_answer[0..100]}..."
575
-
470
+
576
471
  revised_answer
577
472
  end
578
473
 
@@ -582,36 +477,36 @@ module RCrewAI
582
477
  review_content = <<~CONTENT
583
478
  Task: #{task.name}
584
479
  Description: #{task.description}
585
-
480
+
586
481
  Current Iteration: #{iteration}
587
-
482
+
588
483
  Agent Analysis:
589
484
  - Role: #{role}
590
485
  - Current Progress: #{context[:previous_result] || 'Starting task'}
591
486
  - Previous Reasoning: #{context[:previous_reasoning] || 'No previous reasoning'}
592
-
487
+
593
488
  The agent is about to continue reasoning for this task.
594
489
  CONTENT
595
490
 
596
491
  request_human_review(
597
492
  review_content,
598
- review_criteria: ["Task approach", "Progress assessment", "Strategic guidance"],
493
+ review_criteria: ['Task approach', 'Progress assessment', 'Strategic guidance'],
599
494
  timeout: 30,
600
495
  optional: true
601
496
  )
602
- rescue => e
497
+ rescue StandardError => e
603
498
  @logger.warn "Failed to get human reasoning review: #{e.message}"
604
499
  nil
605
500
  end
606
501
 
607
502
  class CLI < Thor
608
- desc "new NAME", "Create a new agent"
503
+ desc 'new NAME', 'Create a new agent'
609
504
  option :role, type: :string, required: true
610
505
  option :goal, type: :string, required: true
611
506
  option :backstory, type: :string
612
507
  option :verbose, type: :boolean, default: false
613
508
  def new(name)
614
- agent = Agent.new(
509
+ Agent.new(
615
510
  name: name,
616
511
  role: options[:role],
617
512
  goal: options[:goal],
@@ -621,16 +516,16 @@ module RCrewAI
621
516
  puts "Agent '#{name}' created with role: #{options[:role]}"
622
517
  end
623
518
 
624
- desc "list", "List all agents"
519
+ desc 'list', 'List all agents'
625
520
  def list
626
- puts "Available agents:"
627
- puts " - researcher (Role: Research Specialist)"
628
- puts " - writer (Role: Content Writer)"
629
- puts " - analyst (Role: Data Analyst)"
521
+ puts 'Available agents:'
522
+ puts ' - researcher (Role: Research Specialist)'
523
+ puts ' - writer (Role: Content Writer)'
524
+ puts ' - analyst (Role: Data Analyst)'
630
525
  end
631
526
  end
632
527
  end
633
528
 
634
529
  class AgentError < Error; end
635
530
  class ToolNotFoundError < AgentError; end
636
- end
531
+ end