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.
- checksums.yaml +4 -4
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +99 -0
- data/CHANGELOG.md +64 -1
- data/README.md +170 -2
- data/ROADMAP.md +84 -0
- 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 +181 -286
- data/lib/rcrewai/async_executor.rb +43 -43
- data/lib/rcrewai/cli.rb +11 -11
- data/lib/rcrewai/configuration.rb +34 -9
- data/lib/rcrewai/crew.rb +134 -39
- data/lib/rcrewai/events.rb +30 -0
- data/lib/rcrewai/flow/state.rb +47 -0
- data/lib/rcrewai/flow/state_store.rb +50 -0
- data/lib/rcrewai/flow.rb +243 -0
- data/lib/rcrewai/human_input.rb +104 -114
- data/lib/rcrewai/knowledge/base.rb +52 -0
- data/lib/rcrewai/knowledge/chunker.rb +31 -0
- data/lib/rcrewai/knowledge/embedder.rb +48 -0
- data/lib/rcrewai/knowledge/sources.rb +83 -0
- data/lib/rcrewai/knowledge/store.rb +58 -0
- data/lib/rcrewai/knowledge.rb +13 -0
- data/lib/rcrewai/legacy_react_runner.rb +172 -0
- data/lib/rcrewai/llm_client.rb +24 -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/output_schema.rb +79 -0
- data/lib/rcrewai/planning.rb +65 -0
- 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 +145 -66
- 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 +20 -10
- data/rcrewai.gemspec +39 -39
- 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)
|
|
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 =
|
|
33
|
-
@
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
313
|
-
|
|
247
|
+
knowledge = retrieve_knowledge(task)
|
|
248
|
+
user << "\n\nRelevant Knowledge:\n#{knowledge}" unless knowledge.empty?
|
|
314
249
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
|
453
|
-
|
|
347
|
+
@logger.warn 'Requesting human intervention for tool failure'
|
|
348
|
+
|
|
454
349
|
choices = [
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
491
|
-
|
|
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
|
|
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: [
|
|
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
|
|
404
|
+
@logger.info 'Final answer approved by human'
|
|
510
405
|
proposed_answer
|
|
511
406
|
else
|
|
512
|
-
@logger.info
|
|
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
|
-
|
|
414
|
+
'How should I handle your feedback?',
|
|
520
415
|
[
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
539
|
-
help_text:
|
|
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
|
|
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: [
|
|
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
|
|
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
|
-
|
|
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
|
|
519
|
+
desc 'list', 'List all agents'
|
|
625
520
|
def list
|
|
626
|
-
puts
|
|
627
|
-
puts
|
|
628
|
-
puts
|
|
629
|
-
puts
|
|
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
|