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
|
@@ -1,39 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
require_relative '../lib/rcrewai'
|
|
4
5
|
|
|
5
|
-
puts
|
|
6
|
-
puts
|
|
6
|
+
puts '🤝 Human-in-the-Loop Example'
|
|
7
|
+
puts '=' * 50
|
|
7
8
|
|
|
8
9
|
# Configure RCrewAI
|
|
9
10
|
RCrewAI.configure do |config|
|
|
10
|
-
config.llm_provider = :openai
|
|
11
|
+
config.llm_provider = :openai # Change as needed
|
|
11
12
|
config.temperature = 0.1
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
puts
|
|
15
|
+
puts '🔧 Setting up crew with human interaction capabilities...'
|
|
15
16
|
|
|
16
17
|
# Create crew
|
|
17
|
-
crew = RCrewAI::Crew.new(
|
|
18
|
+
crew = RCrewAI::Crew.new('human_assisted_crew')
|
|
18
19
|
|
|
19
20
|
# Create agents with human input capabilities
|
|
20
21
|
research_agent = RCrewAI::Agent.new(
|
|
21
|
-
name:
|
|
22
|
-
role:
|
|
23
|
-
goal:
|
|
24
|
-
backstory:
|
|
22
|
+
name: 'research_assistant',
|
|
23
|
+
role: 'Research Specialist',
|
|
24
|
+
goal: 'Conduct thorough research with human oversight',
|
|
25
|
+
backstory: 'You work closely with humans to ensure research quality and accuracy.',
|
|
25
26
|
tools: [RCrewAI::Tools::WebSearch.new, RCrewAI::Tools::FileWriter.new],
|
|
26
27
|
verbose: true,
|
|
27
|
-
human_input: true,
|
|
28
|
-
require_approval_for_tools: true,
|
|
29
|
-
require_approval_for_final_answer: true
|
|
28
|
+
human_input: true, # Enable human input
|
|
29
|
+
require_approval_for_tools: true, # Require approval for tool usage
|
|
30
|
+
require_approval_for_final_answer: true # Require approval for final answers
|
|
30
31
|
)
|
|
31
32
|
|
|
32
33
|
content_creator = RCrewAI::Agent.new(
|
|
33
|
-
name:
|
|
34
|
-
role:
|
|
35
|
-
goal:
|
|
36
|
-
backstory:
|
|
34
|
+
name: 'content_creator',
|
|
35
|
+
role: 'Content Writer',
|
|
36
|
+
goal: 'Create engaging content with human guidance',
|
|
37
|
+
backstory: 'You collaborate with humans to create compelling, accurate content.',
|
|
37
38
|
tools: [RCrewAI::Tools::FileWriter.new, RCrewAI::Tools::FileReader.new],
|
|
38
39
|
verbose: true,
|
|
39
40
|
human_input: true,
|
|
@@ -41,10 +42,10 @@ content_creator = RCrewAI::Agent.new(
|
|
|
41
42
|
)
|
|
42
43
|
|
|
43
44
|
quality_checker = RCrewAI::Agent.new(
|
|
44
|
-
name:
|
|
45
|
-
role:
|
|
46
|
-
goal:
|
|
47
|
-
backstory:
|
|
45
|
+
name: 'quality_checker',
|
|
46
|
+
role: 'Quality Assurance Specialist',
|
|
47
|
+
goal: 'Ensure content meets quality standards',
|
|
48
|
+
backstory: 'You work with humans to maintain high quality standards.',
|
|
48
49
|
tools: [RCrewAI::Tools::FileReader.new],
|
|
49
50
|
verbose: true,
|
|
50
51
|
human_input: true
|
|
@@ -61,22 +62,22 @@ puts "👥 Created crew with #{crew.agents.length} agents (all human-enabled)"
|
|
|
61
62
|
|
|
62
63
|
# Task 1: Research with human confirmation and guidance
|
|
63
64
|
research_task = RCrewAI::Task.new(
|
|
64
|
-
name:
|
|
65
|
-
description:
|
|
65
|
+
name: 'research_ai_trends',
|
|
66
|
+
description: 'Research the latest AI trends for 2024, focusing on practical business applications',
|
|
66
67
|
agent: research_agent,
|
|
67
|
-
expected_output:
|
|
68
|
+
expected_output: 'Comprehensive research document saved as ai_trends_2024.md',
|
|
68
69
|
human_input: true,
|
|
69
70
|
require_confirmation: true, # Human must confirm before starting
|
|
70
71
|
allow_guidance: true, # Allow human to provide guidance during execution
|
|
71
|
-
human_review_points: [:completion]
|
|
72
|
+
human_review_points: [:completion] # Review when completed
|
|
72
73
|
)
|
|
73
74
|
|
|
74
75
|
# Task 2: Content creation with human oversight
|
|
75
76
|
content_task = RCrewAI::Task.new(
|
|
76
|
-
name:
|
|
77
|
-
description:
|
|
77
|
+
name: 'create_ai_article',
|
|
78
|
+
description: 'Write an engaging article based on the research findings about AI trends',
|
|
78
79
|
agent: content_creator,
|
|
79
|
-
expected_output:
|
|
80
|
+
expected_output: 'Well-structured article saved as ai_trends_article.md',
|
|
80
81
|
context: [research_task],
|
|
81
82
|
human_input: true,
|
|
82
83
|
allow_guidance: true,
|
|
@@ -85,10 +86,10 @@ content_task = RCrewAI::Task.new(
|
|
|
85
86
|
|
|
86
87
|
# Task 3: Quality check with human review
|
|
87
88
|
quality_task = RCrewAI::Task.new(
|
|
88
|
-
name:
|
|
89
|
-
description:
|
|
89
|
+
name: 'quality_review',
|
|
90
|
+
description: 'Review the article for accuracy, clarity, and engagement',
|
|
90
91
|
agent: quality_checker,
|
|
91
|
-
expected_output:
|
|
92
|
+
expected_output: 'Quality assessment report with recommendations',
|
|
92
93
|
context: [content_task],
|
|
93
94
|
human_input: true,
|
|
94
95
|
human_review_points: [:completion]
|
|
@@ -102,18 +103,18 @@ puts "📋 Created #{crew.tasks.length} tasks with human interaction points"
|
|
|
102
103
|
|
|
103
104
|
# Demonstrate different human interaction modes
|
|
104
105
|
puts "\n🎯 HUMAN-IN-THE-LOOP EXECUTION MODES"
|
|
105
|
-
puts
|
|
106
|
+
puts '-' * 40
|
|
106
107
|
|
|
107
108
|
puts "\n1️⃣ BASIC HUMAN APPROVAL MODE"
|
|
108
|
-
puts
|
|
109
|
+
puts 'Tasks will request human approval at key decision points...'
|
|
109
110
|
|
|
110
111
|
begin
|
|
111
112
|
results = crew.execute
|
|
112
|
-
|
|
113
|
+
|
|
113
114
|
puts "\n📊 Execution Results:"
|
|
114
115
|
puts " Completed: #{results[:completed_tasks]}/#{results[:total_tasks]}"
|
|
115
116
|
puts " Success Rate: #{results[:success_rate]}%"
|
|
116
|
-
|
|
117
|
+
|
|
117
118
|
# Show any generated files
|
|
118
119
|
output_files = ['ai_trends_2024.md', 'ai_trends_article.md']
|
|
119
120
|
puts "\n📄 Generated Files:"
|
|
@@ -124,20 +125,19 @@ begin
|
|
|
124
125
|
puts " ❌ #{file} (not created)"
|
|
125
126
|
end
|
|
126
127
|
end
|
|
127
|
-
|
|
128
|
-
rescue => e
|
|
128
|
+
rescue StandardError => e
|
|
129
129
|
puts "❌ Execution failed or was cancelled: #{e.message}"
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
puts "\n2️⃣ AGENT-LEVEL HUMAN INTERACTION DEMONSTRATION"
|
|
133
|
-
puts
|
|
133
|
+
puts 'Creating a standalone task to show detailed human interactions...'
|
|
134
134
|
|
|
135
135
|
# Create a simple demonstration task
|
|
136
136
|
demo_agent = RCrewAI::Agent.new(
|
|
137
|
-
name:
|
|
138
|
-
role:
|
|
139
|
-
goal:
|
|
140
|
-
backstory:
|
|
137
|
+
name: 'demo_agent',
|
|
138
|
+
role: 'Demonstration Agent',
|
|
139
|
+
goal: 'Show human interaction capabilities',
|
|
140
|
+
backstory: 'I demonstrate various human interaction patterns.',
|
|
141
141
|
tools: [RCrewAI::Tools::FileWriter.new],
|
|
142
142
|
verbose: true
|
|
143
143
|
)
|
|
@@ -149,10 +149,10 @@ demo_agent.enable_human_input(
|
|
|
149
149
|
)
|
|
150
150
|
|
|
151
151
|
demo_task = RCrewAI::Task.new(
|
|
152
|
-
name:
|
|
153
|
-
description:
|
|
152
|
+
name: 'human_interaction_demo',
|
|
153
|
+
description: 'Write a short summary of Ruby programming benefits and save it to ruby_benefits.txt',
|
|
154
154
|
agent: demo_agent,
|
|
155
|
-
expected_output:
|
|
155
|
+
expected_output: 'A text file containing Ruby programming benefits',
|
|
156
156
|
human_input: true,
|
|
157
157
|
require_confirmation: true,
|
|
158
158
|
allow_guidance: true
|
|
@@ -163,27 +163,26 @@ puts "\n🚀 Starting human interaction demonstration..."
|
|
|
163
163
|
begin
|
|
164
164
|
demo_result = demo_task.execute
|
|
165
165
|
puts "\nDemo result: #{demo_result}"
|
|
166
|
-
|
|
166
|
+
|
|
167
167
|
if File.exist?('ruby_benefits.txt')
|
|
168
168
|
puts "\n📄 Generated demo file:"
|
|
169
|
-
puts File.read('ruby_benefits.txt')[0..200]
|
|
169
|
+
puts "#{File.read('ruby_benefits.txt')[0..200]}..."
|
|
170
170
|
end
|
|
171
|
-
|
|
172
|
-
rescue => e
|
|
171
|
+
rescue StandardError => e
|
|
173
172
|
puts "Demo task result: #{e.message}"
|
|
174
173
|
end
|
|
175
174
|
|
|
176
175
|
puts "\n3️⃣ HUMAN INPUT UTILITY DEMONSTRATION"
|
|
177
|
-
puts
|
|
176
|
+
puts 'Testing different types of human input requests...'
|
|
178
177
|
|
|
179
178
|
# Demonstrate the HumanInput utility directly
|
|
180
179
|
human_input = RCrewAI::HumanInput.new(verbose: true)
|
|
181
180
|
|
|
182
181
|
puts "\nTesting approval request..."
|
|
183
182
|
approval_result = human_input.request_approval(
|
|
184
|
-
|
|
185
|
-
context:
|
|
186
|
-
consequences:
|
|
183
|
+
'Test approval request - this is just a demonstration',
|
|
184
|
+
context: 'This is a test of the human input system',
|
|
185
|
+
consequences: 'Nothing will actually happen',
|
|
187
186
|
timeout: 30
|
|
188
187
|
)
|
|
189
188
|
puts "Approval result: #{approval_result[:approved] ? 'APPROVED' : 'REJECTED'}"
|
|
@@ -191,15 +190,15 @@ puts "Approval result: #{approval_result[:approved] ? 'APPROVED' : 'REJECTED'}"
|
|
|
191
190
|
puts "\nTesting choice request..."
|
|
192
191
|
choice_result = human_input.request_choice(
|
|
193
192
|
"What's your preferred programming language?",
|
|
194
|
-
[
|
|
193
|
+
%w[Ruby Python JavaScript Other],
|
|
195
194
|
timeout: 30
|
|
196
195
|
)
|
|
197
196
|
puts "Choice result: #{choice_result[:choice]}" if choice_result[:valid]
|
|
198
197
|
|
|
199
198
|
puts "\nTesting input request..."
|
|
200
199
|
input_result = human_input.request_input(
|
|
201
|
-
|
|
202
|
-
help_text:
|
|
200
|
+
'Enter a short message (or press Enter to skip):',
|
|
201
|
+
help_text: 'This is just for testing the input system',
|
|
203
202
|
timeout: 20
|
|
204
203
|
)
|
|
205
204
|
puts "Input result: '#{input_result[:input]}'" if input_result[:valid]
|
|
@@ -210,24 +209,24 @@ summary = human_input.session_summary
|
|
|
210
209
|
summary.each { |key, value| puts " #{key}: #{value}" }
|
|
211
210
|
|
|
212
211
|
puts "\n🎯 HUMAN-IN-THE-LOOP FEATURES DEMONSTRATED:"
|
|
213
|
-
puts
|
|
214
|
-
puts
|
|
215
|
-
puts
|
|
216
|
-
puts
|
|
217
|
-
puts
|
|
218
|
-
puts
|
|
219
|
-
puts
|
|
220
|
-
puts
|
|
221
|
-
puts
|
|
222
|
-
puts
|
|
212
|
+
puts ' • Task-level human confirmation before execution'
|
|
213
|
+
puts ' • Agent-level tool approval workflows'
|
|
214
|
+
puts ' • Final answer review and revision capabilities'
|
|
215
|
+
puts ' • Human guidance integration into agent reasoning'
|
|
216
|
+
puts ' • Task completion review and feedback handling'
|
|
217
|
+
puts ' • Error handling with human intervention options'
|
|
218
|
+
puts ' • Flexible human input types (approval, choice, input, review)'
|
|
219
|
+
puts ' • Session tracking and interaction history'
|
|
220
|
+
puts ' • Configurable timeouts and auto-approval modes'
|
|
221
|
+
puts ' • Integration with both sync and async execution'
|
|
223
222
|
|
|
224
223
|
puts "\n💡 USAGE PATTERNS:"
|
|
225
|
-
puts
|
|
226
|
-
puts
|
|
227
|
-
puts
|
|
228
|
-
puts
|
|
229
|
-
puts
|
|
230
|
-
|
|
231
|
-
puts "\n
|
|
232
|
-
puts
|
|
233
|
-
puts
|
|
224
|
+
puts ' 1. Development & Testing: Use human approval for tool usage'
|
|
225
|
+
puts ' 2. Content Creation: Human review of final outputs'
|
|
226
|
+
puts ' 3. Critical Tasks: Human confirmation before execution'
|
|
227
|
+
puts ' 4. Learning & Training: Human guidance during reasoning'
|
|
228
|
+
puts ' 5. Quality Assurance: Human review at completion points'
|
|
229
|
+
|
|
230
|
+
puts "\n#{'=' * 50}"
|
|
231
|
+
puts '🤝 Human-in-the-Loop Demo Complete!'
|
|
232
|
+
puts '=' * 50
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Connect to an MCP server over stdio and expose its tools to an agent.
|
|
5
|
+
#
|
|
6
|
+
# Prerequisites:
|
|
7
|
+
# # The "filesystem" reference MCP server (Node):
|
|
8
|
+
# npm install -g @modelcontextprotocol/server-filesystem
|
|
9
|
+
# # or run it ad-hoc with npx (no global install):
|
|
10
|
+
# # command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
11
|
+
#
|
|
12
|
+
# Run:
|
|
13
|
+
# OPENAI_API_KEY=... ruby examples/mcp_example.rb
|
|
14
|
+
|
|
15
|
+
require_relative '../lib/rcrewai'
|
|
16
|
+
|
|
17
|
+
RCrewAI.configure do |c|
|
|
18
|
+
c.llm_provider = :openai
|
|
19
|
+
c.openai_model = 'gpt-4o-mini'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
RCrewAI::MCP::Client.with_connection(
|
|
23
|
+
command: 'npx',
|
|
24
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp']
|
|
25
|
+
) do |client|
|
|
26
|
+
puts "Connected to MCP server: #{client.server_name}"
|
|
27
|
+
puts 'Available tools:'
|
|
28
|
+
client.tools.each { |t| puts " - #{t.name}: #{t.description}" }
|
|
29
|
+
puts
|
|
30
|
+
|
|
31
|
+
agent = RCrewAI::Agent.new(
|
|
32
|
+
name: 'fs_agent',
|
|
33
|
+
role: 'Filesystem operator',
|
|
34
|
+
goal: 'Read and summarize files using the filesystem tools',
|
|
35
|
+
tools: client.tools
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
task = RCrewAI::Task.new(
|
|
39
|
+
name: 'list_tmp',
|
|
40
|
+
description: 'List the files in /tmp and tell me how many there are.',
|
|
41
|
+
agent: agent,
|
|
42
|
+
expected_output: 'A short summary'
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
result = agent.execute_task(task)
|
|
46
|
+
puts "Answer: #{result[:content]}"
|
|
47
|
+
puts "Tool calls: #{result[:tool_calls_history].length}"
|
|
48
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Native function calling end-to-end.
|
|
5
|
+
#
|
|
6
|
+
# A tool declares a JSON schema via the DSL on Tools::Base. The OpenAI
|
|
7
|
+
# client sends that schema as `tools:` in the request payload, the model
|
|
8
|
+
# replies with a `tool_calls` block, and ToolRunner dispatches the call,
|
|
9
|
+
# threads the result back into the conversation, and continues until the
|
|
10
|
+
# model is done.
|
|
11
|
+
#
|
|
12
|
+
# Run:
|
|
13
|
+
# OPENAI_API_KEY=... ruby examples/native_tools_example.rb
|
|
14
|
+
|
|
15
|
+
require_relative '../lib/rcrewai'
|
|
16
|
+
|
|
17
|
+
# 1. Configure the LLM.
|
|
18
|
+
RCrewAI.configure do |c|
|
|
19
|
+
c.llm_provider = :openai
|
|
20
|
+
c.openai_model = 'gpt-4o-mini'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# 2. Declare a tool with the DSL.
|
|
24
|
+
class WeatherTool < RCrewAI::Tools::Base
|
|
25
|
+
tool_name 'get_weather'
|
|
26
|
+
description 'Get the current weather for a city'
|
|
27
|
+
param :city, type: :string, required: true, description: 'City name'
|
|
28
|
+
param :units, type: :enum, values: %w[metric imperial], default: 'metric'
|
|
29
|
+
|
|
30
|
+
def execute(city:, units: 'metric')
|
|
31
|
+
# In a real tool this would call a weather API. We fake it for demo.
|
|
32
|
+
{
|
|
33
|
+
city: city,
|
|
34
|
+
temperature: units == 'metric' ? 22 : 72,
|
|
35
|
+
conditions: 'sunny'
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 3. Build an agent that has the tool.
|
|
41
|
+
agent = RCrewAI::Agent.new(
|
|
42
|
+
name: 'meteorologist',
|
|
43
|
+
role: 'Helpful weather assistant',
|
|
44
|
+
goal: 'Answer weather questions accurately',
|
|
45
|
+
tools: [WeatherTool.new]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# 4. Define and execute a task.
|
|
49
|
+
task = RCrewAI::Task.new(
|
|
50
|
+
name: 'weather_check',
|
|
51
|
+
description: "What's the weather in Tokyo?",
|
|
52
|
+
agent: agent,
|
|
53
|
+
expected_output: 'A short sentence about the weather'
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
result = agent.execute_task(task)
|
|
57
|
+
|
|
58
|
+
puts "Answer: #{result[:content]}"
|
|
59
|
+
puts 'Tool calls:'
|
|
60
|
+
result[:tool_calls_history].each do |tc|
|
|
61
|
+
puts " - #{tc[:tool]}(#{tc[:args]}) -> #{tc[:result]}"
|
|
62
|
+
end
|
|
63
|
+
puts "Tokens: #{result.dig(:usage, :total_tokens)}"
|
|
64
|
+
puts "Finish: #{result[:finish_reason]}"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Streaming events from agent.execute_task.
|
|
5
|
+
#
|
|
6
|
+
# Multiple sinks can subscribe to the same stream: one prints tokens as
|
|
7
|
+
# they arrive; another tallies cost from Events::Usage. The sinks are
|
|
8
|
+
# fanned out independently — exceptions in one don't kill the others.
|
|
9
|
+
#
|
|
10
|
+
# Run:
|
|
11
|
+
# OPENAI_API_KEY=... ruby examples/streaming_example.rb
|
|
12
|
+
|
|
13
|
+
require_relative '../lib/rcrewai'
|
|
14
|
+
|
|
15
|
+
RCrewAI.configure do |c|
|
|
16
|
+
c.llm_provider = :openai
|
|
17
|
+
c.openai_model = 'gpt-4o-mini'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
agent = RCrewAI::Agent.new(
|
|
21
|
+
name: 'storyteller',
|
|
22
|
+
role: 'Short-story author',
|
|
23
|
+
goal: 'Tell concise, vivid stories'
|
|
24
|
+
)
|
|
25
|
+
task = RCrewAI::Task.new(
|
|
26
|
+
name: 'tell_story',
|
|
27
|
+
description: 'Tell me a 3-sentence story about a robot and a cat.',
|
|
28
|
+
agent: agent,
|
|
29
|
+
expected_output: 'A 3-sentence story'
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Sink 1: print tokens as they arrive.
|
|
33
|
+
printer = lambda do |event|
|
|
34
|
+
print event.text if event.is_a?(RCrewAI::Events::TextDelta)
|
|
35
|
+
$stdout.flush
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Sink 2: tally cost and tokens.
|
|
39
|
+
total_cost = 0.0
|
|
40
|
+
total_tokens = 0
|
|
41
|
+
cost_tracker = lambda do |event|
|
|
42
|
+
next unless event.is_a?(RCrewAI::Events::Usage)
|
|
43
|
+
|
|
44
|
+
total_cost += event.cost_usd || 0
|
|
45
|
+
total_tokens += event.total_tokens || 0
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Combine sinks (each event flows to both):
|
|
49
|
+
fan = RCrewAI::Events.fan_out([printer, cost_tracker])
|
|
50
|
+
|
|
51
|
+
agent.execute_task(task, stream: fan)
|
|
52
|
+
|
|
53
|
+
puts
|
|
54
|
+
puts '---'
|
|
55
|
+
puts "Total tokens: #{total_tokens}"
|
|
56
|
+
puts "Total cost: $#{format('%.4f', total_cost)}"
|