active_harness 0.2.28 → 0.2.30
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/lib/active_harness/costs.rb +24 -1
- data/lib/active_harness/memory.rb +6 -0
- data/lib/active_harness/pipeline/README.md +1 -1
- data/lib/active_harness/pipeline/step.rb +25 -3
- data/lib/active_harness/pipeline.rb +22 -3
- data/lib/active_harness.rb +1 -1
- data/lib/generators/active_harness/install/templates/controllers/ai_controller.rb +4 -3
- data/lib/generators/active_harness/install/templates/pipelines/support_pipeline.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f5f759449bd21052f986bc092a621f2ce39cb4faa630ba1bc759419bc27aac37
|
|
4
|
+
data.tar.gz: 94e8abc9d0a0ca7ef877ea0f1e66ac68899236d7d33f10ac754f3541b3a29d1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a9529f9391337660a325993adf7db17bd2f470f6b2e69cab256a1ac907616d0c76a5c5b4c56f9850399fbfb8d886b3f2d2c2241c6ceb66ea0d83cb0e2d4c724
|
|
7
|
+
data.tar.gz: e1ae97b30a763b761ae28d20b957670d5b8a774d249456cfd84da09830ce6ebc8661c2d19790a69ff2889ff585ce7af307beb13b5d46886708db041fd1c33d73
|
data/lib/active_harness/costs.rb
CHANGED
|
@@ -64,13 +64,31 @@ module ActiveHarness
|
|
|
64
64
|
:cache_write_input_per_million,
|
|
65
65
|
:context_window,
|
|
66
66
|
:max_output_tokens,
|
|
67
|
+
:input_modalities,
|
|
68
|
+
:output_modalities,
|
|
67
69
|
keyword_init: true
|
|
68
70
|
) do
|
|
71
|
+
# Returns capability tags derived from modality data and model id/name.
|
|
72
|
+
# Possible values: "vision", "pdf", "audio", "video", "imggen", "embed"
|
|
73
|
+
def categories
|
|
74
|
+
inp = input_modalities || []
|
|
75
|
+
out = output_modalities || []
|
|
76
|
+
cats = []
|
|
77
|
+
cats << "vision" if inp.include?("image")
|
|
78
|
+
cats << "pdf" if inp.include?("pdf")
|
|
79
|
+
cats << "audio" if inp.include?("audio") || out.include?("audio")
|
|
80
|
+
cats << "video" if inp.include?("video") || out.include?("video")
|
|
81
|
+
cats << "imggen" if out.include?("image")
|
|
82
|
+
cats << "embed" if id.to_s.match?(/embed/i) || name.to_s.match?(/embed/i)
|
|
83
|
+
cats
|
|
84
|
+
end
|
|
85
|
+
|
|
69
86
|
def inspect
|
|
70
87
|
parts = ["id=#{id.inspect}", "provider=#{provider.inspect}"]
|
|
71
88
|
parts << "input=$#{input_per_million}/M" if input_per_million
|
|
72
89
|
parts << "output=$#{output_per_million}/M" if output_per_million
|
|
73
90
|
parts << "ctx=#{context_window}" if context_window
|
|
91
|
+
parts << "cats=#{categories.join(',')}" if categories.any?
|
|
74
92
|
"#<ModelCost #{parts.join(' ')}>"
|
|
75
93
|
end
|
|
76
94
|
end
|
|
@@ -232,12 +250,15 @@ module ActiveHarness
|
|
|
232
250
|
cache_write_input_per_million: cost[:cache_write]
|
|
233
251
|
}.compact
|
|
234
252
|
|
|
253
|
+
mods = m[:modalities] || {}
|
|
235
254
|
{
|
|
236
255
|
id: m[:id],
|
|
237
256
|
name: m[:name] || m[:id],
|
|
238
257
|
provider: ah_provider,
|
|
239
258
|
context_window: m[:context_window] || m.dig(:limit, :context),
|
|
240
259
|
max_output_tokens: m[:max_output_tokens] || m.dig(:limit, :output),
|
|
260
|
+
input_modalities: Array(mods[:input]),
|
|
261
|
+
output_modalities: Array(mods[:output]),
|
|
241
262
|
pricing: standard.any? ? { text_tokens: { standard: standard } } : {}
|
|
242
263
|
}
|
|
243
264
|
end
|
|
@@ -255,7 +276,9 @@ module ActiveHarness
|
|
|
255
276
|
cache_read_input_per_million: standard[:cache_read_input_per_million],
|
|
256
277
|
cache_write_input_per_million: standard[:cache_write_input_per_million],
|
|
257
278
|
context_window: raw[:context_window],
|
|
258
|
-
max_output_tokens: raw[:max_output_tokens]
|
|
279
|
+
max_output_tokens: raw[:max_output_tokens],
|
|
280
|
+
input_modalities: Array(raw[:input_modalities]),
|
|
281
|
+
output_modalities: Array(raw[:output_modalities])
|
|
259
282
|
)
|
|
260
283
|
end
|
|
261
284
|
|
|
@@ -84,6 +84,12 @@ module ActiveHarness
|
|
|
84
84
|
async: false,
|
|
85
85
|
**adapter_opts
|
|
86
86
|
)
|
|
87
|
+
if self.class == Memory
|
|
88
|
+
raise NotImplementedError,
|
|
89
|
+
"Memory cannot be instantiated directly. " \
|
|
90
|
+
"Use Memory::JsonFile, Memory::Postgresql, or Memory::Sqlite."
|
|
91
|
+
end
|
|
92
|
+
|
|
87
93
|
@session_id = session_id
|
|
88
94
|
@depth = depth
|
|
89
95
|
@enabled = enabled
|
|
@@ -21,15 +21,37 @@ module ActiveHarness
|
|
|
21
21
|
lam ? @stop_if = lam : @stop_if
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# DSL: define how to extract the new payload from a result.
|
|
25
|
+
# When provided, the step always updates the payload — even if stop_if is also set.
|
|
26
|
+
# The block receives the Result and must return the new payload value.
|
|
27
|
+
#
|
|
28
|
+
# step :laundry do
|
|
29
|
+
# use PromptLaundryPipeline
|
|
30
|
+
# transform { |result| result.output }
|
|
31
|
+
# stop_if ->(result) { result.processed["stopped"] == true }
|
|
32
|
+
# end
|
|
33
|
+
def transform(&block)
|
|
34
|
+
@transform_block = block if block
|
|
35
|
+
@transform_block
|
|
36
|
+
end
|
|
37
|
+
|
|
24
38
|
# True if agent_class is a Tribunal subclass — tribunal steps do not update payload.
|
|
25
39
|
def tribunal?
|
|
26
40
|
@agent_class.is_a?(Class) && @agent_class <= ActiveHarness::Tribunal
|
|
27
41
|
end
|
|
28
42
|
|
|
29
|
-
#
|
|
30
|
-
#
|
|
43
|
+
# Returns true when this step should update the pipeline payload after execution.
|
|
44
|
+
# A step transforms when:
|
|
45
|
+
# - an explicit transform block is defined (overrides default), OR
|
|
46
|
+
# - no stop_if and not a tribunal (legacy default)
|
|
31
47
|
def transform?
|
|
32
|
-
!tribunal? && @stop_if.nil?
|
|
48
|
+
@transform_block ? true : (!tribunal? && @stop_if.nil?)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Extract the new payload value from result.
|
|
52
|
+
# Uses the user-defined transform block when present; falls back to result.output.
|
|
53
|
+
def extract_payload(result)
|
|
54
|
+
@transform_block ? @transform_block.call(result) : result.output
|
|
33
55
|
end
|
|
34
56
|
end
|
|
35
57
|
end
|
|
@@ -14,7 +14,7 @@ module ActiveHarness
|
|
|
14
14
|
#
|
|
15
15
|
# step :safety_tribunal do
|
|
16
16
|
# use SafetyTribunal
|
|
17
|
-
# stop_if ->(result) { result.verdict == false }
|
|
17
|
+
# stop_if ->(result) { result.processed["verdict"] == false }
|
|
18
18
|
# end
|
|
19
19
|
#
|
|
20
20
|
# on :before_step do |step_name, payload| ... end
|
|
@@ -140,6 +140,20 @@ module ActiveHarness
|
|
|
140
140
|
@stopped
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
+
# Wraps pipeline outcome into a Result so a pipeline can be used as a step
|
|
144
|
+
# inside another pipeline, matching the same interface as Agent and Tribunal.
|
|
145
|
+
#
|
|
146
|
+
# output — final payload (nil when stopped)
|
|
147
|
+
# processed — { "stopped" => bool, "stopped_at" => step_name_string_or_nil }
|
|
148
|
+
def result
|
|
149
|
+
Result.new(
|
|
150
|
+
input: @original_input,
|
|
151
|
+
output: @output,
|
|
152
|
+
processed: { "stopped" => @stopped, "stopped_at" => @stopped_at&.to_s },
|
|
153
|
+
execution_time: @execution_time
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
143
157
|
# Execute all steps sequentially. Returns self for chaining.
|
|
144
158
|
def call
|
|
145
159
|
config = self.class.pipeline_config
|
|
@@ -155,7 +169,7 @@ module ActiveHarness
|
|
|
155
169
|
|
|
156
170
|
@step_results[step.name] = result
|
|
157
171
|
@context[step.name] = result
|
|
158
|
-
@payload = result
|
|
172
|
+
@payload = step.extract_payload(result) if step.transform?
|
|
159
173
|
|
|
160
174
|
fire(:after_step, step.name, result, config)
|
|
161
175
|
fire_step(:after_step, step.name, result, config)
|
|
@@ -210,7 +224,12 @@ module ActiveHarness
|
|
|
210
224
|
end
|
|
211
225
|
|
|
212
226
|
def execute_step(step)
|
|
213
|
-
streams = {
|
|
227
|
+
streams = {
|
|
228
|
+
token: @token_stream,
|
|
229
|
+
agent: @agent_event_stream,
|
|
230
|
+
tribunal: @tribunal_event_stream,
|
|
231
|
+
pipeline: @pipeline_event_stream
|
|
232
|
+
}.compact
|
|
214
233
|
step.agent_class.new(
|
|
215
234
|
input: @payload,
|
|
216
235
|
context: @context.dup,
|
data/lib/active_harness.rb
CHANGED
|
@@ -43,11 +43,12 @@ class AiSupportController < ApplicationController
|
|
|
43
43
|
# Returns verdict: true (safe) or false (rejected).
|
|
44
44
|
# ---------------------------------------------------------------------------
|
|
45
45
|
def tribunal
|
|
46
|
-
|
|
46
|
+
tribunal = SupportGuardTribunal.new(input: params.require(:input))
|
|
47
|
+
tribunal.call
|
|
47
48
|
|
|
48
49
|
render json: {
|
|
49
|
-
verdict:
|
|
50
|
-
time:
|
|
50
|
+
verdict: tribunal.verdict,
|
|
51
|
+
time: tribunal.execution_time
|
|
51
52
|
}
|
|
52
53
|
end
|
|
53
54
|
|
|
@@ -7,7 +7,7 @@ class SupportPipeline < ActiveHarness::Pipeline
|
|
|
7
7
|
# Step 1 — GUARD: reject spam before spending tokens on an answer
|
|
8
8
|
step :spam_guard do
|
|
9
9
|
use SupportGuardTribunal
|
|
10
|
-
stop_if ->(result) { result.verdict == false }
|
|
10
|
+
stop_if ->(result) { result.processed["verdict"] == false }
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
# Step 2 — RESPOND: generate the actual answer
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_harness
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.30
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- the-teacher
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: concurrent-ruby
|