rcrewai-rails 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 +93 -1
- data/README.md +122 -3
- data/app/jobs/rcrewai/rails/crew_execution_job.rb +24 -2
- data/app/models/rcrewai/rails/active_record_checkpoint_store.rb +53 -0
- data/app/models/rcrewai/rails/agent.rb +4 -0
- data/app/models/rcrewai/rails/checkpoint.rb +37 -0
- data/app/models/rcrewai/rails/crew.rb +25 -0
- data/db/migrate/002_add_config_to_rcrewai_agents.rb +1 -1
- data/db/migrate/003_add_output_processing_to_rcrewai_tasks.rb +1 -1
- data/db/migrate/004_add_lifecycle_to_rcrewai_crews.rb +1 -1
- data/db/migrate/005_add_batch_id_to_rcrewai_executions.rb +1 -1
- data/db/migrate/006_create_rcrewai_knowledge_sources.rb +1 -1
- data/db/migrate/007_create_rcrewai_flows.rb +1 -1
- data/db/migrate/008_add_consensus_agents_to_rcrewai_crews.rb +1 -1
- data/db/migrate/010_create_rcrewai_spans.rb +1 -1
- data/db/migrate/011_add_observation_rollups_to_rcrewai_executions.rb +1 -1
- data/db/migrate/012_create_rcrewai_checkpoints.rb +22 -0
- data/lib/generators/rcrewai/rails/install/templates/create_rcrewai_tables.rb +18 -1
- data/lib/generators/rcrewai/rails/install/templates/rcrewai.rb +36 -2
- data/lib/rcrewai/rails/agent_builder.rb +4 -0
- data/lib/rcrewai/rails/configuration.rb +13 -1
- data/lib/rcrewai/rails/interceptors.rb +52 -0
- data/lib/rcrewai/rails/observation/collector.rb +11 -9
- data/lib/rcrewai/rails/observation/span_stack.rb +25 -8
- data/lib/rcrewai/rails/version.rb +1 -1
- data/lib/rcrewai/rails.rb +1 -0
- data/rcrewai-rails.gemspec +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7987750789244c90f421923541a2132e3aab1f6891f6b706ed42544ea4ae2507
|
|
4
|
+
data.tar.gz: 99fe0af08b0ad8f1a4c5bccf93edd2285e32f5966f6c3d93c2f152c239074ffe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: edafeb7cbf6461861bee821981911a5262d0dff09ec034cbb4cb2de123fe5a31a135e25685b7ba756931c665a908cf41e369b474aa33bb7a4beab7b99fcc17a3
|
|
7
|
+
data.tar.gz: f2edb449ac6b178e62392e809b677c103456e1ec6141e35b3dcac52b15c3254c5af0abe7e3165ba2b5f95137b87fcbcae1cf9dfc2cb0082afe3da795af470c3b
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,86 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.0] - 2026-09-10
|
|
11
|
+
|
|
12
|
+
Tracks **rcrewai 0.8.0**, which is backward compatible for this engine — the
|
|
13
|
+
0.7.1 suite passed against it unchanged. This release is about surfacing the
|
|
14
|
+
new capabilities in Rails terms: durable **checkpointing** with resume, **LLM
|
|
15
|
+
interceptors**, and the **event hierarchy**, plus the four new providers.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Checkpointing.** `RcrewAI::Rails::ActiveRecordCheckpointStore` persists
|
|
19
|
+
rcrewai crew checkpoints to the new `rcrewai_checkpoints` table, implementing
|
|
20
|
+
the gem's store contract (`save`/`load`/`list`/`delete`). Enable globally with
|
|
21
|
+
`config.checkpoint_enabled = true`, or per crew via the new
|
|
22
|
+
`checkpoint_enabled` column. Records are stored verbatim as JSON, so a record
|
|
23
|
+
written by any store implementation round-trips identically.
|
|
24
|
+
- **Resume.** `crew.resume_async(execution)` / `crew.resume_sync(execution)`
|
|
25
|
+
re-run a checkpointed execution, replaying tasks that already completed
|
|
26
|
+
instead of paying for them again. Accepts an `Execution` or a bare run id.
|
|
27
|
+
`crew.resumable_executions` lists what can be resumed.
|
|
28
|
+
- **Run lineage.** Executions record `run_id` and `parent_run_id`, so a resumed
|
|
29
|
+
run links back to the one it continues and `RCrewAI::Checkpoint.lineage`
|
|
30
|
+
walks the chain. `RcrewAI::Rails::Checkpoint` exposes `children`, `roots`,
|
|
31
|
+
`completed_task_names` and `failed_task_names`.
|
|
32
|
+
- **LLM interceptors.** `config.llm_before_request` and
|
|
33
|
+
`config.llm_after_response` attach rcrewai 0.8 hooks to every client the
|
|
34
|
+
engine builds. The gem forwards hooks only through `LLMClient.for_provider`,
|
|
35
|
+
and agents resolve their client via `LLMClient.resolve`, which does not — so
|
|
36
|
+
the engine registers them on the built client, covering per-agent LLMs too.
|
|
37
|
+
- **New providers.** `openai_compatible`, `bedrock`, `snowflake` and
|
|
38
|
+
`openai_responses` resolve through an agent's `llm_config` and are documented
|
|
39
|
+
in the generated initializer. (No engine change was required — provider
|
|
40
|
+
values pass through to the gem — but they are now covered by specs.)
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
- **Crew inputs never reached the gem.** `CrewExecutionJob` persisted the
|
|
44
|
+
execution's `inputs` but called `rcrew.execute` without them, so
|
|
45
|
+
`before_kickoff` hooks and `Crew#last_inputs` always saw `{}` and no task
|
|
46
|
+
could interpolate an input. Inputs are now forwarded. Pre-existing, unrelated
|
|
47
|
+
to the upgrade.
|
|
48
|
+
- **Concurrent runs of the same agent shared one span stack.** The observation
|
|
49
|
+
collector keyed open spans by agent name, so when one agent ran twice
|
|
50
|
+
concurrently, one run's tool call could nest under the other run's iteration
|
|
51
|
+
(and its usage, text and errors could land on the wrong span). rcrewai 0.8
|
|
52
|
+
stamps every event with the id of its enclosing run span; the collector now
|
|
53
|
+
scopes its bookkeeping by that id. Events without one — older streams, and
|
|
54
|
+
the engine's own crew/agent spans — keep the previous behavior.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
- Requires `rcrewai ~> 0.8.0`.
|
|
58
|
+
- `SpanStack.key_for` takes an optional second argument (the run id) and
|
|
59
|
+
`push`/`pop`/`current` take an optional `run_id:`. Existing calls without it
|
|
60
|
+
behave as before.
|
|
61
|
+
|
|
62
|
+
### Upgrading
|
|
63
|
+
Existing installs need the new migration:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
$ rails rcrew_ai_rails:install:migrations
|
|
67
|
+
$ rails db:migrate
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This adds `rcrewai_checkpoints`, `rcrewai_crews.checkpoint_enabled`, and
|
|
71
|
+
`run_id`/`parent_run_id` on `rcrewai_executions`. Checkpointing is off by
|
|
72
|
+
default, so nothing changes until you enable it.
|
|
73
|
+
|
|
74
|
+
## [0.7.1] - 2026-08-13
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
- **`rails db:migrate` raised `NameError` on 0.7.0.** The engine registers an
|
|
78
|
+
`RcrewAI` acronym inflection (Zeitwerk needs it to resolve
|
|
79
|
+
`rcrewai/rails/span.rb` to `RcrewAI::Rails::Span`), so Rails camelizes
|
|
80
|
+
`create_rcrewai_tables` to `CreateRcrewAITables` — but the shipped migrations
|
|
81
|
+
declared `CreateRcrewaiTables`. Rails resolves a migration's class from its
|
|
82
|
+
filename, so following the documented install verbatim failed. Nine migrations
|
|
83
|
+
in `db/migrate` and the install template were affected; all now declare the
|
|
84
|
+
name Rails derives. Thanks to the user who reported this from a real install.
|
|
85
|
+
|
|
86
|
+
**If you already installed 0.7.0** and renamed the class by hand, no action is
|
|
87
|
+
needed — your migration works. Otherwise, delete the copied migration, upgrade,
|
|
88
|
+
and re-run the install (or `rcrew_ai_rails:install:migrations`).
|
|
89
|
+
|
|
10
90
|
## [0.7.0] - 2026-08-13
|
|
11
91
|
|
|
12
92
|
### Added
|
|
@@ -36,6 +116,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
36
116
|
- `rcrewai >= 0.7.1` for agent-level tracing. On earlier 0.7.x versions traces
|
|
37
117
|
contain only crew-level spans.
|
|
38
118
|
|
|
119
|
+
### Upgrading
|
|
120
|
+
- **Existing installs need two new migrations.** Do not run the install
|
|
121
|
+
generator (it creates every table and will fail on duplicates). Instead run
|
|
122
|
+
`rails rcrew_ai_rails:install:migrations && rails db:migrate` to copy only
|
|
123
|
+
what is missing: `010_create_rcrewai_spans` (the trace tree) and
|
|
124
|
+
`011_add_observation_rollups_to_rcrewai_executions` (cost/token totals). Both
|
|
125
|
+
are additive — nothing existing is altered or dropped. Observation is enabled
|
|
126
|
+
by default once the tables exist; set `config.observation_enabled = false` to
|
|
127
|
+
upgrade without turning tracing on.
|
|
128
|
+
|
|
39
129
|
## [0.6.1] - 2026-07-07
|
|
40
130
|
|
|
41
131
|
### Fixed
|
|
@@ -152,7 +242,9 @@ existing agents, tasks, and crews build unchanged.
|
|
|
152
242
|
### Changed
|
|
153
243
|
- Rename generators from `rcrew_a_i` to `rcrewai` namespacing.
|
|
154
244
|
|
|
155
|
-
[Unreleased]: https://github.com/gkosmo/rcrewai-rails/compare/v0.
|
|
245
|
+
[Unreleased]: https://github.com/gkosmo/rcrewai-rails/compare/v0.8.0...HEAD
|
|
246
|
+
[0.8.0]: https://github.com/gkosmo/rcrewai-rails/compare/v0.7.1...v0.8.0
|
|
247
|
+
[0.7.1]: https://github.com/gkosmo/rcrewai-rails/compare/v0.7.0...v0.7.1
|
|
156
248
|
[0.7.0]: https://github.com/gkosmo/rcrewai-rails/compare/v0.6.1...v0.7.0
|
|
157
249
|
[0.6.1]: https://github.com/gkosmo/rcrewai-rails/compare/v0.6.0...v0.6.1
|
|
158
250
|
[0.6.0]: https://github.com/gkosmo/rcrewai-rails/compare/v0.5.1...v0.6.0
|
data/README.md
CHANGED
|
@@ -10,11 +10,12 @@ Rails engine for integrating [RcrewAI](https://github.com/gkosmo/rcrewai-rails)
|
|
|
10
10
|
- **Web UI**: Monitor and manage crews through a built-in interface
|
|
11
11
|
- **Rails-Specific Tools**: Pre-built tools for ActiveRecord, ActionMailer, Rails cache, and more
|
|
12
12
|
- **Configuration**: Flexible configuration through Rails initializers
|
|
13
|
-
- **Full rcrewai 0.
|
|
13
|
+
- **Full rcrewai 0.8 feature coverage** (see [rcrewai 0.8 capabilities](#rcrewai-08-capabilities) and [rcrewai 0.7 capabilities](#rcrewai-07-capabilities)):
|
|
14
14
|
- Agent config: reasoning, per-agent LLM, rate limiting, context-window trimming, cognitive memory
|
|
15
15
|
- Task output: structured output schemas, guardrails, file output, multimodal attachments
|
|
16
16
|
- Crew: `before_kickoff`/`after_kickoff` hooks, planning, the `consensual` process, batch execution
|
|
17
17
|
- Knowledge (RAG) sources and Flow persistence
|
|
18
|
+
- Checkpointing with resume, LLM interceptors, and the Bedrock / Snowflake / OpenAI-compatible providers
|
|
18
19
|
|
|
19
20
|
## Observation Engine
|
|
20
21
|
|
|
@@ -86,6 +87,46 @@ This will:
|
|
|
86
87
|
- Add an initializer file for configuration
|
|
87
88
|
- Mount the engine routes in `config/routes.rb`
|
|
88
89
|
|
|
90
|
+
### Upgrading an existing install
|
|
91
|
+
|
|
92
|
+
The install generator above is for **new** installs — it creates every table, so
|
|
93
|
+
running it against an app that already has the RcrewAI tables will fail on
|
|
94
|
+
duplicates.
|
|
95
|
+
|
|
96
|
+
If you are already running rcrewai-rails, pull in only the migrations you are
|
|
97
|
+
missing using the standard Rails engine task:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
$ rails rcrew_ai_rails:install:migrations
|
|
101
|
+
$ rails db:migrate
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
(The task name comes from the engine's railtie name, `rcrew_ai_rails`.)
|
|
105
|
+
|
|
106
|
+
Rails copies only the migrations your app does not already have. Upgrading to
|
|
107
|
+
0.8.0 from 0.7.x adds one:
|
|
108
|
+
|
|
109
|
+
| Migration | Purpose |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `012_create_rcrewai_checkpoints` | `rcrewai_checkpoints`, `rcrewai_crews.checkpoint_enabled`, and `run_id`/`parent_run_id` on executions — checkpointing and resume |
|
|
112
|
+
|
|
113
|
+
Upgrading from 0.6.x adds two more:
|
|
114
|
+
|
|
115
|
+
| Migration | Purpose |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `010_create_rcrewai_spans` | `rcrewai_spans` and `rcrewai_span_events` — the observation engine's trace tree |
|
|
118
|
+
| `011_add_observation_rollups_to_rcrewai_executions` | `total_cost_usd`, `total_tokens`, `span_count`, `error_count` on executions |
|
|
119
|
+
|
|
120
|
+
All are additive: no existing column or table is changed, and nothing is
|
|
121
|
+
dropped. Existing crews, agents, tasks, and executions are unaffected, and
|
|
122
|
+
observation is enabled by default once the tables exist. To upgrade the gem
|
|
123
|
+
without turning tracing on, set `config.observation_enabled = false` in
|
|
124
|
+
`config/initializers/rcrewai.rb` before migrating.
|
|
125
|
+
|
|
126
|
+
If your schema was created by hand (the install generator did not copy a
|
|
127
|
+
migration before 0.7.0, so this is likely), review the copied migrations before
|
|
128
|
+
running `db:migrate` and delete any whose tables you already have.
|
|
129
|
+
|
|
89
130
|
### Manual Routes Setup
|
|
90
131
|
|
|
91
132
|
If you need to mount the routes manually, add this to your `config/routes.rb`:
|
|
@@ -235,10 +276,86 @@ crew.execute_sync(inputs)
|
|
|
235
276
|
CrewExecutionJob.set(wait: 5.minutes).perform_later(crew, inputs)
|
|
236
277
|
```
|
|
237
278
|
|
|
279
|
+
## rcrewai 0.8 capabilities
|
|
280
|
+
|
|
281
|
+
This engine tracks [rcrewai](https://github.com/gkosmo/rcrewAI) `~> 0.8`.
|
|
282
|
+
|
|
283
|
+
### Checkpointing and resume
|
|
284
|
+
|
|
285
|
+
A crew run can record durable per-task state, so an interrupted run resumes
|
|
286
|
+
instead of re-executing (and re-paying for) the tasks that already finished.
|
|
287
|
+
Checkpoints are written to `rcrewai_checkpoints` after each task settles.
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
# Globally, in config/initializers/rcrewai.rb
|
|
291
|
+
config.checkpoint_enabled = true
|
|
292
|
+
|
|
293
|
+
# ...or per crew
|
|
294
|
+
crew.update!(checkpoint_enabled: true)
|
|
295
|
+
|
|
296
|
+
execution = crew.executions.order(:id).last
|
|
297
|
+
execution.run_id # => the checkpointed run
|
|
298
|
+
|
|
299
|
+
# Resume it: completed tasks are replayed, the rest execute.
|
|
300
|
+
crew.resume_sync(execution) # or resume_async(execution)
|
|
301
|
+
crew.resumable_executions # executions that recorded a run id
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
A resumed run gets its own `run_id` and records the original in
|
|
305
|
+
`parent_run_id`, leaving the first run's record intact:
|
|
306
|
+
|
|
307
|
+
```ruby
|
|
308
|
+
store = RcrewAI::Rails::ActiveRecordCheckpointStore.new
|
|
309
|
+
RCrewAI::Checkpoint.lineage(store, resumed.run_id)
|
|
310
|
+
# => ["<original run id>", "<resumed run id>"]
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Any object responding to `save`/`load`/`list`/`delete` can replace the store via
|
|
314
|
+
`config.checkpoint_store`.
|
|
315
|
+
|
|
316
|
+
### LLM interceptors
|
|
317
|
+
|
|
318
|
+
Hooks that run around every LLM request the engine's agents make — useful for
|
|
319
|
+
logging, request tagging, or signing (AWS SigV4 for Bedrock, for instance).
|
|
320
|
+
Return a replacement value to modify the payload or result, or `nil` to leave it
|
|
321
|
+
untouched. A hook that raises is reported and skipped, so instrumentation can
|
|
322
|
+
never break a run.
|
|
323
|
+
|
|
324
|
+
```ruby
|
|
325
|
+
config.llm_before_request = lambda do |payload, context|
|
|
326
|
+
Rails.logger.info("[llm] -> #{context[:provider]}/#{context[:model]}")
|
|
327
|
+
payload
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
config.llm_after_response = lambda do |result, context|
|
|
331
|
+
Rails.logger.info("[llm] <- #{context[:duration_ms]}ms")
|
|
332
|
+
result
|
|
333
|
+
end
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Providers
|
|
337
|
+
|
|
338
|
+
Alongside `openai`, `anthropic`, `google`, `azure` and `ollama`, rcrewai 0.8
|
|
339
|
+
adds four, set as an agent's `llm_config` provider (or
|
|
340
|
+
`config.default_llm_provider`):
|
|
341
|
+
|
|
342
|
+
| Provider | Notes |
|
|
343
|
+
|---|---|
|
|
344
|
+
| `openai_compatible` | Any OpenAI-format endpoint (Groq, Together, Fireworks, vLLM, OpenRouter, a self-hosted gateway). Requires RCrewAI's `base_url`. |
|
|
345
|
+
| `bedrock` | AWS Bedrock via the Converse API. Requires `aws_region`. |
|
|
346
|
+
| `snowflake` | Snowflake Cortex inference. Requires `snowflake_account`. |
|
|
347
|
+
| `openai_responses` | OpenAI's Responses API. Non-streaming only. |
|
|
348
|
+
|
|
349
|
+
### Tracing accuracy
|
|
350
|
+
|
|
351
|
+
rcrewai 0.8 stamps every event with the id of its enclosing run span, and the
|
|
352
|
+
observation collector uses it to keep concurrent runs of the *same* agent apart.
|
|
353
|
+
Previously both runs shared one span stack, so one run's tool call could nest
|
|
354
|
+
under the other's iteration.
|
|
355
|
+
|
|
238
356
|
## rcrewai 0.7 capabilities
|
|
239
357
|
|
|
240
|
-
|
|
241
|
-
following capabilities are configured through columns on the persisted models and
|
|
358
|
+
These capabilities are configured through columns on the persisted models and
|
|
242
359
|
forwarded to the core objects at build time. All are **off/absent by default**, so
|
|
243
360
|
existing records are unaffected — set only what you need.
|
|
244
361
|
|
|
@@ -342,6 +459,8 @@ The gem provides these ActiveRecord models:
|
|
|
342
459
|
- `RcrewAI::Rails::KnowledgeSource` - Knowledge (RAG) sources, owned by an agent or a crew
|
|
343
460
|
- `RcrewAI::Rails::FlowState` - Persisted rcrewai Flow state (resume flows across restarts)
|
|
344
461
|
- `RcrewAI::Rails::FlowRun` - Flow-run tracking (status, inputs, result, timing)
|
|
462
|
+
- `RcrewAI::Rails::Span` / `SpanEvent` - The observation engine's trace tree
|
|
463
|
+
- `RcrewAI::Rails::Checkpoint` - Persisted crew checkpoints (resume and lineage)
|
|
345
464
|
|
|
346
465
|
## API Endpoints
|
|
347
466
|
|
|
@@ -5,7 +5,7 @@ module RcrewAI
|
|
|
5
5
|
|
|
6
6
|
retry_on StandardError, wait: :exponentially_longer, attempts: 3
|
|
7
7
|
|
|
8
|
-
def perform(crew, inputs = {}, batch_id: nil)
|
|
8
|
+
def perform(crew, inputs = {}, batch_id: nil, resume_run_id: nil)
|
|
9
9
|
execution = crew.executions.create!(
|
|
10
10
|
status: "pending",
|
|
11
11
|
inputs: inputs,
|
|
@@ -22,7 +22,16 @@ module RcrewAI
|
|
|
22
22
|
collector = collector_for(execution)
|
|
23
23
|
collector&.start_crew_span(crew_name: crew.name)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
store = checkpoint_store_for(crew)
|
|
26
|
+
result = if resume_run_id
|
|
27
|
+
execution.update!(parent_run_id: resume_run_id)
|
|
28
|
+
rcrew.resume(resume_run_id, checkpoint: store, stream: collector, inputs: inputs)
|
|
29
|
+
else
|
|
30
|
+
rcrew.execute(stream: collector, inputs: inputs, checkpoint: store)
|
|
31
|
+
end
|
|
32
|
+
# The gem assigns the run id during execute, so it can only be
|
|
33
|
+
# recorded once the run has opened.
|
|
34
|
+
execution.update!(run_id: rcrew.run_id) if rcrew.run_id
|
|
26
35
|
|
|
27
36
|
# Close agent spans as successful before finish!, which treats
|
|
28
37
|
# anything still open as an aborted run.
|
|
@@ -50,6 +59,19 @@ module RcrewAI
|
|
|
50
59
|
|
|
51
60
|
private
|
|
52
61
|
|
|
62
|
+
# The checkpoint store for this run, or nil when checkpointing is off.
|
|
63
|
+
# A crew may opt in per-record; otherwise the engine default applies.
|
|
64
|
+
def checkpoint_store_for(crew)
|
|
65
|
+
enabled = if crew.respond_to?(:checkpoint_enabled) && !crew.checkpoint_enabled.nil?
|
|
66
|
+
crew.checkpoint_enabled
|
|
67
|
+
else
|
|
68
|
+
RcrewAI::Rails.config.checkpoint_enabled
|
|
69
|
+
end
|
|
70
|
+
return nil unless enabled
|
|
71
|
+
|
|
72
|
+
RcrewAI::Rails.config.checkpoint_store || ActiveRecordCheckpointStore.new
|
|
73
|
+
end
|
|
74
|
+
|
|
53
75
|
# Translates rcrewai events into the span tree. Returns nil when
|
|
54
76
|
# observation is disabled so no sink is attached at all.
|
|
55
77
|
def collector_for(execution)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module RcrewAI
|
|
2
|
+
module Rails
|
|
3
|
+
# Persists rcrewai crew checkpoints to the DB. Implements the core
|
|
4
|
+
# checkpoint-store contract: save(id, record) / load(id) => record or nil /
|
|
5
|
+
# list => ids / delete(id). Pass an instance as +checkpoint:+ to
|
|
6
|
+
# RCrewAI::Crew#execute or #resume.
|
|
7
|
+
#
|
|
8
|
+
# Records are plain JSON-shaped hashes with string keys (the gem's own
|
|
9
|
+
# stores round-trip through JSON, and Crew reads them back with string
|
|
10
|
+
# keys), so they are stored verbatim rather than mapped onto columns.
|
|
11
|
+
# run_id/parent_run_id/crew_name are mirrored into columns for querying.
|
|
12
|
+
#
|
|
13
|
+
# Unlike the gem's FileStore, an id needs no path sanitizing here -- it is
|
|
14
|
+
# bound as a query parameter, never used to build a filesystem path.
|
|
15
|
+
class ActiveRecordCheckpointStore
|
|
16
|
+
def save(id, record)
|
|
17
|
+
checkpoint = Checkpoint.find_or_initialize_by(run_id: id.to_s)
|
|
18
|
+
checkpoint.data = record
|
|
19
|
+
checkpoint.parent_run_id = record["parent_run_id"] || record[:parent_run_id]
|
|
20
|
+
checkpoint.crew_name = record["crew"] || record[:crew]
|
|
21
|
+
checkpoint.checkpoint_updated_at = parse_time(record["updated_at"] || record[:updated_at])
|
|
22
|
+
checkpoint.save!
|
|
23
|
+
record
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def load(id)
|
|
27
|
+
Checkpoint.find_by(run_id: id.to_s)&.data
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def list
|
|
31
|
+
Checkpoint.order(:id).pluck(:run_id)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def delete(id)
|
|
35
|
+
Checkpoint.where(run_id: id.to_s).delete_all
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# The gem stamps an ISO8601 string. A malformed value must not take down
|
|
42
|
+
# the run -- the column is a convenience mirror, the authoritative copy
|
|
43
|
+
# lives in +data+.
|
|
44
|
+
def parse_time(value)
|
|
45
|
+
return nil if value.blank?
|
|
46
|
+
|
|
47
|
+
Time.zone ? Time.zone.parse(value.to_s) : Time.parse(value.to_s)
|
|
48
|
+
rescue ArgumentError, TypeError
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module RcrewAI
|
|
2
|
+
module Rails
|
|
3
|
+
# One persisted rcrewai checkpoint run record.
|
|
4
|
+
#
|
|
5
|
+
# The gem writes a plain JSON-shaped hash per run (see
|
|
6
|
+
# RCrewAI::Checkpoint.record_for); +data+ holds it verbatim so a record
|
|
7
|
+
# written by any store implementation round-trips identically. run_id and
|
|
8
|
+
# parent_run_id are also mirrored into columns so lineage can be queried in
|
|
9
|
+
# SQL rather than by deserializing every row.
|
|
10
|
+
class Checkpoint < ApplicationRecord
|
|
11
|
+
self.table_name = "rcrewai_checkpoints"
|
|
12
|
+
|
|
13
|
+
serialize :data, coder: JSON
|
|
14
|
+
|
|
15
|
+
validates :run_id, presence: true, uniqueness: true
|
|
16
|
+
|
|
17
|
+
scope :roots, -> { where(parent_run_id: nil) }
|
|
18
|
+
|
|
19
|
+
def children
|
|
20
|
+
self.class.where(parent_run_id: run_id)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The per-task entries of the stored record, keyed by task name.
|
|
24
|
+
def tasks
|
|
25
|
+
(data || {})["tasks"] || {}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def completed_task_names
|
|
29
|
+
tasks.select { |_name, entry| entry["status"] == "completed" }.keys
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def failed_task_names
|
|
33
|
+
tasks.select { |_name, entry| entry["status"] == "failed" }.keys
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -61,6 +61,22 @@ module RcrewAI
|
|
|
61
61
|
CrewExecutionJob.perform_now(self, inputs)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
# Re-runs a checkpointed execution, replaying tasks that already
|
|
65
|
+
# completed instead of paying for them again. Accepts either a run id or
|
|
66
|
+
# an Execution that recorded one.
|
|
67
|
+
def resume_async(run, inputs = {})
|
|
68
|
+
CrewExecutionJob.perform_later(self, inputs, resume_run_id: run_id_for(run))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def resume_sync(run, inputs = {})
|
|
72
|
+
CrewExecutionJob.perform_now(self, inputs, resume_run_id: run_id_for(run))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Executions of this crew that recorded a checkpoint run id, newest first.
|
|
76
|
+
def resumable_executions
|
|
77
|
+
executions.where.not(run_id: nil).order(created_at: :desc)
|
|
78
|
+
end
|
|
79
|
+
|
|
64
80
|
def execute_batch_async(inputs_list)
|
|
65
81
|
batch_id = SecureRandom.uuid
|
|
66
82
|
normalize_batch_inputs(inputs_list).each do |inputs|
|
|
@@ -97,6 +113,15 @@ module RcrewAI
|
|
|
97
113
|
|
|
98
114
|
private
|
|
99
115
|
|
|
116
|
+
# Accepts an Execution or a bare run id, so callers can pass whichever
|
|
117
|
+
# they have without unwrapping it themselves.
|
|
118
|
+
def run_id_for(run)
|
|
119
|
+
id = run.respond_to?(:run_id) ? run.run_id : run
|
|
120
|
+
raise ArgumentError, "no checkpoint run id to resume from" if id.blank?
|
|
121
|
+
|
|
122
|
+
id
|
|
123
|
+
end
|
|
124
|
+
|
|
100
125
|
# Wraps a single inputs hash into a one-element array; leaves an array of
|
|
101
126
|
# hashes as-is. Avoids Array()'s hash-destructuring (Array({a:1}) => [[:a,1]]).
|
|
102
127
|
def normalize_batch_inputs(inputs_list)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class AddOutputProcessingToRcrewAITasks < ActiveRecord::Migration[7.0]
|
|
2
2
|
def change
|
|
3
3
|
# output_file already exists on rcrewai_tasks from the original create
|
|
4
4
|
# table; only the new 0.4/0.5 output-processing options are added here.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class AddLifecycleToRcrewAICrews < ActiveRecord::Migration[7.0]
|
|
2
2
|
def change
|
|
3
3
|
add_column :rcrewai_crews, :planning, :boolean, default: false, null: false
|
|
4
4
|
add_column :rcrewai_crews, :planning_llm, :string
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class AddObservationRollupsToRcrewAIExecutions < ActiveRecord::Migration[7.0]
|
|
2
2
|
def change
|
|
3
3
|
add_column :rcrewai_executions, :total_cost_usd, :decimal, precision: 12, scale: 6
|
|
4
4
|
add_column :rcrewai_executions, :total_tokens, :integer
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class CreateRcrewAICheckpoints < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :rcrewai_checkpoints do |t|
|
|
4
|
+
t.string :run_id, null: false
|
|
5
|
+
t.string :parent_run_id
|
|
6
|
+
t.string :crew_name
|
|
7
|
+
t.text :data, null: false
|
|
8
|
+
t.datetime :checkpoint_updated_at
|
|
9
|
+
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
add_index :rcrewai_checkpoints, :run_id, unique: true
|
|
14
|
+
add_index :rcrewai_checkpoints, :parent_run_id
|
|
15
|
+
|
|
16
|
+
add_column :rcrewai_crews, :checkpoint_enabled, :boolean
|
|
17
|
+
|
|
18
|
+
add_column :rcrewai_executions, :run_id, :string
|
|
19
|
+
add_column :rcrewai_executions, :parent_run_id, :string
|
|
20
|
+
add_index :rcrewai_executions, :run_id
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class CreateRcrewAITables < ActiveRecord::Migration[7.0]
|
|
2
2
|
def change
|
|
3
3
|
create_table :rcrewai_crews do |t|
|
|
4
4
|
t.string :name, null: false
|
|
@@ -16,6 +16,7 @@ class CreateRcrewaiTables < ActiveRecord::Migration[7.0]
|
|
|
16
16
|
t.string :after_kickoff_class
|
|
17
17
|
t.string :after_kickoff_method
|
|
18
18
|
t.text :config
|
|
19
|
+
t.boolean :checkpoint_enabled
|
|
19
20
|
t.boolean :active, default: true
|
|
20
21
|
|
|
21
22
|
t.timestamps
|
|
@@ -112,6 +113,8 @@ class CreateRcrewaiTables < ActiveRecord::Migration[7.0]
|
|
|
112
113
|
t.integer :total_tokens
|
|
113
114
|
t.integer :span_count, default: 0, null: false
|
|
114
115
|
t.integer :error_count, default: 0, null: false
|
|
116
|
+
t.string :run_id
|
|
117
|
+
t.string :parent_run_id
|
|
115
118
|
|
|
116
119
|
t.timestamps
|
|
117
120
|
end
|
|
@@ -119,6 +122,7 @@ class CreateRcrewaiTables < ActiveRecord::Migration[7.0]
|
|
|
119
122
|
add_index :rcrewai_executions, :status
|
|
120
123
|
add_index :rcrewai_executions, :created_at
|
|
121
124
|
add_index :rcrewai_executions, :batch_id
|
|
125
|
+
add_index :rcrewai_executions, :run_id
|
|
122
126
|
|
|
123
127
|
create_table :rcrewai_execution_logs do |t|
|
|
124
128
|
t.references :execution, null: false, foreign_key: { to_table: :rcrewai_executions }
|
|
@@ -204,6 +208,19 @@ class CreateRcrewaiTables < ActiveRecord::Migration[7.0]
|
|
|
204
208
|
add_index :rcrewai_spans, :status
|
|
205
209
|
add_index :rcrewai_spans, %i[execution_id sequence]
|
|
206
210
|
|
|
211
|
+
create_table :rcrewai_checkpoints do |t|
|
|
212
|
+
t.string :run_id, null: false
|
|
213
|
+
t.string :parent_run_id
|
|
214
|
+
t.string :crew_name
|
|
215
|
+
t.text :data, null: false
|
|
216
|
+
t.datetime :checkpoint_updated_at
|
|
217
|
+
|
|
218
|
+
t.timestamps
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
add_index :rcrewai_checkpoints, :run_id, unique: true
|
|
222
|
+
add_index :rcrewai_checkpoints, :parent_run_id
|
|
223
|
+
|
|
207
224
|
create_table :rcrewai_span_events do |t|
|
|
208
225
|
t.references :span, null: false, foreign_key: { to_table: :rcrewai_spans }
|
|
209
226
|
t.string :level, null: false, default: "info"
|
|
@@ -16,7 +16,13 @@ RcrewAI::Rails.configure do |config|
|
|
|
16
16
|
config.persistence_backend = :active_record
|
|
17
17
|
|
|
18
18
|
# Default LLM provider
|
|
19
|
-
# Options: "openai", "anthropic", "
|
|
19
|
+
# Options: "openai", "anthropic", "google", "azure", "ollama", and (rcrewai
|
|
20
|
+
# 0.8+) "openai_compatible", "bedrock", "snowflake", "openai_responses".
|
|
21
|
+
# openai_compatible - any OpenAI-format endpoint (Groq, Together,
|
|
22
|
+
# Fireworks, vLLM, OpenRouter); needs RCrewAI's
|
|
23
|
+
# base_url set.
|
|
24
|
+
# bedrock - AWS Bedrock Converse API; needs aws_region.
|
|
25
|
+
# snowflake - Snowflake Cortex; needs snowflake_account.
|
|
20
26
|
config.default_llm_provider = ENV.fetch("RCREWAI_LLM_PROVIDER", "openai")
|
|
21
27
|
|
|
22
28
|
# Default LLM model
|
|
@@ -67,4 +73,32 @@ RcrewAI.configure do |config|
|
|
|
67
73
|
# Configure other RcrewAI settings
|
|
68
74
|
# config.default_model = "gpt-4"
|
|
69
75
|
# config.temperature = 0.7
|
|
70
|
-
|
|
76
|
+
|
|
77
|
+
# --- Checkpointing (rcrewai 0.8+) -----------------------------------------
|
|
78
|
+
# Records durable per-task state during a crew run so an interrupted run can
|
|
79
|
+
# be resumed instead of re-executing (and re-paying for) completed tasks.
|
|
80
|
+
# Off by default; a crew record can also opt in individually via its
|
|
81
|
+
# checkpoint_enabled column.
|
|
82
|
+
# config.checkpoint_enabled = true
|
|
83
|
+
|
|
84
|
+
# Where checkpoints are stored. Defaults to the bundled ActiveRecord store
|
|
85
|
+
# (the rcrewai_checkpoints table). Any object responding to
|
|
86
|
+
# save/load/list/delete works.
|
|
87
|
+
# config.checkpoint_store = RcrewAI::Rails::ActiveRecordCheckpointStore.new
|
|
88
|
+
|
|
89
|
+
# --- LLM interceptors (rcrewai 0.8+) --------------------------------------
|
|
90
|
+
# Hooks run around every LLM request the engine's agents make. Each receives
|
|
91
|
+
# (payload, context) / (result, context); return a replacement to modify it,
|
|
92
|
+
# or nil to leave it untouched. A hook that raises is reported and skipped,
|
|
93
|
+
# so instrumentation can never break a run.
|
|
94
|
+
#
|
|
95
|
+
# config.llm_before_request = lambda do |payload, context|
|
|
96
|
+
# Rails.logger.info("[llm] -> #{context[:provider]}/#{context[:model]}")
|
|
97
|
+
# payload
|
|
98
|
+
# end
|
|
99
|
+
#
|
|
100
|
+
# config.llm_after_response = lambda do |result, context|
|
|
101
|
+
# Rails.logger.info("[llm] <- #{context[:duration_ms]}ms")
|
|
102
|
+
# result
|
|
103
|
+
# end
|
|
104
|
+
end
|
|
@@ -76,6 +76,10 @@ module RcrewAI
|
|
|
76
76
|
protected
|
|
77
77
|
|
|
78
78
|
def build_agent
|
|
79
|
+
RcrewAI::Rails::Interceptors.apply_to_agent(new_rcrew_agent)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def new_rcrew_agent
|
|
79
83
|
RCrewAI::Agent.new(
|
|
80
84
|
name: @attributes[:name] || default_agent_name,
|
|
81
85
|
role: @attributes[:role] || self.class.agent_role,
|
|
@@ -7,7 +7,9 @@ module RcrewAI
|
|
|
7
7
|
:default_memory_embedder, :default_memory_store,
|
|
8
8
|
:observation_enabled, :observation_capture_prompts,
|
|
9
9
|
:observation_prompt_max_bytes, :observation_flush_mode,
|
|
10
|
-
:observation_flush_every, :observation_retention_days
|
|
10
|
+
:observation_flush_every, :observation_retention_days,
|
|
11
|
+
:checkpoint_enabled, :checkpoint_store,
|
|
12
|
+
:llm_before_request, :llm_after_response
|
|
11
13
|
|
|
12
14
|
def initialize
|
|
13
15
|
@job_queue_name = "default"
|
|
@@ -28,6 +30,16 @@ module RcrewAI
|
|
|
28
30
|
@observation_flush_mode = :batched # :batched | :immediate
|
|
29
31
|
@observation_flush_every = 25
|
|
30
32
|
@observation_retention_days = 30
|
|
33
|
+
# Checkpointing (rcrewai 0.8+). Off by default: it writes a row per
|
|
34
|
+
# task settlement, which an app should opt into rather than inherit.
|
|
35
|
+
@checkpoint_enabled = false
|
|
36
|
+
# Defaults to ActiveRecordCheckpointStore when checkpointing is on.
|
|
37
|
+
# Set to any object responding to save/load/list/delete to override.
|
|
38
|
+
@checkpoint_store = nil
|
|
39
|
+
# LLM interceptor hooks (rcrewai 0.8+). Each is a callable, or an
|
|
40
|
+
# array of callables, applied to every client the engine builds.
|
|
41
|
+
@llm_before_request = nil
|
|
42
|
+
@llm_after_response = nil
|
|
31
43
|
end
|
|
32
44
|
end
|
|
33
45
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RcrewAI
|
|
4
|
+
module Rails
|
|
5
|
+
# Applies the configured LLM interceptor hooks (rcrewai 0.8+) to a client.
|
|
6
|
+
#
|
|
7
|
+
# The gem forwards +before_request:+/+after_response:+ only through
|
|
8
|
+
# LLMClient.for_provider. Agents build their client via LLMClient.resolve,
|
|
9
|
+
# which does not, so hooks configured on the engine would never reach a
|
|
10
|
+
# per-agent client. Registering them on the built client instead covers
|
|
11
|
+
# every path uniformly.
|
|
12
|
+
module Interceptors
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Registers the configured hooks on +client+ and returns it.
|
|
16
|
+
#
|
|
17
|
+
# A client the host supplied ready-made (anything not speaking the hook
|
|
18
|
+
# API, e.g. a bare double or a custom object responding only to #chat)
|
|
19
|
+
# is returned untouched rather than raising -- the gem accepts any
|
|
20
|
+
# object responding to #chat as an llm, and instrumentation must never
|
|
21
|
+
# be the reason a run fails.
|
|
22
|
+
def apply(client, config = RcrewAI::Rails.config)
|
|
23
|
+
return client unless client
|
|
24
|
+
|
|
25
|
+
Array(config.llm_before_request).each do |hook|
|
|
26
|
+
client.before_request(hook) if client.respond_to?(:before_request)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Array(config.llm_after_response).each do |hook|
|
|
30
|
+
client.after_response(hook) if client.respond_to?(:after_response)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
client
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# True when any interceptor is configured, so callers can skip the work
|
|
37
|
+
# entirely in the common case where none are.
|
|
38
|
+
def configured?(config = RcrewAI::Rails.config)
|
|
39
|
+
Array(config.llm_before_request).any? || Array(config.llm_after_response).any?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Registers the hooks on an already-built agent's client.
|
|
43
|
+
def apply_to_agent(agent, config = RcrewAI::Rails.config)
|
|
44
|
+
return agent unless configured?(config)
|
|
45
|
+
return agent unless agent.respond_to?(:llm_client)
|
|
46
|
+
|
|
47
|
+
apply(agent.llm_client, config)
|
|
48
|
+
agent
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -106,11 +106,12 @@ module RcrewAI
|
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
def on_iteration_start(event)
|
|
109
|
+
run = SpanStack.run_id_for(event)
|
|
109
110
|
id = open_span(
|
|
110
111
|
kind: "llm_call", name: "iteration #{event.iteration_index}",
|
|
111
112
|
parent_span_id: agent_span_for(event.agent), agent: event.agent
|
|
112
113
|
)
|
|
113
|
-
@stack.push(agent: event.agent, key: :iteration, id: id)
|
|
114
|
+
@stack.push(agent: event.agent, key: :iteration, id: id, run_id: run)
|
|
114
115
|
end
|
|
115
116
|
|
|
116
117
|
# Returns the agent span that events from +agent+ belong under,
|
|
@@ -126,7 +127,7 @@ module RcrewAI
|
|
|
126
127
|
end
|
|
127
128
|
|
|
128
129
|
def on_iteration_end(event)
|
|
129
|
-
id = @stack.pop(agent: event.agent, key: :iteration)
|
|
130
|
+
id = @stack.pop(agent: event.agent, key: :iteration, run_id: SpanStack.run_id_for(event))
|
|
130
131
|
return unless id
|
|
131
132
|
|
|
132
133
|
merge_attributes(id, "finish_reason" => event.finish_reason.to_s)
|
|
@@ -136,7 +137,8 @@ module RcrewAI
|
|
|
136
137
|
def on_tool_start(event)
|
|
137
138
|
id = open_span(
|
|
138
139
|
kind: "tool_call", name: event.tool.to_s,
|
|
139
|
-
parent_span_id: @stack.current(agent: event.agent
|
|
140
|
+
parent_span_id: @stack.current(agent: event.agent, run_id: SpanStack.run_id_for(event)),
|
|
141
|
+
agent: event.agent,
|
|
140
142
|
attributes: { "args" => event.args }
|
|
141
143
|
)
|
|
142
144
|
@stack.register_call(call_id: event.call_id, span_id: id)
|
|
@@ -160,7 +162,7 @@ module RcrewAI
|
|
|
160
162
|
end
|
|
161
163
|
|
|
162
164
|
def on_usage(event)
|
|
163
|
-
id = @stack.current(agent: event.agent)
|
|
165
|
+
id = @stack.current(agent: event.agent, run_id: SpanStack.run_id_for(event))
|
|
164
166
|
return unless id
|
|
165
167
|
|
|
166
168
|
@writer.update_span(id,
|
|
@@ -176,16 +178,16 @@ module RcrewAI
|
|
|
176
178
|
def on_text_delta(event)
|
|
177
179
|
return if config.observation_capture_prompts == :none
|
|
178
180
|
|
|
179
|
-
@text_buffers[SpanStack.key_for(event.agent)] << event.text.to_s
|
|
181
|
+
@text_buffers[SpanStack.key_for(event.agent, SpanStack.run_id_for(event))] << event.text.to_s
|
|
180
182
|
end
|
|
181
183
|
|
|
182
184
|
# Prefers the event's own text, falling back to the accumulated
|
|
183
185
|
# deltas when the provider sends TextDone without a payload.
|
|
184
186
|
def on_text_done(event)
|
|
185
|
-
buffered = @text_buffers.delete(SpanStack.key_for(event.agent))
|
|
187
|
+
buffered = @text_buffers.delete(SpanStack.key_for(event.agent, SpanStack.run_id_for(event)))
|
|
186
188
|
return if config.observation_capture_prompts == :none
|
|
187
189
|
|
|
188
|
-
id = @stack.current(agent: event.agent)
|
|
190
|
+
id = @stack.current(agent: event.agent, run_id: SpanStack.run_id_for(event))
|
|
189
191
|
return unless id
|
|
190
192
|
|
|
191
193
|
text = event.text.to_s
|
|
@@ -196,14 +198,14 @@ module RcrewAI
|
|
|
196
198
|
def on_thinking(event)
|
|
197
199
|
return if config.observation_capture_prompts == :none
|
|
198
200
|
|
|
199
|
-
id = @stack.current(agent: event.agent)
|
|
201
|
+
id = @stack.current(agent: event.agent, run_id: SpanStack.run_id_for(event))
|
|
200
202
|
return unless id
|
|
201
203
|
|
|
202
204
|
merge_attributes(id, "thinking" => truncate(event.text.to_s))
|
|
203
205
|
end
|
|
204
206
|
|
|
205
207
|
def on_error(event)
|
|
206
|
-
id = @stack.current(agent: event.agent)
|
|
208
|
+
id = @stack.current(agent: event.agent, run_id: SpanStack.run_id_for(event))
|
|
207
209
|
return unless id
|
|
208
210
|
|
|
209
211
|
merge_attributes(id, "error" => event.error.to_s)
|
|
@@ -23,17 +23,34 @@ module RcrewAI
|
|
|
23
23
|
|
|
24
24
|
# Normalizes an agent key. Blank/nil agents share a single reserved
|
|
25
25
|
# bucket that cannot collide with a real agent name.
|
|
26
|
-
def self.key_for(agent)
|
|
26
|
+
def self.key_for(agent, run_id = nil)
|
|
27
27
|
key = agent.to_s
|
|
28
|
-
key
|
|
28
|
+
key = UNTAGGED if key.empty?
|
|
29
|
+
# rcrewai 0.8+ stamps every event with the id of the enclosing run
|
|
30
|
+
# span. Scoping the stack by it keeps two concurrent runs of the
|
|
31
|
+
# *same* agent apart -- keyed on name alone they share one stack, so
|
|
32
|
+
# one run's tool call nests under the other's iteration. Events from
|
|
33
|
+
# older streams (and the engine's own crew/agent spans) carry no run
|
|
34
|
+
# id and keep the previous name-only key.
|
|
35
|
+
run = run_id.to_s
|
|
36
|
+
run.empty? ? key : "#{key}\u0000#{run}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The run-span id an event belongs to, or nil for events that predate
|
|
40
|
+
# the 0.8 event hierarchy or are emitted outside a runner span.
|
|
41
|
+
def self.run_id_for(event)
|
|
42
|
+
return nil unless event.respond_to?(:parent_id)
|
|
43
|
+
|
|
44
|
+
id = event.parent_id
|
|
45
|
+
id.nil? || id.to_s.empty? ? nil : id
|
|
29
46
|
end
|
|
30
47
|
|
|
31
48
|
def next_sequence
|
|
32
49
|
@mutex.synchronize { @sequence += 1 }
|
|
33
50
|
end
|
|
34
51
|
|
|
35
|
-
def push(agent:, key:, id:)
|
|
36
|
-
@mutex.synchronize { @stacks[self.class.key_for(agent)] << { key: key, id: id } }
|
|
52
|
+
def push(agent:, key:, id:, run_id: nil)
|
|
53
|
+
@mutex.synchronize { @stacks[self.class.key_for(agent, run_id)] << { key: key, id: id } }
|
|
37
54
|
id
|
|
38
55
|
end
|
|
39
56
|
|
|
@@ -41,9 +58,9 @@ module RcrewAI
|
|
|
41
58
|
#
|
|
42
59
|
# Reads use +fetch+ rather than +[]+: the default block auto-vivifies
|
|
43
60
|
# on read, so querying unknown agents would retain a key forever.
|
|
44
|
-
def pop(agent:, key:)
|
|
61
|
+
def pop(agent:, key:, run_id: nil)
|
|
45
62
|
@mutex.synchronize do
|
|
46
|
-
stack = @stacks.fetch(self.class.key_for(agent), nil)
|
|
63
|
+
stack = @stacks.fetch(self.class.key_for(agent, run_id), nil)
|
|
47
64
|
next nil if stack.nil?
|
|
48
65
|
|
|
49
66
|
index = stack.rindex { |frame| frame[:key] == key }
|
|
@@ -53,8 +70,8 @@ module RcrewAI
|
|
|
53
70
|
end
|
|
54
71
|
end
|
|
55
72
|
|
|
56
|
-
def current(agent:)
|
|
57
|
-
@mutex.synchronize { @stacks.fetch(self.class.key_for(agent), nil)&.last&.fetch(:id) }
|
|
73
|
+
def current(agent:, run_id: nil)
|
|
74
|
+
@mutex.synchronize { @stacks.fetch(self.class.key_for(agent, run_id), nil)&.last&.fetch(:id) }
|
|
58
75
|
end
|
|
59
76
|
|
|
60
77
|
def register_call(call_id:, span_id:)
|
data/lib/rcrewai/rails.rb
CHANGED
|
@@ -8,6 +8,7 @@ require "stimulus-rails"
|
|
|
8
8
|
require_relative "rails/version"
|
|
9
9
|
require_relative "rails/engine"
|
|
10
10
|
require_relative "rails/configuration"
|
|
11
|
+
require_relative "rails/interceptors"
|
|
11
12
|
require_relative "rails/crew_builder"
|
|
12
13
|
require_relative "rails/agent_builder"
|
|
13
14
|
require_relative "rails/observation/span_stack"
|
data/rcrewai-rails.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rcrewai-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- gkosmo
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.8.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.8.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: rails
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -210,9 +210,11 @@ files:
|
|
|
210
210
|
- app/controllers/rcrewai/rails/tools_controller.rb
|
|
211
211
|
- app/jobs/rcrewai/rails/crew_execution_job.rb
|
|
212
212
|
- app/jobs/rcrewai/rails/task_execution_job.rb
|
|
213
|
+
- app/models/rcrewai/rails/active_record_checkpoint_store.rb
|
|
213
214
|
- app/models/rcrewai/rails/active_record_state_store.rb
|
|
214
215
|
- app/models/rcrewai/rails/agent.rb
|
|
215
216
|
- app/models/rcrewai/rails/application_record.rb
|
|
217
|
+
- app/models/rcrewai/rails/checkpoint.rb
|
|
216
218
|
- app/models/rcrewai/rails/crew.rb
|
|
217
219
|
- app/models/rcrewai/rails/execution.rb
|
|
218
220
|
- app/models/rcrewai/rails/execution_log.rb
|
|
@@ -259,6 +261,7 @@ files:
|
|
|
259
261
|
- db/migrate/009_add_memory_config_to_agents_and_clean_crews.rb
|
|
260
262
|
- db/migrate/010_create_rcrewai_spans.rb
|
|
261
263
|
- db/migrate/011_add_observation_rollups_to_rcrewai_executions.rb
|
|
264
|
+
- db/migrate/012_create_rcrewai_checkpoints.rb
|
|
262
265
|
- docs/api.html
|
|
263
266
|
- docs/capabilities.html
|
|
264
267
|
- docs/examples.html
|
|
@@ -275,6 +278,7 @@ files:
|
|
|
275
278
|
- lib/rcrewai/rails/configuration.rb
|
|
276
279
|
- lib/rcrewai/rails/crew_builder.rb
|
|
277
280
|
- lib/rcrewai/rails/engine.rb
|
|
281
|
+
- lib/rcrewai/rails/interceptors.rb
|
|
278
282
|
- lib/rcrewai/rails/observation/collector.rb
|
|
279
283
|
- lib/rcrewai/rails/observation/pruner.rb
|
|
280
284
|
- lib/rcrewai/rails/observation/rollup.rb
|