rcrewai-rails 0.6.1 → 0.7.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 +31 -1
- data/README.md +44 -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/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 +60 -0
- 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: e21a3a1057f9221cca3d280f2307b219d15a636995284c781404c4ed3d283fa6
|
|
4
|
+
data.tar.gz: 9b715b0cc17839eb5fb23f578905d962c4b6450d0aae2b8b1f494d549497a8b3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1be59f83c847e48c36b0ac61527ca9dedc61b903763be6145d6dd26354575a74d2dee3fc807a804e6f847660347c8368a28d70f33df6cb57d75d2b952b98657f
|
|
7
|
+
data.tar.gz: 0f37c9c304ef0264afeb363f07ca1b23c72045d9686869fe9325e336a79f30bc1930d08f97470a6235ad7d4dc8d1e5ebab4c78eb3de9d9ae1129ea42cb48dca8
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.0] - 2026-08-13
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Observation engine: span-tree tracing of every crew execution with per-agent,
|
|
14
|
+
per-LLM-call and per-tool-call detail (timings, token counts, cost). Includes a
|
|
15
|
+
trace waterfall at `/rcrewai/executions/:id/observation`, a cost/performance
|
|
16
|
+
dashboard at `/rcrewai/observations/costs`, live monitoring over Turbo Streams,
|
|
17
|
+
and a `rcrewai:observation:prune` rake task with retention configuration
|
|
18
|
+
(`observation_retention_days`).
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- The install generator only wrote an initializer: it never copied a migration
|
|
22
|
+
and never mounted the engine, so following the documented install produced no
|
|
23
|
+
database tables and no reachable UI. It now creates a timestamped
|
|
24
|
+
`create_rcrewai_tables` migration, mounts the engine at `/rcrewai`, and
|
|
25
|
+
installs the fully documented initializer.
|
|
26
|
+
- The install migration template was missing the `rcrewai_tools` table and the
|
|
27
|
+
tasks table's `agent` reference, so a fresh install shipped a `Tool` model with
|
|
28
|
+
no backing table. The template now matches the schema the test suite validates.
|
|
29
|
+
|
|
30
|
+
### Deprecated
|
|
31
|
+
- `Execution#log` and `ExecutionLog`, superseded by the observation engine. Both
|
|
32
|
+
still work and now emit a deprecation warning; scheduled for removal one minor
|
|
33
|
+
version after this release.
|
|
34
|
+
|
|
35
|
+
### Requires
|
|
36
|
+
- `rcrewai >= 0.7.1` for agent-level tracing. On earlier 0.7.x versions traces
|
|
37
|
+
contain only crew-level spans.
|
|
38
|
+
|
|
10
39
|
## [0.6.1] - 2026-07-07
|
|
11
40
|
|
|
12
41
|
### Fixed
|
|
@@ -123,7 +152,8 @@ existing agents, tasks, and crews build unchanged.
|
|
|
123
152
|
### Changed
|
|
124
153
|
- Rename generators from `rcrew_a_i` to `rcrewai` namespacing.
|
|
125
154
|
|
|
126
|
-
[Unreleased]: https://github.com/gkosmo/rcrewai-rails/compare/v0.
|
|
155
|
+
[Unreleased]: https://github.com/gkosmo/rcrewai-rails/compare/v0.7.0...HEAD
|
|
156
|
+
[0.7.0]: https://github.com/gkosmo/rcrewai-rails/compare/v0.6.1...v0.7.0
|
|
127
157
|
[0.6.1]: https://github.com/gkosmo/rcrewai-rails/compare/v0.6.0...v0.6.1
|
|
128
158
|
[0.6.0]: https://github.com/gkosmo/rcrewai-rails/compare/v0.5.1...v0.6.0
|
|
129
159
|
[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:
|
|
@@ -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
|
|
@@ -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
|