Skip to content

Conversation

@yossiovadia
Copy link
Collaborator

Context

While investigating Issue #647 (PII detection confidence issues), I discovered that PII classification appears to be hardcoded to ModernBERT, even though:

  • LoRA PII models exist in the model directory
  • The Rust layer already has auto-detection infrastructure via lora_config.json checks
  • Other classifiers (intent, jailbreak) use the auto-detecting code path

Observations

Current behavior:

  • PII uses: InitModernBertPIITokenClassifier() / ClassifyModernBertPIITokens()
  • Testing with ModernBERT: 27% success rate (10/37 test cases)

After switching to auto-detection:

  • PII uses: InitCandleBertTokenClassifier() / ClassifyCandleBertTokens() (same as intent classifier)
  • Testing with LoRA: 73% success rate (27/37 test cases)

Proposed Changes

I wanted to share this potential fix for your review. The changes are minimal (23 lines in classifier.go) and leverage existing auto-detection infrastructure:

Current (Hardcoded):

success := candle_binding.InitModernBertPIITokenClassifier(modelID, useCPU)
result := candle_binding.ClassifyModernBertPIITokens(text, configPath)

Proposed (Auto-Detection):

success := candle_binding.InitCandleBertTokenClassifier(modelID, numClasses, useCPU)
result := candle_binding.ClassifyCandleBertTokens(text)

The Rust layer handles auto-detection by checking for lora_config.json presence.

Test Results

Created comprehensive test suite (37 diverse PII cases):

Approach Success Rate Notes
ModernBERT (current) 27% (10/37) Low confidence, wrong entity types
LoRA (proposed) 73% (27/37) Higher confidence, correct entity types

Example improvements:

  • ✅ Email detection: EMAIL_ADDRESS (0.9) instead of PERSON (0.52)
  • ✅ SSN detection: US_SSN (0.9) instead of failed/low confidence
  • ✅ Credit Card: CREDIT_CARD (0.9) - previously failed
  • ✅ Phone: PHONE_NUMBER (0.9) - previously failed

Files in This PR

  1. classifier.go - Switch to auto-detecting functions
  2. config.e2e.yaml - Update test config to use LoRA model
  3. 06-a-test-pii-direct.py - New: Comprehensive PII test suite
  4. pii-confidence-benchmark.py - New: Statistical benchmark tool

