Class: ActiveHarness::Agent

Inherits:
Object
  • Object
show all
Defined in:
lib/active_harness/agent.rb

Overview

DSL base class for agents and guard agents — one class, two roles.

Main agent (class-method style — one-liner):

SupportAgent.call(input: "hi", context: {})

Main agent (instance style — store params, call later):

agent  = SupportAgent.new(input: "hi", language: :ru, context: {})
result = agent.call
result = agent.call(constraints: { max_input_length: 200 })  # merge extra params

Guard agent (same class, called positionally by the engine guard chain):

class InjectionGuard < ActiveHarness::Agent
  model { use provider: :openai, model: "gpt-4.1-mini" }
  system_prompt MyGuardPrompt
  system_language :en
end

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input: nil, context: {}, constraints: {}, language: nil, translate: nil, options: {}) ⇒ Agent

Build an agent instance with preset call parameters. Any keyword accepted by .call is valid here.

agent  = SupportAgent.new(input: "hello", language: :ru)
result = agent.call                              # use stored params
result = agent.call(constraints: { max_input_length: 200 })  # merge overrides


213
214
215
216
217
218
219
220
221
222
223
# File 'lib/active_harness/agent.rb', line 213

def initialize(input: nil, context: {}, constraints: {}, language: nil,
               translate: nil, options: {})
  @stored_params = {
    input:       input,
    context:     context,
    constraints: constraints,
    language:    language,
    translate:   translate,
    options:     options
  }
end

Class Attribute Details

.last_run_promptObject (readonly)

Debug info from the most recent guard-mode .call (class-level).



169
170
171
# File 'lib/active_harness/agent.rb', line 169

def last_run_prompt
  @last_run_prompt
end

.last_run_responseObject (readonly)

Debug info from the most recent guard-mode .call (class-level).



169
170
171
# File 'lib/active_harness/agent.rb', line 169

def last_run_response
  @last_run_response
end

Class Method Details

.after(hook = nil, guard_name = nil, &block) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
# File 'lib/active_harness/agent.rb', line 156

def after(hook = nil, guard_name = nil, &block)
  if hook
    key = (hook == :guard && guard_name) ? :"after_guard_#{guard_name}" : :"after_#{hook}"
    agent_config[:callbacks] ||= {}
    agent_config[:callbacks][key] ||= []
    agent_config[:callbacks][key] << block
  else
    agent_config[:guard_after_callbacks] ||= []
    agent_config[:guard_after_callbacks] << block
  end
end

.agent_configObject

Each subclass gets its own isolated config hash.



172
173
174
# File 'lib/active_harness/agent.rb', line 172

def agent_config
  @agent_config ||= {}
end

.before(hook = nil, guard_name = nil, &block) ⇒ Object

Callbacks ———————————————————— All callbacks receive TWO arguments — (payload, current_value) — and must return the new current_value. The payload is read-only context; current_value is what gets threaded through the pipeline stage.

Main-agent pipeline hooks:

before(:guards)  { |payload, input|    input.strip   }  # String  → String
after(:guards)   { |payload, result|   result        }  # InputResult → InputResult
before(:guard, :injection_guard) { |payload, input| input.downcase }
after(:guard,  :injection_guard) { |payload, result| result }
before(:request) { |payload, prompt|   prompt        }  # Hash → Hash
after(:request)  { |payload, response| response      }  # ModelResponse → ModelResponse

Guard-mode hooks (when this class acts as a guard):

before { |payload, input|  input.strip  }  # String → String
after  { |payload, result| result        }  # InputResult → InputResult


144
145
146
147
148
149
150
151
152
153
154
# File 'lib/active_harness/agent.rb', line 144

def before(hook = nil, guard_name = nil, &block)
  if hook
    key = (hook == :guard && guard_name) ? :"before_guard_#{guard_name}" : :"before_#{hook}"
    agent_config[:callbacks] ||= {}
    agent_config[:callbacks][key] ||= []
    agent_config[:callbacks][key] << block
  else
    agent_config[:guard_before_callbacks] ||= []
    agent_config[:guard_before_callbacks] << block
  end
end

.call(*args, input: nil, context: {}, constraints: {}, language: nil, translate: nil, options: {}) ⇒ Object

Entry point.

Main mode: MyAgent.call(input: “…”, context: {}, language: :en, translate: fn) Guard mode: MyGuard.call(payload) — called by the engine guard chain

MyGuard.call("raw string")        — manual / test use
MyGuard.call(prev_input_result)   — manual / test use


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/active_harness/agent.rb', line 26

