rcrewai 0.7.0 → 0.8.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/CHANGELOG.md +51 -1
- data/ROADMAP.md +144 -75
- data/docs/api/agent.md +8 -0
- data/docs/api/crew.md +53 -3
- data/docs/api/index.md +14 -13
- data/docs/api/task.md +7 -0
- data/docs/examples/async-execution.md +8 -5
- data/docs/examples/tool-composition.md +3 -3
- data/docs/index.md +22 -7
- data/docs/tutorials/agent-options.md +128 -0
- data/docs/tutorials/consensual-process.md +58 -0
- data/docs/tutorials/flows.md +135 -0
- data/docs/tutorials/index.md +45 -0
- data/docs/tutorials/knowledge.md +80 -0
- data/docs/tutorials/memory.md +90 -0
- data/lib/rcrewai/checkpoint/cli.rb +98 -0
- data/lib/rcrewai/checkpoint.rb +145 -0
- data/lib/rcrewai/cli.rb +12 -1
- data/lib/rcrewai/configuration.rb +7 -0
- data/lib/rcrewai/crew.rb +88 -2
- data/lib/rcrewai/events.rb +71 -5
- data/lib/rcrewai/legacy_react_runner.rb +14 -9
- data/lib/rcrewai/llm_client.rb +20 -15
- data/lib/rcrewai/llm_clients/anthropic.rb +13 -4
- data/lib/rcrewai/llm_clients/azure.rb +2 -2
- data/lib/rcrewai/llm_clients/base.rb +55 -1
- data/lib/rcrewai/llm_clients/bedrock.rb +134 -0
- data/lib/rcrewai/llm_clients/google.rb +13 -4
- data/lib/rcrewai/llm_clients/ollama.rb +18 -4
- data/lib/rcrewai/llm_clients/openai.rb +19 -15
- data/lib/rcrewai/llm_clients/openai_compatible.rb +34 -0
- data/lib/rcrewai/llm_clients/openai_responses.rb +127 -0
- data/lib/rcrewai/llm_clients/snowflake_cortex.rb +42 -0
- data/lib/rcrewai/process.rb +41 -2
- data/lib/rcrewai/task.rb +3 -2
- data/lib/rcrewai/tool_runner.rb +16 -10
- data/lib/rcrewai/version.rb +1 -1
- data/lib/rcrewai.rb +3 -0
- metadata +12 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: tutorial
|
|
3
|
+
title: Advanced Agent & Task Options
|
|
4
|
+
description: Per-agent LLM, reasoning, rate limiting, context window, multimodal, structured output, guardrails, and lifecycle hooks
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Advanced Agent & Task Options
|
|
8
|
+
|
|
9
|
+
A tour of the production controls added across 0.4–0.7. All are opt-in; agents
|
|
10
|
+
and tasks behave as before when you don't set them.
|
|
11
|
+
|
|
12
|
+
## Per-agent LLM
|
|
13
|
+
|
|
14
|
+
Give each agent its own provider/model instead of only the global default:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
worker = RCrewAI::Agent.new(name: 'worker', role: '...', goal: '...',
|
|
18
|
+
llm: { provider: :openai, model: 'gpt-4o-mini' })
|
|
19
|
+
manager = RCrewAI::Agent.new(name: 'manager', role: '...', goal: '...',
|
|
20
|
+
llm: { provider: :anthropic, model: 'claude-3-opus-20240229' })
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Accepts a provider symbol, an options hash, or a pre-built client. Overrides
|
|
24
|
+
never mutate the global configuration.
|
|
25
|
+
|
|
26
|
+
## Reasoning
|
|
27
|
+
|
|
28
|
+
Have an agent draft a plan before answering. The trace is exposed on the result
|
|
29
|
+
and doesn't pollute `task.result`:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
agent = RCrewAI::Agent.new(name: '...', role: '...', goal: '...',
|
|
33
|
+
reasoning: true, max_reasoning_attempts: 3)
|
|
34
|
+
result = agent.execute_task(task)
|
|
35
|
+
result[:reasoning] # the plan
|
|
36
|
+
result[:content] # the answer
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Rate limiting
|
|
40
|
+
|
|
41
|
+
Cap an agent's LLM calls to stay under provider limits (thread-safe, holds under
|
|
42
|
+
async execution):
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
agent = RCrewAI::Agent.new(name: '...', role: '...', goal: '...', max_rpm: 20)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Context-window management
|
|
49
|
+
|
|
50
|
+
Trim history to fit the model's context window (oldest non-system messages drop
|
|
51
|
+
first; system + latest always kept):
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
agent = RCrewAI::Agent.new(name: '...', role: '...', goal: '...',
|
|
55
|
+
respect_context_window: true)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Multimodal input
|
|
59
|
+
|
|
60
|
+
Pass images to a vision-capable model via task attachments (local files are
|
|
61
|
+
base64-encoded; URLs pass through). Supported on OpenAI/Azure.
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
task = RCrewAI::Task.new(
|
|
65
|
+
name: 'describe', description: 'What is in this chart?', agent: agent,
|
|
66
|
+
attachments: [
|
|
67
|
+
{ type: :image, path: 'chart.png' },
|
|
68
|
+
{ type: :image, url: 'https://example.com/photo.jpg' }
|
|
69
|
+
]
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Structured output & guardrails
|
|
74
|
+
|
|
75
|
+
Validate, transform, and persist a task's result:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
task = RCrewAI::Task.new(
|
|
79
|
+
name: 'extract', description: '...', agent: agent,
|
|
80
|
+
|
|
81
|
+
output_schema: { type: 'object', properties: { title: { type: 'string' } },
|
|
82
|
+
required: ['title'] }, # -> task.structured_output
|
|
83
|
+
|
|
84
|
+
guardrail: ->(out) { [out.length < 5000, 'too long'] }, # [ok, value_or_error]
|
|
85
|
+
|
|
86
|
+
output_file: 'out/report.md', markdown: true
|
|
87
|
+
)
|
|
88
|
+
task.execute
|
|
89
|
+
task.structured_output # validated object
|
|
90
|
+
task.raw_result # unprocessed string
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Schema/guardrail failures re-run the agent with the error fed back.
|
|
94
|
+
|
|
95
|
+
## Planning
|
|
96
|
+
|
|
97
|
+
Run a planner pass that drafts a per-task plan before execution:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
crew = RCrewAI::Crew.new('research', planning: true) # optional planning_llm:
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Lifecycle hooks & batch runs
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
crew.before_kickoff { |inputs| inputs.merge(started_at: Time.now) }
|
|
107
|
+
crew.after_kickoff { |result| notify(result); result }
|
|
108
|
+
|
|
109
|
+
crew.execute(inputs: { topic: 'ruby' })
|
|
110
|
+
crew.last_inputs # the resolved inputs
|
|
111
|
+
|
|
112
|
+
# Run the crew once per input set:
|
|
113
|
+
results = crew.kickoff_for_each(inputs: [{ topic: 'ruby' }, { topic: 'python' }])
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Training & testing
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
crew.train(n_iterations: 3, filename: 'training.json') # collect feedback
|
|
120
|
+
crew.test(n_iterations: 5) # score repeated runs
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## See also
|
|
124
|
+
|
|
125
|
+
- [Flows]({{ site.baseurl }}/tutorials/flows)
|
|
126
|
+
- [Knowledge (RAG)]({{ site.baseurl }}/tutorials/knowledge)
|
|
127
|
+
- [Cognitive Memory]({{ site.baseurl }}/tutorials/memory)
|
|
128
|
+
- [Consensual Process]({{ site.baseurl }}/tutorials/consensual-process)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: tutorial
|
|
3
|
+
title: Consensual Process
|
|
4
|
+
description: Multi-agent consensus — agents propose competing answers and vote to pick one
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Consensual Process
|
|
8
|
+
|
|
9
|
+
For decisions where multiple perspectives matter, the `:consensual` process has
|
|
10
|
+
several agents propose competing answers and vote to pick the best one.
|
|
11
|
+
|
|
12
|
+
> **Since 0.7.0.** Earlier versions treated `:consensual` as a stub that ran
|
|
13
|
+
> tasks sequentially. It now performs real consensus — if you relied on the old
|
|
14
|
+
> behavior, use `process: :sequential`.
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
18
|
+
For each task:
|
|
19
|
+
|
|
20
|
+
1. **Propose** — up to `consensus_agents` agents (default 3, capped from the
|
|
21
|
+
crew) each produce a candidate answer.
|
|
22
|
+
2. **Vote** — every participant scores each candidate 0–10 against the task's
|
|
23
|
+
description and expected output.
|
|
24
|
+
3. **Pick** — the highest total score wins. Ties break toward the task's
|
|
25
|
+
assigned agent.
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
crew = RCrewAI::Crew.new('panel', process: :consensual, consensus_agents: 3)
|
|
29
|
+
crew.add_agent(junior)
|
|
30
|
+
crew.add_agent(senior)
|
|
31
|
+
crew.add_task(task)
|
|
32
|
+
|
|
33
|
+
result = crew.execute # each task goes through propose → vote → pick
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Cost
|
|
37
|
+
|
|
38
|
+
Consensus multiplies LLM calls: roughly `N` proposals + `N × N` scoring calls per
|
|
39
|
+
task, where `N` is `consensus_agents` (default 3). The cap keeps cost bounded even
|
|
40
|
+
on large crews — raise or lower it to trade thoroughness for cost.
|
|
41
|
+
|
|
42
|
+
## Edge cases
|
|
43
|
+
|
|
44
|
+
- **One agent** → a single proposal (no meaningful vote), still a valid result.
|
|
45
|
+
- **A proposer errors** → that candidate is dropped; consensus continues with the
|
|
46
|
+
rest.
|
|
47
|
+
- **All proposals fail** → the task is marked failed.
|
|
48
|
+
|
|
49
|
+
## When to use it
|
|
50
|
+
|
|
51
|
+
Reach for `:consensual` when answer quality benefits from diversity and
|
|
52
|
+
cross-checking — design decisions, judgment calls, ambiguous tasks. For
|
|
53
|
+
straightforward pipelines, `:sequential` or `:hierarchical` is cheaper.
|
|
54
|
+
|
|
55
|
+
## Runnable example
|
|
56
|
+
|
|
57
|
+
See [`examples/consensual_process_example.rb`](https://github.com/gkosmo/rcrewAI/blob/main/examples/consensual_process_example.rb)
|
|
58
|
+
— runs without an API key.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: tutorial
|
|
3
|
+
title: Flows — Event-Driven Workflows
|
|
4
|
+
description: Build structured, stateful workflows with start/listen/router, combinators, and persistence
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Flows
|
|
8
|
+
|
|
9
|
+
Crews are great for "have these agents produce these outputs." **Flows** are the
|
|
10
|
+
second orchestration pillar — for workflows that need explicit branching, joins,
|
|
11
|
+
persistent state, or coordination across multiple crews and plain Ruby steps.
|
|
12
|
+
|
|
13
|
+
Subclass `RCrewAI::Flow` and wire methods together with a class-level DSL.
|
|
14
|
+
|
|
15
|
+
## A first flow
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require 'rcrewai'
|
|
19
|
+
|
|
20
|
+
class ArticleFlow < RCrewAI::Flow
|
|
21
|
+
start :outline
|
|
22
|
+
def outline
|
|
23
|
+
state.sections = %w[intro body conclusion]
|
|
24
|
+
state.sections.length # return value is passed to listeners of :outline
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
listen :outline
|
|
28
|
+
def draft(section_count)
|
|
29
|
+
state.words = section_count * 100
|
|
30
|
+
state.words
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
router :draft
|
|
34
|
+
def review(words)
|
|
35
|
+
words >= 250 ? :publish : :expand # a router returns a label
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
listen :publish
|
|
39
|
+
def publish = state.status = 'published'
|
|
40
|
+
|
|
41
|
+
listen :expand
|
|
42
|
+
def expand = state.status = 'needs more work'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
flow = ArticleFlow.new
|
|
46
|
+
flow.kickoff(inputs: { author: 'Ada' })
|
|
47
|
+
flow.state.status # => "published"
|
|
48
|
+
flow.state.id # => automatic UUID
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## The DSL
|
|
52
|
+
|
|
53
|
+
- **`start :method`** — an entry point. A flow can have several; all run first.
|
|
54
|
+
- **`listen :trigger`** — runs the following method after `:trigger` completes,
|
|
55
|
+
receiving its return value.
|
|
56
|
+
- **`router :trigger`** — like `listen`, but the method's return value becomes a
|
|
57
|
+
**label** that other `listen` methods can trigger on. This is how you branch.
|
|
58
|
+
|
|
59
|
+
### Combining triggers
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
listen and_(:fetch_a, :fetch_b) # fires once, after BOTH complete
|
|
63
|
+
def merge(...); end
|
|
64
|
+
|
|
65
|
+
listen or_(:cache_hit, :cache_miss) # fires when EITHER completes
|
|
66
|
+
def proceed(...); end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## State
|
|
70
|
+
|
|
71
|
+
`state` is a schemaless object with an automatic UUID. Read and write attributes
|
|
72
|
+
directly (`state.foo = 1`), and seed initial values via `kickoff(inputs:)`:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
flow.kickoff(inputs: { topic: 'ruby', max_words: 800 })
|
|
76
|
+
flow.state.topic # => "ruby"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Persistence — pause and resume
|
|
80
|
+
|
|
81
|
+
Pass a `state_store:` and a flow's state is saved after each run, so you can
|
|
82
|
+
restore it later by id:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
store = RCrewAI::Flow::FileStateStore.new('tmp/flows') # or your own #save/#load
|
|
86
|
+
flow = ArticleFlow.new(state_store: store)
|
|
87
|
+
flow.kickoff
|
|
88
|
+
id = flow.state.id
|
|
89
|
+
|
|
90
|
+
# ...later, even in a fresh process...
|
|
91
|
+
resumed = ArticleFlow.new(state_store: store)
|
|
92
|
+
resumed.restore(id)
|
|
93
|
+
resumed.state.status # => recovered
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Built-in stores: `RCrewAI::Flow::MemoryStateStore` (volatile) and
|
|
97
|
+
`RCrewAI::Flow::FileStateStore` (JSON on disk). Any object responding to
|
|
98
|
+
`#save(id, hash)` / `#load(id)` works.
|
|
99
|
+
|
|
100
|
+
## Running a crew inside a flow
|
|
101
|
+
|
|
102
|
+
A flow step is just a method, so it can kick off a whole crew:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
class ResearchFlow < RCrewAI::Flow
|
|
106
|
+
def initialize(crew:, **opts)
|
|
107
|
+
super(**opts)
|
|
108
|
+
@crew = crew
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
start :run
|
|
112
|
+
def run
|
|
113
|
+
state.crew_result = @crew.execute(inputs: { topic: state.topic })
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Human feedback
|
|
119
|
+
|
|
120
|
+
Pause a flow for input with `human_feedback`:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
listen :draft
|
|
124
|
+
def approve(_draft)
|
|
125
|
+
state.approved = human_feedback('Approve this draft?')
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Provide a handler for non-interactive runs:
|
|
130
|
+
`ArticleFlow.new(feedback_handler: ->(prompt) { auto_approve(prompt) })`.
|
|
131
|
+
|
|
132
|
+
## Runnable example
|
|
133
|
+
|
|
134
|
+
See [`examples/flow_example.rb`](https://github.com/gkosmo/rcrewAI/blob/main/examples/flow_example.rb)
|
|
135
|
+
— it runs without an API key.
|
data/docs/tutorials/index.md
CHANGED
|
@@ -62,6 +62,51 @@ Welcome to the RCrewAI tutorials! These step-by-step guides will take you from b
|
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
|
+
## ✨ Capabilities (0.4 – 0.7)
|
|
66
|
+
|
|
67
|
+
The features that grew RCrewAI beyond the classic crew model.
|
|
68
|
+
|
|
69
|
+
### [Flows — Event-Driven Workflows]({{ site.baseurl }}/tutorials/flows)
|
|
70
|
+
**Orchestrate with branching and state.** `start`/`listen`/`router`, `and_`/`or_`
|
|
71
|
+
combinators, schemaless state with a UUID, persistence and resume, running crews
|
|
72
|
+
as steps, and `human_feedback` pause points.
|
|
73
|
+
|
|
74
|
+
**Prerequisites:** Getting Started tutorial
|
|
75
|
+
**Difficulty:** Intermediate ⭐⭐
|
|
76
|
+
|
|
77
|
+
### [Knowledge (RAG)]({{ site.baseurl }}/tutorials/knowledge)
|
|
78
|
+
**Ground agents in your documents.** String/file/PDF/CSV/URL sources, chunking,
|
|
79
|
+
multi-provider embeddings, and agent- or crew-level attachment with automatic
|
|
80
|
+
retrieval into the prompt.
|
|
81
|
+
|
|
82
|
+
**Prerequisites:** Getting Started tutorial
|
|
83
|
+
**Difficulty:** Intermediate ⭐⭐
|
|
84
|
+
|
|
85
|
+
### [Cognitive Memory]({{ site.baseurl }}/tutorials/memory)
|
|
86
|
+
**Agents that remember.** Semantic recall (embeddings + cosine), optional SQLite
|
|
87
|
+
persistence, and short-term/long-term/entity/tool memory types — with a
|
|
88
|
+
zero-config default.
|
|
89
|
+
|
|
90
|
+
**Prerequisites:** Getting Started tutorial
|
|
91
|
+
**Difficulty:** Intermediate ⭐⭐
|
|
92
|
+
|
|
93
|
+
### [Consensual Process]({{ site.baseurl }}/tutorials/consensual-process)
|
|
94
|
+
**Multi-agent voting.** Agents propose competing answers and score each other to
|
|
95
|
+
pick the best; tune cost with `consensus_agents`.
|
|
96
|
+
|
|
97
|
+
**Prerequisites:** Getting Started tutorial
|
|
98
|
+
**Difficulty:** Beginner ⭐
|
|
99
|
+
|
|
100
|
+
### [Advanced Agent & Task Options]({{ site.baseurl }}/tutorials/agent-options)
|
|
101
|
+
**Production controls.** Per-agent LLM, reasoning passes, rate limiting,
|
|
102
|
+
context-window management, multimodal input, structured output, guardrails,
|
|
103
|
+
planning, lifecycle hooks, and batch runs.
|
|
104
|
+
|
|
105
|
+
**Prerequisites:** Getting Started tutorial
|
|
106
|
+
**Difficulty:** Intermediate ⭐⭐
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
65
110
|
## 🏗️ Architecture & Scaling
|
|
66
111
|
|
|
67
112
|
### [Working with Multiple Crews]({{ site.baseurl }}/tutorials/multiple-crews)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: tutorial
|
|
3
|
+
title: Knowledge (RAG)
|
|
4
|
+
description: Ground agents in your own documents with retrieval-augmented generation
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Knowledge (RAG)
|
|
8
|
+
|
|
9
|
+
Give agents access to your own documents. Sources are chunked, embedded, and
|
|
10
|
+
stored in a vector store; at execution time the most relevant chunks are
|
|
11
|
+
injected into the agent's task prompt automatically.
|
|
12
|
+
|
|
13
|
+
## Building a knowledge base
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
require 'rcrewai'
|
|
17
|
+
|
|
18
|
+
kb = RCrewAI::Knowledge::Base.new(sources: [
|
|
19
|
+
RCrewAI::Knowledge::StringSource.new('Refunds are available within 30 days.'),
|
|
20
|
+
RCrewAI::Knowledge::FileSource.new('docs/policy.txt'),
|
|
21
|
+
RCrewAI::Knowledge::PdfSource.new('handbook.pdf'),
|
|
22
|
+
RCrewAI::Knowledge::CsvSource.new('faq.csv'),
|
|
23
|
+
RCrewAI::Knowledge::UrlSource.new('https://example.com/faq')
|
|
24
|
+
])
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Attaching knowledge
|
|
28
|
+
|
|
29
|
+
**Agent-level** (role-specific):
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
support = RCrewAI::Agent.new(
|
|
33
|
+
name: 'support', role: 'Support specialist', goal: 'Answer using company policy',
|
|
34
|
+
knowledge: kb
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Or pass raw sources and let the agent build the base:
|
|
38
|
+
support = RCrewAI::Agent.new(name: 'support', role: '...', goal: '...',
|
|
39
|
+
knowledge_sources: [RCrewAI::Knowledge::StringSource.new('...')])
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Crew-level** (shared with every agent):
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
crew = RCrewAI::Crew.new('support_crew', knowledge: kb)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
When a task runs, chunks relevant to the task description are retrieved and added
|
|
49
|
+
to the prompt under a "Relevant Knowledge" heading.
|
|
50
|
+
|
|
51
|
+
## Embeddings — pick a provider
|
|
52
|
+
|
|
53
|
+
Embeddings default to OpenAI's `text-embedding-3-small`. Since 0.6.1 the embedder
|
|
54
|
+
is multi-provider:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
# Local, no API key:
|
|
58
|
+
embedder = RCrewAI::Knowledge::Embedder.new(provider: :ollama, model: 'nomic-embed-text')
|
|
59
|
+
|
|
60
|
+
# Or :azure / :google. (:anthropic has no embeddings API and raises.)
|
|
61
|
+
kb = RCrewAI::Knowledge::Base.new(sources: [...], embedder: embedder)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Any object responding to `embed(texts) -> [[float, ...], ...]` can be substituted.
|
|
65
|
+
|
|
66
|
+
## Chunking and the vector store
|
|
67
|
+
|
|
68
|
+
`Knowledge::Base.new` accepts `chunk_size:` and `overlap:` to tune how documents
|
|
69
|
+
are split. The default vector store is in-memory with cosine similarity; the
|
|
70
|
+
store is pluggable if you need a different backend.
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
kb = RCrewAI::Knowledge::Base.new(sources: [...], chunk_size: 800, overlap: 100)
|
|
74
|
+
kb.search('what is the refund window?', k: 3) # => top-k relevant chunks
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Runnable example
|
|
78
|
+
|
|
79
|
+
See [`examples/knowledge_rag_example.rb`](https://github.com/gkosmo/rcrewAI/blob/main/examples/knowledge_rag_example.rb)
|
|
80
|
+
— it runs without an API key using a fake embedder.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: tutorial
|
|
3
|
+
title: Cognitive Memory
|
|
4
|
+
description: Semantic, persistent, multi-type agent memory
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Cognitive Memory
|
|
8
|
+
|
|
9
|
+
Agents remember what they've done and recall it on future tasks. Memory is
|
|
10
|
+
**zero-config by default** (in-memory, word-overlap recall) and becomes far more
|
|
11
|
+
capable when you add an embedder (semantic recall) and a store (persistence).
|
|
12
|
+
|
|
13
|
+
## Zero-config
|
|
14
|
+
|
|
15
|
+
Every agent gets a `Memory` scoped to itself. Nothing to set up:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
agent = RCrewAI::Agent.new(name: 'engineer', role: '...', goal: '...')
|
|
19
|
+
# agent.memory records executions and recalls relevant ones automatically
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Semantic recall
|
|
23
|
+
|
|
24
|
+
Pass an embedder and recall becomes semantic — the agent finds conceptually
|
|
25
|
+
related past work even when the wording differs:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
embedder = RCrewAI::Knowledge::Embedder.new # or provider: :ollama
|
|
29
|
+
agent = RCrewAI::Agent.new(name: 'engineer', role: '...', goal: '...',
|
|
30
|
+
memory: { embedder: embedder })
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Recall falls back to word-overlap similarity without an embedder, and embedding
|
|
34
|
+
failures fall back gracefully — **memory never breaks agent execution.**
|
|
35
|
+
|
|
36
|
+
## Persistence
|
|
37
|
+
|
|
38
|
+
Give memory a SQLite store and it survives restarts:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
store = RCrewAI::Memory::SqliteStore.new(path: '~/.rcrewai/memory.db')
|
|
42
|
+
agent = RCrewAI::Agent.new(name: 'engineer', role: '...', goal: '...',
|
|
43
|
+
memory: { embedder: embedder, store: store })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The default store is `InMemoryStore` (volatile). `SqliteStore` accepts
|
|
47
|
+
`max_candidates:` (default 1000) to bound how many recent rows a search scans,
|
|
48
|
+
keeping recall fast as memory grows.
|
|
49
|
+
|
|
50
|
+
## Memory types
|
|
51
|
+
|
|
52
|
+
The `Memory` facade exposes four underlying types:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
agent.memory.short_term # recent executions (capped, semantic recall)
|
|
56
|
+
agent.memory.long_term # durable, deduped insights from successful runs
|
|
57
|
+
agent.memory.entity # facts about entities (people, systems) seen in work
|
|
58
|
+
agent.memory.tool # tool-call history + outcomes
|
|
59
|
+
|
|
60
|
+
agent.memory.entity.entities # => ["Alice", "AWS", ...]
|
|
61
|
+
agent.memory.long_term.recall('...', limit: 3)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Better entity extraction
|
|
65
|
+
|
|
66
|
+
By default entities are extracted heuristically (capitalized tokens). For
|
|
67
|
+
multi-word names, plug in an LLM extractor:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
extractor = RCrewAI::Memory::LlmEntityExtractor.new(agent.llm_client)
|
|
71
|
+
agent = RCrewAI::Agent.new(name: '...', role: '...', goal: '...',
|
|
72
|
+
memory: { entity_extractor: extractor })
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Scoping
|
|
76
|
+
|
|
77
|
+
Memory is scoped per agent, so agents sharing a persistent store don't read each
|
|
78
|
+
other's memories. Override with `memory: { scope: 'shared' }` for deliberate
|
|
79
|
+
sharing.
|
|
80
|
+
|
|
81
|
+
## The classic API still works
|
|
82
|
+
|
|
83
|
+
`add_execution`, `add_tool_usage`, `relevant_executions`, `tool_usage_for`,
|
|
84
|
+
`clear_short_term!`, `clear_all!`, and `stats` behave as before — the cognitive
|
|
85
|
+
system is a drop-in upgrade.
|
|
86
|
+
|
|
87
|
+
## Runnable example
|
|
88
|
+
|
|
89
|
+
See [`examples/cognitive_memory_example.rb`](https://github.com/gkosmo/rcrewAI/blob/main/examples/cognitive_memory_example.rb)
|
|
90
|
+
— semantic recall + SQLite persistence, no API key required.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
|
|
5
|
+
module RCrewAI
|
|
6
|
+
module Checkpoint
|
|
7
|
+
# Inspects checkpoints written by a crew run.
|
|
8
|
+
class CLI < Thor
|
|
9
|
+
DEFAULT_DIR = '.rcrewai/checkpoints'
|
|
10
|
+
|
|
11
|
+
class_option :dir, type: :string, default: DEFAULT_DIR,
|
|
12
|
+
desc: 'Directory holding checkpoint files'
|
|
13
|
+
|
|
14
|
+
desc 'list', 'List saved checkpoint runs'
|
|
15
|
+
def list
|
|
16
|
+
ids = store.list
|
|
17
|
+
if ids.empty?
|
|
18
|
+
puts 'No checkpoints found.'
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
puts "Checkpoints in #{options[:dir]}:"
|
|
23
|
+
ids.sort.each do |id|
|
|
24
|
+
record = store.load(id)
|
|
25
|
+
next unless record
|
|
26
|
+
|
|
27
|
+
puts " #{id} #{summarize(record)}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
desc 'info RUN_ID', 'Show a checkpoint in detail'
|
|
32
|
+
def info(run_id)
|
|
33
|
+
record = store.load(run_id)
|
|
34
|
+
unless record
|
|
35
|
+
puts "No checkpoint for run id #{run_id}."
|
|
36
|
+
return
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
puts "run: #{record['run_id']}"
|
|
40
|
+
puts "crew: #{record['crew']}"
|
|
41
|
+
puts "updated: #{record['updated_at']}"
|
|
42
|
+
print_lineage(run_id, record)
|
|
43
|
+
print_tasks(record)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc 'delete RUN_ID', 'Delete a checkpoint'
|
|
47
|
+
def delete(run_id)
|
|
48
|
+
unless store.load(run_id)
|
|
49
|
+
puts "No checkpoint for run id #{run_id}."
|
|
50
|
+
return
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
store.delete(run_id)
|
|
54
|
+
puts "Deleted checkpoint #{run_id}."
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Thor exits non-zero on an unhandled error rather than swallowing it.
|
|
58
|
+
def self.exit_on_failure?
|
|
59
|
+
true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def store
|
|
65
|
+
@store ||= FileStore.new(options[:dir])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def summarize(record)
|
|
69
|
+
tasks = record['tasks'] || {}
|
|
70
|
+
done = tasks.count { |_n, t| t['status'] == 'completed' }
|
|
71
|
+
"#{record['crew']} #{done}/#{tasks.size} tasks"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def print_lineage(run_id, record)
|
|
75
|
+
return unless record['parent_run_id']
|
|
76
|
+
|
|
77
|
+
chain = Checkpoint.lineage(store, run_id)
|
|
78
|
+
puts "lineage: #{chain.join(' -> ')}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def print_tasks(record)
|
|
82
|
+
tasks = record['tasks'] || {}
|
|
83
|
+
return puts 'tasks: (none)' if tasks.empty?
|
|
84
|
+
|
|
85
|
+
puts 'tasks:'
|
|
86
|
+
tasks.each do |name, entry|
|
|
87
|
+
secs = entry['execution_time']
|
|
88
|
+
timing = secs ? format(' (%.2fs)', secs) : ''
|
|
89
|
+
puts " #{status_mark(entry['status'])} #{name} #{entry['status']}#{timing}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def status_mark(status)
|
|
94
|
+
status == 'completed' ? '+' : '!'
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|