Class: Vangrail::Engine

Inherits:
Object
  • Object
show all
Defined in:
lib/vangrail/engine.rb

Overview

Runs ordered rails over text and reports one Result.

The rules are short enough to state in full:

  • Rails run in the order given. The first :blocked ends the pass.
  • A :modified result replaces the text for every rail after it, and the engine reports :modified unless something later blocks.
  • A rail that raises is not a rail that passed. on_error: :allow (the default) keeps going and marks the pass uncertain; :block stops.
  • An empty rail list returns :passed with certain false. Nothing ran.

Threading rewrites through later rails is the part worth being explicit about: a redaction rail that runs before a policy rail should have the policy rail judge the redacted text, not the original.

Defined Under Namespace

Classes: Screening

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input: [], context: [], output: [], on_error: :allow, cache: true) ⇒ Engine

Returns a new instance of Engine.

Raises:

  • (ArgumentError)


26
27
28
29
30
31
32
33
34
# File 'lib/vangrail/engine.rb', line 26

def initialize(input: [], context: [], output: [], on_error: :allow, cache: true)
  @input_rails = Array(input)
  @context_rails = Array(context)
  @output_rails = Array(output)
  @on_error = on_error.to_sym
  raise ArgumentError, 'on_error must be :allow or :block' unless %i[allow block].include?(@on_error)

  @cache = cache.is_a?(ResultCache) ? cache : (ResultCache.new if cache)
end

Instance Attribute Details

#cacheObject (readonly)

Returns the value of attribute cache.



24
25
26
# File 'lib/vangrail/engine.rb', line 24

def cache
  @cache
end

#context_railsObject (readonly)

Returns the value of attribute context_rails.



24
25
26
# File 'lib/vangrail/engine.rb', line 24

def context_rails
  @context_rails
end

#input_railsObject (readonly)

Returns the value of attribute input_rails.



24
25
26
# File 'lib/vangrail/engine.rb', line 24

def input_rails
  @input_rails
end

#on_errorObject (readonly)

Returns the value of attribute on_error.



24
25
26
# File 'lib/vangrail/engine.rb', line 24

def on_error
  @on_error
end

#output_railsObject (readonly)

Returns the value of attribute output_rails.



24
25
26
# File 'lib/vangrail/engine.rb', line 24

def output_rails
  @output_rails
end

Instance Method Details

#check_context(text, **context) ⇒ Object

One retrieved document, before it goes anywhere near a prompt.



45
46
47
# File 'lib/vangrail/engine.rb', line 45

def check_context(text, **context)
  run(:context, context_rails, text, context)
end

#check_input(text, context = {}) ⇒ Object



36
37
38
# File 'lib/vangrail/engine.rb', line 36

def check_input(text, context = {})
  run(:input, input_rails, text, context)
end

#check_output(text, user_input: nil, passages: nil, **context) ⇒ Object



40
41
42
# File 'lib/vangrail/engine.rb', line 40

def check_output(text, user_input: nil, passages: nil, **context)
  run(:output, output_rails, text, context.merge(user_input: user_input, passages: passages))
end

#describeObject



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/vangrail/engine.rb', line 129

def describe
  return 'no rails' if empty?

  parts = []
  parts << "input=#{rail_names(:input).join('+')}" unless input_rails.empty?
  parts << "context=#{rail_names(:context).join('+')}" unless context_rails.empty?
  parts << "output=#{rail_names(:output).join('+')}" unless output_rails.empty?
  parts << "on_error=#{on_error}"
  parts << 'offline' if offline?
  parts.join(' ')
end

#empty?Boolean

Returns:

  • (Boolean)


114
115
116
# File 'lib/vangrail/engine.rb', line 114

def empty?
  input_rails.empty? && context_rails.empty? && output_rails.empty?
end

#offline?Boolean

True when every configured rail decides without a network call, which is the only case where an unreachable endpoint cannot weaken the check.

Returns:

  • (Boolean)


109
110
111
112
# File 'lib/vangrail/engine.rb', line 109

def offline?
  all = input_rails + context_rails + output_rails
  !all.empty? && all.all?(&:offline?)
end

#rail_names(side) ⇒ Object



103
104
105
# File 'lib/vangrail/engine.rb', line 103

def rail_names(side)
  rails(side).map(&:name)
end

#rails(side) ⇒ Object



95
96
97
98
99
100
101
# File 'lib/vangrail/engine.rb', line 95

def rails(side)
  case side.to_sym
  when :input then input_rails
  when :context then context_rails
  else output_rails
  end
end

#screen(documents, **context) ⇒ Object

Screens a set of retrieved documents and reports what survived.

A document that fails is dropped rather than failing the whole turn. One poisoned wiki page should cost a reader that page, not their answer, and an application that refuses outright teaches its readers that the guardrail is the problem.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/vangrail/engine.rb', line 55

def screen(documents, **context)
  kept = []
  rejected = []
  uncertain = nil

  Array(documents).each_with_index do |document, index|
    result = check_context(text_of(document), **context, document: document, index: index)
    uncertain ||= result unless result.certain?
    if result.blocked?
      rejected << { document: document, result: result }
    else
      kept << (result.modified? ? replace_text(document, result.content) : document)
    end
  end

  Screening.new(kept: kept, rejected: rejected, certain: uncertain.nil?, reason: uncertain&.reason)
end

#to_hObject



118
119
120
121
122
123
124
125
126
127
# File 'lib/vangrail/engine.rb', line 118

def to_h
  {
    'input' => rail_names(:input),
    'context' => (rail_names(:context) unless context_rails.empty?),
    'output' => rail_names(:output),
    'on_error' => on_error.to_s,
    'offline' => offline?,
    'cache' => cache&.to_h
  }.compact
end