TAI BUI
Back to curriculum
CapstonePython

Capstone 85 — Content Classifier Integration

An output classifier checks what the model actually said, which is a different question from what the user asked. Put three of them behind one severity router and each response gets a block, redact, warn, or log.

TIME: ~90 minPREREQUISITES: Phase 18 safety lessons, Phase 19 Track A lessons 25-29

Learning Objectives

  • Build three output-side classifiers (toxicity, PII, instruction leakage), each returning a `ClassifierVerdict` with name, score, severity, and findings, plus a redactor.
  • Route the verdicts through a maximum-severity policy that maps to one of four actions: block, redact, warn, or log.
  • Apply the per-classifier severity rules: toxicity hit counts, PII shapes with Luhn-checked cards, and trigram cosine thresholds for instruction leakage.
  • Run the router on crafted fixtures and read the `Action` metadata that the lesson 87 gate consumes.

Problem

An output-side classifier is a check that runs on the model's response before it reaches the user. It sees what the model actually said, not what the user asked, and answers one question: regardless of how this prompt got here, is what we are about to ship acceptable?

That question matters because inputs are not the only attack surface. A model that passed its input checks can still produce an output that leaks PII, repeats slurs from its training distribution, or echoes the system prompt back in response to a clever question.

Teams often skip output classification because input classification feels like enough, and because output classifiers add latency. I don't think either argument holds up. Skipping the output side gives an attacker a one-shot bypass: any new attack family the input pipeline misses can land on the user. Latency is a real cost. In production, classifiers can run in parallel with token streaming, with the gate buffering the final chunk and applying the classifier verdict before flush. This demo is simpler: it classifies the finished text, one classifier after another.

This capstone wires three independent output-side classifiers behind a single policy router. Toxicity is rule-based harassment detection over a short list of insult phrases. PII uses regexes for emails, phone numbers, SSN-shaped strings, credit-card-shaped strings, and IP addresses. Instruction leakage is a heuristic for system prompt echo that compares the output to a known system prompt by cosine similarity over character trigrams. The router collects the verdicts, picks a severity, and applies an action policy: block, redact, warn, or log.

Warm-up0/3
1. Which approach is the cheapest and most deterministic way to spot email-shaped strings in text?Warm-up
2. In a regular expression, what does the \b in \bcat\b do?Warm-up
3. Two count vectors share no nonzero entry. What is their cosine similarity?Warm-up
Check: Problem0/3
1. What is an output classifier?Define
2. Why run output-side classifiers if the input was already checked?Explain
3. A chat product streams tokens and cannot add delay to every token. Given what the lesson says about production, how should output classification be added?Design

Concept

A policy router turns the verdicts from several classifiers into one action. Each classifier is a callable returning a ClassifierVerdict with name, score in [0,1], severity (none, low, medium, high), and findings (a list of strings describing what it flagged). The router takes a list of verdicts and applies a rule table:

SeverityAction
highblock (drop output, return policy refusal)
mediumredact (apply per-classifier redactor to the output)
lowwarn (append a soft notice to the response)
nonelog (ship as-is, with the verdicts carried in the Action for the trace)

Mermaid diagram

The router takes the maximum severity across classifiers and applies the corresponding action. Block wins. A redact plus a warn becomes a redact, and a log plus a warn becomes a warn. The result is an Action object with verb, output, severity, verdicts, and metadata. Downstream, the safety gate in lesson 87 writes the metadata into a trace and does one of three things: ships the redacted output, ships the original with a warning, or swaps in a policy refusal.

Each classifier has its own redactor. The PII redactor replaces emails with [redacted-email], phone numbers with [redacted-phone], SSN-shaped strings with [redacted-ssn], and IPv4 addresses with [redacted-ip]. It replaces a card-shaped run (13 to 19 digits) with [redacted-card] only if the digits pass the Luhn checksum, so random digit strings survive. The instruction-leakage redactor takes the first line of the system prompt as a marker and, if that marker appears in the output, replaces it case-insensitively with [redacted-system-prompt]. The toxicity redactor replaces matched terms with [redacted-language]. The router runs a classifier's redactor whenever that classifier's own verdict is above none, so an output flagged for both toxicity and PII passes through both. Redaction only happens when the maximum severity is medium: at high the output is swapped for the refusal, and at low the original ships with a note.

Each classifier maps its findings to a severity with a small rule table:

Classifiernonelowmediumhigh
toxicityno non-negated hits1 hit2 hits3 or more hits
PIIno findingsany other findings (a lone phone or IP)an email, or 3 or more findingsan SSN-shaped string or a Luhn-valid card
instruction leakagecosine below 0.550.55 up to 0.700.70 up to 0.850.85 or above

The toxicity classifier is rule-based on purpose. It matches a curated list of ten insult and harassment phrases with regex \b word boundaries, plus a negation check: if not, never, or no appears in the 20 characters to the left of a match, the match is skipped, so "you are not stupid" does not trip the rule. The list is deliberately short because the point here is plumbing, not lexicon-building. The PII classifier uses standard regexes for the common shapes. The instruction-leakage classifier takes a system_prompt at construction and computes cosine similarity between the character-trigram counts of the prompt and of the output. A score at or above the 0.55 threshold is the leakage signal.