Questions for Maintainers

  1. Was the ModernBERT hardcoding intentional? Or just an oversight that could be updated?
  2. Is the auto-detection approach acceptable? It's already used for intent/jailbreak classifiers
  3. Confidence uniformity: LoRA returns exactly 0.9 for all detections (vs ModernBERT's varied scores). Is this expected behavior for LoRA models? See detailed analysis

Testing

# Run direct PII tests (37 cases)
python3 e2e-tests/06-a-test-pii-direct.py

# Run comprehensive benchmark (84 prompts with statistics)
python3 e2e-tests/pii-confidence-benchmark.py

Backward Compatibility

  • ✅ Falls back to Traditional/ModernBERT when LoRA not available
  • ✅ No breaking config changes
  • use_modernbert field behavior unchanged (was already ignored)

Happy to adjust this approach based on your architectural preferences. The main goal is enabling better PII detection for Issue #647.

@netlify
Copy link

netlify bot commented Nov 13, 2025

Deploy Preview for vllm-semantic-router ready!

Name Link
🔨 Latest commit 13b254f
🔍 Latest deploy log https://app.netlify.com/projects/vllm-semantic-router/deploys/691f7a3169c66100085bf1a2
😎 Deploy Preview https://deploy-preview-648--vllm-semantic-router.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions
Copy link

github-actions bot commented Nov 13, 2025

👥 vLLM Semantic Team Notification

The following members have been identified for the changed files in this PR and have been automatically assigned:

📁 e2e-tests

Owners: @yossiovadia
Files changed:

  • e2e-tests/06-a-test-pii-direct.py
  • e2e-tests/pii-confidence-benchmark.py

📁 config

Owners: @rootfs, @Xunzhuo
Files changed:

  • config/testing/config.e2e.yaml

📁 deploy

Owners: @rootfs, @Xunzhuo
Files changed:

  • deploy/helm/semantic-router/crds/vllm.ai_intelligentpools.yaml
  • deploy/helm/semantic-router/crds/vllm.ai_intelligentroutes.yaml
  • deploy/helm/semantic-router/values.yaml
  • deploy/kubernetes/ai-gateway/semantic-router-values/values.yaml
  • deploy/kubernetes/aibrix/semantic-router-values/values.yaml
  • deploy/kubernetes/crds/vllm.ai_intelligentpools.yaml
  • deploy/kubernetes/crds/vllm.ai_intelligentroutes.yaml

📁 e2e

Owners: @Xunzhuo
Files changed:

  • e2e/profiles/ai-gateway/values.yaml
  • e2e/profiles/aibrix/profile.go
  • e2e/profiles/dynamic-config/crds/intelligentpool.yaml
  • e2e/profiles/dynamic-config/crds/intelligentroute.yaml
  • e2e/profiles/dynamic-config/profile.go
  • e2e/profiles/dynamic-config/values.yaml
  • e2e/testcases/pii_detection.go

📁 src

Owners: @rootfs, @Xunzhuo, @wangchen615
Files changed:

  • src/semantic-router/pkg/apis/vllm.ai/v1alpha1/types.go
  • src/semantic-router/pkg/apis/vllm.ai/v1alpha1/zz_generated.deepcopy.go
  • src/semantic-router/pkg/classification/classifier.go
  • src/semantic-router/pkg/classification/classifier_test.go
  • src/semantic-router/pkg/extproc/extproc_test.go
  • src/semantic-router/pkg/extproc/router.go
  • src/semantic-router/pkg/k8s/reconciler.go
  • src/semantic-router/pkg/services/classification.go
  • src/semantic-router/pkg/utils/pii/policy.go

vLLM

🎉 Thanks for your contributions!

This comment was automatically generated based on the OWNER files in the repository.

@yossiovadia
Copy link
Collaborator Author

Further Investigation on 0.9 Confidence

I conducted additional investigation to understand where the uniform 0.9 confidence originates.

Test: Pure Python Model Inference

Created a standalone Python test that loads the LoRA PII model directly using HuggingFace transformers (bypassing ALL semantic-router code - no Go, no Rust FFI).

Test script: https://gist.github.com/yossiovadia/c1a0e822e836d73db68ea9fe9e321adc

Results:

Total PII detections: 23
Unique confidence values: 23 (ALL DIFFERENT\!)

Confidence distribution:
  Min: 0.203108
  Max: 0.996829
  Mean: 0.633599
  Std Dev: 0.278768

Sample detections:
  Email "john": 0.985991
  Phone "(": 0.996829
  SSN "123": 0.991570
  SSN "45": 0.619693

Conclusion: The LoRA PII model itself produces varied probabilistic confidence scores, not uniform 0.9.

Code Path Analysis

Traced the inference path through the codebase:

  1. Go Layer (classifier.go:182-184):

    func (c *PIIInferenceImpl) ClassifyTokens(...) (...) {
        return candle_binding.ClassifyCandleBertTokens(text)  // Direct passthrough
    }

    No confidence manipulation ✓

  2. Rust FFI (classify.rs:621):

    .map(|r| (r.token.clone(), r.label_name.clone(), r.confidence))  // Direct passthrough

    No confidence manipulation ✓

  3. LoRA Classifier (bert_lora.rs:844):

    results.push((token.clone(), predicted_class, confidence));  // Direct from softmax

    No confidence manipulation ✓

  4. PII Aggregation (pii_lora.rs:133,144):

    occurrences.push(PIIOccurrence {
        confidence: *confidence,  // Individual token confidence
    });
    // Final confidence = average of all token confidences
    confidence_scores.iter().sum::<f32>() / confidence_scores.len() as f32

    Uses averaging of token-level scores ✓

Hypothesis

The uniform 0.9 likely comes from aggregation behavior in pii_lora.rs. When the Rust LoRA inference processes tokens through the model, the token-level confidences (which the Python model shows are varied) get aggregated into an entity-level confidence.

The mystery: Why does this averaging consistently produce exactly 0.9? This suggests either:

  1. Token-level confidences from the Rust LoRA path are different from Python's inference
  2. The aggregation logic has some normalization we haven't identified
  3. There's a threshold or post-processing step in the Candle framework's LoRA handling

How to Reproduce

# Install dependencies
pip install peft transformers torch

# Run the test
python3 test_lora_pii_pure_python.py

The test will show the model produces varied confidence scores when accessed directly via Python/HuggingFace.

Bottom Line

The uniform 0.9 is not from our Issue #647 changes or any hardcoded value in semantic-router. It appears to be an artifact of how the Rust/Candle LoRA implementation processes and aggregates token classifications, which differs from the Python/HuggingFace inference path.

Despite this quirk, the core improvement remains: 27% → 73% success rate with correct entity types.

@yossiovadia
Copy link
Collaborator Author

Further Investigation: Jailbreak LoRA Confidence Comparison

To isolate the uniform 0.9 confidence issue, I tested the jailbreak LoRA model using the same semantic-router pathway (Go → Rust → Candle).

Test Results:

Jailbreak LoRA Model via Classification API (/api/v1/classify/security):

  • 14 unique confidence values from 15 test cases
  • ✅ Confidence range: 0.9917 to 0.9999
  • NO uniform 0.9 issue

Test script: https://gist.github.com/yossiovadia/3c016171b776d2ed1b62cacdeb452e7a

Sample output:

Total tests: 15
Unique confidence values: 14
Min confidence: 0.9917060137
Max confidence: 0.9999994040

🔍 Uniform 0.9 issue: NO ✅
✅ Jailbreak model shows VARIED confidence scores

Comparison:

Model Architecture Pathway Confidence Behavior
PII LoRA Token Classification Go → Rust → Candle ❌ Uniform 0.9 (121/121)
Jailbreak LoRA Sequence Classification Go → Rust → Candle ✅ Varied (0.9917-0.9999)
PII LoRA Token Classification Python direct ✅ Varied (0.203-0.997) [gist]

Root Cause:

The uniform 0.9 confidence is specific to PII token classification in the Rust/Candle pathway:

Jailbreak implementation (security_lora.rs:81-82):

let (predicted_class, confidence) = self.bert_classifier.classify_text(text)
  • Sequence classification → returns raw model confidence

PII implementation (pii_lora.rs:87-89, 142-148):

let token_results = self.bert_token_classifier.classify_tokens(text)
let final_confidence = confidence_scores.iter().sum::<f32>() / confidence_scores.len() as f32
  • Token classification → averages confidence scores across detected PII tokens

The uniform 0.9 originates from the Rust token classification implementation (classify_tokens() or averaging logic).

@github-actions github-actions bot deleted a comment Nov 15, 2025
@Xunzhuo
Copy link
Member

Xunzhuo commented Nov 16, 2025

Clipboard_Screenshot_1763306903 Clipboard_Screenshot_1763307058

https://github.com/vllm-project/semantic-router/actions/runs/19407606041?pr=648

Copy link
Member

@Xunzhuo Xunzhuo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you change e2e config for ai-gateway as well? deploy/kubernetes/ai-gateway/semantic-router-values/values.yaml. but this is still weird for me, this should be backward compatible right? But this makes the previous full param classification not work

yossiovadia added a commit to yossiovadia/semantic-router that referenced this pull request Nov 18, 2025
…orm 0.9 confidence

Issue vllm-project#647 reported uniform 0.9 confidence scores in PII detection.

Root cause: Training with FP16 (torch.float16) compresses confidence score
distributions due to limited mantissa precision (~10-11 significant bits).
Token classification requires precise per-token probability distributions.

Fix: Force torch.float32 for all PII token classification training, ensuring
proper confidence score variance and accurate entity detection probabilities.

This fix complements PR vllm-project#648 which enables LoRA PII model auto-detection.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Signed-off-by: Yossi Ovadia <[email protected]>
yossiovadia added a commit to yossiovadia/semantic-router that referenced this pull request Nov 18, 2025
…lm-project#681 merge

After PR vllm-project#681 merge, Categories no longer have ModelScores field.
The reasoning config moved to Decisions.ModelRefs, but there's no
direct mapping from category names to decision names.

Set useReasoning=false as safe default until proper category-to-decision
mapping is implemented.

Related: PR vllm-project#648, PR vllm-project#681
Signed-off-by: Yossi Ovadia <[email protected]>
yossiovadia added a commit to yossiovadia/semantic-router that referenced this pull request Nov 18, 2025
Critical bug fix for PR vllm-project#648 CI failures.

**Root Cause:**
The new auto-detecting PII classifier API was not receiving the PII
configuration mapping (pii_type_mapping.json), causing:
- 0% PII detection accuracy (classifier didn't know which entities to detect)
- 0/100 requests blocked (blocking policy received incomplete results)

**The Bug:**
Changed from ClassifyModernBertPIITokens(text, configPath) to
ClassifyCandleBertTokens(text) - dropping the configPath parameter.

**The Fix:**
Use ClassifyCandleBertTokensWithLabels(text, id2labelJSON) to pass
the PII entity mapping configuration to the classifier.

**Testing:**
- Local testing worked because it was using old code (ModernBERT path)
- CI failed because it builds from PR branch (new auto-detect path)
- This fix ensures both LoRA and Traditional paths receive PII config

**Related:**
- Fixes CI test failures in integration-test jobs
- LoRA loading still shows 'hidden_act' error but falls back to ModernBERT
- ModernBERT fallback now works correctly with this fix

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Signed-off-by: Yossi Ovadia <[email protected]>
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 18, 2025
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 19, 2025
@yossiovadia yossiovadia force-pushed the fix/pii-lora-auto-detect branch from a9f3877 to c2f59cd Compare November 19, 2025 00:25
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 19, 2025
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 19, 2025
@yossiovadia yossiovadia force-pushed the fix/pii-lora-auto-detect branch from f3b7253 to c298067 Compare November 19, 2025 01:04
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 19, 2025
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 19, 2025
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 19, 2025
@yossiovadia yossiovadia force-pushed the fix/pii-lora-auto-detect branch from 4822c60 to 20ef032 Compare November 19, 2025 04:32
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 19, 2025
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 19, 2025
@yossiovadia yossiovadia force-pushed the fix/pii-lora-auto-detect branch from 70aba9b to 27224c9 Compare November 20, 2025 00:36
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 20, 2025
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 20, 2025
yossiovadia added a commit to yossiovadia/semantic-router that referenced this pull request Nov 20, 2025
The previous policy.go changes added a default_decision fallback mechanism
that broke aibrix profile. Aibrix uses static YAML config and doesn't have
a default_decision defined. Reverting to the upstream version from PR vllm-project#688
that passed aibrix tests successfully.

Related to vllm-project#648

Signed-off-by: Yossi Ovadia <[email protected]>
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 20, 2025
@yossiovadia yossiovadia force-pushed the fix/pii-lora-auto-detect branch from b398c1f to 4ce1bbc Compare November 20, 2025 14:57
yossiovadia added a commit to yossiovadia/semantic-router that referenced this pull request Nov 20, 2025
The aibrix profile uses static YAML configuration (not CRDs). The PII policy
checker has a fallback to 'default_decision' when no decision name is provided,
but aibrix didn't define this decision. This caused all PII detection requests
to be allowed (0% accuracy).

Adding default_decision with PII enabled (pii_types_allowed: []) ensures that
the fallback logic works correctly for aibrix, matching the behavior expected
by the E2E PII detection tests.

Related to vllm-project#648

Signed-off-by: Yossi Ovadia <[email protected]>
yossiovadia and others added 10 commits November 20, 2025 07:08
…ed classification

This change ensures the ExtProc router uses the same UnifiedClassifier
(LoRA-based) instance as the Classification API, fixing inconsistent
model selection behavior.

**Problem:**
- Classification API (port 8080) used UnifiedClassifier (LoRA models)
- ExtProc router (port 8801) used legacy Classifier (traditional BERT)
- This caused different classification results for the same query,
  leading to incorrect model selection in category-based routing

**Solution:**
1. Wire UnifiedClassifier from ClassificationService to legacy Classifier
2. Add delegation in Classifier.ClassifyCategoryWithEntropy() to use
   UnifiedClassifier when available
3. Add GetUnifiedClassifier() method to ClassificationService

**Changes:**
- router.go: Wire UnifiedClassifier to Classifier during initialization
- classifier.go: Delegate to UnifiedClassifier before trying in-tree
  classifier, add classifyWithUnifiedClassifier() helper method
- classification.go: Add GetUnifiedClassifier() getter method

Related to vllm-project#640

Co-Authored-By: Claude <[email protected]>
Signed-off-by: Yossi Ovadia <[email protected]>
Switch PII classification from hardcoded ModernBERT to auto-detecting
Candle BERT classifier. The Rust layer already has built-in auto-detection
that checks for lora_config.json and routes to LoRA or Traditional models.

Changes:
1. Init: Use InitCandleBertTokenClassifier (has auto-detect built-in)
2. Inference: Use ClassifyCandleBertTokens (auto-routes to initialized classifier)

This enables LoRA PII models to work automatically without config changes,
providing higher confidence scores for PII entity detection.

Fixes vllm-project#647

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Signed-off-by: Yossi Ovadia <[email protected]>
…config

Add two comprehensive PII testing tools and update e2e configuration to use
LoRA PII model instead of broken ModernBERT model.

Changes:
1. Add 06-a-test-pii-direct.py - 37 comprehensive PII test cases
   - Tests email, SSN, credit card, phone, person names, addresses, etc.
   - Validates confidence scores and entity type accuracy
   - Compares ModernBERT vs LoRA performance

2. Add pii-confidence-benchmark.py - 84-prompt benchmark tool
   - Tests diverse PII patterns and formats
   - Outputs detailed statistics (precision, recall, F1 score)
   - Generates JSON results for analysis
   - Measures processing time and confidence distribution

3. Update config/testing/config.e2e.yaml
   - Change model_id to lora_pii_detector_bert-base-uncased_model
   - Update pii_mapping_path to match LoRA model structure
   - Required because ModernBERT model is incompatible with auto-detection code

Note: The old ModernBERT PII model lacks the hidden_act field required by
Traditional BERT classifier, causing fatal initialization errors.

Test Results with LoRA model:
- Overall: 88% accuracy (74/84 prompts)
- Precision: 95.5% (when detected, almost always correct)
- Recall: 90.0% (detects 90% of actual PII)
- F1 Score: 0.926
- All confidence scores: 0.9 (uniform, see caveat in vllm-project#647)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Signed-off-by: Yossi Ovadia <[email protected]>
Update MockPIIInitializer.Init() to include numClasses parameter
to match the PIIInitializer interface changes.

This fixes the CI test failure where the mock didn't properly
implement the updated interface signature.

Signed-off-by: Yossi Ovadia <[email protected]>
- Run black formatter on Python test files
- Update MockPIIInitializer to match interface changes

Fixes CI pre-commit and test-and-build failures.

Signed-off-by: Yossi Ovadia <[email protected]>
Implement graceful fallback strategy for PII initialization:
1. Try auto-detecting InitCandleBertTokenClassifier (enables LoRA)
2. Fallback to InitModernBertPIITokenClassifier if auto-detect fails

This maintains backward compatibility with existing ModernBERT models
that have incomplete configs (e.g., missing hidden_act field) while
still enabling LoRA PII models when available.

Also disable PII for caching tests (not needed for those test cases).

Resolves test failures while preserving the 27% → 73% improvement.

Signed-off-by: Yossi Ovadia <[email protected]>
…lm-project#681 merge

After PR vllm-project#681 merge, Categories no longer have ModelScores field.
The reasoning config moved to Decisions.ModelRefs, but there's no
direct mapping from category names to decision names.

Set useReasoning=false as safe default until proper category-to-decision
mapping is implemented.

Related: PR vllm-project#648, PR vllm-project#681
Signed-off-by: Yossi Ovadia <[email protected]>
…or all requests

This fixes the E2E PII detection test failures (0% detection rate) by ensuring
PII detection is always enabled, even when no specific decision matches.

Previously, requests with model='MoM' (used by E2E tests) did not match any
decision criteria, causing decisionName to be empty. This triggered the check:
  if decisionName == '' { return false } // PII detection disabled

The fix adds a catch-all default_decision with:
- priority: 1 (lowest - matches only if nothing else does)
- type: 'always' (matches any request)
- pii_types_allowed: [] (blocks ALL PII for safety)

This ensures the 100 E2E PII test cases will be blocked correctly.

Fixes vllm-project#647 E2E test failures

Signed-off-by: Yossi Ovadia <[email protected]>
After PR vllm-project#681 introduced decision-based routing, PII detection requires a
decision to be selected. Using model="MoM" triggers domain classification,
but PII test data is domain-agnostic, so no domain matches, no decision is
selected, and PII detection gets disabled.

Solution: Use model="base-model" directly which matches all decisions in
the CRD. This ensures a decision is selected and PII detection is enabled.

This still tests LoRA PII auto-detection as configured in the classifier
settings, but ensures the decision-based PII plugin is activated.

Signed-off-by: Yossi Ovadia <[email protected]>
… accuracy)

This commit addresses all root causes of the 0% PII detection accuracy in E2E tests
by applying 5 critical fixes across reconciler, policy checker, E2E framework, and CRDs.

The E2E PII test failures were caused by a chain of 5 distinct issues:

1. **CRD Decision Loading Bug** - Reconciler didn't copy decisions to top-level RouterConfig
2. **Race Condition** - Tests ran before CRD reconciliation completed
3. **Invalid CRD Model References** - Using LoRA adapter name as model name
4. **Missing Default Decision Fallback (IsPIIEnabled)** - PII detection disabled when no decision matched
5. **Missing Default Decision Fallback (CheckPolicy)** - PII policy enforcement failed even after detection

**File**: `src/semantic-router/pkg/k8s/reconciler.go:266`

Added:
```go
// CRITICAL: Also update top-level Decisions field for PII policy lookups
// The Decisions field is used by GetDecisionByName() which is called by PII policy checker
newConfig.Decisions = intelligentRouting.Decisions
```

**Impact**: This is the critical root cause fix. Without this, `GetDecisionByName()`
always returned nil because it looks up from `c.Decisions`, not `c.IntelligentRouting.Decisions`.

**File**: `e2e/profiles/dynamic-config/profile.go:239-251`

Added `waitForCRDReconciliation()` with 10-second delay after CRD deployment.

**Impact**: Prevents race condition where tests execute before the reconciler's
5-second polling cycle completes, ensuring CRD configuration is fully loaded.

**Files**:
- `deploy/helm/semantic-router/crds/vllm.ai_intelligentpools.yaml`
- `deploy/kubernetes/crds/vllm.ai_intelligentpools.yaml`
- `src/semantic-router/pkg/apis/vllm.ai/v1alpha1/types.go`

Added `PIIModelConfig` type with Kubernetes validation for:
- `modelPath` (required, 1-500 chars)
- `modelType` (optional, max 50 chars, e.g., "auto" for auto-detection)
- `threshold` (optional, 0.0-1.0)
- `useCPU` (optional boolean)

**Impact**: Enables proper CRD validation and configuration for LoRA PII auto-detection.

**Files**:
- `e2e/profiles/dynamic-config/crds/intelligentpool.yaml`
- `e2e/profiles/dynamic-config/crds/intelligentroute.yaml`

Added PII model configuration to IntelligentPool:
```yaml
piiModel:
  modelPath: "models/lora_pii_detector_bert-base-uncased_model"
  modelType: "auto"
  threshold: 0.7
  useCPU: true
```

Added default catch-all decision to IntelligentRoute:
```yaml
- name: "default_decision"
  priority: 1
  signals:
    operator: "OR"
    conditions:
      - type: "keyword"
        name: "catch_all"
  modelRefs:
    - model: "base-model"
      loraName: "general-expert"
  plugins:
    - type: "pii"
      configuration:
        enabled: true
        pii_types_allowed: []
```

**Impact**: Ensures PII detection is always enabled for unmatched requests with proper model configuration.

**File**: `src/semantic-router/pkg/utils/pii/policy.go`

Applied fallback to `"default_decision"` in both functions:

**IsPIIEnabled** (lines 17-21):
```go
if decisionName == "" {
    decisionName = "default_decision"
    logging.Infof("No decision specified, trying default decision: %s", decisionName)
}
```

**CheckPolicy** (lines 53-57):
```go
if decisionName == "" {
    decisionName = "default_decision"
    logging.Infof("No decision specified for CheckPolicy, trying default decision: %s", decisionName)
}
```

**Impact**: Enables PII detection and policy enforcement even when no specific route matches,
by falling back to the catch-all `default_decision` configured in the CRD.

**File**: `deploy/helm/semantic-router/values.yaml`

Added to model downloads:
```yaml
- name: lora_pii_detector_bert-base-uncased_model
  repo: LLM-Semantic-Router/lora_pii_detector_bert-base-uncased_model
```

**Impact**: Ensures LoRA PII detection model is available for auto-detection feature.

**Before Fix**: 0% PII Detection Accuracy (0/100 tests passed)
**After Fix**: 100% PII Detection Accuracy (100/100 tests passed)

Verified locally using Kind cluster with `dynamic-config` profile:
- All 100 PII test cases correctly blocked
- No false negatives
- Proper PII entity detection (PERSON, CREDIT_CARD, EMAIL, IP_ADDRESS, etc.)
- Decision-based routing working correctly with CRD configuration

Each fix addresses a different layer of the PII detection pipeline:

1. **Reconciler Fix** - Enabled CRD decisions to be loaded into memory
2. **Race Condition Fix** - Ensured decisions were loaded before tests ran
3. **CRD Schema Updates** - Added proper validation and configuration support
4. **CRD Configuration** - Provided actual default decision and PII model config
5. **Policy Fallbacks** - Enabled PII detection/enforcement when no route matched

Without any single fix, the test would still fail with 0% accuracy.

Core Fixes:
- src/semantic-router/pkg/k8s/reconciler.go
- src/semantic-router/pkg/utils/pii/policy.go
- e2e/profiles/dynamic-config/profile.go

CRD Schemas:
- deploy/helm/semantic-router/crds/vllm.ai_intelligentpools.yaml
- deploy/kubernetes/crds/vllm.ai_intelligentpools.yaml
- src/semantic-router/pkg/apis/vllm.ai/v1alpha1/types.go
- src/semantic-router/pkg/apis/vllm.ai/v1alpha1/zz_generated.deepcopy.go

E2E Test Configuration:
- e2e/profiles/dynamic-config/crds/intelligentpool.yaml
- e2e/profiles/dynamic-config/crds/intelligentroute.yaml
- e2e/profiles/dynamic-config/values.yaml

Helm Chart:
- deploy/helm/semantic-router/values.yaml

Minor YAML Formatting (no functional change):
- deploy/helm/semantic-router/crds/vllm.ai_intelligentroutes.yaml
- deploy/kubernetes/crds/vllm.ai_intelligentroutes.yaml

Fixes vllm-project#647

Signed-off-by: Yossi Ovadia <[email protected]>
@yossiovadia yossiovadia force-pushed the fix/pii-lora-auto-detect branch from 9aa2b1e to f7a957b Compare November 20, 2025 15:09
yossiovadia added a commit to yossiovadia/semantic-router that referenced this pull request Nov 20, 2025
The aibrix profile uses static YAML configuration (not CRDs). The PII policy
checker has a fallback to 'default_decision' when no decision name is provided,
but aibrix didn't define this decision. This caused all PII detection requests
to be allowed (0% accuracy).

Adding default_decision with PII enabled (pii_types_allowed: []) ensures that
the fallback logic works correctly for aibrix, matching the behavior expected
by the E2E PII detection tests.

Related to vllm-project#648

Signed-off-by: Yossi Ovadia <[email protected]>
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 20, 2025
@yossiovadia yossiovadia force-pushed the fix/pii-lora-auto-detect branch from f7a957b to 444fbf8 Compare November 20, 2025 15:50
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 20, 2025
…r ai-gateway

This commit fixes PII detection test failures for both dynamic-config and ai-gateway
profiles by implementing profile-specific model configuration and switching ai-gateway
from ModernBERT to LoRA-based PII detection.

## Problem

The PII detection E2E test was hardcoding "model": "general-expert", which caused
different issues across profiles:

1. **Dynamic-config**: Using "general-expert" directly bypassed the decision engine,
   resulting in decision="" (empty string), causing PII policy lookups to fail → 0% accuracy

2. **AI-gateway**: Using outdated ModernBERT PII model which wasn't detecting any PII
   entities during requests → 0% accuracy

## Root Causes

**Dynamic-config failure**:
- When test uses model="general-expert" directly, semantic router treats it as a
  specified model (reason_code="model_specified"), NOT triggering decision engine
- Without decision routing, no decision name is set (decision="")
- PII policy code requires a valid decision name to check policies
- Result: PII detection disabled, 0/100 tests passed

**AI-gateway failure**:
- Profile was using legacy ModernBERT PII model (models/pii_classifier_modernbert-base_presidio_token_model)
- ModernBERT classifier initialized but never actually called during requests
- No "PII token classification" logs or "Detected PII" messages in test runs
- LoRA PII model proven to work correctly in dynamic-config profile
- Result: No PII detection, 0/100 tests passed

## Solution

### 1. Make PII Test Model Name Configurable (e2e/testcases/pii_detection.go)

**Change**: Added E2E_TEST_MODEL environment variable support
```go
// Get model name from environment, default to "general-expert" for backward compatibility
modelName := os.Getenv("E2E_TEST_MODEL")
if modelName == "" {
    modelName = "general-expert"
}
```

**Why**: Different profiles need different model names:
- **dynamic-config**: Needs "MoM" to trigger decision engine routing
- **ai-gateway**: Can use "general-expert" (already configured as direct model)

**Impact**: Enables per-profile model configuration without test code changes

### 2. Configure Dynamic-Config to Use MoM Model (e2e/profiles/dynamic-config/profile.go)

**Change**: Set environment variable in Setup() method
```go
// Configure PII test to use MoM model for decision-based routing
os.Setenv("E2E_TEST_MODEL", "MoM")
```

**Why**:
- "MoM" (Mixture of Models) triggers the decision engine
- Decision engine classifies request → matches decision → enables PII detection
- "general-expert" bypasses decision engine → no decision → PII detection fails

**Impact**: Dynamic-config now gets 100/100 PII tests passed (100% accuracy)

### 3. Switch AI-Gateway to LoRA PII Detection (e2e/profiles/ai-gateway/values.yaml)

**Change 1**: Updated pii_model configuration to use LoRA auto-detection
```yaml
pii_model:
  model_id: "models/lora_pii_detector_bert-base-uncased_model"
  model_type: "auto"  # Enables LoRA auto-detection
  threshold: 0.7
  use_cpu: true
  pii_mapping_path: "models/lora_pii_detector_bert-base-uncased_model/pii_type_mapping.json"
```

**Why**:
- ModernBERT PII model was not detecting any PII (0% accuracy)
- LoRA PII model proven to work (dynamic-config achieved 100% accuracy)
- model_type: "auto" enables automatic LoRA model detection
- Same model used across all profiles for consistency

**Change 2**: Added default_decision for fallback PII detection
```yaml
- name: default_decision
  description: "Default catch-all decision - blocks all PII for safety"
  priority: 0
  plugins:
    - type: "pii"
      configuration:
        enabled: true
        pii_types_allowed: []
```

**Why**:
- PII policy code (src/semantic-router/pkg/utils/pii/policy.go) falls back to "default_decision"
- When decision name is empty or not found, policy.go tries default_decision
- Ensures PII detection is always enabled, even for unmatched requests

**Impact**: AI-gateway now gets 100/100 PII tests passed (100% accuracy)

## Testing

**Before Fix**:
- dynamic-config: 0/100 PII tests passed (0% accuracy)
- ai-gateway: 0/100 PII tests passed (0% accuracy)

**After Fix**:
- dynamic-config: 100/100 PII tests passed (100% accuracy) ✅
- ai-gateway: 100/100 PII tests passed (100% accuracy) ✅

**Test Commands**:
```bash
# Dynamic-config profile
make e2e-cleanup && make e2e-test E2E_PROFILE=dynamic-config E2E_VERBOSE=true E2E_KEEP_CLUSTER=true

# AI-gateway profile
make e2e-cleanup && make e2e-test E2E_PROFILE=ai-gateway E2E_VERBOSE=true E2E_KEEP_CLUSTER=true
```

## Files Changed

1. **e2e/testcases/pii_detection.go** (Lines 136-140)
   - Added E2E_TEST_MODEL environment variable support
   - Defaults to "general-expert" for backward compatibility
   - Enables profile-specific model configuration

2. **e2e/profiles/dynamic-config/profile.go** (Lines 46-47)
   - Sets E2E_TEST_MODEL=MoM in Setup()
   - Forces decision-based routing for PII tests
   - Ensures decision name is populated for PII policy checks

3. **e2e/profiles/ai-gateway/values.yaml** (Lines 413-432, 490-497)
   - Added default_decision for PII policy fallback
   - Switched pii_model from ModernBERT to LoRA auto-detection
   - Aligned with dynamic-config's working configuration

## Why This Works

**Dynamic-config**:
1. Test uses model="MoM" (via E2E_TEST_MODEL env var)
2. Triggers decision engine → classifies to decision (e.g., "other_decision")
3. Decision has PII plugin enabled → PII detection runs
4. LoRA PII classifier detects entities → policy blocks request ✅

**AI-gateway**:
1. Test uses model="general-expert" (default, no env var set)
2. Routes to decision (either matched or falls back to default_decision)
3. Decision has PII plugin enabled → PII detection runs
4. LoRA PII classifier detects entities → policy blocks request ✅

Both profiles now use the same proven LoRA PII detection model with 100% accuracy.

Signed-off-by: Yossi Ovadia <[email protected]>
@yossiovadia yossiovadia force-pushed the fix/pii-lora-auto-detect branch from 444fbf8 to 8034e8a Compare November 20, 2025 19:56
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 20, 2025
This commit fixes PII detection test failures for the aibrix profile by switching
from the non-functional ModernBERT PII model to LoRA-based auto-detection, matching
the configuration already proven to work in dynamic-config and ai-gateway profiles.

## Problem

The aibrix profile PII detection test was failing with 0% accuracy (0/100 tests passed).
All 100 PII test requests were passing through without being blocked, even though they
contained sensitive data like credit cards, SSNs, emails, and IP addresses.

## Root Causes

**Same issues as ai-gateway had before the previous fix**:

1. **Using outdated ModernBERT PII model**:
   - Profile was using `models/pii_classifier_modernbert-base_presidio_token_model`
   - ModernBERT classifier initialized but never detected any PII entities
   - No "Detected PII" or "PII token classification" logs during test runs
   - Result: 0% detection accuracy

2. **Missing default_decision fallback**:
   - No catch-all decision for PII policy fallback mechanism
   - PII policy code (src/semantic-router/pkg/utils/pii/policy.go) falls back to "default_decision"
   - Without it, edge cases with empty decision names would disable PII detection

3. **No profile-specific model configuration**:
   - Test was using inherited E2E_TEST_MODEL from previous test runs
   - Not explicitly configured for aibrix's model: vllm-llama3-8b-instruct
   - Could cause inconsistent behavior across test runs

## Solution

### 1. Switch AIBrix to LoRA PII Detection (deploy/kubernetes/aibrix/semantic-router-values/values.yaml)

**Change 1**: Updated pii_model configuration (lines 459-466)
```yaml
pii_model:
  # Support both traditional (modernbert) and LoRA-based PII detection
  # When model_type is "auto", the system will auto-detect LoRA configuration
  model_id: "models/lora_pii_detector_bert-base-uncased_model"
  model_type: "auto"  # Enables LoRA auto-detection
  threshold: 0.7
  use_cpu: true
  pii_mapping_path: "models/lora_pii_detector_bert-base-uncased_model/pii_type_mapping.json"
```

**Why**:
- ModernBERT PII model was not detecting any PII (0% accuracy in tests)
- LoRA PII model proven to work in both dynamic-config and ai-gateway (100% accuracy)
- `model_type: "auto"` enables automatic LoRA model detection
- Uses same battle-tested model across all profiles for consistency
- Aligns aibrix configuration with the working profiles

**Change 2**: Added default_decision for fallback (lines 386-401)
```yaml
- name: default_decision
  description: "Default catch-all decision - blocks all PII for safety"
  priority: 0
  rules:
    operator: "OR"
    conditions:
      - type: "domain"
        name: "other"
  modelRefs:
    - model: vllm-llama3-8b-instruct
  plugins:
    - type: "pii"
      configuration:
        enabled: true
        pii_types_allowed: []
```

**Why**:
- PII policy code falls back to "default_decision" when decision lookup fails
- Priority 0 ensures it's only used as last resort
- Blocks all PII types for maximum safety
- Prevents edge cases where PII detection would be disabled
- Required by fallback mechanism in src/semantic-router/pkg/utils/pii/policy.go

### 2. Configure AIBrix Profile Test Model (e2e/profiles/aibrix/profile.go)

**Change**: Set environment variable in Setup() method (lines 84-85)
```go
// Configure PII test to use vllm-llama3-8b-instruct model
os.Setenv("E2E_TEST_MODEL", deploymentDemoLLM)
```
where `deploymentDemoLLM = "vllm-llama3-8b-instruct"`

**Why**:
- AIBrix uses different model names than dynamic-config/ai-gateway
- Ensures test explicitly uses the correct aibrix model
- Prevents reliance on environment variable inheritance from other tests
- Matches the approach used in dynamic-config profile (sets E2E_TEST_MODEL=MoM)
- Makes test behavior predictable and independent

## Testing

**Before Fix**:
- aibrix: 0/100 PII tests passed (0% accuracy) ❌

**After Fix**:
- aibrix: 100/100 PII tests passed (100% accuracy) ✅

**Test Command**:
```bash
make e2e-cleanup && make e2e-test E2E_PROFILE=aibrix E2E_VERBOSE=true E2E_KEEP_CLUSTER=true
```

**Verified**: No impact on other profiles (dynamic-config and ai-gateway) as changes are isolated to aibrix-specific files only.

## Files Changed

1. **deploy/kubernetes/aibrix/semantic-router-values/values.yaml** (Lines 386-401, 459-466)
   - Added default_decision for PII policy fallback
   - Switched pii_model from ModernBERT to LoRA auto-detection
   - Aligned with dynamic-config and ai-gateway working configuration

2. **e2e/profiles/aibrix/profile.go** (Lines 84-85)
   - Sets E2E_TEST_MODEL=vllm-llama3-8b-instruct in Setup()
   - Ensures profile-specific model configuration
   - Makes test behavior independent and predictable

## Why This Works

**AIBrix flow**:
1. Test uses model="vllm-llama3-8b-instruct" (via E2E_TEST_MODEL env var)
2. Routes to decision (either matched or falls back to default_decision)
3. Decision has PII plugin enabled → PII detection runs
4. LoRA PII classifier detects entities (credit cards, SSNs, emails, etc.)
5. Policy blocks request → 100% accuracy ✅

All three profiles (dynamic-config, ai-gateway, aibrix) now use the same proven
LoRA PII detection model with 100% accuracy across all E2E tests.

## Summary of All Profiles

| Profile | PII Detection | Configuration |
|---------|--------------|---------------|
| dynamic-config | 100/100 (100%) ✅ | LoRA auto-detection, model=MoM |
| ai-gateway | 100/100 (100%) ✅ | LoRA auto-detection, model=general-expert |
| aibrix | 100/100 (100%) ✅ | LoRA auto-detection, model=vllm-llama3-8b-instruct |

Signed-off-by: Yossi Ovadia <[email protected]>
@yossiovadia yossiovadia force-pushed the fix/pii-lora-auto-detect branch from 8034e8a to 13b254f Compare November 20, 2025 20:29
@github-actions github-actions bot deleted a comment from codecov-commenter Nov 20, 2025
@yossiovadia
Copy link
Collaborator Author

Finally :)

I'm working on a shorter PR , please dont merge/approve this one. i'll use it as reference.

@yossiovadia
Copy link
Collaborator Author

This will be resolved by a cleaner #709 option.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants