Class: Insika::Safety::OutputValidator
- Inherits:
-
Object
- Object
- Insika::Safety::OutputValidator
- Defined in:
- lib/insika/safety/output_validator.rb
Overview
Post-turn output validator. Runs as an after_task hook:
with the FINAL assistant text assembled, it does a richer check than the
stream filter can — an unverified discount/price promise, a tone slip — and
FLAGS it (audit) rather than blocking. It is honest about the streaming limit
for a streaming agent the text is already out the door, so this is
detection, not prevention; true pre-emission blocking needs streaming:false
(a per-profile override left for a later slice).
Contract: as an after_task hook it receives the TurnState (the :task subject),
appends any finding to state.guardrail_flags, and returns the state
unchanged. The Executor (single emitter) turns each flag into a
:guardrail_flagged event after the turn — the hook itself never emits.
Two tiers, like the input side:
1. deterministic — residual PII/secret in the final text (a belt to the
stream filter's suspenders) via Detectors;
2. LLM validator (opt-in, same moderator model) — promise/tone judgment the
regex can't make. Pure over an injected `ask`; fail-open on any error.
Instance Method Summary collapse
-
#call(state) ⇒ Object
after_task hook body.
-
#initialize(ask_factory: nil, grounding: nil) ⇒ OutputValidator
constructor
ask_factory(optional): ->(config) { ->(prompt)text | nil }, built by the Safety::Factory from the utility_model.
Constructor Details
#initialize(ask_factory: nil, grounding: nil) ⇒ OutputValidator
ask_factory (optional): ->(config) { ->(prompt)text | nil }, built by the
Safety::Factory from the utility_model. nil = deterministic only.
grounding (optional): a GroundingValidator — its step runs
FIRST in #call, before the config.output gate (D9: grounding is
evidence integrity, independent of the guardrails opt-in).
33 34 35 36 |
# File 'lib/insika/safety/output_validator.rb', line 33 def initialize(ask_factory: nil, grounding: nil) @ask_factory = ask_factory @grounding = grounding end |
Instance Method Details
#call(state) ⇒ Object
after_task hook body. Idempotent and defensive: never raises out (a hook error must not fail a committed turn). Grounding runs BEFORE the output gate so an agent with guardrails off and grounding on still gets the check.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/insika/safety/output_validator.rb', line 41 def call(state) state = @grounding&.call(state) || state config = Config.from_profile(state.profile) return state unless config.output text = state.response_content.to_s return state if text.empty? flags = [] flags.concat(deterministic_flags(text, config.corpus)) flags.concat(llm_flags(text, config)) if config.moderator? state.guardrail_flags = Array(state.guardrail_flags) + flags unless flags.empty? state rescue StandardError state # fail-open: auditing must never break a completed turn end |