Class: ActiveHarness::GuardRunner

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

Overview

Sends a single guard check request to the model and parses the response. Instantiated and called by GuardAgent.call — not used directly from the pipeline.

Constant Summary collapse

REQUIRED_FIELDS =

Required top-level fields in every guard JSON response.

%w[safe valid risk_level processed].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(guard_agent_class, payload:) ⇒ GuardRunner

Returns a new instance of GuardRunner.

Parameters:

  • guard_agent_class (Class)

    subclass of Agent used in guard mode

  • payload (Payload)

    the per-guard payload (input, context, language, options, …)



14
15
16
17
# File 'lib/active_harness/pipeline/guard_runner.rb', line 14

def initialize(guard_agent_class, payload:)
  @guard_agent_class = guard_agent_class
  @payload           = payload
end

Instance Attribute Details

#last_guard_promptObject (readonly)

Returns the value of attribute last_guard_prompt.



10
11
12
# File 'lib/active_harness/pipeline/guard_runner.rb', line 10

def last_guard_prompt
  @last_guard_prompt
end

#last_guard_responseObject (readonly)

Returns the value of attribute last_guard_response.



10
11
12
# File 'lib/active_harness/pipeline/guard_runner.rb', line 10

def last_guard_response
  @last_guard_response
end

Instance Method Details

#run(raw:, processed:) ⇒ InputResult

Parameters:

  • raw (String)

    original, untransformed input

  • processed (String)

    (possibly transformed) string to send to the model

Returns:



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/active_harness/pipeline/guard_runner.rb', line 22

def run(raw:, processed:)
  max_retries = @guard_agent_class.agent_config.fetch(:guard_retries) {
    ActiveHarness.config.guard_retries
  }

  system_msg    = build_system_message
  messages      = [system_msg, { role: "user", content: processed }]
  last_error    = nil

  (max_retries + 1).times do |attempt|
    if attempt > 0
      # Tell the model exactly what was wrong with its previous response.
      messages << { role: "assistant", content: @last_guard_response.to_s }
      messages << {
        role:    "user",
        content: "Your previous response was invalid: #{last_error}. " \
                 "Respond ONLY with valid JSON matching the required schema. " \
                 "Required fields: #{REQUIRED_FIELDS.join(', ')}."
      }
    end

    @last_guard_prompt = messages.dup

    model_cfg = @guard_agent_class.agent_config[:model]
    use_entry = model_cfg[:use]
    request   = ModelRequest.new(
      provider:        use_entry[:provider],
      model:           use_entry[:model],
      messages:        messages,
      response_format: :json
    )

    runner   = FallbackRunner.new(model_cfg)
    response = runner.run(request)

    @last_guard_response = response.content

    begin
      return parse_guard_response(raw, response.content)
    rescue Errors::GuardResponseError => e
      last_error = e.message
      # loop continues
    end
  end

  # All attempts exhausted — fail safe: treat as blocked.
  InputResult.new(
    raw:        raw,
    processed:  raw,
    safe:       false,
    valid:      false,
    risk_level: :high,
    errors:     ["Guard validation failed after #{max_retries + 1} attempt(s): #{last_error}"]
  )
end