rcrewai-rails 0.6.1 → 0.7.1
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 +58 -1
- data/README.md +78 -0
- data/app/controllers/rcrewai/rails/observations_controller.rb +27 -0
- data/app/jobs/rcrewai/rails/crew_execution_job.rb +18 -29
- data/app/models/rcrewai/rails/execution.rb +6 -0
- data/app/models/rcrewai/rails/span.rb +57 -0
- data/app/models/rcrewai/rails/span_event.rb +19 -0
- data/app/views/rcrewai/rails/observations/_span.html.erb +51 -0
- data/app/views/rcrewai/rails/observations/costs.html.erb +52 -0
- data/app/views/rcrewai/rails/observations/show.html.erb +47 -0
- data/config/routes.rb +6 -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 +42 -0
- data/db/migrate/011_add_observation_rollups_to_rcrewai_executions.rb +8 -0
- data/docs/api.html +223 -0
- data/docs/capabilities.html +268 -0
- data/docs/examples.html +257 -0
- data/docs/getting-started.html +275 -0
- data/docs/index.html +57 -207
- data/lib/generators/rcrewai/rails/install/install_generator.rb +28 -16
- data/lib/generators/rcrewai/rails/install/templates/create_rcrewai_tables.rb +61 -1
- data/lib/generators/rcrewai/rails/install/templates/rcrewai.rb +17 -0
- data/lib/rcrewai/rails/configuration.rb +10 -1
- data/lib/rcrewai/rails/observation/collector.rb +301 -0
- data/lib/rcrewai/rails/observation/pruner.rb +48 -0
- data/lib/rcrewai/rails/observation/rollup.rb +60 -0
- data/lib/rcrewai/rails/observation/span_stack.rb +76 -0
- data/lib/rcrewai/rails/observation/writer.rb +89 -0
- data/lib/rcrewai/rails/version.rb +1 -1
- data/lib/rcrewai/rails.rb +13 -0
- data/lib/tasks/rcrewai_observation.rake +10 -0
- data/rcrewai-rails.gemspec +1 -1
- metadata +21 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ec3c830730933e9510d7749b89decd73af35f5b69e5f6f441a21d0cca9290b61
|
|
4
|
+
data.tar.gz: 698877cbbaf6cd49924144a585b5d32a4817ddd1e51307d34cd488f51ba5d0a7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 94f1e5d38e34c5a8668a4c1be005d3fe4cf6e6e29353ea580de7581485262948af2824f166feb58e4903e59e3aef8dfd3eb1c3ecf8642da021b1f15e3025e89d
|
|
7
|
+
data.tar.gz: 71e6c33d7e34a248ce650556a1cb5bc900d20f282f02fccb82fe2fadd71744ea45fddb618962da777488213084f0048057817e6572f46f27320d40d685b6e14f
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.1] - 2026-08-13
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **`rails db:migrate` raised `NameError` on 0.7.0.** The engine registers an
|
|
14
|
+
`RcrewAI` acronym inflection (Zeitwerk needs it to resolve
|
|
15
|
+
`rcrewai/rails/span.rb` to `RcrewAI::Rails::Span`), so Rails camelizes
|
|
16
|
+
`create_rcrewai_tables` to `CreateRcrewAITables` — but the shipped migrations
|
|
17
|
+
declared `CreateRcrewaiTables`. Rails resolves a migration's class from its
|
|
18
|
+
filename, so following the documented install verbatim failed. Nine migrations
|
|
19
|
+
in `db/migrate` and the install template were affected; all now declare the
|
|
20
|
+
name Rails derives. Thanks to the user who reported this from a real install.
|
|
21
|
+
|
|
22
|
+
**If you already installed 0.7.0** and renamed the class by hand, no action is
|
|
23
|
+
needed — your migration works. Otherwise, delete the copied migration, upgrade,
|
|
24
|
+
and re-run the install (or `rcrew_ai_rails:install:migrations`).
|
|
25
|
+
|
|
26
|
+
## [0.7.0] - 2026-08-13
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- Observation engine: span-tree tracing of every crew execution with per-agent,
|
|
30
|
+
per-LLM-call and per-tool-call detail (timings, token counts, cost). Includes a
|
|
31
|
+
trace waterfall at `/rcrewai/executions/:id/observation`, a cost/performance
|
|
32
|
+
dashboard at `/rcrewai/observations/costs`, live monitoring over Turbo Streams,
|
|
33
|
+
and a `rcrewai:observation:prune` rake task with retention configuration
|
|
34
|
+
(`observation_retention_days`).
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- The install generator only wrote an initializer: it never copied a migration
|
|
38
|
+
and never mounted the engine, so following the documented install produced no
|
|
39
|
+
database tables and no reachable UI. It now creates a timestamped
|
|
40
|
+
`create_rcrewai_tables` migration, mounts the engine at `/rcrewai`, and
|
|
41
|
+
installs the fully documented initializer.
|
|
42
|
+
- The install migration template was missing the `rcrewai_tools` table and the
|
|
43
|
+
tasks table's `agent` reference, so a fresh install shipped a `Tool` model with
|
|
44
|
+
no backing table. The template now matches the schema the test suite validates.
|
|
45
|
+
|
|
46
|
+
### Deprecated
|
|
47
|
+
- `Execution#log` and `ExecutionLog`, superseded by the observation engine. Both
|
|
48
|
+
still work and now emit a deprecation warning; scheduled for removal one minor
|
|
49
|
+
version after this release.
|
|
50
|
+
|
|
51
|
+
### Requires
|
|
52
|
+
- `rcrewai >= 0.7.1` for agent-level tracing. On earlier 0.7.x versions traces
|
|
53
|
+
contain only crew-level spans.
|
|
54
|
+
|
|
55
|
+
### Upgrading
|
|
56
|
+
- **Existing installs need two new migrations.** Do not run the install
|
|
57
|
+
generator (it creates every table and will fail on duplicates). Instead run
|
|
58
|
+
`rails rcrew_ai_rails:install:migrations && rails db:migrate` to copy only
|
|
59
|
+
what is missing: `010_create_rcrewai_spans` (the trace tree) and
|
|
60
|
+
`011_add_observation_rollups_to_rcrewai_executions` (cost/token totals). Both
|
|
61
|
+
are additive — nothing existing is altered or dropped. Observation is enabled
|
|
62
|
+
by default once the tables exist; set `config.observation_enabled = false` to
|
|
63
|
+
upgrade without turning tracing on.
|
|
64
|
+
|
|
10
65
|
## [0.6.1] - 2026-07-07
|
|
11
66
|
|
|
12
67
|
### Fixed
|
|
@@ -123,7 +178,9 @@ existing agents, tasks, and crews build unchanged.
|
|
|
123
178
|
### Changed
|
|
124
179
|
- Rename generators from `rcrew_a_i` to `rcrewai` namespacing.
|
|
125
180
|
|
|
126
|
-
[Unreleased]: https://github.com/gkosmo/rcrewai-rails/compare/v0.
|
|
181
|
+
[Unreleased]: https://github.com/gkosmo/rcrewai-rails/compare/v0.7.1...HEAD
|
|
182
|
+
[0.7.1]: https://github.com/gkosmo/rcrewai-rails/compare/v0.7.0...v0.7.1
|
|
183
|
+
[0.7.0]: https://github.com/gkosmo/rcrewai-rails/compare/v0.6.1...v0.7.0
|
|
127
184
|
[0.6.1]: https://github.com/gkosmo/rcrewai-rails/compare/v0.6.0...v0.6.1
|
|
128
185
|
[0.6.0]: https://github.com/gkosmo/rcrewai-rails/compare/v0.5.1...v0.6.0
|
|
129
186
|
[0.5.1]: https://github.com/gkosmo/rcrewai-rails/compare/v0.5.0...v0.5.1
|
data/README.md
CHANGED
|
@@ -16,6 +16,50 @@ Rails engine for integrating [RcrewAI](https://github.com/gkosmo/rcrewai-rails)
|
|
|
16
16
|
- Crew: `before_kickoff`/`after_kickoff` hooks, planning, the `consensual` process, batch execution
|
|
17
17
|
- Knowledge (RAG) sources and Flow persistence
|
|
18
18
|
|
|
19
|
+
## Observation Engine
|
|
20
|
+
|
|
21
|
+
Every crew execution is traced as a tree of spans — crew, agent, task, LLM call, and
|
|
22
|
+
tool call — carrying timings, token counts, and cost.
|
|
23
|
+
|
|
24
|
+
- **Trace view** at `/rcrewai/executions/:id/observation`: a waterfall of the run, with
|
|
25
|
+
prompts, tool arguments, and errors on each span.
|
|
26
|
+
- **Cost and performance** at `/rcrewai/observations/costs`: spend and token totals
|
|
27
|
+
across recent executions.
|
|
28
|
+
- **Live monitoring**: the trace view updates over Turbo Streams while a run is in progress.
|
|
29
|
+
|
|
30
|
+
Configure it in `config/initializers/rcrewai.rb`:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
config.observation_enabled = true
|
|
34
|
+
config.observation_capture_prompts = :truncated # :none | :truncated | :full
|
|
35
|
+
config.observation_prompt_max_bytes = 4_096
|
|
36
|
+
config.observation_flush_mode = :batched # :batched | :immediate
|
|
37
|
+
config.observation_retention_days = 30
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Prompt text is truncated by default: full prompts can be large and may contain personal
|
|
41
|
+
data. Set `:full` only when you need lossless replay.
|
|
42
|
+
|
|
43
|
+
Prune old spans with the bundled rake task:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
rake rcrewai:observation:prune # uses observation_retention_days
|
|
47
|
+
rake rcrewai:observation:prune DAYS=7
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Limitations
|
|
51
|
+
|
|
52
|
+
Token and cost data depend on rcrewai's **streaming** execution path. `Usage` events are
|
|
53
|
+
only emitted when an agent runs via `ToolRunner`, which passes a `stream:` to the LLM
|
|
54
|
+
client — not via `LegacyReactRunner`, which does not. `ToolRunner` is selected when the
|
|
55
|
+
tools have JSON schemas **and** the LLM client reports `supports_native_tools?`. OpenAI,
|
|
56
|
+
Anthropic, and Google all report true, so cost capture works normally with them. With a
|
|
57
|
+
provider or configuration that falls back to `LegacyReactRunner` (for example Ollama
|
|
58
|
+
without native tools), **the cost dashboard will be empty rather than showing an error**.
|
|
59
|
+
|
|
60
|
+
Agent-level tracing requires **rcrewai >= 0.7.1**, the version that threads the event
|
|
61
|
+
stream down to agent execution. On earlier versions traces contain only crew-level spans.
|
|
62
|
+
|
|
19
63
|
## Installation
|
|
20
64
|
|
|
21
65
|
Add this line to your application's Gemfile:
|
|
@@ -42,6 +86,40 @@ This will:
|
|
|
42
86
|
- Add an initializer file for configuration
|
|
43
87
|
- Mount the engine routes in `config/routes.rb`
|
|
44
88
|
|
|
89
|
+
### Upgrading an existing install
|
|
90
|
+
|
|
91
|
+
The install generator above is for **new** installs — it creates every table, so
|
|
92
|
+
running it against an app that already has the RcrewAI tables will fail on
|
|
93
|
+
duplicates.
|
|
94
|
+
|
|
95
|
+
If you are already running rcrewai-rails, pull in only the migrations you are
|
|
96
|
+
missing using the standard Rails engine task:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
$ rails rcrew_ai_rails:install:migrations
|
|
100
|
+
$ rails db:migrate
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
(The task name comes from the engine's railtie name, `rcrew_ai_rails`.)
|
|
104
|
+
|
|
105
|
+
Rails copies only the migrations your app does not already have. Upgrading to
|
|
106
|
+
0.7.0 from 0.6.x adds two:
|
|
107
|
+
|
|
108
|
+
| Migration | Purpose |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `010_create_rcrewai_spans` | `rcrewai_spans` and `rcrewai_span_events` — the observation engine's trace tree |
|
|
111
|
+
| `011_add_observation_rollups_to_rcrewai_executions` | `total_cost_usd`, `total_tokens`, `span_count`, `error_count` on executions |
|
|
112
|
+
|
|
113
|
+
Both are additive: no existing column or table is changed, and nothing is
|
|
114
|
+
dropped. Existing crews, agents, tasks, and executions are unaffected, and
|
|
115
|
+
observation is enabled by default once the tables exist. To upgrade the gem
|
|
116
|
+
without turning tracing on, set `config.observation_enabled = false` in
|
|
117
|
+
`config/initializers/rcrewai.rb` before migrating.
|
|
118
|
+
|
|
119
|
+
If your schema was created by hand (the install generator did not copy a
|
|
120
|
+
migration before 0.7.0, so this is likely), review the copied migrations before
|
|
121
|
+
running `db:migrate` and delete any whose tables you already have.
|
|
122
|
+
|
|
45
123
|
### Manual Routes Setup
|
|
46
124
|
|
|
47
125
|
If you need to mount the routes manually, add this to your `config/routes.rb`:
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module RcrewAI
|
|
2
|
+
module Rails
|
|
3
|
+
class ObservationsController < ApplicationController
|
|
4
|
+
# Hard ceiling on how deep the span partial will recurse. Real traces
|
|
5
|
+
# are a handful of levels deep; this only exists so a malformed tree
|
|
6
|
+
# cannot recurse until the request dies.
|
|
7
|
+
MAX_SPAN_DEPTH = 50
|
|
8
|
+
|
|
9
|
+
def show
|
|
10
|
+
@execution = Execution.find(params[:execution_id])
|
|
11
|
+
# Load the whole tree in one query and nest in memory — a recursive
|
|
12
|
+
# per-node query would be N+1 on deep traces.
|
|
13
|
+
@spans = @execution.spans.ordered.to_a
|
|
14
|
+
@children = @spans.group_by(&:parent_span_id)
|
|
15
|
+
@roots = @children[nil] || []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def costs
|
|
19
|
+
@executions = Execution.where.not(total_cost_usd: nil)
|
|
20
|
+
.order(created_at: :desc)
|
|
21
|
+
.limit(100)
|
|
22
|
+
@total_cost = @executions.sum(&:total_cost_usd)
|
|
23
|
+
@total_tokens = @executions.sum { |e| e.total_tokens.to_i }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -11,6 +11,7 @@ module RcrewAI
|
|
|
11
11
|
inputs: inputs,
|
|
12
12
|
batch_id: batch_id
|
|
13
13
|
)
|
|
14
|
+
collector = nil
|
|
14
15
|
|
|
15
16
|
begin
|
|
16
17
|
execution.start!
|
|
@@ -18,7 +19,16 @@ module RcrewAI
|
|
|
18
19
|
|
|
19
20
|
rcrew = crew.to_rcrew
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
collector = collector_for(execution)
|
|
23
|
+
collector&.start_crew_span(crew_name: crew.name)
|
|
24
|
+
|
|
25
|
+
result = rcrew.execute(stream: collector)
|
|
26
|
+
|
|
27
|
+
# Close agent spans as successful before finish!, which treats
|
|
28
|
+
# anything still open as an aborted run.
|
|
29
|
+
collector&.finish_open_agent_spans(status: "ok")
|
|
30
|
+
collector&.finish_crew_span(status: "ok")
|
|
31
|
+
collector&.finish!
|
|
22
32
|
|
|
23
33
|
execution.complete!(result)
|
|
24
34
|
execution.log("info", "Crew execution completed", { result: result })
|
|
@@ -27,6 +37,7 @@ module RcrewAI
|
|
|
27
37
|
|
|
28
38
|
result
|
|
29
39
|
rescue => e
|
|
40
|
+
collector&.finish!
|
|
30
41
|
execution.fail!(e)
|
|
31
42
|
execution.log("error", "Crew execution failed", {
|
|
32
43
|
error: e.message,
|
|
@@ -39,34 +50,12 @@ module RcrewAI
|
|
|
39
50
|
|
|
40
51
|
private
|
|
41
52
|
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
when RCrewAI::Events::IterationStart
|
|
49
|
-
execution.log("debug", "Iteration #{event.iteration_index} start", { agent: event.agent })
|
|
50
|
-
when RCrewAI::Events::IterationEnd
|
|
51
|
-
execution.log("debug", "Iteration end", { agent: event.agent, finish_reason: event.finish_reason })
|
|
52
|
-
when RCrewAI::Events::ToolCallStart
|
|
53
|
-
execution.log("info", "Tool call: #{event.tool}", { args: event.args, agent: event.agent })
|
|
54
|
-
when RCrewAI::Events::ToolCallResult
|
|
55
|
-
execution.log("info", "Tool result: #{event.tool}", { duration_ms: event.duration_ms, agent: event.agent })
|
|
56
|
-
when RCrewAI::Events::ToolCallError
|
|
57
|
-
execution.log("error", "Tool error: #{event.tool}", { error: event.error, agent: event.agent })
|
|
58
|
-
when RCrewAI::Events::Usage
|
|
59
|
-
execution.log("debug", "Usage", {
|
|
60
|
-
prompt_tokens: event.prompt_tokens,
|
|
61
|
-
completion_tokens: event.completion_tokens,
|
|
62
|
-
total_tokens: event.total_tokens,
|
|
63
|
-
cost_usd: event.cost_usd,
|
|
64
|
-
agent: event.agent
|
|
65
|
-
})
|
|
66
|
-
when RCrewAI::Events::Error
|
|
67
|
-
execution.log("error", "Crew error", { error: event.error, agent: event.agent })
|
|
68
|
-
end
|
|
69
|
-
end
|
|
53
|
+
# Translates rcrewai events into the span tree. Returns nil when
|
|
54
|
+
# observation is disabled so no sink is attached at all.
|
|
55
|
+
def collector_for(execution)
|
|
56
|
+
return nil unless RcrewAI::Rails.config.observation_enabled
|
|
57
|
+
|
|
58
|
+
RcrewAI::Rails::Observation::Collector.new(execution: execution)
|
|
70
59
|
end
|
|
71
60
|
|
|
72
61
|
def notify_completion(crew, execution, result)
|
|
@@ -5,6 +5,7 @@ module RcrewAI
|
|
|
5
5
|
|
|
6
6
|
belongs_to :crew
|
|
7
7
|
has_many :execution_logs, dependent: :destroy
|
|
8
|
+
has_many :spans, dependent: :destroy
|
|
8
9
|
|
|
9
10
|
validates :status, inclusion: { in: %w[pending running completed failed cancelled] }
|
|
10
11
|
|
|
@@ -74,7 +75,12 @@ module RcrewAI
|
|
|
74
75
|
%w[completed failed cancelled].include?(status)
|
|
75
76
|
end
|
|
76
77
|
|
|
78
|
+
# Deprecated: superseded by the observation engine's span tree.
|
|
79
|
+
# Scheduled for removal one minor version after the engine ships.
|
|
77
80
|
def log(level, message, details = {})
|
|
81
|
+
RcrewAI::Rails.deprecator_warn(
|
|
82
|
+
"Execution#log and ExecutionLog are deprecated; use the observation engine (Execution#spans)."
|
|
83
|
+
)
|
|
78
84
|
execution_logs.create!(
|
|
79
85
|
level: level,
|
|
80
86
|
message: message,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module RcrewAI
|
|
2
|
+
module Rails
|
|
3
|
+
class Span < ApplicationRecord
|
|
4
|
+
self.table_name = "rcrewai_spans"
|
|
5
|
+
|
|
6
|
+
KINDS = %w[crew agent task llm_call tool_call].freeze
|
|
7
|
+
STATUSES = %w[running ok error].freeze
|
|
8
|
+
|
|
9
|
+
belongs_to :execution
|
|
10
|
+
belongs_to :parent, class_name: "RcrewAI::Rails::Span",
|
|
11
|
+
foreign_key: :parent_span_id, optional: true
|
|
12
|
+
has_many :children, class_name: "RcrewAI::Rails::Span",
|
|
13
|
+
foreign_key: :parent_span_id, dependent: :destroy
|
|
14
|
+
has_many :span_events, dependent: :destroy
|
|
15
|
+
|
|
16
|
+
validates :kind, inclusion: { in: KINDS }
|
|
17
|
+
validates :status, inclusion: { in: STATUSES }
|
|
18
|
+
validates :name, :trace_id, :started_at, :sequence, presence: true
|
|
19
|
+
|
|
20
|
+
scope :roots, -> { where(parent_span_id: nil).order(:sequence) }
|
|
21
|
+
scope :ordered, -> { order(:sequence) }
|
|
22
|
+
scope :errored, -> { where(status: "error") }
|
|
23
|
+
scope :running, -> { where(status: "running") }
|
|
24
|
+
scope :llm_calls, -> { where(kind: "llm_call") }
|
|
25
|
+
scope :tool_calls, -> { where(kind: "tool_call") }
|
|
26
|
+
|
|
27
|
+
def attributes_hash
|
|
28
|
+
raw = self[:attributes_json]
|
|
29
|
+
return {} if raw.blank?
|
|
30
|
+
|
|
31
|
+
JSON.parse(raw)
|
|
32
|
+
rescue JSON::ParserError
|
|
33
|
+
{}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def attributes_hash=(hash)
|
|
37
|
+
self[:attributes_json] = hash.nil? ? nil : JSON.generate(hash)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def finish!(status: "ok", ended_at: Time.current)
|
|
41
|
+
update!(
|
|
42
|
+
status: status,
|
|
43
|
+
ended_at: ended_at,
|
|
44
|
+
duration_ms: ((ended_at - started_at) * 1000).round
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def running?
|
|
49
|
+
status == "running"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def errored?
|
|
53
|
+
status == "error"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module RcrewAI
|
|
2
|
+
module Rails
|
|
3
|
+
class SpanEvent < ApplicationRecord
|
|
4
|
+
self.table_name = "rcrewai_span_events"
|
|
5
|
+
|
|
6
|
+
LEVELS = %w[debug info warn error].freeze
|
|
7
|
+
|
|
8
|
+
belongs_to :span
|
|
9
|
+
|
|
10
|
+
validates :level, inclusion: { in: LEVELS }
|
|
11
|
+
validates :name, presence: true
|
|
12
|
+
|
|
13
|
+
serialize :details, coder: JSON
|
|
14
|
+
|
|
15
|
+
scope :errors, -> { where(level: "error") }
|
|
16
|
+
scope :recent, -> { order(timestamp: :desc) }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<%#
|
|
2
|
+
Renders one span and, recursively, its children.
|
|
3
|
+
|
|
4
|
+
`children` is the parent_span_id => [spans] map built once by the
|
|
5
|
+
controller. Children are read from that map, never via `span.children`,
|
|
6
|
+
which would issue a query per node and turn a deep trace into an N+1.
|
|
7
|
+
|
|
8
|
+
A malformed tree (a parent/child cycle, e.g. a bad backfill) would
|
|
9
|
+
otherwise recurse until the request dies, so recursion is bounded by
|
|
10
|
+
MAX_SPAN_DEPTH and by `seen`, which drops any span already on the path.
|
|
11
|
+
%>
|
|
12
|
+
<% depth = local_assigns.fetch(:depth, 0) %>
|
|
13
|
+
<% children = local_assigns.fetch(:children, {}) %>
|
|
14
|
+
<% seen = local_assigns.fetch(:seen, []) %>
|
|
15
|
+
<% max_depth = RcrewAI::Rails::ObservationsController::MAX_SPAN_DEPTH %>
|
|
16
|
+
|
|
17
|
+
<div class="span-row span-<%= span.status %> span-kind-<%= span.kind %>" id="span-<%= span.id %>">
|
|
18
|
+
<div class="span-line" style="padding-left: <%= depth * 20 %>px">
|
|
19
|
+
<span class="span-kind"><%= span.kind %></span>
|
|
20
|
+
<span class="span-name"><%= span.name %></span>
|
|
21
|
+
<span class="span-status status-<%= span.status %>"><%= span.status %></span>
|
|
22
|
+
<% if span.duration_ms %>
|
|
23
|
+
<span class="span-duration"><%= span.duration_ms %>ms</span>
|
|
24
|
+
<% end %>
|
|
25
|
+
<% if span.total_tokens.to_i.positive? %>
|
|
26
|
+
<span class="span-tokens"><%= span.total_tokens %> tokens</span>
|
|
27
|
+
<% end %>
|
|
28
|
+
<% if span.cost_usd.present? %>
|
|
29
|
+
<span class="span-cost">$<%= span.cost_usd %></span>
|
|
30
|
+
<% end %>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<% attrs = span.attributes_hash %>
|
|
34
|
+
<% if attrs.present? %>
|
|
35
|
+
<details class="span-attributes" style="margin-left: <%= (depth * 20) + 20 %>px">
|
|
36
|
+
<summary>Details</summary>
|
|
37
|
+
<pre class="code-block"><%= JSON.pretty_generate(attrs) %></pre>
|
|
38
|
+
</details>
|
|
39
|
+
<% end %>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<% if depth < max_depth %>
|
|
43
|
+
<% (children[span.id] || []).each do |child| %>
|
|
44
|
+
<% next if seen.include?(child.id) %>
|
|
45
|
+
<%= render "rcrewai/rails/observations/span",
|
|
46
|
+
span: child,
|
|
47
|
+
children: children,
|
|
48
|
+
depth: depth + 1,
|
|
49
|
+
seen: seen + [span.id] %>
|
|
50
|
+
<% end %>
|
|
51
|
+
<% end %>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<div class="page-header">
|
|
2
|
+
<h1>Cost & Performance</h1>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<div class="metadata-grid">
|
|
6
|
+
<div class="meta-item">
|
|
7
|
+
<strong>Total Cost:</strong> $<%= @total_cost %>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="meta-item">
|
|
10
|
+
<strong>Total Tokens:</strong> <%= @total_tokens %>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="meta-item">
|
|
13
|
+
<strong>Executions:</strong> <%= @executions.size %>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="section">
|
|
18
|
+
<h2>Recent Executions</h2>
|
|
19
|
+
|
|
20
|
+
<% if @executions.empty? %>
|
|
21
|
+
<p class="empty-state">No executions with recorded cost yet.</p>
|
|
22
|
+
<% else %>
|
|
23
|
+
<table class="table">
|
|
24
|
+
<thead>
|
|
25
|
+
<tr>
|
|
26
|
+
<th>Execution</th>
|
|
27
|
+
<th>Crew</th>
|
|
28
|
+
<th>Status</th>
|
|
29
|
+
<th>Spans</th>
|
|
30
|
+
<th>Errors</th>
|
|
31
|
+
<th>Tokens</th>
|
|
32
|
+
<th>Cost</th>
|
|
33
|
+
<th></th>
|
|
34
|
+
</tr>
|
|
35
|
+
</thead>
|
|
36
|
+
<tbody>
|
|
37
|
+
<% @executions.each do |execution| %>
|
|
38
|
+
<tr>
|
|
39
|
+
<td><%= link_to "##{execution.id}", execution_path(execution) %></td>
|
|
40
|
+
<td><%= execution.crew&.name %></td>
|
|
41
|
+
<td><span class="status-<%= execution.status %>"><%= execution.status.humanize %></span></td>
|
|
42
|
+
<td><%= execution.span_count.to_i %></td>
|
|
43
|
+
<td><%= execution.error_count.to_i %></td>
|
|
44
|
+
<td><%= execution.total_tokens.to_i %></td>
|
|
45
|
+
<td>$<%= execution.total_cost_usd %></td>
|
|
46
|
+
<td><%= link_to "Trace", execution_observation_path(execution), class: "btn btn-sm" %></td>
|
|
47
|
+
</tr>
|
|
48
|
+
<% end %>
|
|
49
|
+
</tbody>
|
|
50
|
+
</table>
|
|
51
|
+
<% end %>
|
|
52
|
+
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<div class="page-header">
|
|
2
|
+
<h1>Trace for Execution #<%= @execution.id %></h1>
|
|
3
|
+
<div class="actions">
|
|
4
|
+
<%= link_to "Back to Execution", execution_path(@execution), class: "btn" %>
|
|
5
|
+
<%= link_to "Costs", observation_costs_path, class: "btn" %>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="execution-details">
|
|
10
|
+
<div class="status-banner status-<%= @execution.status %>">
|
|
11
|
+
<strong>Status:</strong> <%= @execution.status.humanize %>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="metadata-grid">
|
|
15
|
+
<div class="meta-item">
|
|
16
|
+
<strong>Spans:</strong> <%= @execution.span_count.to_i %>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="meta-item">
|
|
19
|
+
<strong>Errors:</strong> <%= @execution.error_count.to_i %>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="meta-item">
|
|
22
|
+
<strong>Tokens:</strong> <%= @execution.total_tokens.to_i %>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="meta-item">
|
|
25
|
+
<strong>Cost:</strong> <%= @execution.total_cost_usd.present? ? "$#{@execution.total_cost_usd}" : "N/A" %>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="section">
|
|
31
|
+
<h2>Waterfall</h2>
|
|
32
|
+
|
|
33
|
+
<div class="span-tree" id="execution-<%= @execution.id %>-spans">
|
|
34
|
+
<% if @roots.empty? %>
|
|
35
|
+
<p class="empty-state">No spans recorded for this execution.</p>
|
|
36
|
+
<% else %>
|
|
37
|
+
<% @roots.each do |span| %>
|
|
38
|
+
<%= render "rcrewai/rails/observations/span",
|
|
39
|
+
span: span, children: @children, depth: 0, seen: [] %>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<% if defined?(turbo_stream_from) && @execution.running? %>
|
|
46
|
+
<%= turbo_stream_from "rcrewai_execution_#{@execution.id}" %>
|
|
47
|
+
<% end %>
|
data/config/routes.rb
CHANGED
|
@@ -16,6 +16,12 @@ RcrewAI::Rails::Engine.routes.draw do
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
resources :executions, only: [] do
|
|
20
|
+
resource :observation, only: [:show], controller: "observations"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
get "observations/costs", to: "observations#costs", as: :observation_costs
|
|
24
|
+
|
|
19
25
|
resources :agents do
|
|
20
26
|
resources :tools
|
|
21
27
|
end
|
|
@@ -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
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
class CreateRcrewAISpans < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :rcrewai_spans do |t|
|
|
4
|
+
t.references :execution, null: false, foreign_key: { to_table: :rcrewai_executions }
|
|
5
|
+
t.bigint :parent_span_id
|
|
6
|
+
t.string :trace_id, null: false
|
|
7
|
+
t.string :kind, null: false
|
|
8
|
+
t.string :name, null: false
|
|
9
|
+
t.string :status, null: false, default: "running"
|
|
10
|
+
t.datetime :started_at, null: false
|
|
11
|
+
t.datetime :ended_at
|
|
12
|
+
t.integer :duration_ms
|
|
13
|
+
t.integer :prompt_tokens
|
|
14
|
+
t.integer :completion_tokens
|
|
15
|
+
t.integer :total_tokens
|
|
16
|
+
t.decimal :cost_usd, precision: 12, scale: 6
|
|
17
|
+
t.text :attributes_json
|
|
18
|
+
t.integer :sequence, null: false
|
|
19
|
+
|
|
20
|
+
t.timestamps
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
add_index :rcrewai_spans, :parent_span_id
|
|
24
|
+
add_index :rcrewai_spans, :trace_id
|
|
25
|
+
add_index :rcrewai_spans, :kind
|
|
26
|
+
add_index :rcrewai_spans, :status
|
|
27
|
+
add_index :rcrewai_spans, %i[execution_id sequence]
|
|
28
|
+
|
|
29
|
+
create_table :rcrewai_span_events do |t|
|
|
30
|
+
t.references :span, null: false, foreign_key: { to_table: :rcrewai_spans }
|
|
31
|
+
t.string :level, null: false, default: "info"
|
|
32
|
+
t.string :name, null: false
|
|
33
|
+
t.text :details
|
|
34
|
+
t.datetime :timestamp, null: false
|
|
35
|
+
|
|
36
|
+
t.timestamps
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
add_index :rcrewai_span_events, :level
|
|
40
|
+
add_index :rcrewai_span_events, :timestamp
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
class AddObservationRollupsToRcrewAIExecutions < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
add_column :rcrewai_executions, :total_cost_usd, :decimal, precision: 12, scale: 6
|
|
4
|
+
add_column :rcrewai_executions, :total_tokens, :integer
|
|
5
|
+
add_column :rcrewai_executions, :span_count, :integer, default: 0, null: false
|
|
6
|
+
add_column :rcrewai_executions, :error_count, :integer, default: 0, null: false
|
|
7
|
+
end
|
|
8
|
+
end
|