|
| 1 | +## Overview |
| 2 | + |
| 3 | +The **LLM‑Router** project ships with a modular plugin system that lets you plug‑in **anonymizers** |
| 4 | +(also called *maskers*) and **guardrails** into request‑processing pipelines. |
| 5 | +Each plugin implements a tiny, well‑defined interface (`apply`) and can be composed |
| 6 | +in an ordered list to form a **pipeline**. The pipelines are instantiated by the |
| 7 | +`MaskerPipeline` and `GuardrailPipeline` classes and are driven automatically by the |
| 8 | +endpoint logic in `endpoint_i.py`. |
| 9 | + |
| 10 | +--- |
| 11 | + |
| 12 | +## 1. Anonymizers (Maskers) |
| 13 | + |
| 14 | +### 1.1 What they do |
| 15 | + |
| 16 | +* **Goal** – Remove or replace personally‑identifiable information (PII) from a payload before it reaches the LLM or |
| 17 | + external service. |
| 18 | +* **Typical strategy** – Run a pipeline of maskers, to locate spans that correspond to IDs, etc., and replace each span |
| 19 | + with a placeholder such as `{{MASKED_ITEM}}`. |
| 20 | + |
| 21 | +### 1.2 Built‑in anonymizer plugins |
| 22 | + |
| 23 | +Full list of `FastMaskerPlugin` masking strategies is located in [README.md](llm_router_plugins/maskers/fast_masker/README.md) file. |
| 24 | + |
| 25 | +| Plugin | Description | Technical notes | |
| 26 | +|------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |
| 27 | +| **FastMaskerPlugin** (`fast_masker_plugin.py`) | A thin wrapper around the `FastMasker` utility class. It receives a JSON‑compatible payload and returns the same payload with all detected PII masked. | Implements `PluginInterface`. The heavy lifting is delegated to `FastMasker.mask_payload(payload)`. No extra I/O; the `FastMasker` instance is created once in `__init__`. | |
| 28 | + |
| 29 | +### 1.3 How a masker is used |
| 30 | + |
| 31 | +1. The endpoint (e.g. `EndpointI._do_masking_if_needed`) checks the global flag `FORCE_MASKING`. |
| 32 | +2. If enabled, it creates a `MaskerPipeline` with the list of masker plugin identifiers (e.g. `["fast_masker"]`). |
| 33 | +3. The pipeline calls each plugin’s `apply` method sequentially, feeding the output of one as the input to the next. |
| 34 | +4. The final payload – now stripped of PII – proceeds to the rest of the request flow (guardrails, model dispatch, |
| 35 | + etc.). |
| 36 | + |
| 37 | +--- |
| 38 | + |
| 39 | +## 2. Guardrails |
| 40 | + |
| 41 | +### 2.1 What they do |
| 42 | + |
| 43 | +* **Goal** – Verify that a request (or its response) complies with policy rules (e.g. no hateful, illegal, or unsafe |
| 44 | + content). |
| 45 | +* **Typical strategy** – Split the payload into manageable text chunks, run a pipeline of guardrails, |
| 46 | + aggregate per‑chunk scores, and decide whether the overall request is safe. |
| 47 | + |
| 48 | +### 2.2 Built‑in guardrail plugins |
| 49 | + |
| 50 | +| Plugin | Description | Technical notes | |
| 51 | +|----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |
| 52 | +| **NASKGuardPlugin** (`nask_guard_plugin.py`) | An HTTP‑based guardrail that forwards the payload to the external NASK guardrail service (`/nask_guard` endpoint) and returns a boolean *safe* flag together with the raw response. | Inherits from `HttpPluginInterface`. The `apply` method calls `_request(payload)` (provided by the base class) and extracts `results["safe"]`. Errors are caught and logged; on failure the plugin returns `(False, {})`. | |
| 53 | +| **(Implicit) GuardrailProcessor** (`processor.py`) | Not a plugin per‑se, but the core logic used by the internal NASK guardrail Flask route (`nask_guardrail`). It tokenises the payload, creates overlapping chunks, runs a Hugging‑Face `text‑classification` pipeline, and produces a detailed safety report. | Handles model loading (`AutoTokenizer`, `pipeline("text‑classification")`), chunking (`_chunk_text`), and scoring thresholds (`MIN_SCORE_FOR_SAFE`, `MIN_SCORE_FOR_NOT_SAFE`). Returns a dict: `{"safe": <bool>, "detailed": [...]}`. | |
| 54 | + |
| 55 | +### 2.3 How a guardrail is used |
| 56 | + |
| 57 | +1. The endpoint calls `_is_request_guardrail_safe(payload)` (or the analogous response guardrail). |
| 58 | +2. If `FORCE_GUARDRAIL_REQUEST` is true, a `GuardrailPipeline` is built from the configured plugin IDs (e.g. |
| 59 | + `["nask_guard"]`). |
| 60 | +3. The pipeline iterates over each guardrail plugin; each `apply` returns `(is_safe, message)`. |
| 61 | +4. The first plugin that reports `is_safe=False` short‑circuits the pipeline and the request is rejected with a 400/500 |
| 62 | + error payload. |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +## 3. Pipelines |
| 67 | + |
| 68 | +Both masker and guardrail pipelines share the same design pattern: |
| 69 | + |
| 70 | +| Class | Purpose | |
| 71 | +|-----------------------------------------------------------|------------------------------------------------------------------------------------| |
| 72 | +| **MaskerPipeline** (`pipeline.py` – masker version) | Executes a list of masker plugins in order, transforming the payload step‑by‑step. | |
| 73 | +| **GuardrailPipeline** (`pipeline.py` – guardrail version) | Executes guardrail plugins sequentially, stopping on the first failure. | |
| 74 | + |
| 75 | +### 3.1 Registration |
| 76 | + |
| 77 | +* Plugins are registered lazily via `MaskerRegistry.register(name, logger)` or |
| 78 | + `GuardrailRegistry.register(name, logger)`. |
| 79 | +* The registry maps a string identifier (e.g. `"fast_masker"`) to a concrete plugin class, allowing pipelines to resolve |
| 80 | + the classes at runtime. |
| 81 | + |
| 82 | +### 3.2 Configuration |
| 83 | + |
| 84 | +All plugin identifiers are stored in environment variables or constants such as: |
| 85 | + |
| 86 | +```python |
| 87 | +MASKING_STRATEGY_PIPELINE = ["fast_masker"] |
| 88 | +GUARDRAIL_STRATEGY_PIPELINE_REQUEST = ["nask_guard"] |
| 89 | +``` |
| 90 | + |
| 91 | +These lists are consumed by the endpoint initialization (`EndpointI._prepare_masker_pipeline`, |
| 92 | +`EndpointI._prepare_guardrails_pipeline`). |
| 93 | + |
| 94 | +--- |
| 95 | + |
| 96 | +## 4. Adding a New Plugin |
| 97 | + |
| 98 | +1. **Create a subclass** of either `PluginInterface` (for maskers) or `HttpPluginInterface` / a custom guardrail base. |
| 99 | +2. **Define a `name` class attribute** – this is the identifier used in pipeline configuration. |
| 100 | +3. **Implement `apply(self, payload: Dict) -> Dict`** (masker) **or `apply(self, payload: Dict) -> Tuple[bool, Dict]` |
| 101 | + ** (guardrail). |
| 102 | +4. **Register the plugin** – either automatically via the registry’s `register` call in the pipeline constructor, or |
| 103 | + manually by calling `MaskerRegistry.register(name=MyPlugin.name, logger=logger)`. |
| 104 | + |
| 105 | +*Example stub for a new masker:* |
| 106 | + |
| 107 | +```python |
| 108 | +# my_custom_masker.py |
| 109 | +from llm_router_plugins.maskers.plugin_interface import PluginInterface |
| 110 | +import logging |
| 111 | +from typing import Dict, Optional |
| 112 | + |
| 113 | + |
| 114 | +class MyCustomMasker(PluginInterface): |
| 115 | + name = "my_custom_masker" |
| 116 | + |
| 117 | + def __init__(self, logger: Optional[logging.Logger] = None): |
| 118 | + super().__init__(logger=logger) |
| 119 | + # Load any heavy resources here (e.g., a spaCy model) |
| 120 | + |
| 121 | + def apply(self, payload: Dict) -> Dict: |
| 122 | + # Perform your masking logic and return the modified payload |
| 123 | + return payload |
| 124 | +``` |
| 125 | + |
| 126 | +After placing the file in `llm_router_plugins/maskers/plugins/`, you can enable it by adding `"my_custom_masker"` to |
| 127 | +`MASKING_STRATEGY_PIPELINE`. |
| 128 | + |
| 129 | +--- |
| 130 | + |
| 131 | +## 5. Summary |
| 132 | + |
| 133 | +* **Anonymizers** (`FastMaskerPlugin`, `BANonymizer`) scrub PII from requests. |
| 134 | +* **Guardrails** (`NASKGuardPlugin`, internal `GuardrailProcessor`) enforce safety policies. |
| 135 | +* **Pipelines** (`MaskerPipeline`, `GuardrailPipeline`) orchestrate the sequential execution of these plugins, |
| 136 | + short‑circuiting on failure for guardrails. |
| 137 | +* The system is **extensible**: new plugins are just classes that obey the tiny interface contract and can be referenced |
| 138 | + by name in the configuration. |
| 139 | + |
| 140 | +These components together give the LLM‑Router a flexible, policy‑driven request‑processing stack that can be tailored to |
| 141 | +any deployment scenario. |
0 commit comments