active_harness 0.2.25 → 0.2.27
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/agent/models.rb +1 -1
- data/lib/active_harness/agent/output_parser.rb +1 -1
- data/lib/active_harness/agent/prompt.rb +1 -0
- data/lib/active_harness/agent.rb +8 -3
- data/lib/active_harness/pipeline/README.md +252 -0
- data/lib/active_harness/pipeline.rb +9 -19
- data/lib/active_harness/providers/anthropic.rb +8 -1
- data/lib/active_harness/result.rb +14 -2
- data/lib/active_harness/tribunal/dsl.rb +3 -3
- data/lib/active_harness/tribunal/processing.rb +1 -1
- data/lib/active_harness/tribunal.rb +17 -3
- data/lib/active_harness.rb +1 -1
- data/lib/generators/active_harness/install/templates/tribunals/support_guard_tribunal.rb +1 -1
- data/lib/generators/active_harness/tribunal/templates/tribunal.rb.tt +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5b406bc5feeebdb4cc26e215a65d25dd931605684c6f05ee7cdd71de5910a1d6
|
|
4
|
+
data.tar.gz: a34d2e22840ae906d47b0e27fe271d97b42d97a0fce64a19a58e15c296f00bf7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f53ee25213ac7404840349cee732aa59b1dc25168b4e362028e7542fd51a0c51e9f2a2af4b8cbad9a552e74a9c4769d147b7d642e3b051df9c293af58bc87f34
|
|
7
|
+
data.tar.gz: d56efdd52b38afd9153903611fd860e316a59eb8f5d5b5c3ed0510b22647b8008a3b22f4e0c8430322548bb82a50c69cc4d6c086e8d965653c22d795fd43cae7
|
|
@@ -14,7 +14,7 @@ module ActiveHarness
|
|
|
14
14
|
# Output format for this agent.
|
|
15
15
|
#
|
|
16
16
|
# format :text # default — output is returned as-is
|
|
17
|
-
# format :json # output is parsed; result.
|
|
17
|
+
# format :json # output is parsed; result.processed is a Ruby Hash/Array
|
|
18
18
|
def format(type)
|
|
19
19
|
unless %i[text json].include?(type)
|
|
20
20
|
raise ArgumentError, "Unknown format :#{type}. Valid values: :text, :json"
|
|
@@ -39,7 +39,7 @@ module ActiveHarness
|
|
|
39
39
|
begin
|
|
40
40
|
parsed = JSON.parse(clean)
|
|
41
41
|
|
|
42
|
-
# :after_parse — return value replaces
|
|
42
|
+
# :after_parse — return value replaces processed result stored in Result
|
|
43
43
|
transform_hook(:after_parse, parsed)
|
|
44
44
|
rescue JSON::ParserError => e
|
|
45
45
|
# :parse_error — if hook returns non-nil, it is used as fallback value
|
data/lib/active_harness/agent.rb
CHANGED
|
@@ -14,6 +14,7 @@ module ActiveHarness
|
|
|
14
14
|
input: nil,
|
|
15
15
|
context: {},
|
|
16
16
|
params: {},
|
|
17
|
+
memory: nil,
|
|
17
18
|
models: nil,
|
|
18
19
|
streams: {}
|
|
19
20
|
)
|
|
@@ -21,6 +22,7 @@ module ActiveHarness
|
|
|
21
22
|
input: input,
|
|
22
23
|
context: context,
|
|
23
24
|
params: params,
|
|
25
|
+
memory: memory,
|
|
24
26
|
models: models,
|
|
25
27
|
streams: streams
|
|
26
28
|
).call
|
|
@@ -49,7 +51,8 @@ module ActiveHarness
|
|
|
49
51
|
# -------------------------------------------------------------------------
|
|
50
52
|
attr_accessor :input,
|
|
51
53
|
:context,
|
|
52
|
-
:params
|
|
54
|
+
:params,
|
|
55
|
+
:memory
|
|
53
56
|
attr_reader :result,
|
|
54
57
|
:token_stream,
|
|
55
58
|
:event_stream
|
|
@@ -63,6 +66,7 @@ module ActiveHarness
|
|
|
63
66
|
input: nil,
|
|
64
67
|
context: {},
|
|
65
68
|
params: {},
|
|
69
|
+
memory: nil,
|
|
66
70
|
models: nil,
|
|
67
71
|
streams: {}
|
|
68
72
|
)
|
|
@@ -71,6 +75,7 @@ module ActiveHarness
|
|
|
71
75
|
normalize_input!
|
|
72
76
|
@context = context
|
|
73
77
|
@params = params
|
|
78
|
+
@memory = memory
|
|
74
79
|
@models_override = Array(models) if models
|
|
75
80
|
@token_stream = streams[:token]
|
|
76
81
|
@event_stream = streams[:agent]
|
|
@@ -140,13 +145,13 @@ module ActiveHarness
|
|
|
140
145
|
|
|
141
146
|
def build_result(response, entry, attempts, elapsed)
|
|
142
147
|
raw = response[:content]
|
|
143
|
-
|
|
148
|
+
processed = parse_output(raw)
|
|
144
149
|
usage = response[:usage]
|
|
145
150
|
|
|
146
151
|
Result.new(
|
|
147
152
|
input: @input,
|
|
148
153
|
output: raw,
|
|
149
|
-
|
|
154
|
+
processed: processed,
|
|
150
155
|
system_prompt: @system_prompt,
|
|
151
156
|
provider: entry[:provider],
|
|
152
157
|
model: entry[:model],
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Pipeline
|
|
2
|
+
|
|
3
|
+
A pipeline chains multiple agents and tribunals into a sequential workflow.
|
|
4
|
+
Each step receives the current payload, can transform it, and can stop the pipeline early.
|
|
5
|
+
|
|
6
|
+
## Basic usage
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
class SupportPipeline < ActiveHarness::Pipeline
|
|
10
|
+
step :translate, TranslationAgent
|
|
11
|
+
|
|
12
|
+
step :injection_guard do
|
|
13
|
+
use InjectionGuardAgent
|
|
14
|
+
stop_if ->(result) { result.processed["detected"] == true }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
step :safety_tribunal do
|
|
18
|
+
use SafetyTribunal
|
|
19
|
+
stop_if ->(result) { result.verdict == false }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
pipeline = SupportPipeline.new(input: "Hello", context: { user_id: 1 })
|
|
24
|
+
pipeline.call
|
|
25
|
+
|
|
26
|
+
pipeline.output # => final payload string (nil if stopped)
|
|
27
|
+
pipeline.stopped? # => false
|
|
28
|
+
pipeline.step_results # => { translate: <Result>, injection_guard: <Result>, ... }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Step types
|
|
32
|
+
|
|
33
|
+
There are two kinds of classes a step can use.
|
|
34
|
+
|
|
35
|
+
**Agent step** — runs the agent, takes `result.output` as the new payload:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
step :translate, TranslationAgent
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Tribunal step** — runs the tribunal, returns a `Result` with `processed["verdict"]`.
|
|
42
|
+
Payload is never updated by a tribunal step (it always has `stop_if`):
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
step :safety_tribunal do
|
|
46
|
+
use SafetyTribunal
|
|
47
|
+
stop_if ->(result) { result.processed["verdict"] == false }
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Payload propagation
|
|
52
|
+
|
|
53
|
+
The payload starts as the value passed to `input:` and flows through the steps:
|
|
54
|
+
|
|
55
|
+
| Condition | Payload after step |
|
|
56
|
+
|-----------|--------------------|
|
|
57
|
+
| Agent step, no `stop_if` | Updated to `result.output` |
|
|
58
|
+
| Agent step with `stop_if` | Unchanged (guard step) |
|
|
59
|
+
| Tribunal step | Unchanged |
|
|
60
|
+
|
|
61
|
+
After each step the result is also stored in `context[step_name]`,
|
|
62
|
+
so later steps can read earlier results via `@context[:translate]` etc.
|
|
63
|
+
|
|
64
|
+
## Stopping the pipeline
|
|
65
|
+
|
|
66
|
+
Any step can stop the pipeline by defining `stop_if`:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
step :injection_guard do
|
|
70
|
+
use InjectionGuardAgent
|
|
71
|
+
stop_if ->(result) { result.processed["detected"] == true }
|
|
72
|
+
end
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
When the condition is true:
|
|
76
|
+
- remaining steps are skipped
|
|
77
|
+
- `pipeline.stopped?` returns `true`
|
|
78
|
+
- `pipeline.stopped_at` holds the step name
|
|
79
|
+
- `pipeline.output` is `nil`
|
|
80
|
+
|
|
81
|
+
## Events and hooks
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
class SupportPipeline < ActiveHarness::Pipeline
|
|
85
|
+
on_agent_event do |event, result| ... end # fires for every agent inside
|
|
86
|
+
on_tribunal_event do |event, verdict| ... end # fires for every tribunal inside
|
|
87
|
+
on_pipeline_event do |event, *args| ... end # :before_step, :after_step, :stopped, :complete
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Runtime streams can be passed at construction time:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
SupportPipeline.new(
|
|
95
|
+
input: "...",
|
|
96
|
+
streams: { token: token_lambda, agent: agent_lambda }
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Memory
|
|
101
|
+
|
|
102
|
+
A memory object can be attached to the pipeline. It is loaded before the first step
|
|
103
|
+
and a record is written after successful completion (skipped if the pipeline stops early):
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
mem = ActiveHarness::Memory::JsonFile.new(file_name: "session_42")
|
|
107
|
+
|
|
108
|
+
SupportPipeline.new(input: "...", memory: mem).call
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Proposal: universal step interface
|
|
114
|
+
|
|
115
|
+
Currently `Pipeline::Step` special-cases two concrete classes: `Agent` and `Tribunal`.
|
|
116
|
+
This section explores making the pipeline open to any entity — a plain Ruby object,
|
|
117
|
+
a lambda, a nested pipeline, an HTTP call, a cache lookup — with no inheritance required.
|
|
118
|
+
|
|
119
|
+
The core question is: **what must a step return so the pipeline can drive it?**
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### Option A — Duck-type protocol (minimal change)
|
|
124
|
+
|
|
125
|
+
Define a lightweight protocol. Any object that satisfies it can be a step:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
# Contract: class responds to .new(input:, context:, params:, streams:)
|
|
129
|
+
# Instance responds to .call → returns an object with:
|
|
130
|
+
# .output — new payload (String or any value); nil keeps payload unchanged
|
|
131
|
+
# .stop? — true signals the pipeline to halt (replaces stop_if in step DSL)
|
|
132
|
+
|
|
133
|
+
class UppercaseStep
|
|
134
|
+
def initialize(input:, **); @input = input; end
|
|
135
|
+
|
|
136
|
+
def call
|
|
137
|
+
Pipeline::StepResult.new(output: @input.upcase)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
step :upcase, UppercaseStep
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`Pipeline::StepResult` would be a tiny value object:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
Pipeline::StepResult = Struct.new(:output, :stop, keyword_init: true) do
|
|
148
|
+
def stop? = stop
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Pros:** almost no change to existing code; agents and tribunals get thin adapters.
|
|
153
|
+
**Cons:** every custom step must construct `StepResult`; slightly more boilerplate.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### Option B — Callable (lambda / proc) as a step
|
|
158
|
+
|
|
159
|
+
Allow any `Proc`/`lambda` directly, without a wrapper class:
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
step :sanitize, ->(payload, ctx) { payload.strip }
|
|
163
|
+
|
|
164
|
+
step :length_guard, ->(payload, ctx) {
|
|
165
|
+
payload.length > 1000 ? Pipeline::Stop : payload
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Return value rules:
|
|
170
|
+
- any value other than `Pipeline::Stop` → becomes new payload
|
|
171
|
+
- `Pipeline::Stop` (or `Pipeline::Stop.new(reason)`) → halts the pipeline
|
|
172
|
+
|
|
173
|
+
**Pros:** perfect for simple transformations and guards; zero boilerplate.
|
|
174
|
+
**Cons:** no access to `params:` or `streams:` without enlarging the lambda signature;
|
|
175
|
+
harder to test in isolation.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
### Option C — Rack-style env hash
|
|
180
|
+
|
|
181
|
+
Each step receives and returns a single hash (`env`), similar to Rack middleware:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# env keys: :input, :output, :context, :params, :streams
|
|
185
|
+
# Return env to continue, return Pipeline::Stop to halt.
|
|
186
|
+
|
|
187
|
+
class UppercaseStep
|
|
188
|
+
def call(env)
|
|
189
|
+
env.merge(output: env[:input].upcase)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
class LengthGuard
|
|
194
|
+
def call(env)
|
|
195
|
+
env[:input].length > 1000 ? Pipeline::Stop.new("too long") : env
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Steps become stateless (no `initialize`) — a single instance can be reused:
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
UPCASE = UppercaseStep.new
|
|
204
|
+
|
|
205
|
+
step :upcase, UPCASE
|
|
206
|
+
step :length_guard, LengthGuard.new
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Pros:** stateless, composable, easy to test (`call(env)` in one line); nested
|
|
210
|
+
pipelines become trivial — a pipeline is just another object with `call(env)`.
|
|
211
|
+
**Cons:** largest departure from the current API; requires migrating Agent/Tribunal wrappers.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### Option D — `Pipeline::Callable` module (explicit contract)
|
|
216
|
+
|
|
217
|
+
A module that documents the contract and provides `StepResult` helpers:
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
class EnrichStep
|
|
221
|
+
include ActiveHarness::Pipeline::Callable # documents intent, no magic
|
|
222
|
+
|
|
223
|
+
def initialize(input:, context:, **); @input = input; @context = context; end
|
|
224
|
+
|
|
225
|
+
def call
|
|
226
|
+
data = ExternalService.fetch(@context[:user_id])
|
|
227
|
+
result(output: "#{@input} [enriched: #{data}]") # helper from Callable
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Agents and Tribunals include `Callable` automatically, so they work as before.
|
|
233
|
+
Any plain class can opt in with one `include`.
|
|
234
|
+
|
|
235
|
+
**Pros:** clear opt-in contract; helpers reduce boilerplate; IDE-friendly.
|
|
236
|
+
**Cons:** still requires `include`; doesn't help lambdas or nested pipelines directly.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
### Comparison
|
|
241
|
+
|
|
242
|
+
| | No inheritance | Lambda support | Nested pipeline | Migration cost |
|
|
243
|
+
|---|---|---|---|---|
|
|
244
|
+
| **A — duck type** | yes | with wrapper | yes | low |
|
|
245
|
+
| **B — lambda** | yes | native | no | low |
|
|
246
|
+
| **C — Rack env** | yes | yes (`.call`) | yes (trivially) | high |
|
|
247
|
+
| **D — module** | yes (opt-in) | with wrapper | yes | low |
|
|
248
|
+
|
|
249
|
+
**Recommendation:** start with **B** (lambda steps) for simple cases and **A** (duck-type
|
|
250
|
+
protocol + `StepResult`) for structured steps — both require minimal changes to the
|
|
251
|
+
existing engine. Option C is the most powerful but is a bigger refactor; consider it
|
|
252
|
+
if nested pipelines or stateless reuse become a real need.
|
|
@@ -7,7 +7,7 @@ module ActiveHarness
|
|
|
7
7
|
# class SupportPipeline < ActiveHarness::Pipeline
|
|
8
8
|
# step :injection_guard do
|
|
9
9
|
# use InjectionGuardAgent
|
|
10
|
-
# stop_if ->(result) { result.
|
|
10
|
+
# stop_if ->(result) { result.processed["detected"] == true }
|
|
11
11
|
# end
|
|
12
12
|
#
|
|
13
13
|
# step :translate, TranslationAgent # shorthand — no stop_if
|
|
@@ -44,7 +44,7 @@ module ActiveHarness
|
|
|
44
44
|
# Full block form:
|
|
45
45
|
# step :injection_guard do
|
|
46
46
|
# use InjectionGuardAgent
|
|
47
|
-
# stop_if ->(result) { result.
|
|
47
|
+
# stop_if ->(result) { result.processed["detected"] == true }
|
|
48
48
|
# end
|
|
49
49
|
def step(name, agent_class = nil, &block)
|
|
50
50
|
pipeline_config[:steps] << Pipeline::Step.new(name, agent_class, &block)
|
|
@@ -210,23 +210,13 @@ module ActiveHarness
|
|
|
210
210
|
end
|
|
211
211
|
|
|
212
212
|
def execute_step(step)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
).call
|
|
221
|
-
else
|
|
222
|
-
agent_streams = { token: @token_stream, agent: @agent_event_stream }.compact
|
|
223
|
-
step.agent_class.new(
|
|
224
|
-
input: @payload,
|
|
225
|
-
context: @context.dup,
|
|
226
|
-
params: @params,
|
|
227
|
-
streams: agent_streams
|
|
228
|
-
).call.result
|
|
229
|
-
end
|
|
213
|
+
streams = { token: @token_stream, agent: @agent_event_stream, tribunal: @tribunal_event_stream }.compact
|
|
214
|
+
step.agent_class.new(
|
|
215
|
+
input: @payload,
|
|
216
|
+
context: @context.dup,
|
|
217
|
+
params: @params,
|
|
218
|
+
streams: streams
|
|
219
|
+
).call.result
|
|
230
220
|
end
|
|
231
221
|
end
|
|
232
222
|
end
|
|
@@ -25,7 +25,14 @@ module ActiveHarness
|
|
|
25
25
|
"anthropic-version" => ANTHROPIC_VERSION
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
return call_streaming(
|
|
28
|
+
return call_streaming(
|
|
29
|
+
url: config.anthropic_api_url,
|
|
30
|
+
headers: headers,
|
|
31
|
+
body: body,
|
|
32
|
+
stream: stream,
|
|
33
|
+
provider: :anthropic,
|
|
34
|
+
model: model
|
|
35
|
+
) if stream
|
|
29
36
|
|
|
30
37
|
raw = post_json(URI(config.anthropic_api_url), headers: headers, body: body)
|
|
31
38
|
data = parse!(raw)
|
|
@@ -4,8 +4,20 @@ module ActiveHarness
|
|
|
4
4
|
# Minimal result wrapper returned by Agent#call.
|
|
5
5
|
#
|
|
6
6
|
# output — raw string from the provider
|
|
7
|
-
#
|
|
7
|
+
# processed — for format :json: a Ruby Hash/Array; for format :text: same as output
|
|
8
8
|
# usage — token counts: { input_tokens:, output_tokens:, total_tokens: } or nil for streaming
|
|
9
9
|
# cost — { input_cost:, output_cost:, total_cost: } in USD, or nil if pricing unavailable
|
|
10
|
-
Result = Struct.new(
|
|
10
|
+
Result = Struct.new(
|
|
11
|
+
:input,
|
|
12
|
+
:output,
|
|
13
|
+
:processed,
|
|
14
|
+
:system_prompt,
|
|
15
|
+
:provider, :model,
|
|
16
|
+
:temperature,
|
|
17
|
+
:model_list,
|
|
18
|
+
:attempts,
|
|
19
|
+
:execution_time,
|
|
20
|
+
:usage,
|
|
21
|
+
:cost,
|
|
22
|
+
keyword_init: true)
|
|
11
23
|
end
|
|
@@ -13,7 +13,7 @@ module ActiveHarness
|
|
|
13
13
|
# Receives the full results array; return value becomes #verdict.
|
|
14
14
|
# Takes priority over +verdict+ strategy if both are declared.
|
|
15
15
|
#
|
|
16
|
-
# process { |results| results.all? { |r| r.
|
|
16
|
+
# process { |results| results.all? { |r| r.processed["result"] == true } }
|
|
17
17
|
def process(&block)
|
|
18
18
|
tribunal_config[:process] = block
|
|
19
19
|
end
|
|
@@ -31,11 +31,11 @@ module ActiveHarness
|
|
|
31
31
|
# The block receives a single Result and must return a truthy/falsy value.
|
|
32
32
|
#
|
|
33
33
|
# verdict :unanimous do |result|
|
|
34
|
-
# result.
|
|
34
|
+
# result.processed["result"] == true
|
|
35
35
|
# end
|
|
36
36
|
#
|
|
37
37
|
# verdict :majority, may_fail: 1 do |result|
|
|
38
|
-
# result.
|
|
38
|
+
# result.processed["result"] == true
|
|
39
39
|
# end
|
|
40
40
|
VALID_STRATEGIES = %i[unanimous majority].freeze
|
|
41
41
|
|
|
@@ -2,7 +2,7 @@ module ActiveHarness
|
|
|
2
2
|
class Tribunal
|
|
3
3
|
# Instance-level process block — overrides class-level block.
|
|
4
4
|
#
|
|
5
|
-
# tribunal.process { |results| results.count { |r| r.
|
|
5
|
+
# tribunal.process { |results| results.count { |r| r.processed["ok"] } >= 2 }
|
|
6
6
|
def process(&block)
|
|
7
7
|
@process_block = block
|
|
8
8
|
self
|
|
@@ -17,14 +17,14 @@ module ActiveHarness
|
|
|
17
17
|
# timeout: 7
|
|
18
18
|
# )
|
|
19
19
|
# tribunal.on(:after_agent) { |result| puts result.model }
|
|
20
|
-
# tribunal.process { |results| results.all? { |r| r.
|
|
20
|
+
# tribunal.process { |results| results.all? { |r| r.processed["result"] == true } }
|
|
21
21
|
# tribunal.call
|
|
22
22
|
#
|
|
23
23
|
# Subclass with DSL:
|
|
24
24
|
# class ContentQualityTribunal < ActiveHarness::Tribunal
|
|
25
25
|
# agents PolitenessAgent, ConstructivenessAgent
|
|
26
26
|
# on(:after_agent) { |result| puts result.model }
|
|
27
|
-
# process { |results| results.all? { |r| r.
|
|
27
|
+
# process { |results| results.all? { |r| r.processed["result"] == true } }
|
|
28
28
|
# end
|
|
29
29
|
# ContentQualityTribunal.new(input: "...").call
|
|
30
30
|
#
|
|
@@ -48,7 +48,8 @@ module ActiveHarness
|
|
|
48
48
|
# -------------------------------------------------------------------------
|
|
49
49
|
attr_accessor :input,
|
|
50
50
|
:context,
|
|
51
|
-
:params
|
|
51
|
+
:params,
|
|
52
|
+
:memory
|
|
52
53
|
attr_reader :results,
|
|
53
54
|
:errors,
|
|
54
55
|
:verdict,
|
|
@@ -62,6 +63,7 @@ module ActiveHarness
|
|
|
62
63
|
input: nil,
|
|
63
64
|
context: {},
|
|
64
65
|
params: {},
|
|
66
|
+
memory: nil,
|
|
65
67
|
agents: nil,
|
|
66
68
|
timeout: 7,
|
|
67
69
|
streams: {},
|
|
@@ -72,6 +74,7 @@ module ActiveHarness
|
|
|
72
74
|
@input = input
|
|
73
75
|
@context = context
|
|
74
76
|
@params = params
|
|
77
|
+
@memory = memory
|
|
75
78
|
@agents = agents || config[:agents]
|
|
76
79
|
@timeout = timeout
|
|
77
80
|
@process_block = config[:process]
|
|
@@ -89,6 +92,17 @@ module ActiveHarness
|
|
|
89
92
|
@agent_execution_times = []
|
|
90
93
|
end
|
|
91
94
|
|
|
95
|
+
# Returns a Result with processed: { "verdict" => @verdict } so the pipeline
|
|
96
|
+
# can handle agents and tribunals through the same interface.
|
|
97
|
+
def result
|
|
98
|
+
Result.new(
|
|
99
|
+
input: @input,
|
|
100
|
+
output: nil,
|
|
101
|
+
processed: { "verdict" => @verdict },
|
|
102
|
+
execution_time: @execution_time
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
92
106
|
# Run all agents in parallel, then compute the verdict.
|
|
93
107
|
# Returns self so calls can be chained: tribunal.call.verdict
|
|
94
108
|
#
|
data/lib/active_harness.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.27
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- the-teacher
|
|
@@ -54,6 +54,7 @@ files:
|
|
|
54
54
|
- lib/active_harness/memory/adapter/postgresql.rb
|
|
55
55
|
- lib/active_harness/memory/adapter/sqlite.rb
|
|
56
56
|
- lib/active_harness/pipeline.rb
|
|
57
|
+
- lib/active_harness/pipeline/README.md
|
|
57
58
|
- lib/active_harness/pipeline/hooks.rb
|
|
58
59
|
- lib/active_harness/pipeline/step.rb
|
|
59
60
|
- lib/active_harness/providers/PROVIDER_CONTRACT.md
|