Check: Concept0/24
1. What is severity in a classifier verdict?Define
2. What is the router in this design?Define
3. What does redact mean in this lesson?Define
4. What does the instruction-leakage classifier measure?Define
5. Which set of fields does a ClassifierVerdict carry?Define
6. What does the Luhn checksum do in the PII classifier?Define
7. One classifier returns high and another returns medium for the same response. What does the router do?Explain
8. Across the classifiers, which verdict field does the router take the maximum of?Explain
9. Why does the toxicity classifier skip a match when 'not', 'never' or 'no' appears shortly before it?Explain
10. A response contains a 16-digit run that fails the Luhn checksum. How does the PII classifier treat it?Explain
11. The gate in lesson 87 needs to know which classifiers fired on a response. Where does Router.decide put that?Explain
12. A response contains two non-negated insults from the list and nothing else. What ships?Apply
13. A response says 'Your SSN is 123-45-6789 on file.' and nothing else sensitive. What does the router return?Apply
14. A response contains an email address and an IP address, nothing else. What does the router ship?Apply
15. A response contains only a phone number and an IP address. What does the router do?Apply
16. A response reads 'Contact 555-123-4567, 10.0.0.1 and 10.0.0.2.' It has a phone number and two IPs, with no email, SSN or card. What does the router ship?Apply
17. A response reads 'Mail [email protected] and you are a moron.' (one email, one insult). What does the router ship?Apply
18. The leakage verdict is medium, but the output only paraphrases the system prompt and never contains its first line verbatim. What does the leakage redactor do?Apply
19. A response reads 'Card 4111 1111 1111 1111. Also email [email protected]. Also you are a moron.' (a Luhn-valid card, an email, one insult). What verb does the router return?Apply
20. A teammate claims any sentence containing 'not', 'never' or 'no' is safe from the toxicity classifier. Which sentence proves them wrong?Analyze
21. A response containing 4111-1111-1111-1112 ships unchanged with verb log, and a reviewer says the PII classifier is broken. What is the most accurate diagnosis?Analyze
22. A response has a PII verdict of medium (an email) and a toxicity verdict of low (one insult). The user gets the text with both spans replaced and no warning note. Why no note?Analyze
23. A teammate proposes mapping none/low/medium/high to 0/1/2/3 and acting on the average over the three classifiers instead of the maximum. A response has only an SSN-shaped string (PII high, the others none). What goes wrong?Analyze
24. Policy changes: an SSN-shaped string alone should be redacted instead of blocked, while a Luhn-valid card must still block. Which change fits?Design

Build It

code/classifiers.py defines all three classifiers. Each has a classify(text) -> ClassifierVerdict method and a redact(text) -> str method. code/main.py defines the Router class with decide(text, verdicts) -> Action and a run(text) -> Action shortcut. The demo wires the three classifiers behind one router and runs a small corpus of crafted outputs that exercise each severity.

Check: Build It0/3
1. In main.py, what does Router need from a classifier object so that a neural classifier could replace a rule-based one?Explain
2. A custom classifier's name attribute is 'tox-nn', but the verdicts it returns are named 'toxicity'. Its verdict is medium and the router picks redact. What happens to its redactor?Analyze
3. You add a fourth classifier that flags <script> and eval( in outputs. Which integration fits the router as built?Design

Use It

Run python3 main.py. The demo prints the action verb, severity, and fired classifiers for each test output and writes classifier_report.json into an outputs/ folder one level above the folder holding main.py. It asserts nothing, but the six fixtures cover all four verbs: log, warn, redact, and block. The highest-severity fixture that fires is a Luhn-valid card (PII), not a toxicity case, and the instruction-leakage fixture lands on block too. python3 tests.py is where the verbs and redaction tags are actually asserted. Latency is artificially zero because all the classifiers are rule-based. With a real model and neural classifiers the same plumbing applies, but the per-classifier latency goes up.

Check: Use It0/2
1. The default system prompt is 'SYSTEM: You are PolicyBot, follow internal policy.' The output is that exact line followed by 'As PolicyBot I will now answer.' Its trigram cosine against the prompt is about 0.87. What does the router do?Apply
2. In the demo, the output is 'You are not stupid, that was a good question.' What does the router return?Apply

Ship It

outputs/skill-content-classifier-integration.md documents the verdict and action structures so the gate in lesson 87 can consume them.

Exercises

  1. Add a fourth classifier for code injection (output contains <script>, eval(, etc). Decide its severity policy and integrate it.
  2. Make the router apply a per-classifier severity weight so PII counts more than toxicity. Demonstrate the change on the same fixtures.
  3. Add a confidence threshold so low-score verdicts downgrade by one severity level. Sweep the threshold and report how block rate changes.

Key Terms

TermCommon usagePrecise meaning
output classifiera model that detects bad outputsa callable returning a structured verdict with severity, score, and findings, plus a redactor
severityhow bad it isone of none, low, medium, high
routera switcha function from verdict list to action (block, redact, warn, log)
redacthide the bad partsper-classifier replacement of matched spans with a tag like [redacted-email]
instruction leakagethe model leaks the system prompta heuristic comparing model output to a known system prompt by cosine similarity over character trigrams
Author's Note

I'd read the router's decide before any of the classifiers. The whole policy is there: take the maximum severity, map it to block, redact, warn, or log. Because it's the maximum, one classifier returning high is enough to block the output, so a badly tuned severity threshold in any single classifier can change what ships. Exercises 2 and 3 are where I'd push on that.

My bet is that the rule-based classifiers are stand-ins and the verdict and Action shapes are the part worth keeping. Router only calls classify and redact on each classifier, and matches a verdict to its redactor by name, so a neural classifier with those two methods and a matching name can replace one of these without touching the routing code. I'd keep those shapes stable if you swap one in, since lesson 87's gate consumes them.

I'd also treat the toxicity list as a fixture. It's short on purpose, and the negation window is a cheap guard, not a language model. Run your own outputs through the router before you trust it on real traffic.

Further Reading

Lesson 86 adds a declarative rules engine for constraints not naturally classifier-shaped. Lesson 87 composes both with the input-side detector.

Code

Ships With