Class: Insika::Safety::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/safety/config.rb

Overview

Per-agent guardrail configuration, read from profile.guardrails. OPT-IN like capabilities: an agent that says nothing gets the CONSERVATIVE default — deterministic detectors ON, LLM moderator OFF.

Tolerant of the JSON round-trip (string OR symbol keys / values), same discipline as ModelPolicy — the StoredProfileSource persists this as a plain Hash and it comes back stringified.

guardrails: {
input:      true|false,        # run the input guardrail middleware (default true)
output:     true|false,        # run the output filter + validator (default true)
moderator:  "provider/model"|nil,  # LLM moderator model; nil = deterministic only
strictness: "low"|"medium"|"high", # which input categories fire (default medium)
responses:  { <category> => "<safe reply>", ... }  # per-agent override, see below
corpora:    { "languages" => ["en"], "extra" => { "abuse" => ["/\\bdupa\\b/i"] } }
}

corpora is the removability knob for the shipped pt-BR corpus: languages filters the shipped families (nil = all, [] = none, ["en"] = the EN-only corpus — dropping the pt-BR input heuristics AND the CPF/CNPJ output redaction, a documented consequence), extra adds source-string patterns per family. Absent = the full shipped default, byte-for-byte today's behavior (parity). Validation fails loud (Insika::ValidationError naming the value): the doctor's guardrail-corpora check compiles every declaration, so a typo'd language surfaces at boot — insika doctor exits non-zero — never mid-turn.

responses is the CONFIGURATION-OVER-CONVENTION knob. The engine ships neutral built-in refusals (Safety::SafeResponses::DEFAULTS), but this is OSS across arbitrary businesses/languages, so we never hard-bake tone: an agent overrides the safe reply per category (injection/sexual/abuse/escalate/ …) or sets a single catch-all default. Resolution order (SafeResponses.for): agentagent → built-in → built-in.

Constant Summary collapse

STRICTNESS_CATEGORIES =

strictness -> input categories that the deterministic scan runs. low = only injection (+ output redaction) — highest confidence, fewest FPs medium = injection + sexual + abuse (the default) high = same families, reserved for future broader lists

{
  low: %i[injection],
  medium: %i[injection sexual abuse],
  high: %i[injection sexual abuse]
}.freeze
DEFAULT_STRICTNESS =
:medium

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input:, output:, moderator:, strictness:, responses: {}, corpora: nil) ⇒ Config

Returns a new instance of Config.



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/insika/safety/config.rb', line 53

def initialize(input:, output:, moderator:, strictness:, responses: {}, corpora: nil)
  @input = input
  @output = output
  @moderator = moderator
  @strictness = strictness
  @responses = responses # { "category" => "safe reply" }, agent override map
  @corpora = corpora     # { "languages" => [...]?, "extra" => {...}? } | nil (nil = the shipped default)
  # Built ONCE at construction: the compiled corpus the whole turn reads.
  @corpus = Corpus.compile(languages: corpora && corpora["languages"],
                           extra: (corpora && corpora["extra"]) || {})
end

Instance Attribute Details

#corporaObject (readonly)

Returns the value of attribute corpora.



51
52
53
# File 'lib/insika/safety/config.rb', line 51

def corpora
  @corpora
end

#inputObject (readonly)

Returns the value of attribute input.



51
52
53
# File 'lib/insika/safety/config.rb', line 51

def input
  @input
end

#moderatorObject (readonly)

Returns the value of attribute moderator.



51
52
53
# File 'lib/insika/safety/config.rb', line 51

def moderator
  @moderator
end

#outputObject (readonly)

Returns the value of attribute output.



51
52
53
# File 'lib/insika/safety/config.rb', line 51

def output
  @output
end

#responsesObject (readonly)

Returns the value of attribute responses.



51
52
53
# File 'lib/insika/safety/config.rb', line 51

def responses
  @responses
end

#strictnessObject (readonly)

Returns the value of attribute strictness.



51
52
53
# File 'lib/insika/safety/config.rb', line 51

def strictness
  @strictness
end

Class Method Details

.from_hash(raw) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
# File 'lib/insika/safety/config.rb', line 69

def self.from_hash(raw)
  h = symbolize(raw)
  new(
    input: bool(h.fetch(:input, true)),
    output: bool(h.fetch(:output, true)),
    moderator: presence(h[:moderator]),
    strictness: normalize_strictness(h[:strictness]),
    responses: normalize_responses(h[:responses]),
    corpora: normalize_corpora(h[:corpora])
  )
end

.from_profile(profile) ⇒ Object

Builds a Config from a profile. A nil/empty guardrails -> the conservative default (see from_hash).



67
# File 'lib/insika/safety/config.rb', line 67

def self.from_profile(profile) = from_hash(profile.guardrails)

Instance Method Details

#corpusObject

The compiled corpus for this agent — never nil: absent corpora -> the full shipped default.



83
# File 'lib/insika/safety/config.rb', line 83

def corpus = @corpus

#corpus_languagesObject

The resolved corpus languages (for the doctor's enumeration, C3): nil corpora (or nil languages) = ALL shipped languages.



87
88
89
90
# File 'lib/insika/safety/config.rb', line 87

def corpus_languages
  langs = @corpora && @corpora["languages"]
  langs.nil? ? Corpus::DEFAULTS.keys : langs
end

#enabled?Boolean

A guardrail is fully off only when BOTH sides are disabled — cheap early-out.

Returns:

  • (Boolean)


96
# File 'lib/insika/safety/config.rb', line 96

def enabled? = @input || @output

#input_categoriesObject

Input categories the deterministic scan should run, per strictness.



93
# File 'lib/insika/safety/config.rb', line 93

def input_categories = STRICTNESS_CATEGORIES.fetch(@strictness, STRICTNESS_CATEGORIES[DEFAULT_STRICTNESS])

#moderator?Boolean

Returns:

  • (Boolean)


98
# File 'lib/insika/safety/config.rb', line 98

def moderator? = !@moderator.nil?