actionagent 1.2.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +14 -3
- data/app/assets/builds/action_agent.css +1 -1
- data/app/assets/builds/action_agent.js +69 -43
- data/app/controllers/action_agent/api/agent_runs_controller.rb +28 -8
- data/app/controllers/action_agent/api/agents_controller.rb +191 -56
- data/app/controllers/action_agent/api/analytics_controller.rb +31 -9
- data/app/controllers/action_agent/api/base_controller.rb +16 -0
- data/app/controllers/action_agent/api/dashboard_assistant_controller.rb +83 -0
- data/app/controllers/action_agent/api/evaluations_controller.rb +252 -7
- data/app/controllers/action_agent/api/interaction_messages_controller.rb +98 -0
- data/app/controllers/action_agent/api/mcp_controller.rb +13 -3
- data/app/controllers/action_agent/api/mcp_servers_controller.rb +28 -8
- data/app/controllers/action_agent/api/metrics_controller.rb +44 -11
- data/app/controllers/action_agent/api/provider_models_controller.rb +1 -1
- data/app/controllers/action_agent/api/sandboxes_controller.rb +6 -0
- data/app/controllers/action_agent/api/session_recordings_controller.rb +34 -12
- data/app/controllers/action_agent/api/templates_controller.rb +25 -21
- data/app/controllers/action_agent/api/traces_controller.rb +25 -5
- data/app/controllers/action_agent/api/usage_controller.rb +20 -0
- data/app/controllers/action_agent/application_controller.rb +25 -2
- data/app/controllers/action_agent/dashboard_controller.rb +3 -1
- data/app/controllers/concerns/action_agent/api/agent_serialization.rb +53 -0
- data/app/jobs/action_agent/agent_execution_job.rb +40 -20
- data/app/jobs/action_agent/application_job.rb +7 -3
- data/app/jobs/action_agent/evaluation_run_job.rb +18 -0
- data/app/jobs/action_agent/sandbox_cleanup_job.rb +13 -10
- data/app/models/action_agent/agent.rb +74 -23
- data/app/models/action_agent/agent_run.rb +99 -0
- data/app/models/action_agent/agent_template.rb +22 -7
- data/app/models/action_agent/evaluation.rb +64 -4
- data/app/models/action_agent/evaluation_run.rb +190 -2
- data/app/models/action_agent/evaluation_scenario.rb +59 -0
- data/app/models/action_agent/evaluation_scenario_result.rb +86 -0
- data/app/models/action_agent/recording_action.rb +11 -7
- data/app/models/action_agent/sandbox_session.rb +1 -1
- data/app/models/action_agent/session_recording.rb +31 -8
- data/app/models/action_agent/telemetry_trace.rb +126 -3
- data/app/models/concerns/action_agent/adapter_aware.rb +19 -0
- data/app/models/concerns/action_agent/ownable.rb +15 -2
- data/app/queries/action_agent/metrics_report.rb +498 -0
- data/app/serializers/action_agent/agent_message_serializer.rb +1 -0
- data/app/services/action_agent/agent_execution_service.rb +294 -16
- data/app/services/action_agent/agent_registrar.rb +7 -6
- data/app/services/action_agent/agent_toolbox.rb +49 -7
- data/app/services/action_agent/dashboard_assistant_service.rb +342 -0
- data/app/services/action_agent/evaluation_evidence.rb +234 -0
- data/app/services/action_agent/evaluation_runner_service.rb +13 -3
- data/app/services/action_agent/evaluation_tool_resolver.rb +162 -0
- data/app/services/action_agent/mcp_catalog.rb +46 -8
- data/app/services/action_agent/mcp_client.rb +167 -0
- data/app/services/action_agent/mcp_recording_middleware.rb +2 -2
- data/app/services/action_agent/mcp_tool_dispatcher.rb +116 -0
- data/app/services/action_agent/playwright_mcp_client.rb +11 -126
- data/app/services/action_agent/sandbox_orchestrator.rb +12 -1
- data/app/services/action_agent/scenario_evaluation_runner.rb +260 -0
- data/app/services/action_agent/tool_discovery.rb +22 -8
- data/config/routes.rb +36 -3
- data/lib/action_agent/assistant_request_filter.rb +22 -0
- data/lib/action_agent/engine.rb +106 -19
- data/lib/action_agent/version.rb +1 -1
- data/lib/action_agent.rb +104 -6
- data/lib/generators/action_agent/install_generator.rb +20 -7
- data/lib/generators/action_agent/templates/action_agent.rb.erb +12 -0
- data/lib/generators/action_agent/templates/create_active_agent_evaluation_scenarios.rb.erb +79 -0
- data/lib/tasks/action_agent.rake +9 -0
- metadata +22 -5
data/lib/action_agent/engine.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "assistant_request_filter"
|
|
4
|
+
|
|
3
5
|
module ActionAgent
|
|
4
6
|
# Rails engine for the Active Agent dashboard: the agent builder, runs,
|
|
5
7
|
# conversations, evaluations, traces, metrics, sandboxes and session
|
|
@@ -14,13 +16,39 @@ module ActionAgent
|
|
|
14
16
|
engine_name "action_agent"
|
|
15
17
|
|
|
16
18
|
# Basenames this engine spells differently from Zeitwerk's default
|
|
17
|
-
# camelization, consulted by the inflections initializer below.
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
# camelization, consulted by the inflections initializer below. An engine
|
|
20
|
+
# file that wants a genuine acronym in its constant adds its basename here
|
|
21
|
+
# rather than relying on the host to register one.
|
|
22
|
+
INFLECTION_OVERRIDES = {
|
|
23
|
+
"mcp_catalog" => "MCPCatalog",
|
|
24
|
+
"mcp_client" => "MCPClient",
|
|
25
|
+
"mcp_tool_dispatcher" => "MCPToolDispatcher",
|
|
26
|
+
"mcp_controller" => "MCPController",
|
|
27
|
+
"mcp_recording_middleware" => "MCPRecordingMiddleware",
|
|
28
|
+
"mcp_servers_controller" => "MCPServersController",
|
|
29
|
+
"playwright_mcp_client" => "PlaywrightMCPClient"
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
# The spelling default camelization produces for each overridden file,
|
|
33
|
+
# mapped to the constant the file actually defines. The const_missing shim
|
|
34
|
+
# below uses it to answer lookups that camelized with default inflections
|
|
35
|
+
# (a plain host's router, or caller code written against the pre-acronym
|
|
36
|
+
# names) with the acronym constant.
|
|
37
|
+
DEFAULT_SPELLINGS = INFLECTION_OVERRIDES.to_h { |basename, constant|
|
|
38
|
+
[ Zeitwerk::Inflector.new.camelize(basename, nil), constant ]
|
|
39
|
+
}.freeze
|
|
22
40
|
|
|
23
41
|
config.action_agent = ActiveSupport::OrderedOptions.new
|
|
42
|
+
config.app_middleware.insert_before Rails::Rack::Logger, ActionAgent::AssistantRequestFilter
|
|
43
|
+
|
|
44
|
+
# Whether a request is a browser asking for a page, as opposed to an API
|
|
45
|
+
# or MCP client: the routes use it to tell the dashboard's client-side
|
|
46
|
+
# deep links apart from protocol traffic on the same path.
|
|
47
|
+
def self.html_request?(request)
|
|
48
|
+
Array(request.accepts).any? { |type| type.respond_to?(:html?) && type.html? }
|
|
49
|
+
rescue StandardError
|
|
50
|
+
false
|
|
51
|
+
end
|
|
24
52
|
|
|
25
53
|
# The dashboard's JS and CSS ship prebuilt in the gem. Adding the
|
|
26
54
|
# directory to the host app's asset paths is what lets a plain
|
|
@@ -34,14 +62,13 @@ module ActionAgent
|
|
|
34
62
|
end
|
|
35
63
|
|
|
36
64
|
# This engine's constants are spelled the way Zeitwerk's own inflector
|
|
37
|
-
# spells them — Api,
|
|
65
|
+
# spells them — Api, ApiKey — but an engine's files are
|
|
38
66
|
# autoloaded by the host's `rails.main` loader, under the *host's*
|
|
39
67
|
# inflections. A host that declares `inflect.acronym "API"` or "MCP" (both
|
|
40
68
|
# common, and documented by Rails) makes Zeitwerk expect
|
|
41
|
-
# ActionAgent::API::TracesController
|
|
42
|
-
# that
|
|
43
|
-
#
|
|
44
|
-
# raises Zeitwerk::NameError.
|
|
69
|
+
# ActionAgent::API::TracesController from a file
|
|
70
|
+
# that defines ActionAgent::Api::TracesController. The constant never
|
|
71
|
+
# resolves and the request raises Zeitwerk::NameError.
|
|
45
72
|
#
|
|
46
73
|
# Every path under this engine therefore camelizes with Zeitwerk's default
|
|
47
74
|
# rules, ignoring whatever acronyms the host has registered. Applied by
|
|
@@ -51,7 +78,8 @@ module ActionAgent
|
|
|
51
78
|
# engine's files from the host's.
|
|
52
79
|
#
|
|
53
80
|
# Basenames whose spelling this engine cannot express through default
|
|
54
|
-
# camelization (a genuine acronym it wants uppercased) go in
|
|
81
|
+
# camelization (a genuine acronym it wants uppercased) go in
|
|
82
|
+
# INFLECTION_OVERRIDES.
|
|
55
83
|
initializer "action_agent.inflections", before: :set_autoload_paths do
|
|
56
84
|
engine_root = File.join(root.to_s, "")
|
|
57
85
|
default = Zeitwerk::Inflector.new
|
|
@@ -74,15 +102,22 @@ module ActionAgent
|
|
|
74
102
|
# So the namespace answers to both. `const_missing` rather than an eager
|
|
75
103
|
# alias because the controllers are autoloaded on demand, and naming them at
|
|
76
104
|
# boot would load the whole dashboard.
|
|
77
|
-
# The router does not consult the autoloader's inflector, so
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
# all-caps run in a missing constant is retried
|
|
81
|
-
# camelization produces
|
|
82
|
-
#
|
|
83
|
-
#
|
|
105
|
+
# The router does not consult the autoloader's inflector, so its lookups
|
|
106
|
+
# miss in both directions. An acronym host asks for
|
|
107
|
+
# ActionAgent::API::TracesController while the constant is
|
|
108
|
+
# Api::TracesController: an all-caps run in a missing constant is retried
|
|
109
|
+
# in the spelling default camelization produces (API -> Api). A plain host
|
|
110
|
+
# asks for Api::McpServersController while the constant is
|
|
111
|
+
# MCPServersController (the file is in INFLECTION_OVERRIDES): a missing
|
|
112
|
+
# constant matching an override's default spelling is retried as the
|
|
113
|
+
# acronym constant, via DEFAULT_SPELLINGS — which also keeps caller code
|
|
114
|
+
# written against the pre-acronym names resolving. Only the engine's own
|
|
115
|
+
# namespaces are touched, and only for a constant that is already missing.
|
|
84
116
|
inflection_shim = Module.new do
|
|
85
117
|
def const_missing(name)
|
|
118
|
+
acronym = ActionAgent::Engine::DEFAULT_SPELLINGS[name.to_s]
|
|
119
|
+
return const_get(acronym, false) if acronym && const_defined?(acronym, false)
|
|
120
|
+
|
|
86
121
|
relaxed = name.to_s.gsub(/([A-Z])([A-Z]+)(?=[A-Z][a-z]|\d|\z)/) { "#{$1}#{$2.downcase}" }
|
|
87
122
|
|
|
88
123
|
return super if relaxed == name.to_s || !const_defined?(relaxed, false)
|
|
@@ -102,11 +137,63 @@ module ActionAgent
|
|
|
102
137
|
end
|
|
103
138
|
end
|
|
104
139
|
|
|
140
|
+
# API keys and provider credentials are encrypted at rest (`encrypts` on
|
|
141
|
+
# ApiKey and ProviderKey), which needs Active Record Encryption keys the
|
|
142
|
+
# host app may never have generated — `rails db:encryption:init` is a
|
|
143
|
+
# step most installs skip, and the engine's own reference host had
|
|
144
|
+
# skipped it too. Without keys, every credential write and every
|
|
145
|
+
# authenticated MCP request raised Errors::Configuration as an HTML 500.
|
|
146
|
+
#
|
|
147
|
+
# Rails seeds ActiveRecord::Encryption from credentials and
|
|
148
|
+
# config.active_record.encryption in its own
|
|
149
|
+
# "active_record_encryption.configuration" initializer. Keys derived
|
|
150
|
+
# after that one land in config but never reach the encryptor, so the
|
|
151
|
+
# config reads back correct while every credential write raises
|
|
152
|
+
# Errors::Configuration — which is what a host that skipped
|
|
153
|
+
# db:encryption:init actually hits. Naming it in `before:` is what puts
|
|
154
|
+
# the derivation ahead of it. When the host set no keys — neither here
|
|
155
|
+
# nor in credentials — derive stable ones from secret_key_base, overridable
|
|
156
|
+
# through the ACTIVE_RECORD_ENCRYPTION_* variables, exactly as the
|
|
157
|
+
# activeagents.ai platform does. Host-provided keys are never
|
|
158
|
+
# overridden, and the derivation is stable as long as secret_key_base is.
|
|
159
|
+
initializer "action_agent.active_record_encryption", before: "active_record_encryption.configuration" do |app|
|
|
160
|
+
next unless ActionAgent.encrypt_credentials
|
|
161
|
+
next unless app.config.respond_to?(:active_record)
|
|
162
|
+
|
|
163
|
+
encryption = app.config.active_record.encryption
|
|
164
|
+
configured = %i[primary_key deterministic_key key_derivation_salt].any? { |key| encryption[key].present? }
|
|
165
|
+
configured ||= begin
|
|
166
|
+
app.credentials.dig(:active_record_encryption, :primary_key).present?
|
|
167
|
+
rescue StandardError
|
|
168
|
+
false
|
|
169
|
+
end
|
|
170
|
+
next if configured
|
|
171
|
+
|
|
172
|
+
derive = lambda do |purpose|
|
|
173
|
+
app.key_generator.generate_key("active_record_encryption.#{purpose}", 32).unpack1("H*")
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
encryption.primary_key = ENV["ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY"].presence || derive.call("primary_key")
|
|
177
|
+
encryption.deterministic_key = ENV["ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY"].presence || derive.call("deterministic_key")
|
|
178
|
+
encryption.key_derivation_salt = ENV["ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT"].presence || derive.call("key_derivation_salt")
|
|
179
|
+
end
|
|
180
|
+
|
|
105
181
|
initializer "action_agent.assets", before: :append_assets_path do |app|
|
|
106
182
|
builds = root.join("app", "assets", "builds").to_s
|
|
107
183
|
next unless File.directory?(builds)
|
|
108
184
|
|
|
109
|
-
|
|
185
|
+
# The prebuilt bundles are served through the host's asset pipeline —
|
|
186
|
+
# propshaft (the Rails default) or sprockets-rails. A host without one
|
|
187
|
+
# (a `rails --api` app, or one that removed propshaft) used to get a
|
|
188
|
+
# silently blank dashboard with two 404s; say why instead.
|
|
189
|
+
unless app.config.respond_to?(:assets)
|
|
190
|
+
Rails.logger&.warn(
|
|
191
|
+
"[ActionAgent] the dashboard's assets cannot be served: this app has no asset pipeline. " \
|
|
192
|
+
"Add propshaft (or sprockets-rails) to the Gemfile so action_agent.js and action_agent.css " \
|
|
193
|
+
"are served from the engine's app/assets/builds."
|
|
194
|
+
)
|
|
195
|
+
next
|
|
196
|
+
end
|
|
110
197
|
|
|
111
198
|
if app.config.assets.respond_to?(:paths)
|
|
112
199
|
app.config.assets.paths << builds unless app.config.assets.paths.include?(builds)
|
data/lib/action_agent/version.rb
CHANGED
data/lib/action_agent.rb
CHANGED
|
@@ -83,7 +83,10 @@ require "action_agent/compatibility"
|
|
|
83
83
|
#
|
|
84
84
|
# ActionAgent.configure do |config|
|
|
85
85
|
# config.authentication_method = ->(controller) { controller.authenticate_admin! }
|
|
86
|
-
#
|
|
86
|
+
# # Sandboxes run in the in-memory mock unless the app registers a real
|
|
87
|
+
# # backend (see sandbox_backends) and names it here:
|
|
88
|
+
# config.sandbox_backends = { "incus" => "IncusSandboxService" }
|
|
89
|
+
# config.sandbox_service = :incus
|
|
87
90
|
# end
|
|
88
91
|
#
|
|
89
92
|
# == Multi-tenant Mode
|
|
@@ -171,7 +174,10 @@ module ActionAgent
|
|
|
171
174
|
# @return [String, nil]
|
|
172
175
|
attr_accessor :layout
|
|
173
176
|
|
|
174
|
-
#
|
|
177
|
+
# Which sandbox backend to provision with: :mock (the only one the
|
|
178
|
+
# engine ships — an in-memory fake that runs nothing) or the name of a
|
|
179
|
+
# backend the host registered in sandbox_backends. An unregistered name
|
|
180
|
+
# falls back to :mock with a logged warning.
|
|
175
181
|
# @return [Symbol]
|
|
176
182
|
attr_accessor :sandbox_service
|
|
177
183
|
|
|
@@ -217,9 +223,11 @@ module ActionAgent
|
|
|
217
223
|
# @return [Proc, nil]
|
|
218
224
|
attr_accessor :provider_credentials_resolver
|
|
219
225
|
|
|
220
|
-
#
|
|
221
|
-
# { "cloud_run" => "CloudRunService" }. The engine ships :mock
|
|
222
|
-
#
|
|
226
|
+
# Sandbox backends contributed by the host app, as
|
|
227
|
+
# { "cloud_run" => "CloudRunService" }. The engine ships only :mock;
|
|
228
|
+
# every real backend (Docker/Incus, Cloud Run, Kubernetes) lives in the
|
|
229
|
+
# app that operates it, which registers it here and selects it with
|
|
230
|
+
# sandbox_service.
|
|
223
231
|
# @return [Hash{String => String}]
|
|
224
232
|
attr_accessor :sandbox_backends
|
|
225
233
|
|
|
@@ -228,12 +236,62 @@ module ActionAgent
|
|
|
228
236
|
# @return [Boolean]
|
|
229
237
|
attr_accessor :execution_enabled
|
|
230
238
|
|
|
239
|
+
# Whether the "Ask ActiveAgents" assistant is available.
|
|
240
|
+
#
|
|
241
|
+
# The assistant is a tool for developing and CI-ing agents: it sends
|
|
242
|
+
# recorded prompts, outputs and evaluation report excerpts to a model
|
|
243
|
+
# provider, which is the right trade in a development or CI workspace
|
|
244
|
+
# and a decision nobody should inherit by default in production. Left
|
|
245
|
+
# unset it is on in development and test only. Set it to true to run it
|
|
246
|
+
# somewhere else deliberately, or false to remove it everywhere.
|
|
247
|
+
# @return [Boolean, nil]
|
|
248
|
+
attr_accessor :assistant_enabled
|
|
249
|
+
|
|
250
|
+
# Resolves a host application's runner for one scenario evaluation.
|
|
251
|
+
# Return nil for the engine's normal Agent#test_execute path, or a callable
|
|
252
|
+
# accepting evaluation:, owner:, scenarios:, models:, on_result: and
|
|
253
|
+
# returning an ActiveAgent::Evals::Report. The host runs its own agent and
|
|
254
|
+
# judge and yields every result to on_result for dashboard persistence.
|
|
255
|
+
# @return [Proc, nil]
|
|
256
|
+
attr_accessor :scenario_evaluation_adapter_resolver
|
|
257
|
+
|
|
231
258
|
# Where the dashboard's upgrade CTAs should send people. Unset in a
|
|
232
259
|
# self-hosted install, where there is nothing to upgrade, and the CTAs
|
|
233
260
|
# say so instead of linking nowhere.
|
|
234
261
|
# @return [String, nil]
|
|
235
262
|
attr_accessor :upgrade_url
|
|
236
263
|
|
|
264
|
+
# The host app's sign-out endpoint, which the header's "Sign out" item
|
|
265
|
+
# POSTs to (with _method=delete and the CSRF token). The engine has no
|
|
266
|
+
# session of its own; unset, the menu item is not shown.
|
|
267
|
+
# @return [String, nil]
|
|
268
|
+
attr_accessor :sign_out_path
|
|
269
|
+
|
|
270
|
+
# Where a browser is sent when it asks for a dashboard page without a
|
|
271
|
+
# valid session — the host app's sign-in page. Unset, an unauthenticated
|
|
272
|
+
# page request gets a minimal session-expired page instead of a bare
|
|
273
|
+
# 401; API clients always get the 401.
|
|
274
|
+
# @return [String, nil]
|
|
275
|
+
attr_accessor :sign_in_path
|
|
276
|
+
|
|
277
|
+
# Answers GET <mount>/api/usage — the plan meter the Organization view
|
|
278
|
+
# and the Run Agents quota banner read. Receives (owner) and returns a
|
|
279
|
+
# Hash in the platform's shape:
|
|
280
|
+
#
|
|
281
|
+
# { runs_used: 12, runs_limit: 100, runs_remaining: 88,
|
|
282
|
+
# can_run: true, plan: "pro" }
|
|
283
|
+
#
|
|
284
|
+
# Unset means unlimited: the engine reports UNLIMITED_USAGE and the
|
|
285
|
+
# views hide the meter.
|
|
286
|
+
# @return [Proc, nil]
|
|
287
|
+
attr_accessor :usage_resolver
|
|
288
|
+
|
|
289
|
+
# What a dashboard with no usage_resolver reports: no limit, nothing
|
|
290
|
+
# counted, always allowed.
|
|
291
|
+
UNLIMITED_USAGE = {
|
|
292
|
+
runs_used: 0, runs_limit: nil, runs_remaining: nil, can_run: true, plan: nil, unlimited: true
|
|
293
|
+
}.freeze
|
|
294
|
+
|
|
237
295
|
# Called after the dashboard performs a metered action, as
|
|
238
296
|
# (owner, kind) — the counterpart to quota_checker, for host apps that
|
|
239
297
|
# track usage against a plan. Unset means nothing is counted.
|
|
@@ -262,6 +320,16 @@ module ActionAgent
|
|
|
262
320
|
# @return [Boolean]
|
|
263
321
|
attr_accessor :encrypt_credentials
|
|
264
322
|
|
|
323
|
+
# MCP servers the host app itself serves or connects, appended to the
|
|
324
|
+
# built-in catalog (MCPCatalog) so the MCP Services view lists them and
|
|
325
|
+
# telemetry traffic attributes to them. Each entry is a hash shaped like
|
|
326
|
+
# a catalog entry: +key+ and +name+ at minimum, plus any of the optional
|
|
327
|
+
# fields (+description+, +transport+, +url+, +categories+, +docs_url+,
|
|
328
|
+
# +first_party+); +tool_hints+ names the bare tool names that belong to
|
|
329
|
+
# the server. A built-in entry keeps its key on collision.
|
|
330
|
+
# @return [Array<Hash>]
|
|
331
|
+
attr_accessor :mcp_catalog
|
|
332
|
+
|
|
265
333
|
# Value stored in polymorphic *_type columns for dashboard agents
|
|
266
334
|
# (agent_memories.memorable_type, agent_contexts.contextable_type).
|
|
267
335
|
# Unset means the class name. A host app whose existing rows were
|
|
@@ -283,6 +351,16 @@ module ActionAgent
|
|
|
283
351
|
@execution_enabled != false
|
|
284
352
|
end
|
|
285
353
|
|
|
354
|
+
# Returns whether the dashboard assistant is available. Unconfigured, it
|
|
355
|
+
# follows the environment: development and test yes, everywhere else no.
|
|
356
|
+
#
|
|
357
|
+
# @return [Boolean]
|
|
358
|
+
def assistant_enabled?
|
|
359
|
+
return @assistant_enabled == true unless @assistant_enabled.nil?
|
|
360
|
+
|
|
361
|
+
Rails.env.local?
|
|
362
|
+
end
|
|
363
|
+
|
|
286
364
|
# Tells the host app that +owner+ performed +kind+. Never raises: a
|
|
287
365
|
# bookkeeping failure must not fail the action that was already taken.
|
|
288
366
|
def record_usage(owner, kind)
|
|
@@ -292,6 +370,19 @@ module ActionAgent
|
|
|
292
370
|
nil
|
|
293
371
|
end
|
|
294
372
|
|
|
373
|
+
# The usage meter for +owner+. Never raises: a bookkeeping failure must
|
|
374
|
+
# not take the views that display it down with it.
|
|
375
|
+
#
|
|
376
|
+
# @return [Hash] the platform's usage shape, UNLIMITED_USAGE by default
|
|
377
|
+
def usage_for(owner)
|
|
378
|
+
return UNLIMITED_USAGE.dup if usage_resolver.nil?
|
|
379
|
+
|
|
380
|
+
usage_resolver.call(owner) || UNLIMITED_USAGE.dup
|
|
381
|
+
rescue StandardError => e
|
|
382
|
+
Rails.logger.warn("[ActionAgent] usage lookup failed: #{e.message}")
|
|
383
|
+
UNLIMITED_USAGE.dup
|
|
384
|
+
end
|
|
385
|
+
|
|
295
386
|
# Asks the host app whether +owner+ may perform +kind+.
|
|
296
387
|
#
|
|
297
388
|
# @return [String, Hash, nil] denial message or payload, nil when allowed
|
|
@@ -381,7 +472,7 @@ module ActionAgent
|
|
|
381
472
|
@trace_model_class = nil
|
|
382
473
|
@use_inertia = false
|
|
383
474
|
@layout = nil
|
|
384
|
-
@sandbox_service = :
|
|
475
|
+
@sandbox_service = :mock
|
|
385
476
|
@sandbox_limits = nil
|
|
386
477
|
@storage_service = nil
|
|
387
478
|
@ingest_api_key = nil
|
|
@@ -390,13 +481,20 @@ module ActionAgent
|
|
|
390
481
|
@provider_credentials_resolver = nil
|
|
391
482
|
@sandbox_backends = {}
|
|
392
483
|
@execution_enabled = true
|
|
484
|
+
@assistant_enabled = nil
|
|
485
|
+
|
|
486
|
+
@scenario_evaluation_adapter_resolver = nil
|
|
393
487
|
@table_name_prefix = "active_agent_"
|
|
394
488
|
@agent_polymorphic_name = nil
|
|
395
489
|
@encrypt_credentials = true
|
|
396
490
|
@trace_retention = nil
|
|
397
491
|
@trace_owner_resolver = nil
|
|
398
492
|
@usage_recorder = nil
|
|
493
|
+
@usage_resolver = nil
|
|
399
494
|
@upgrade_url = nil
|
|
495
|
+
@sign_out_path = nil
|
|
496
|
+
@sign_in_path = nil
|
|
497
|
+
@mcp_catalog = []
|
|
400
498
|
end
|
|
401
499
|
end
|
|
402
500
|
|
|
@@ -64,12 +64,20 @@ module ActionAgent
|
|
|
64
64
|
|
|
65
65
|
if existing_migration?("create_active_agent_dashboard_tables")
|
|
66
66
|
say_status :skip, "create_active_agent_dashboard_tables already exists", :yellow
|
|
67
|
-
|
|
67
|
+
else
|
|
68
|
+
migration_template(
|
|
69
|
+
"create_active_agent_dashboard_tables.rb.erb",
|
|
70
|
+
"db/migrate/create_active_agent_dashboard_tables.rb"
|
|
71
|
+
)
|
|
68
72
|
end
|
|
69
73
|
|
|
74
|
+
# Scenario suites arrived after the dashboard tables shipped, so an
|
|
75
|
+
# install that already has those still needs this one.
|
|
76
|
+
return if existing_migration?("create_active_agent_evaluation_scenarios")
|
|
77
|
+
|
|
70
78
|
migration_template(
|
|
71
|
-
"
|
|
72
|
-
"db/migrate/
|
|
79
|
+
"create_active_agent_evaluation_scenarios.rb.erb",
|
|
80
|
+
"db/migrate/create_active_agent_evaluation_scenarios.rb"
|
|
73
81
|
)
|
|
74
82
|
end
|
|
75
83
|
|
|
@@ -102,10 +110,15 @@ module ActionAgent
|
|
|
102
110
|
say "\n"
|
|
103
111
|
say "Next steps:"
|
|
104
112
|
say " 1. Run migrations: rails db:migrate"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
# Nested under the environment key: ActiveAgent::Configuration.load
|
|
114
|
+
# reads only the current environment's subtree when the file has one,
|
|
115
|
+
# and the framework's own generator writes an env-keyed file — so a
|
|
116
|
+
# top-level telemetry: block is silently ignored.
|
|
117
|
+
say " 2. Configure telemetry in config/active_agent.yml, under each environment key:"
|
|
118
|
+
say " development:"
|
|
119
|
+
say " telemetry:"
|
|
120
|
+
say " enabled: true"
|
|
121
|
+
say " local_storage: true"
|
|
109
122
|
say " 3. Visit /activeagents to view the dashboard"
|
|
110
123
|
unless options[:traces_only]
|
|
111
124
|
say "\n"
|
|
@@ -70,6 +70,18 @@ ActionAgent.configure do |config|
|
|
|
70
70
|
# nothing rather than everything. An empty dashboard for a signed-in user
|
|
71
71
|
# means the resolver above returned nil.
|
|
72
72
|
|
|
73
|
+
# ==========================================================================
|
|
74
|
+
# Ask ActiveAgents assistant
|
|
75
|
+
# ==========================================================================
|
|
76
|
+
#
|
|
77
|
+
# A tool for developing and CI-ing agents. Answering a question sends
|
|
78
|
+
# recorded prompts, outputs and evaluation report excerpts to a model
|
|
79
|
+
# provider, so the page and its API are on in development and test only.
|
|
80
|
+
# Set it to true to run it in another environment deliberately, or false
|
|
81
|
+
# to remove it everywhere.
|
|
82
|
+
#
|
|
83
|
+
# config.assistant_enabled = true
|
|
84
|
+
|
|
73
85
|
# ==========================================================================
|
|
74
86
|
# UI
|
|
75
87
|
# ==========================================================================
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Adds scenario suites to evaluations: the user-authored prompts an
|
|
4
|
+
# evaluation replays through its agent (once per candidate model), and the
|
|
5
|
+
# per-scenario, per-model result each replay produced. Emitted alongside the
|
|
6
|
+
# dashboard tables on a fresh install, and on its own for an install that
|
|
7
|
+
# predates scenario evaluations.
|
|
8
|
+
#
|
|
9
|
+
# Table names follow ActionAgent.table_name_prefix, and JSON columns are
|
|
10
|
+
# jsonb on PostgreSQL and json elsewhere, the same way as
|
|
11
|
+
# create_active_agent_dashboard_tables.
|
|
12
|
+
class CreateActiveAgentEvaluationScenarios < ActiveRecord::Migration<%= migration_version %>
|
|
13
|
+
def change
|
|
14
|
+
prefix = ActionAgent.table_name_prefix
|
|
15
|
+
|
|
16
|
+
create_table "#{prefix}evaluation_scenarios" do |t|
|
|
17
|
+
t.bigint :evaluation_id, null: false
|
|
18
|
+
t.string :key, null: false
|
|
19
|
+
t.string :group
|
|
20
|
+
t.text :prompt, null: false
|
|
21
|
+
t.text :notes
|
|
22
|
+
t.column :expectations, json_type, **json_default({})
|
|
23
|
+
t.integer :position, default: 0, null: false
|
|
24
|
+
t.boolean :enabled, default: true, null: false
|
|
25
|
+
t.timestamps
|
|
26
|
+
t.index [ :evaluation_id, :key ], unique: true, name: "index_#{prefix}evaluation_scenarios_on_evaluation_and_key"
|
|
27
|
+
t.index [ :evaluation_id, :group ], name: "index_#{prefix}evaluation_scenarios_on_evaluation_and_group"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
create_table "#{prefix}evaluation_scenario_results" do |t|
|
|
31
|
+
t.bigint :evaluation_run_id, null: false
|
|
32
|
+
t.bigint :evaluation_scenario_id, null: false
|
|
33
|
+
t.bigint :agent_run_id
|
|
34
|
+
t.string :model, null: false
|
|
35
|
+
t.string :provider
|
|
36
|
+
t.integer :status, default: 0, null: false
|
|
37
|
+
t.float :score
|
|
38
|
+
t.column :scores, json_type, **json_default({})
|
|
39
|
+
t.text :output
|
|
40
|
+
t.column :tool_calls, json_type, **json_default([])
|
|
41
|
+
t.integer :duration_ms
|
|
42
|
+
t.integer :input_tokens
|
|
43
|
+
t.integer :output_tokens
|
|
44
|
+
t.decimal :cost, precision: 12, scale: 6
|
|
45
|
+
t.string :fault
|
|
46
|
+
t.text :recommendation
|
|
47
|
+
t.column :diagnosis, json_type, **json_default({})
|
|
48
|
+
t.text :error_message
|
|
49
|
+
t.timestamps
|
|
50
|
+
t.index :evaluation_run_id, name: "index_#{prefix}evaluation_scenario_results_on_run"
|
|
51
|
+
t.index :evaluation_scenario_id, name: "index_#{prefix}evaluation_scenario_results_on_scenario"
|
|
52
|
+
t.index [ :evaluation_run_id, :model ], name: "index_#{prefix}evaluation_scenario_results_on_run_and_model"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Which scenarios and models a run covered, so a partial run (one group,
|
|
56
|
+
# one question) reads as such rather than as the whole suite.
|
|
57
|
+
unless column_exists?("#{prefix}evaluation_runs", :selection)
|
|
58
|
+
add_column "#{prefix}evaluation_runs", :selection, json_type, **json_default({})
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def json_type
|
|
65
|
+
@json_type ||= postgres? ? :jsonb : :json
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# MySQL rejects a default on a JSON column outright, so the column is
|
|
69
|
+
# created without one there.
|
|
70
|
+
def json_default(value, null: nil)
|
|
71
|
+
return {} unless postgres?
|
|
72
|
+
|
|
73
|
+
null.nil? ? { default: value } : { default: value, null: null }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def postgres?
|
|
77
|
+
connection.adapter_name.to_s.downcase.include?("postgres")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :action_agent do
|
|
4
|
+
desc "Seed the dashboard's default agent templates (idempotent — existing slugs are kept)"
|
|
5
|
+
task seed_templates: :environment do
|
|
6
|
+
ActionAgent::AgentTemplate.seed_defaults!
|
|
7
|
+
puts "Agent template library: #{ActionAgent::AgentTemplate.count} templates"
|
|
8
|
+
end
|
|
9
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: actionagent
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Bowen
|
|
@@ -15,7 +15,7 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '1.
|
|
18
|
+
version: '1.4'
|
|
19
19
|
- - "<"
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
21
|
version: '2'
|
|
@@ -25,7 +25,7 @@ dependencies:
|
|
|
25
25
|
requirements:
|
|
26
26
|
- - ">="
|
|
27
27
|
- !ruby/object:Gem::Version
|
|
28
|
-
version: '1.
|
|
28
|
+
version: '1.4'
|
|
29
29
|
- - "<"
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
31
|
version: '2'
|
|
@@ -128,8 +128,10 @@ files:
|
|
|
128
128
|
- app/controllers/action_agent/api/analytics_controller.rb
|
|
129
129
|
- app/controllers/action_agent/api/api_keys_controller.rb
|
|
130
130
|
- app/controllers/action_agent/api/base_controller.rb
|
|
131
|
+
- app/controllers/action_agent/api/dashboard_assistant_controller.rb
|
|
131
132
|
- app/controllers/action_agent/api/evaluations_controller.rb
|
|
132
133
|
- app/controllers/action_agent/api/instance_tiers_controller.rb
|
|
134
|
+
- app/controllers/action_agent/api/interaction_messages_controller.rb
|
|
133
135
|
- app/controllers/action_agent/api/interactions_controller.rb
|
|
134
136
|
- app/controllers/action_agent/api/mcp_controller.rb
|
|
135
137
|
- app/controllers/action_agent/api/mcp_servers_controller.rb
|
|
@@ -142,11 +144,14 @@ files:
|
|
|
142
144
|
- app/controllers/action_agent/api/tools_controller.rb
|
|
143
145
|
- app/controllers/action_agent/api/trace_reports_controller.rb
|
|
144
146
|
- app/controllers/action_agent/api/traces_controller.rb
|
|
147
|
+
- app/controllers/action_agent/api/usage_controller.rb
|
|
145
148
|
- app/controllers/action_agent/application_controller.rb
|
|
146
149
|
- app/controllers/action_agent/dashboard_controller.rb
|
|
147
150
|
- app/controllers/action_agent/traces_controller.rb
|
|
151
|
+
- app/controllers/concerns/action_agent/api/agent_serialization.rb
|
|
148
152
|
- app/jobs/action_agent/agent_execution_job.rb
|
|
149
153
|
- app/jobs/action_agent/application_job.rb
|
|
154
|
+
- app/jobs/action_agent/evaluation_run_job.rb
|
|
150
155
|
- app/jobs/action_agent/process_telemetry_traces_job.rb
|
|
151
156
|
- app/jobs/action_agent/sandbox_cleanup_job.rb
|
|
152
157
|
- app/jobs/action_agent/sandbox_provision_job.rb
|
|
@@ -165,6 +170,8 @@ files:
|
|
|
165
170
|
- app/models/action_agent/application_record.rb
|
|
166
171
|
- app/models/action_agent/evaluation.rb
|
|
167
172
|
- app/models/action_agent/evaluation_run.rb
|
|
173
|
+
- app/models/action_agent/evaluation_scenario.rb
|
|
174
|
+
- app/models/action_agent/evaluation_scenario_result.rb
|
|
168
175
|
- app/models/action_agent/model_pricing.rb
|
|
169
176
|
- app/models/action_agent/provider_key.rb
|
|
170
177
|
- app/models/action_agent/recording_action.rb
|
|
@@ -178,6 +185,7 @@ files:
|
|
|
178
185
|
- app/models/concerns/action_agent/ownable.rb
|
|
179
186
|
- app/models/concerns/action_agent/session_recordable.rb
|
|
180
187
|
- app/queries/action_agent/agent_executions.rb
|
|
188
|
+
- app/queries/action_agent/metrics_report.rb
|
|
181
189
|
- app/serializers/action_agent/agent_message_serializer.rb
|
|
182
190
|
- app/serializers/action_agent/interaction_preview.rb
|
|
183
191
|
- app/serializers/action_agent/telemetry_trace_serializer.rb
|
|
@@ -186,12 +194,18 @@ files:
|
|
|
186
194
|
- app/services/action_agent/agent_registrar.rb
|
|
187
195
|
- app/services/action_agent/agent_scorecard.rb
|
|
188
196
|
- app/services/action_agent/agent_toolbox.rb
|
|
197
|
+
- app/services/action_agent/dashboard_assistant_service.rb
|
|
198
|
+
- app/services/action_agent/evaluation_evidence.rb
|
|
189
199
|
- app/services/action_agent/evaluation_runner_service.rb
|
|
200
|
+
- app/services/action_agent/evaluation_tool_resolver.rb
|
|
190
201
|
- app/services/action_agent/mcp_catalog.rb
|
|
202
|
+
- app/services/action_agent/mcp_client.rb
|
|
191
203
|
- app/services/action_agent/mcp_recording_middleware.rb
|
|
204
|
+
- app/services/action_agent/mcp_tool_dispatcher.rb
|
|
192
205
|
- app/services/action_agent/mock_sandbox_backend.rb
|
|
193
206
|
- app/services/action_agent/playwright_mcp_client.rb
|
|
194
207
|
- app/services/action_agent/sandbox_orchestrator.rb
|
|
208
|
+
- app/services/action_agent/scenario_evaluation_runner.rb
|
|
195
209
|
- app/services/action_agent/session_recording_service.rb
|
|
196
210
|
- app/services/action_agent/tool_discovery.rb
|
|
197
211
|
- app/views/action_agent/dashboard/index.html.erb
|
|
@@ -203,6 +217,7 @@ files:
|
|
|
203
217
|
- app/views/layouts/action_agent/react.html.erb
|
|
204
218
|
- config/routes.rb
|
|
205
219
|
- lib/action_agent.rb
|
|
220
|
+
- lib/action_agent/assistant_request_filter.rb
|
|
206
221
|
- lib/action_agent/compatibility.rb
|
|
207
222
|
- lib/action_agent/engine.rb
|
|
208
223
|
- lib/action_agent/version.rb
|
|
@@ -211,7 +226,9 @@ files:
|
|
|
211
226
|
- lib/generators/action_agent/templates/action_agent.rb.erb
|
|
212
227
|
- lib/generators/action_agent/templates/add_agent_id_to_active_agent_telemetry_traces.rb.erb
|
|
213
228
|
- lib/generators/action_agent/templates/create_active_agent_dashboard_tables.rb.erb
|
|
229
|
+
- lib/generators/action_agent/templates/create_active_agent_evaluation_scenarios.rb.erb
|
|
214
230
|
- lib/generators/action_agent/templates/create_active_agent_telemetry_traces.rb.erb
|
|
231
|
+
- lib/tasks/action_agent.rake
|
|
215
232
|
homepage: https://activeagents.ai
|
|
216
233
|
licenses:
|
|
217
234
|
- MIT
|
|
@@ -227,14 +244,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
227
244
|
requirements:
|
|
228
245
|
- - ">="
|
|
229
246
|
- !ruby/object:Gem::Version
|
|
230
|
-
version: 3.
|
|
247
|
+
version: 3.2.0
|
|
231
248
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
232
249
|
requirements:
|
|
233
250
|
- - ">="
|
|
234
251
|
- !ruby/object:Gem::Version
|
|
235
252
|
version: '0'
|
|
236
253
|
requirements: []
|
|
237
|
-
rubygems_version: 4.0.
|
|
254
|
+
rubygems_version: 4.0.16
|
|
238
255
|
specification_version: 4
|
|
239
256
|
summary: The Active Agent dashboard, as a mountable Rails engine
|
|
240
257
|
test_files: []
|