rcrewai 0.3.0 → 0.5.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 +20 -0
- data/CHANGELOG.md +55 -1
- data/README.md +250 -0
- data/ROADMAP.md +90 -0
- data/docs/upgrading-to-0.4.md +191 -0
- data/examples/flow_example.rb +89 -0
- data/examples/knowledge_rag_example.rb +72 -0
- data/examples/planning_and_training_example.rb +72 -0
- data/examples/structured_output_example.rb +92 -0
- data/lib/rcrewai/agent.rb +72 -6
- data/lib/rcrewai/agent_augmentations.rb +75 -0
- data/lib/rcrewai/configuration.rb +20 -0
- data/lib/rcrewai/context_window.rb +75 -0
- data/lib/rcrewai/crew.rb +122 -6
- 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/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 +7 -1
- data/lib/rcrewai/llm_client.rb +23 -0
- data/lib/rcrewai/multimodal.rb +67 -0
- data/lib/rcrewai/output_schema.rb +79 -0
- data/lib/rcrewai/planning.rb +65 -0
- data/lib/rcrewai/rate_limiter.rb +94 -0
- data/lib/rcrewai/task.rb +90 -2
- data/lib/rcrewai/tool_runner.rb +7 -1
- data/lib/rcrewai/version.rb +1 -1
- data/lib/rcrewai.rb +5 -0
- metadata +22 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 68a381d21e047e37972fda09fd7e9faa93da77e6fe01a040a4c77b9ee0066aca
|
|
4
|
+
data.tar.gz: 3818acc6b9b6eb71d27d4099cb1c1fc574adbdd0a3a8128389803c051c364774
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 65d796bde314db55466f7f4f043db9eeb772dee3e72914d2bc27947eb6dfe84ce246b89a6181d473c3f7d175d353fbc9531914c8baa5b7b010e02229324bac27
|
|
7
|
+
data.tar.gz: b2fffaab35dc672b12d2a5aea5fd0b02e6519b2f4a5ca98daaf66bef740551e2e674f4cc39967f3d987ed9dfe1c13e7dec4c8a30e5b86e2140242e347dd96a11
|
data/.rubocop.yml
CHANGED
|
@@ -1 +1,21 @@
|
|
|
1
1
|
inherit_from: .rubocop_todo.yml
|
|
2
|
+
|
|
3
|
+
Naming/MethodParameterName:
|
|
4
|
+
# `k` (top-k retrieval) and short math vars for vector similarity are
|
|
5
|
+
# conventional and clearer than forced longer names. The rest are RuboCop's
|
|
6
|
+
# defaults, restated because AllowedNames replaces rather than extends.
|
|
7
|
+
AllowedNames:
|
|
8
|
+
- k
|
|
9
|
+
- a
|
|
10
|
+
- b
|
|
11
|
+
- io
|
|
12
|
+
- id
|
|
13
|
+
- to
|
|
14
|
+
- by
|
|
15
|
+
- 'on'
|
|
16
|
+
- in
|
|
17
|
+
- at
|
|
18
|
+
- ip
|
|
19
|
+
- db
|
|
20
|
+
- os
|
|
21
|
+
- pp
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2026-07-03
|
|
11
|
+
|
|
12
|
+
Polish release completing the roadmap backlog: crew lifecycle hooks, batch
|
|
13
|
+
execution, rate limiting, per-agent reasoning, context-window management, and
|
|
14
|
+
multimodal image input. All additive — existing code runs unchanged.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Crew lifecycle hooks: `Crew#before_kickoff` and `Crew#after_kickoff` register callbacks that run before/after execution. A `before_kickoff` hook receives the inputs hash (passed via `crew.execute(inputs:)`) and may transform it; an `after_kickoff` hook receives the result and may transform it. Multiple hooks run in registration order. The (possibly transformed) inputs are exposed on `Crew#last_inputs`. (#15)
|
|
18
|
+
- `Crew#kickoff_for_each(inputs:)` runs the crew once per input set and returns one result per input, in order. Runs are isolated — each execution starts from only its own inputs. (#16)
|
|
19
|
+
- Rate limiting: `Agent.new(max_rpm:)` throttles the agent's LLM calls to the given requests-per-minute using a thread-safe rolling-window `RCrewAI::RateLimiter`. The agent's client is transparently wrapped (`RateLimiter::ThrottledClient`) so every `chat` acquires a slot first; `max_rpm` nil/0 means unlimited. (#17)
|
|
20
|
+
- Reasoning: `Agent.new(reasoning: true)` runs a reasoning/planning pass before answering — the LLM drafts a short plan for the task, which is injected into the answer prompt and surfaced on the result hash as `:reasoning` (without polluting `task.result`). Bounded by `max_reasoning_attempts` (default 3), retrying if the model returns empty output; if every attempt is empty, execution proceeds without a plan. Off by default. (#18)
|
|
21
|
+
- Context-window management: `Agent.new(respect_context_window: true)` trims the message history to fit the model's context window before each LLM call, dropping the oldest non-system messages while always keeping system messages and the latest message. The new `RCrewAI::ContextWindow` module provides the token estimate (chars/4 heuristic), a per-model window-size table, and the `fit` trimmer. Off by default. (#19)
|
|
22
|
+
- Multimodal input: `Task.new(attachments:)` accepts image attachments (`{ type: :image, url: '...' }` or `{ type: :image, path: '...' }`). When a task has attachments, the agent builds an OpenAI-style multimodal user message (text + `image_url` parts); local files are base64-encoded into data URLs with a mime type inferred from the extension. Supported on OpenAI/Azure; other providers raise a clear `Multimodal::UnsupportedProviderError`. The new `RCrewAI::Multimodal` module builds the content parts. (#20)
|
|
23
|
+
|
|
24
|
+
## [0.4.0] - 2026-07-03
|
|
25
|
+
|
|
26
|
+
This release closes the feature-parity gap with the modern CrewAI framework,
|
|
27
|
+
adding its second pillar (**Flows**) alongside **Knowledge (RAG)**, structured
|
|
28
|
+
output, guardrails, planning, and training/testing. See `ROADMAP.md`.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
#### Flows (#11)
|
|
33
|
+
- `RCrewAI::Flow` — an event-driven workflow engine (CrewAI's second pillar). Subclass it and declare methods with a class-level DSL: `start`, `listen`, `router`, and the `and_` / `or_` trigger combinators. `kickoff` runs the graph to a fixed point; routers emit labels that listeners trigger on.
|
|
34
|
+
- Flow state (`Flow::State`) is a schemaless object with an automatic UUID, seedable via `kickoff(inputs:)`.
|
|
35
|
+
- Flow persistence: pluggable state stores (`Flow::MemoryStateStore`, `Flow::FileStateStore`, or any `#save`/`#load` object); `flow.restore(id)` resumes a persisted run.
|
|
36
|
+
- Flows can invoke a `Crew` as a step and pause for input via `#human_feedback`.
|
|
37
|
+
|
|
38
|
+
#### Knowledge / RAG (#9)
|
|
39
|
+
- `RCrewAI::Knowledge` module adds retrieval-augmented context. Sources (`StringSource`, `FileSource`, `PdfSource`, `CsvSource`, `UrlSource`) are chunked, embedded, and stored in an in-memory cosine-similarity vector store (no external DB required).
|
|
40
|
+
- Attach via `Agent.new(knowledge:)` / `knowledge_sources:` (role-specific) or `Crew.new(knowledge:)` / `knowledge_sources:` (shared with all agents); relevant chunks are injected into each task's prompt at execution.
|
|
41
|
+
- The embedder (`Knowledge::Embedder`, default OpenAI `text-embedding-3-small`) and vector store are pluggable.
|
|
42
|
+
|
|
43
|
+
#### Task output processing (#6, #7, #8)
|
|
44
|
+
- Structured output: `Task.new(output_schema:)` validates and coerces the agent's output against a JSON-schema subset, exposing the parsed object via `Task#structured_output` (and the raw string via `Task#raw_result`). JSON embedded in surrounding prose or a fenced code block is extracted automatically; output that doesn't conform re-runs the agent with the error fed back.
|
|
45
|
+
- Guardrails: `Task.new(guardrail:)` takes a callable returning `[ok, value_or_error]` to validate and transform output before it flows downstream, retrying up to `guardrail_max_retries` (default 3) with the rejection reason fed back to the agent.
|
|
46
|
+
- Output persistence & formatting: `Task.new(output_file:)` writes the result to disk (`create_directory:` controls parent-dir creation, default true), and `markdown: true` prepends a heading when the output isn't already a markdown document.
|
|
47
|
+
- `RCrewAI::OutputSchema` — a small JSON-schema-subset validator/coercer used by structured task output.
|
|
48
|
+
|
|
49
|
+
#### Per-agent LLM (#5)
|
|
50
|
+
- `Agent.new(llm:)` accepts a provider symbol (`:anthropic`), an options hash (`{ provider:, model:, api_key:, temperature: }`), or a pre-built client instance. Agents in the same crew can use different providers/models (e.g. a cheap worker model and a stronger manager model). Omitting `llm:` keeps the previous global-configuration behavior.
|
|
51
|
+
- `Configuration#with_overrides` returns a copy of the configuration with per-agent overrides applied, leaving global state untouched.
|
|
52
|
+
|
|
53
|
+
#### Planning (#10)
|
|
54
|
+
- `Crew.new(planning: true)` runs a single planner pass before execution that asks an LLM to draft a short plan for each task and folds it into the task's description. Optional `planning_llm:` selects the planner client (defaults to the global provider). Best-effort — a planner error or unparseable output leaves tasks unchanged and execution proceeds.
|
|
55
|
+
- `Task#enrich_description` appends supplementary guidance (used by the planner) without discarding the original instructions.
|
|
56
|
+
|
|
57
|
+
#### Training & testing (#12)
|
|
58
|
+
- `Crew#train(n_iterations:, filename:)` runs the crew repeatedly, collects feedback after each iteration (via a `feedback:` callable, defaulting to a human prompt), and persists it as JSON.
|
|
59
|
+
- `Crew#test(n_iterations:)` runs the crew repeatedly and reports per-run and average scores (via a `scorer:` callable, defaulting to the run's success rate).
|
|
60
|
+
|
|
10
61
|
## [0.3.0] - 2026-05-12
|
|
11
62
|
|
|
12
63
|
### Added
|
|
@@ -128,5 +179,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
128
179
|
- CLI usage documentation
|
|
129
180
|
- Real-world use cases and examples
|
|
130
181
|
|
|
131
|
-
[Unreleased]: https://github.com/gkosmo/rcrewAI/compare/v0.
|
|
182
|
+
[Unreleased]: https://github.com/gkosmo/rcrewAI/compare/v0.5.0...HEAD
|
|
183
|
+
[0.5.0]: https://github.com/gkosmo/rcrewAI/compare/v0.4.0...v0.5.0
|
|
184
|
+
[0.4.0]: https://github.com/gkosmo/rcrewAI/compare/v0.3.0...v0.4.0
|
|
185
|
+
[0.3.0]: https://github.com/gkosmo/rcrewAI/compare/v0.1.0...v0.3.0
|
|
132
186
|
[0.1.0]: https://github.com/gkosmo/rcrewAI/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -19,6 +19,8 @@ RCrewAI is a Ruby implementation of the CrewAI framework, allowing you to create
|
|
|
19
19
|
- **🏗️ Hierarchical Teams**: Manager agents that coordinate and delegate tasks to specialist agents
|
|
20
20
|
- **🔒 Production Ready**: Security controls, error handling, logging, monitoring, and sandboxing
|
|
21
21
|
- **🎯 Flexible Orchestration**: Sequential, hierarchical, and concurrent execution modes
|
|
22
|
+
- **🌊 Flows**: Event-driven workflows with `start`/`listen`/`router`, branching, and persistent state
|
|
23
|
+
- **📚 Knowledge (RAG)**: Ground agents in your own documents with built-in retrieval
|
|
22
24
|
- **💎 Ruby-First Design**: Built specifically for Ruby developers with idiomatic patterns
|
|
23
25
|
|
|
24
26
|
## 📦 Installation
|
|
@@ -169,6 +171,254 @@ RCrewAI.configure do |config|
|
|
|
169
171
|
end
|
|
170
172
|
```
|
|
171
173
|
|
|
174
|
+
### Per-agent LLM
|
|
175
|
+
|
|
176
|
+
The `RCrewAI.configure` block sets the crew-wide default. Any agent can override
|
|
177
|
+
it with the `llm:` option, so a single crew can mix providers and models — for
|
|
178
|
+
example a cheap model for workers and a stronger one for the manager:
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
# Provider only (uses that provider's configured model + key)
|
|
182
|
+
researcher = RCrewAI::Agent.new(name: 'researcher', role: '...', goal: '...',
|
|
183
|
+
llm: :anthropic)
|
|
184
|
+
|
|
185
|
+
# Provider + model (and optionally api_key / temperature)
|
|
186
|
+
manager = RCrewAI::Agent.new(name: 'manager', role: '...', goal: '...',
|
|
187
|
+
llm: { provider: :anthropic, model: 'claude-3-opus-20240229' })
|
|
188
|
+
|
|
189
|
+
worker = RCrewAI::Agent.new(name: 'worker', role: '...', goal: '...',
|
|
190
|
+
llm: { provider: :openai, model: 'gpt-4o-mini' })
|
|
191
|
+
|
|
192
|
+
# Or pass a pre-built client instance
|
|
193
|
+
worker = RCrewAI::Agent.new(name: 'worker', role: '...', goal: '...',
|
|
194
|
+
llm: my_client)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Omit `llm:` to use the global `RCrewAI.configure` settings. Overrides never
|
|
198
|
+
mutate the global configuration.
|
|
199
|
+
|
|
200
|
+
## 📤 Structured Output, Guardrails & File Output
|
|
201
|
+
|
|
202
|
+
Tasks can validate, transform, and persist their output:
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
task = RCrewAI::Task.new(
|
|
206
|
+
name: 'extract',
|
|
207
|
+
description: 'Extract the article title and word count as JSON',
|
|
208
|
+
agent: analyst,
|
|
209
|
+
|
|
210
|
+
# Structured output — validated & coerced against a JSON schema.
|
|
211
|
+
# Non-conforming output re-runs the agent with the error fed back.
|
|
212
|
+
output_schema: {
|
|
213
|
+
type: 'object',
|
|
214
|
+
properties: { title: { type: 'string' }, words: { type: 'integer' } },
|
|
215
|
+
required: ['title']
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
# Guardrail — ->(output) { [ok, value_or_error] }. On rejection the agent
|
|
219
|
+
# re-runs (up to guardrail_max_retries) with the reason appended.
|
|
220
|
+
guardrail: ->(out) { [out.length < 5000, 'must be under 5000 chars'] },
|
|
221
|
+
guardrail_max_retries: 3,
|
|
222
|
+
|
|
223
|
+
# Persist the result. Parent dirs are created unless create_directory: false.
|
|
224
|
+
output_file: 'out/report.md',
|
|
225
|
+
markdown: true
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
task.execute
|
|
229
|
+
task.structured_output # => { "title" => "...", "words" => 1234 }
|
|
230
|
+
task.raw_result # => the unprocessed string the agent produced
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## 🗺️ Planning
|
|
234
|
+
|
|
235
|
+
Enable `planning:` on a crew to run a planner pass before execution. The planner
|
|
236
|
+
drafts a short plan for each task and folds it into the task description, giving
|
|
237
|
+
the executing agent a head start:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
crew = RCrewAI::Crew.new('research_crew', planning: true)
|
|
241
|
+
# Optionally use a dedicated (e.g. stronger) planner model:
|
|
242
|
+
crew = RCrewAI::Crew.new('research_crew', planning: true,
|
|
243
|
+
planning_llm: { provider: :anthropic, model: 'claude-3-opus-20240229' })
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Planning is best-effort: if the planner errors or returns unparseable output,
|
|
247
|
+
the crew runs with the original tasks unchanged.
|
|
248
|
+
|
|
249
|
+
## 🏋️ Training & Testing
|
|
250
|
+
|
|
251
|
+
Iterate on a crew by training it with feedback or scoring repeated runs:
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
# Train: run N times, collect feedback after each run, persist to JSON.
|
|
255
|
+
crew.train(n_iterations: 3, filename: 'training.json')
|
|
256
|
+
|
|
257
|
+
# Provide feedback programmatically instead of prompting a human:
|
|
258
|
+
crew.train(n_iterations: 3, filename: 'training.json',
|
|
259
|
+
feedback: ->(iteration, result) { "run #{iteration}: #{result[:success_rate]}%" })
|
|
260
|
+
|
|
261
|
+
# Test: run N times and score each run (defaults to success_rate).
|
|
262
|
+
crew.test(n_iterations: 5)
|
|
263
|
+
# => { iterations: 5, scores: [...], average_score: 92.0 }
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## 🪝 Kickoff Hooks & Batch Runs
|
|
267
|
+
|
|
268
|
+
Run setup/teardown around a crew, and batch it over many inputs:
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
crew.before_kickoff { |inputs| inputs.merge(started_at: Time.now) } # may transform inputs
|
|
272
|
+
crew.after_kickoff { |result| notify(result); result } # may transform result
|
|
273
|
+
|
|
274
|
+
crew.execute(inputs: { topic: 'ruby' })
|
|
275
|
+
crew.last_inputs # => the (possibly transformed) inputs the run used
|
|
276
|
+
|
|
277
|
+
# Batch: run the crew once per input set, results returned in order.
|
|
278
|
+
results = crew.kickoff_for_each(inputs: [
|
|
279
|
+
{ topic: 'ruby' },
|
|
280
|
+
{ topic: 'python' }
|
|
281
|
+
])
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## ⏱️ Rate Limiting
|
|
285
|
+
|
|
286
|
+
Cap an agent's LLM calls to stay under provider limits. Calls beyond the cap
|
|
287
|
+
block until the rolling 60-second window frees up:
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
agent = RCrewAI::Agent.new(name: 'a', role: '...', goal: '...', max_rpm: 20)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The limiter (`RCrewAI::RateLimiter`) is thread-safe, so it holds under async
|
|
294
|
+
execution. `max_rpm: nil` (the default) or `0` means unlimited.
|
|
295
|
+
|
|
296
|
+
## 🧠 Reasoning
|
|
297
|
+
|
|
298
|
+
Have an agent think through a plan before answering. The reasoning trace is
|
|
299
|
+
surfaced on the result and does not pollute `task.result`:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
agent = RCrewAI::Agent.new(name: 'a', role: '...', goal: '...',
|
|
303
|
+
reasoning: true, max_reasoning_attempts: 3)
|
|
304
|
+
|
|
305
|
+
result = agent.execute_task(task)
|
|
306
|
+
result[:reasoning] # => the plan the agent drafted before answering
|
|
307
|
+
result[:content] # => the final answer
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Off by default. If the reasoning pass keeps returning empty output past
|
|
311
|
+
`max_reasoning_attempts`, the agent proceeds without a plan.
|
|
312
|
+
|
|
313
|
+
## 🪟 Context Window Management
|
|
314
|
+
|
|
315
|
+
Keep long tool-use loops or large injected context from overflowing the model's
|
|
316
|
+
context window. When enabled, the oldest non-system messages are dropped to fit
|
|
317
|
+
before each LLM call (system messages and the latest message are always kept):
|
|
318
|
+
|
|
319
|
+
```ruby
|
|
320
|
+
agent = RCrewAI::Agent.new(name: 'a', role: '...', goal: '...',
|
|
321
|
+
respect_context_window: true)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Window sizes come from `RCrewAI::ContextWindow` (with a conservative default for
|
|
325
|
+
unknown models); headroom for the response is reserved from `max_tokens`. Off by
|
|
326
|
+
default.
|
|
327
|
+
|
|
328
|
+
## 🖼️ Multimodal Input
|
|
329
|
+
|
|
330
|
+
Pass images to a vision-capable model via task attachments. Local files are
|
|
331
|
+
base64-encoded automatically; URLs pass through:
|
|
332
|
+
|
|
333
|
+
```ruby
|
|
334
|
+
RCrewAI.configure { |c| c.llm_provider = :openai; c.openai_model = 'gpt-4o' }
|
|
335
|
+
|
|
336
|
+
task = RCrewAI::Task.new(
|
|
337
|
+
name: 'describe', description: 'What is in this chart?', agent: agent,
|
|
338
|
+
attachments: [
|
|
339
|
+
{ type: :image, path: 'chart.png' },
|
|
340
|
+
{ type: :image, url: 'https://example.com/photo.jpg' }
|
|
341
|
+
]
|
|
342
|
+
)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Supported on OpenAI and Azure; other providers raise a clear error when
|
|
346
|
+
attachments are present.
|
|
347
|
+
|
|
348
|
+
## 📚 Knowledge (RAG)
|
|
349
|
+
|
|
350
|
+
Ground agents in your own documents. Sources are chunked, embedded, and stored
|
|
351
|
+
in an in-memory vector store; the most relevant chunks are injected into each
|
|
352
|
+
task's prompt automatically.
|
|
353
|
+
|
|
354
|
+
```ruby
|
|
355
|
+
kb = RCrewAI::Knowledge::Base.new(sources: [
|
|
356
|
+
RCrewAI::Knowledge::StringSource.new('Our refund window is 30 days.'),
|
|
357
|
+
RCrewAI::Knowledge::FileSource.new('docs/policy.txt'),
|
|
358
|
+
RCrewAI::Knowledge::PdfSource.new('handbook.pdf'),
|
|
359
|
+
RCrewAI::Knowledge::UrlSource.new('https://example.com/faq')
|
|
360
|
+
])
|
|
361
|
+
|
|
362
|
+
# Agent-level (role-specific) knowledge:
|
|
363
|
+
support = RCrewAI::Agent.new(name: 'support', role: '...', goal: '...', knowledge: kb)
|
|
364
|
+
|
|
365
|
+
# Or pass raw sources and let the agent build the base:
|
|
366
|
+
support = RCrewAI::Agent.new(name: 'support', role: '...', goal: '...',
|
|
367
|
+
knowledge_sources: [RCrewAI::Knowledge::StringSource.new('...')])
|
|
368
|
+
|
|
369
|
+
# Crew-level knowledge is shared with every agent:
|
|
370
|
+
crew = RCrewAI::Crew.new('support_crew', knowledge: kb)
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Embeddings default to OpenAI's `text-embedding-3-small`; pass a custom
|
|
374
|
+
`embedder:` (anything responding to `embed(texts)`) or vector store to swap the
|
|
375
|
+
backend.
|
|
376
|
+
|
|
377
|
+
## 🌊 Flows
|
|
378
|
+
|
|
379
|
+
Beyond crews, RCrewAI has **Flows** — an event-driven workflow engine for
|
|
380
|
+
orchestrating steps (and whole crews) with explicit branching and state:
|
|
381
|
+
|
|
382
|
+
```ruby
|
|
383
|
+
class ArticleFlow < RCrewAI::Flow
|
|
384
|
+
start :outline
|
|
385
|
+
def outline
|
|
386
|
+
state.sections = %w[intro body conclusion]
|
|
387
|
+
state.sections.length
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
listen :outline
|
|
391
|
+
def draft(section_count)
|
|
392
|
+
state.words = section_count * 100
|
|
393
|
+
state.words
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
router :draft
|
|
397
|
+
def review(words)
|
|
398
|
+
words >= 250 ? :publish : :expand
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
listen :publish
|
|
402
|
+
def publish = state.status = 'published'
|
|
403
|
+
|
|
404
|
+
listen :expand
|
|
405
|
+
def expand = state.status = 'needs more work'
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
flow = ArticleFlow.new
|
|
409
|
+
flow.kickoff(inputs: { author: 'me' })
|
|
410
|
+
flow.state.status # => "published"
|
|
411
|
+
flow.state.id # => automatic UUID
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
- `start` / `listen` / `router` wire methods into a graph; a listener receives
|
|
415
|
+
its trigger's return value.
|
|
416
|
+
- Combine triggers with `and_(:a, :b)` (all) and `or_(:a, :b)` (any).
|
|
417
|
+
- **State** is a schemaless object with a UUID, seedable via `kickoff(inputs:)`.
|
|
418
|
+
- **Persistence**: pass `state_store:` (`RCrewAI::Flow::FileStateStore.new(dir)`
|
|
419
|
+
or your own `#save`/`#load`) and call `flow.restore(id)` to resume.
|
|
420
|
+
- Invoke a `Crew` inside any step, or pause with `human_feedback('Approve?')`.
|
|
421
|
+
|
|
172
422
|
## 💡 Examples
|
|
173
423
|
|
|
174
424
|
### Hierarchical Team with Human Oversight
|
data/ROADMAP.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# RCrewAI Roadmap
|
|
2
|
+
|
|
3
|
+
This roadmap tracks feature parity between **RCrewAI** (Ruby) and the upstream
|
|
4
|
+
[**crewai**](https://pypi.org/project/crewai/) Python framework.
|
|
5
|
+
|
|
6
|
+
## Current status
|
|
7
|
+
|
|
8
|
+
- **RCrewAI:** `0.3.0` (2026-05-12)
|
|
9
|
+
- **Upstream crewai:** `1.15.x` (mid-2026)
|
|
10
|
+
|
|
11
|
+
RCrewAI is a faithful port of CrewAI's **"Crews"** mental model (Agents / Tasks /
|
|
12
|
+
Crew, sequential + hierarchical processes, tools, memory, human-in-the-loop). As
|
|
13
|
+
of `0.3.0` the LLM plumbing is modern: native function calling across all five
|
|
14
|
+
providers, a tool-schema DSL, typed streaming events, MCP client, and per-model
|
|
15
|
+
pricing.
|
|
16
|
+
|
|
17
|
+
Since CrewAI's `1.0`, the framework grew a second pillar (**Flows**) plus
|
|
18
|
+
**Knowledge (RAG)**, **Guardrails**, **structured output**, **Planning**, and
|
|
19
|
+
**Training/Testing**. RCrewAI now implements all of these — see the matrix below.
|
|
20
|
+
|
|
21
|
+
**Status: complete.** All milestone issues (#5–#12) shipped in v0.4.0, and all
|
|
22
|
+
backlog items (#15–#20) are done and merged to `main` (awaiting the next
|
|
23
|
+
release). There is no outstanding roadmap work.
|
|
24
|
+
|
|
25
|
+
## Parity matrix
|
|
26
|
+
|
|
27
|
+
| Concept | crewai | RCrewAI 0.3.0 | Target |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| Agents / Tasks / Crew | ✅ | ✅ | — |
|
|
30
|
+
| Sequential / hierarchical process | ✅ | ✅ | — |
|
|
31
|
+
| Native function calling + tool DSL | ✅ | ✅ (0.3.0) | — |
|
|
32
|
+
| Streaming events | ✅ | ✅ (0.3.0) | — |
|
|
33
|
+
| MCP client | ✅ | ✅ (0.3.0) | — |
|
|
34
|
+
| Per-model pricing / cost | ✅ | ✅ (0.3.0) | — |
|
|
35
|
+
| Per-agent LLM override | ✅ | ✅ (#5) | ✅ done |
|
|
36
|
+
| Structured output (schema) | ✅ | ✅ (#6) | ✅ done |
|
|
37
|
+
| Task guardrails | ✅ | ✅ (#7) | ✅ done |
|
|
38
|
+
| `output_file` / markdown | ✅ | ✅ (#8) | ✅ done |
|
|
39
|
+
| Knowledge / RAG | ✅ | ✅ (#9) | ✅ done |
|
|
40
|
+
| Planning | ✅ | ✅ (#10) | ✅ done |
|
|
41
|
+
| Flows (`start`/`listen`/`router`) | ✅ | ✅ (#11) | ✅ done |
|
|
42
|
+
| Flow state + persistence | ✅ | ✅ (#11) | ✅ done |
|
|
43
|
+
| Training / Testing | ✅ | ✅ (#12) | ✅ done |
|
|
44
|
+
| Reasoning, rate-limiting, batch kickoff, hooks, context window, multimodal | ✅ | ✅ (#15–#20) | ✅ done |
|
|
45
|
+
|
|
46
|
+
## Milestones (highest leverage first)
|
|
47
|
+
|
|
48
|
+
### 0.3.1 — Per-agent LLM override
|
|
49
|
+
Let `Agent.new(llm:)` accept a provider/model, instead of only the global
|
|
50
|
+
`RCrewAI.configure`. Unblocks mixed-model crews (cheap model for workers, strong
|
|
51
|
+
model for the manager).
|
|
52
|
+
|
|
53
|
+
### 0.4.0 — Structured output & guardrails
|
|
54
|
+
Builds directly on the 0.3.0 tool-schema/JSON-schema plumbing.
|
|
55
|
+
- `Task.new(output_schema:)` → validated, coerced structured result.
|
|
56
|
+
- `Task.new(guardrail:)` → proc/object that validates & transforms output, with
|
|
57
|
+
bounded retries (`guardrail_max_retries`).
|
|
58
|
+
- `output_file:` + `markdown:` output formatting.
|
|
59
|
+
|
|
60
|
+
### 0.5.0 — Knowledge (RAG) & Planning
|
|
61
|
+
- Knowledge sources: string, `.txt`, PDF (have `pdf-reader`), CSV, JSON, URL
|
|
62
|
+
(have `nokogiri`). Embeddings client + a pluggable vector store (start with an
|
|
63
|
+
in-memory / SQLite cosine store; no hard Chroma dependency).
|
|
64
|
+
- Attach at agent **and** crew level.
|
|
65
|
+
- `Crew.new(planning: true)` → a planner pass that drafts a step plan before
|
|
66
|
+
execution.
|
|
67
|
+
|
|
68
|
+
### 0.6.0 — Flows
|
|
69
|
+
The flagship. A Ruby DSL mirroring CrewAI Flows:
|
|
70
|
+
- `start`, `listen`, `router` decorators/class-methods.
|
|
71
|
+
- `and_` / `or_` trigger combinators.
|
|
72
|
+
- Structured flow **state** (a plain struct/`Data` or dry-struct) with a UUID.
|
|
73
|
+
- `@persist`-equivalent state persistence across restarts.
|
|
74
|
+
- `human_feedback` pause/resume point.
|
|
75
|
+
|
|
76
|
+
### 0.7.0 — Training & Testing
|
|
77
|
+
- `crew.train(n_iterations:, filename:)` capturing human feedback.
|
|
78
|
+
- `crew.test(n_iterations:, model:)` scoring runs.
|
|
79
|
+
|
|
80
|
+
### Backlog — ✅ all complete
|
|
81
|
+
|
|
82
|
+
Formerly polish items with no set version; all shipped in the `[Unreleased]`
|
|
83
|
+
changes (see CHANGELOG):
|
|
84
|
+
|
|
85
|
+
- [#15](https://github.com/gkosmo/rcrewAI/issues/15) — `before_kickoff` / `after_kickoff` lifecycle hooks ✅
|
|
86
|
+
- [#16](https://github.com/gkosmo/rcrewAI/issues/16) — `kickoff_for_each` batch execution ✅
|
|
87
|
+
- [#17](https://github.com/gkosmo/rcrewAI/issues/17) — `max_rpm` rate limiting ✅
|
|
88
|
+
- [#18](https://github.com/gkosmo/rcrewAI/issues/18) — per-agent reasoning (`reasoning:`, `max_reasoning_attempts:`) ✅
|
|
89
|
+
- [#19](https://github.com/gkosmo/rcrewAI/issues/19) — `respect_context_window` history trimming ✅
|
|
90
|
+
- [#20](https://github.com/gkosmo/rcrewAI/issues/20) — multimodal agents (image/file inputs) ✅
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Upgrading to RCrewAI 0.4
|
|
2
|
+
|
|
3
|
+
RCrewAI 0.4 closes the feature-parity gap with the modern CrewAI framework. It
|
|
4
|
+
adds CrewAI's second pillar — **Flows** — alongside **Knowledge (RAG)**,
|
|
5
|
+
structured task output, guardrails, planning, and training/testing.
|
|
6
|
+
|
|
7
|
+
**0.4 is fully backward compatible.** There are no breaking changes: existing
|
|
8
|
+
0.3 code runs unchanged. Everything below is opt-in.
|
|
9
|
+
|
|
10
|
+
Each new capability has a runnable example under `examples/` — see the links
|
|
11
|
+
throughout.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. What you must do
|
|
16
|
+
|
|
17
|
+
Nothing. Upgrade the gem and your existing code keeps working:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
gem 'rcrewai', '~> 0.4'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 2. What you can do (new capabilities)
|
|
26
|
+
|
|
27
|
+
### 2a. Per-agent LLM
|
|
28
|
+
|
|
29
|
+
Give each agent its own provider/model instead of only the global default.
|
|
30
|
+
Pass a provider symbol, an options hash, or a pre-built client:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
worker = RCrewAI::Agent.new(name: 'worker', role: '...', goal: '...',
|
|
34
|
+
llm: { provider: :openai, model: 'gpt-4o-mini' })
|
|
35
|
+
manager = RCrewAI::Agent.new(name: 'manager', role: '...', goal: '...',
|
|
36
|
+
llm: { provider: :anthropic, model: 'claude-3-opus-20240229' })
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Omitting `llm:` keeps the global `RCrewAI.configure` behavior. Overrides never
|
|
40
|
+
mutate the global configuration.
|
|
41
|
+
|
|
42
|
+
### 2b. Structured output, guardrails, and file output
|
|
43
|
+
|
|
44
|
+
Post-process a task's result after the agent produces it:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
task = RCrewAI::Task.new(
|
|
48
|
+
name: 'extract', description: '...', agent: agent,
|
|
49
|
+
|
|
50
|
+
# Validate & coerce against a JSON schema. Non-conforming output re-runs the
|
|
51
|
+
# agent with the error fed back. Parsed object on task.structured_output.
|
|
52
|
+
output_schema: { type: 'object', properties: { title: { type: 'string' } },
|
|
53
|
+
required: ['title'] },
|
|
54
|
+
|
|
55
|
+
# Validate & transform. ->(output) { [ok, value_or_error] }. Retries up to
|
|
56
|
+
# guardrail_max_retries (default 3) with the reason fed back to the agent.
|
|
57
|
+
guardrail: ->(out) { [out.length < 5000, 'too long'] },
|
|
58
|
+
|
|
59
|
+
# Persist the result (parent dirs created unless create_directory: false).
|
|
60
|
+
output_file: 'out/report.md', markdown: true
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
task.execute
|
|
64
|
+
task.structured_output # => { "title" => "..." }
|
|
65
|
+
task.raw_result # => the unprocessed string the agent produced
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
See `examples/structured_output_example.rb`.
|
|
69
|
+
|
|
70
|
+
### 2c. Knowledge (RAG)
|
|
71
|
+
|
|
72
|
+
Ground agents in your own documents. Sources are chunked, embedded, and stored
|
|
73
|
+
in an in-memory cosine vector store; relevant chunks are injected into each
|
|
74
|
+
task's prompt automatically.
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
kb = RCrewAI::Knowledge::Base.new(sources: [
|
|
78
|
+
RCrewAI::Knowledge::StringSource.new('Refunds within 30 days.'),
|
|
79
|
+
RCrewAI::Knowledge::FileSource.new('docs/policy.txt'),
|
|
80
|
+
RCrewAI::Knowledge::PdfSource.new('handbook.pdf'),
|
|
81
|
+
RCrewAI::Knowledge::UrlSource.new('https://example.com/faq')
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
# Agent-level (role-specific):
|
|
85
|
+
agent = RCrewAI::Agent.new(name: 'support', role: '...', goal: '...', knowledge: kb)
|
|
86
|
+
|
|
87
|
+
# Or pass raw sources and let the agent build the base:
|
|
88
|
+
agent = RCrewAI::Agent.new(name: 'support', role: '...', goal: '...',
|
|
89
|
+
knowledge_sources: [RCrewAI::Knowledge::StringSource.new('...')])
|
|
90
|
+
|
|
91
|
+
# Crew-level knowledge is shared with every agent:
|
|
92
|
+
crew = RCrewAI::Crew.new('support', knowledge: kb)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Embeddings default to OpenAI's `text-embedding-3-small`; pass a custom
|
|
96
|
+
`embedder:` (anything responding to `embed(texts)`) to swap the backend.
|
|
97
|
+
See `examples/knowledge_rag_example.rb`.
|
|
98
|
+
|
|
99
|
+
### 2d. Planning
|
|
100
|
+
|
|
101
|
+
Have a planner pass draft a per-task plan before execution:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
crew = RCrewAI::Crew.new('research_crew', planning: true)
|
|
105
|
+
# Optionally use a dedicated (stronger) planner model:
|
|
106
|
+
crew = RCrewAI::Crew.new('research_crew', planning: true,
|
|
107
|
+
planning_llm: { provider: :anthropic, model: 'claude-3-opus-20240229' })
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The plan is folded into each task's description. Planning is best-effort: a
|
|
111
|
+
planner error or unparseable output leaves tasks unchanged.
|
|
112
|
+
|
|
113
|
+
### 2e. Training & testing
|
|
114
|
+
|
|
115
|
+
Iterate on a crew with feedback, or score repeated runs:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
# Run N times, collect feedback after each run, persist to JSON.
|
|
119
|
+
crew.train(n_iterations: 3, filename: 'training.json')
|
|
120
|
+
|
|
121
|
+
# Provide feedback programmatically instead of prompting a human:
|
|
122
|
+
crew.train(n_iterations: 3, filename: 'training.json',
|
|
123
|
+
feedback: ->(iteration, result) { "run #{iteration}: #{result[:success_rate]}%" })
|
|
124
|
+
|
|
125
|
+
# Run N times and score each run (defaults to success_rate).
|
|
126
|
+
crew.test(n_iterations: 5)
|
|
127
|
+
# => { iterations: 5, scores: [...], average_score: 92.0 }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
See `examples/planning_and_training_example.rb`.
|
|
131
|
+
|
|
132
|
+
### 2f. Flows
|
|
133
|
+
|
|
134
|
+
Flows are an event-driven workflow engine — the biggest addition in 0.4.
|
|
135
|
+
Subclass `RCrewAI::Flow` and wire methods with a class-level DSL:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
class ArticleFlow < RCrewAI::Flow
|
|
139
|
+
start :outline
|
|
140
|
+
def outline
|
|
141
|
+
state.sections = %w[intro body conclusion]
|
|
142
|
+
state.sections.length
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
listen :outline
|
|
146
|
+
def draft(section_count)
|
|
147
|
+
state.words = section_count * 100
|
|
148
|
+
state.words
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
router :draft
|
|
152
|
+
def review(words)
|
|
153
|
+
words >= 250 ? :publish : :expand
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
listen :publish
|
|
157
|
+
def publish = state.status = 'published'
|
|
158
|
+
|
|
159
|
+
listen :expand
|
|
160
|
+
def expand = state.status = 'needs more work'
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
flow = ArticleFlow.new
|
|
164
|
+
flow.kickoff(inputs: { author: 'me' })
|
|
165
|
+
flow.state.status # => "published"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
- `start` / `listen` / `router` wire methods into a graph; a listener receives
|
|
169
|
+
its trigger's return value, and a router's return becomes a label listeners
|
|
170
|
+
fire on.
|
|
171
|
+
- Combine triggers with `and_(:a, :b)` (all) and `or_(:a, :b)` (any).
|
|
172
|
+
- **State** is a schemaless object with a UUID, seedable via `kickoff(inputs:)`.
|
|
173
|
+
- **Persistence**: pass `state_store:`
|
|
174
|
+
(`RCrewAI::Flow::FileStateStore.new(dir)` or your own `#save`/`#load`) and
|
|
175
|
+
call `flow.restore(id)` to resume.
|
|
176
|
+
- Invoke a `Crew` inside any step, or pause with `human_feedback('Approve?')`.
|
|
177
|
+
|
|
178
|
+
See `examples/flow_example.rb`.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## When to use Flows vs. Crews
|
|
183
|
+
|
|
184
|
+
- **Crew** — a team of agents collaborating on a set of tasks (sequential,
|
|
185
|
+
hierarchical, or async). Reach for a crew when the work is "have these agents
|
|
186
|
+
produce these outputs."
|
|
187
|
+
- **Flow** — explicit, branching orchestration with state. Reach for a flow
|
|
188
|
+
when you need conditional paths, joins, persistence/resumption, or you want to
|
|
189
|
+
coordinate multiple crews and plain Ruby steps.
|
|
190
|
+
|
|
191
|
+
They compose: a Flow step can kick off a Crew.
|