Class: Insika::Safety::OutputValidator
- Inherits:
-
Object
- Object
- Insika::Safety::OutputValidator
- Defined in:
- lib/insika/safety/output_validator.rb
Overview
Post-turn output validator (RFC-0009 §3.2 / D3). 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
(D3): 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) ⇒ OutputValidator
constructor
ask_factory(optional): ->(config) { ->(prompt)text | nil }, built by the Safety::Factory from the utility_model.
Constructor Details
#initialize(ask_factory: nil) ⇒ OutputValidator
ask_factory (optional): ->(config) { ->(prompt)text | nil }, built by the
Safety::Factory from the utility_model. nil = deterministic only.
30 31 32 |
# File 'lib/insika/safety/output_validator.rb', line 30 def initialize(ask_factory: nil) @ask_factory = ask_factory 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).
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/insika/safety/output_validator.rb', line 36 def call(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)) 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 |