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