A proof-of-concept .NET solution that redacts personally identifiable information (PII) from user prompts **before** sending them to a large language model (LLM). The design demonstrates enterprise-grade separation of concerns using SOLID principles, dependency injection, and the `Microsoft.Extensions.AI` abstractions.
## Purpose
Financial and customer-service prompts often contain regulated data (names, government IDs, account numbers). This POC shows how to:
1. Accept a console prompt
2. Detect PII using **Regex**, **ONNX NER**, and **domain rules**
3. Replace values with stable placeholders
4. Send only the **sanitized** prompt to an LLM (mocked for now)
| **Regex** | Email, phone, PAN, Aadhaar, credit card | Deterministic, format-bound identifiers with stable rules that are easy to audit and test |
| **ONNX NER** | Person names | Contextual entities without rigid formats; names vary widely in surface form |
| **Domain rules** | Loan number, customer ID, account number | Business-specific identifiers defined by internal systems, not inferable from generic models alone |
## Why the LLM Receives Only Sanitized Text
The placeholder map (`<PERSON_1>` → original value) is kept **in-process** for audit or downstream de-tokenization. Only the sanitized prompt crosses the LLM boundary. This reduces data-exposure risk and supports compliance requirements for regulated workloads.
| `PiiRedaction.Infrastructure` | ONNX model runner, `IChatClient` mock implementation |
## Prerequisites
- [.NET SDK](https://dotnet.microsoft.com/download) 10.x (or compatible SDK for `net10.0`)
- **ONNX NER model** for person-name detection (see [ONNX Model Setup](#onnx-model-setup))
- Python 3.10+ (only for the model download script)
> **Note:** This environment targets `net10.0` because .NET 10 SDK is installed. The architecture is identical to the planned .NET 9 layout; change `TargetFramework` in `.csproj` files if you use .NET 9 SDK.
For pushing this repository to Xenovex Git (`xts.xenovex.com`), see **[docs/solution-guide.md § Git remote setup](docs/solution-guide.md#11-git-remote-setup-xenovex)**.
By default the console app runs **16 curated sample prompts** covering English and Tamil/Tanglish/mixed person names, regex identifiers, domain IDs, combined scenarios, and a clean no-PII ticket. No flags are required for Tamil samples — they run in the default batch alongside English.
1.**Select a category** from the dropdown (e.g. **Career Guidance**, **Banking & Financial**) or leave **All** to see every prompt. Use the search box for finer filtering.
2.**Click a test prompt** in the left panel to load it into the input box (previous results are cleared automatically).
3. Click **Redact** to run the full detection pipeline. The status bar shows model availability, script composition (LatinOnly / TamilOnly / Mixed), and elapsed time.
4. Review **Sanitized Output**, detected entities, and the placeholder map in the right panel. A leak warning appears if any detected value remains in the sanitized text.
5. Optionally click **Send Mock LLM** to send only the sanitized prompt to the mock LLM.
The harness uses the same DI registrations and `IPromptSanitizer` pipeline as the console app, with thin application services (`IRedactionAppService`, `ITestPromptCatalog`, `IScriptAnalysisService`, `IModelStatusService`) following SOLID principles.
Person names are detected via **ONNX NER** using `dslim/bert-base-NER` (or a compatible token-classification export). A real model is **required** for person-name detection; there is no regex or heuristic fallback.
The English script exports [`dslim/bert-base-NER`](https://huggingface.co/dslim/bert-base-NER) via Hugging Face Optimum when Python is available. The Tamil script exports [`prachuryyaIITG/SampurNER_Tamil_IndicBERTv2`](https://huggingface.co/prachuryyaIITG/SampurNER_Tamil_IndicBERTv2). Otherwise each script downloads pre-exported ONNX assets from Hugging Face directly.
Set `EnableTamilNer` to `false` in `appsettings.json` to revert to English-only routing.
Both runners share `OnnxTokenClassifierRunner` for ONNX Runtime inference and BIO label decoding. Overlapping person spans from mixed-script prompts are merged (longer span wins).
The application depends on `ILlmPromptService` (Core) and `IChatClient` (Microsoft.Extensions.AI). To use Azure OpenAI later, replace the mock registration in [`ServiceCollectionExtensions.cs`](src/PiiRedaction.ConsoleApp/DependencyInjection/ServiceCollectionExtensions.cs):
`MockLlmPromptService` already uses `IChatClient`, so it works with any registered chat client implementation.
## Sample Execution Output
```
=== PII Redaction POC ===
Original Prompt:
Customer Ravi Kumar with email ravi.kumar@gmail.com and phone 9876543210 has LoanNumber LN-456789 and PAN ABCDE1234F. Please summarize this customer issue.
Detected PII:
[PERSON ] Ravi Kumar (Ner)
[EMAIL ] ravi.kumar@gmail.com (Regex)
[PHONE ] 9876543210 (Regex)
[LOAN_NUMBER ] LN-456789 (Domain)
[PAN ] ABCDE1234F (Regex)
Sanitized Prompt:
Customer <PERSON_1> with email <EMAIL_1> and phone <PHONE_1> has LoanNumber <LOAN_NUMBER_1> and PAN <PAN_1>. Please summarize this customer issue.
Internal Placeholder Map (not sent to LLM):
<EMAIL_1> -> ravi.kumar@gmail.com
<LOAN_NUMBER_1> -> LN-456789
<PAN_1> -> ABCDE1234F
<PERSON_1> -> Ravi Kumar
<PHONE_1> -> 9876543210
Mock LLM Response:
[Mock LLM Response] Received sanitized prompt (146 chars). No original PII was transmitted.
```
## SOLID Principles Applied
| Principle | Application |
|-----------|-------------|
| **Single Responsibility** | Each detector, redactor, and runner has one job; `Program.cs` only orchestrates |
| **Open/Closed** | Add new `IPiiDetector` implementations without changing merge logic |
| **Liskov Substitution** | All detectors are interchangeable via `IPiiDetector` |
| **Interface Segregation** | Separate interfaces for detection, redaction, sanitization, and LLM |
Fast CI runs without the ONNX model: fake-based tests always execute; tests marked **`Category=RealModel`** or **`Category=TamilNer`** are skipped when the corresponding ONNX models are absent. Download models first:
- **`PromptScenarioCatalog`** — five focused end-to-end English scenarios (canonical demo, multi-regex, duplicate people, overlap stress, no-PII negative)
- **`TamilPromptScenarioCatalog`** — five Tamil/Tanglish/mixed golden scenarios (fake NER for person spans)
- **`ProductionPipelineFactory`** — builds the same Domain → Regex → OnnxNer composite stack as production DI; `CreateWithRealModel(runner)` wires a real runner; `CreateWithRoutingRealModels` wires English + Tamil routing