def call(*args, input: nil, context: {}, constraints: {}, language: nil, translate: nil, options: {})
  if args.any?
    first   = args.first
    payload = first.is_a?(Payload) ? first : Payload.new(
      input:     first.is_a?(InputResult) ? first.processed : first.to_s,
      context:   context,
      language:  language,
      translate: translate,
      options:   options
    )
    call_as_guard(payload)
  else
    Engine.new(agent_config).call(
      input: input, context: context, constraints: constraints,
      language: language, translate: translate
    )
  end
end

.constraint(name, value) ⇒ Object

Declares a default constraint for this agent. Call-time constraints (passed to .call) override agent-level defaults.

Supported constraints:

constraint :max_input_length, 500   # reject inputs longer than 500 chars


97
98
99
100
# File 'lib/active_harness/agent.rb', line 97

def constraint(name, value)
  agent_config[:constraints]       ||= {}
  agent_config[:constraints][name]   = value
end

.default_error_answer(text_or_callable) ⇒ Object

Accepts a String or a callable (proc/lambda). When a callable is given, it is called with the Payload at block time:

default_error_answer ->(payload) { payload.translate&.call("my.key") || "Fallback text" }


111
112
113
# File 'lib/active_harness/agent.rb', line 111

def default_error_answer(text_or_callable)
  agent_config[:default_error_answer] = text_or_callable
end

.guard(klass, name: nil, **options) ⇒ Object

Registers a guard class for the safety chain (main agent only). Optional per-registration options are forwarded to the guard at call time. If name: is not given, defaults to the class name as a symbol (e.g., :InjectionGuard).

Examples:

guard InjectionGuard
guard InjectionGuard, name: :injection_guard
guard TopicGuard, name: :topic, allowed_topics: [:ruby, :programming]


59
60
61
62
63
# File 'lib/active_harness/agent.rb', line 59

def guard(klass, name: nil, **options)
  guard_name = (name || klass.name).to_sym
  agent_config[:guards] ||= []
  agent_config[:guards] << { klass: klass, options: options, name: guard_name }
end

.guard_retries(n) ⇒ Object

How many times to retry when a guard returns invalid/unparseable JSON. Overrides ActiveHarness.config.guard_retries for this specific guard.



104
105
106
# File 'lib/active_harness/agent.rb', line 104

def guard_retries(n)
  agent_config[:guard_retries] = n
end

.model(&block) ⇒ Object



65
66
67
68
69
# File 'lib/active_harness/agent.rb', line 65

def model(&block)
  config = ModelConfig.new
  config.instance_eval(&block)
  agent_config[:model] = config.to_h
end

.output(type, schema: nil) ⇒ Object



83
84
85
86
# File 'lib/active_harness/agent.rb', line 83

def output(type, schema: nil)
  agent_config[:output_type]   = type
  agent_config[:output_schema] = schema
end

.param(name, required: false) ⇒ Object



71
72
73
74
75
76
# File 'lib/active_harness/agent.rb', line 71

def param(name, required: false)
  agent_config[:params]          ||= []
  agent_config[:required_params] ||= []
  agent_config[:params] << { name: name, required: required }
  agent_config[:required_params] << name if required
end

.risk_tolerance(level) ⇒ Object



88
89
90
# File 'lib/active_harness/agent.rb', line 88

def risk_tolerance(level)
  agent_config[:risk_tolerance] = level
end

.setup(&block) ⇒ Object

Initializer hook — runs once before the pipeline starts. Receives the Payload and must return it (optionally modified).

Example:

setup do |payload|
  payload.meta[:started_at] = Time.now
  payload.context[:locale]  = determine_locale(payload.language)
  payload
end


124
125
126
# File 'lib/active_harness/agent.rb', line 124

def setup(&block)
  agent_config[:setup] = block
end

.system_language(lang) ⇒ Object

DSL ——————————————————————-



47
48
49
# File 'lib/active_harness/agent.rb', line 47

def system_language(lang)
  agent_config[:system_language] = lang
end

.system_prompt(text) ⇒ Object Also known as: prompt



78
79
80
# File 'lib/active_harness/agent.rb', line 78

def system_prompt(text)
  agent_config[:system_prompt] = text
end

Instance Method Details

#call(**overrides) ⇒ Object

Execute the agent. overrides is merged into the stored params —any key present in overrides wins.



227
228
229
230
231
232
233
234
# File 'lib/active_harness/agent.rb', line 227

def call(**overrides)
  params = @stored_params.merge(overrides) do |_key, stored, override|
    # For Hash values (context, constraints) do a shallow merge so callers
    # can add keys without replacing the whole hash.
    (stored.is_a?(Hash) && override.is_a?(Hash)) ? stored.merge(override) : override
  end
  self.class.call(**params)
end