Class: Riffer::Guardrails::Runner
- Inherits:
-
Object
- Object
- Riffer::Guardrails::Runner
- Defined in:
- lib/riffer/guardrails/runner.rb,
sig/generated/riffer/guardrails/runner.rbs
Overview
Executes guardrails sequentially, passing each one's output to the next; if any blocks, execution stops and a tripwire is returned.
Instance Attribute Summary collapse
-
#context ⇒ Object
readonly
The context passed to guardrails.
-
#guardrail_configs ⇒ Array[Hash[Symbol, untyped]]
readonly
The guardrail configs to execute.
-
#phase ⇒ Symbol
readonly
The execution phase (:before or :after).
-
#tags ⇒ Hash[String, String]
readonly
The normalized per-call tags, stamped as
riffer.tag.*on guardrail spans.
Instance Method Summary collapse
-
#detect_changed_indices(old_data, new_data) ⇒ Array[Integer]
-- : (untyped, untyped) -> Array.
-
#execute_guardrail(guardrail, data, messages:) ⇒ Riffer::Guardrails::Result
-- : (Riffer::Guardrail, untyped, messages: Array?) -> Riffer::Guardrails::Result.
-
#guardrail_span_attributes(guardrail) ⇒ Hash[String, untyped]
-- : (Riffer::Guardrail) -> Hash[String, untyped].
-
#initialize(guardrail_configs, phase:, context: nil, tags: {}) ⇒ Runner
constructor
-- : (Array[Hash[Symbol, untyped]], phase: Symbol, ?context: untyped, ?tags: Hash[String, String]) -> void.
-
#instantiate_guardrail(config) ⇒ Riffer::Guardrail
-- : (Hash[Symbol, untyped]) -> Riffer::Guardrail.
-
#record_guardrail_outcome(span, result) ⇒ void
A block is a handled outcome, so its span status stays unset — an error span status is reserved for a raised exception.
-
#run(data, messages: nil) ⇒ [ untyped, Riffer::Guardrails::Tripwire?, Array[Riffer::Guardrails::Modification] ]
Runs the guardrails sequentially.
-
#run_guardrail_phase(guardrail, data, messages:) ⇒ Riffer::Guardrails::Result
-- : (Riffer::Guardrail, untyped, messages: Array?) -> Riffer::Guardrails::Result.
Constructor Details
#initialize(guardrail_configs, phase:, context: nil, tags: {}) ⇒ Runner
-- : (Array[Hash[Symbol, untyped]], phase: Symbol, ?context: untyped, ?tags: Hash[String, String]) -> void
21 22 23 24 25 26 |
# File 'lib/riffer/guardrails/runner.rb', line 21 def initialize(guardrail_configs, phase:, context: nil, tags: {}) @guardrail_configs = guardrail_configs @phase = phase @context = context @tags = end |
Instance Attribute Details
#context ⇒ Object (readonly)
The context passed to guardrails.
14 15 16 |
# File 'lib/riffer/guardrails/runner.rb', line 14 def context @context end |
#guardrail_configs ⇒ Array[Hash[Symbol, untyped]] (readonly)
The guardrail configs to execute.
8 9 10 |
# File 'lib/riffer/guardrails/runner.rb', line 8 def guardrail_configs @guardrail_configs end |
#phase ⇒ Symbol (readonly)
The execution phase (:before or :after).
11 12 13 |
# File 'lib/riffer/guardrails/runner.rb', line 11 def phase @phase end |
#tags ⇒ Hash[String, String] (readonly)
The normalized per-call tags, stamped as riffer.tag.* on guardrail spans.
17 18 19 |
# File 'lib/riffer/guardrails/runner.rb', line 17 def @tags end |
Instance Method Details
#detect_changed_indices(old_data, new_data) ⇒ Array[Integer]
-- : (untyped, untyped) -> Array
76 77 78 79 80 81 82 83 |
# File 'lib/riffer/guardrails/runner.rb', line 76 def detect_changed_indices(old_data, new_data) if old_data.is_a?(Array) && new_data.is_a?(Array) max_len = [old_data.length, new_data.length].max (0...max_len).select { |i| old_data[i] != new_data[i] } else (old_data == new_data) ? [] : [0] end end |
#execute_guardrail(guardrail, data, messages:) ⇒ Riffer::Guardrails::Result
-- : (Riffer::Guardrail, untyped, messages: Array?) -> Riffer::Guardrails::Result
87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/riffer/guardrails/runner.rb', line 87 def execute_guardrail(guardrail, data, messages:) Riffer::Tracing.in_span("execute_guardrail #{guardrail.name}", attributes: guardrail_span_attributes(guardrail), kind: :internal) do |span| result = run_guardrail_phase(guardrail, data, messages: ) record_guardrail_outcome(span, result) result rescue => error # The backend records the exception and error status on the re-raise; # error.type is the one semconv attribute it doesn't set. span.set_attribute("error.type", error.class.name) raise end end |
#guardrail_span_attributes(guardrail) ⇒ Hash[String, untyped]
-- : (Riffer::Guardrail) -> Hash[String, untyped]
115 116 117 118 119 120 |
# File 'lib/riffer/guardrails/runner.rb', line 115 def guardrail_span_attributes(guardrail) { "riffer.guardrail.name" => guardrail.name, "riffer.guardrail.phase" => phase.to_s }.merge(.transform_keys { |key| "riffer.tag.#{key}" }) end |
#instantiate_guardrail(config) ⇒ Riffer::Guardrail
-- : (Hash[Symbol, untyped]) -> Riffer::Guardrail
69 70 71 72 |
# File 'lib/riffer/guardrails/runner.rb', line 69 def instantiate_guardrail(config) = config[:options] #: Hash[Symbol, untyped] config[:class].new(**) end |
#record_guardrail_outcome(span, result) ⇒ void
This method returns an undefined value.
A block is a handled outcome, so its span status stays unset — an error span status is reserved for a raised exception.
: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Guardrails::Result) -> void
126 127 128 129 |
# File 'lib/riffer/guardrails/runner.rb', line 126 def record_guardrail_outcome(span, result) span.set_attribute("riffer.guardrail.action", result.type.to_s) span.set_attribute("riffer.tripwire.reason", result.data) if result.block? end |
#run(data, messages: nil) ⇒ [ untyped, Riffer::Guardrails::Tripwire?, Array[Riffer::Guardrails::Modification] ]
Runs the guardrails sequentially. For the :before phase data is the
messages array; for :after it's the response (and messages must be
provided).
: (untyped, ?messages: Array?) -> [untyped, Riffer::Guardrails::Tripwire?, Array[Riffer::Guardrails::Modification]]
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 |
# File 'lib/riffer/guardrails/runner.rb', line 33 def run(data, messages: nil) current_data = data modifications = [] #: Array[Riffer::Guardrails::Modification] guardrail_configs.each do |config| guardrail = instantiate_guardrail(config) result = execute_guardrail(guardrail, current_data, messages: ) if result.block? tripwire = Riffer::Guardrails::Tripwire.new( reason: result.data, guardrail: guardrail.class, phase: phase, metadata: result. ) return [current_data, tripwire, modifications] end if result.transform? modifications << Riffer::Guardrails::Modification.new( guardrail: guardrail.class, phase: phase, message_indices: detect_changed_indices(current_data, result.data) ) end current_data = result.data end [current_data, nil, modifications] end |
#run_guardrail_phase(guardrail, data, messages:) ⇒ Riffer::Guardrails::Result
-- : (Riffer::Guardrail, untyped, messages: Array?) -> Riffer::Guardrails::Result
102 103 104 105 106 107 108 109 110 111 |
# File 'lib/riffer/guardrails/runner.rb', line 102 def run_guardrail_phase(guardrail, data, messages:) case phase when :before guardrail.process_input(data, context: context) when :after guardrail.process_output(data, messages: || [], context: context) else raise Riffer::Error, "Unexpected guardrail phase: #{phase}. Valid phases: #{Riffer::Guardrails::PHASES.join(", ")}" end end |