Skip to content
GitHub

Architecture

Internal design of the lexigram-ai-guard package.


The guardrail layer sits between user input and the LLM, and between the LLM response and the caller:

flowchart LR
    User[User / Client]
    IG[Input Guards<br/>PromptInjectionDetector · PIIDetector<br/>InputLengthGuard · TopicRestrictor<br/>LLMInjectionDetector · LLMJailbreakDetector]
    LLM[LLM Provider]
    OG[Output Guards<br/>PIIRedactor · OutputLengthGuard]
    Response[Response to Caller]

    User --> IG
    IG -->|blocked| User
    IG -->|passed / redacted| LLM
    LLM --> OG
    OG -->|blocked| LLM
    OG -->|passed / redacted| Response

The pipeline uses a fire-and-forget pattern per check — each guard returns a verdict (PASS, BLOCK, WARN, REDACT) and the aggregate action drives the outcome. Blocked content is rejected before reaching the LLM; redacted content is sanitised in-flight.


flowchart BT
    PP[GuardPipeline]
    IN[Input Guards<br/>AbstractInputGuard]
    OUT[Output Guards<br/>AbstractOutputGuard]
    GR[GuardCheckResult<br/>passed · action · details · redacted_content]
    AG[AggregateGuardResult<br/>passed · action · final_content]

    PP --> IN
    PP --> OUT
    IN --> GR
    OUT --> GR
    AG -->|aggregates| GR

Each implements InputGuardProtocol.check(content, *, messages, metadata) → Result[GuardResultProtocol, GuardError] and subclasses AbstractInputGuard. Guards run in registration order; a BLOCK verdict stops subsequent guards. REDACT passes the redacted content to the next guard.

Each implements OutputGuardProtocol.check(content, *, original_input, metadata) → Result[GuardResultProtocol, GuardError] and subclasses AbstractOutputGuard. Same sequential semantics as input guards but operate on LLM response content.

GuardPipeline holds ordered lists of input and output guards. Exposes check_input() and check_output() — both return Result[AggregateGuardResult, GuardError]. Supports parallel=True for concurrent guard execution (disables redaction chaining).

ActionSeveritypassedDescription
PASS0TrueContent is safe
WARN1TrueContent is borderline, allowed
REDACT2TrueContent sanitised, allowed
BLOCK3FalseContent unsafe, rejected

sequenceDiagram
    participant Client
    participant PP as GuardPipeline
    participant IG as Input Guards
    participant LLM as LLM Provider
    participant OG as Output Guards
    participant Response

    Client->>PP: check_input(content)
    PP->>IG: guard[0].check(content)
    IG-->>PP: GuardCheckResult (PASS / BLOCK / WARN / REDACT)
    alt BLOCK
        PP-->>Client: AggregateGuardResult(blocked=True)
    else WARN or REDACT or PASS
        PP->>IG: guard[N].check(redacted_content)
        IG-->>PP: GuardCheckResult
        PP-->>Client: AggregateGuardResult(final_content)
    end
    Client->>LLM: send safe content
    LLM-->>Client: response
    Client->>PP: check_output(response)
    PP->>OG: guard[0].check(response)
    OG-->>PP: GuardCheckResult
    alt BLOCK
        PP-->>Client: AggregateGuardResult(blocked=True)
    else PASS / REDACT
        PP-->>Client: AggregateGuardResult(final_content)
    end
    Client->>Response: return final content

GuardDirectionMethodActionsSource
PromptInjectionDetectorInputRegex heuristics (override, roleplay, exfiltration patterns)block, warninput/injection.py
PIIDetectorInputRegex scan (EMAIL, PHONE, SSN, CREDIT_CARD, IP_ADDRESS, AWS_KEY)block, warn, redactinput/pii.py
InputLengthGuardInputCharacter countblock, warninput/length.py
TopicRestrictorInputKeyword / phrase word-boundary matchblock, warninput/topic.py
LLMInjectionDetectorInputLLM classifier (fast model judge)block, warninput/llm_injection.py
LLMJailbreakDetectorInputLLM classifier (5 jailbreak categories)block, warninput/llm_jailbreak.py
PIIRedactorOutputRegex scan + [REDACTED:<TYPE>] substitutionredact, blockoutput/pii_redactor.py
OutputLengthGuardOutputCharacter countblock, warnoutput/length.py

Heuristic guards (first four input guards) run synchronously under an async wrapper. LLM-based guards are optional — enabled via GuardConfig.enable_llm_guards=True and resolved from the container during the provider boot() phase.


sequenceDiagram
    participant App as LexigramApplication
    participant GP as GuardProvider
    participant C as Container
    participant LLMClient as LLMClientProtocol (optional)

    App->>GP: GuardProvider(config)
    App->>GP: register(container)
    GP->>GP: _build_pipeline() — heuristic guards
    GP->>C: singleton(GuardConfig)
    GP->>C: singleton(GuardPipeline)
    App->>GP: boot(container)
    opt enable_llm_guards=True
        GP->>C: resolve_optional(LLMClientProtocol)
        alt LLM available
            GP->>GP: add_input_guard(LLMInjectionDetector)
            GP->>GP: add_input_guard(LLMJailbreakDetector)
        else LLM not registered
            GP->>GP: skip — log warning
        end
    end
    App->>GP: shutdown()
    Note over GP: No cleanup required<br/>(in-process domain provider)

When GuardConfig.enabled is False, the provider registers a no-op pipeline with zero guards. LLM-based guards are appended during boot() after heuristic guards — they are not part of _build_pipeline().


The package re-exports its protocols from lexigram.contracts.ai.guards for convenience at lexigram.ai.guard.protocols:

ProtocolContracts SourcePurpose
InputGuardProtocolcontracts/ai/guards.py:61Input guard contract (check with messages+metadata)
OutputGuardProtocolcontracts/ai/guards.py:94Output guard contract (check with original_input+metadata)
GuardPipelineProtocolcontracts/ai/guards.py:127Pipeline contract (check_input + check_output)
GuardResultProtocolcontracts/ai/guards.py:27Immutable guard result with action+redacted_content

The package also uses:

  • GuardConfigBaseConfig subclass with env-prefix LEX_AI_GUARD__
  • GuardError (contracts base) — extended by GuardConfigurationError, GuardPipelineError
  • GuardCheckResult / AggregateGuardResult — frozen dataclass value objects
  • InputGuardTriggeredEvent / OutputGuardTriggeredEvent — domain events for audit
  • GuardInputCheckedHook / GuardOutputCheckedHook / GuardPipelineCompletedHook — hook payloads
  • guarded decorator — attaches guard metadata to async functions (marker only, container resolves the pipeline)

PointMechanism
Custom input guardSubclass AbstractInputGuard, implement check()
Custom output guardSubclass AbstractOutputGuard, implement check()
Custom heuristic detectorAdd regex/pattern class and wrap in AbstractInputGuard
Custom LLM-based detectorImplement LLMClientProtocol and subclass AbstractInputGuard
Custom response handlerInspect AggregateGuardResult.action in calling code
Pipeline orderingReorder guards in provider _build_pipeline() or append via add_input_guard() / add_output_guard()
Configuration overrideGuardConfig fields + environment variables (LEX_AI_GUARD__*)
Audit integrationSubscribe to InputGuardTriggeredEvent / OutputGuardTriggeredEvent
Hook integrationRegister listeners for GuardInputCheckedHook / GuardOutputCheckedHook / GuardPipelineCompletedHook
Guarded decoratorAnnotate service methods with @guarded(input_guards=[...], output_guards=[...])