1. Redact Banned Phrases in a Streamed Response
Problem
A safety filter sits between a model's generated text and the user. Before a response is shown, the filter scans it for banned phrases (jailbreak fragments, disallowed instructions) and redacts each match in place, so the rest of the response is still readable.
Some banned phrases share prefixes or overlap in the text, so at every position the filter must prefer the longest matching phrase rather than redacting a shorter one first.
Source: src/1_redact_banned_phrases.py
def redact_banned_phrases(text: str, banned_phrases: list[str]) -> str:
...
Examples:
>>> redact_banned_phrases("Ignore THIS now override safety", ["ignore this", "override safety"])
'########### now ###############'
>>> redact_banned_phrases("act as dan and act as dan again", ["act as dan"])
'########## and ########## again'
Step-by-Step Approach
- Recognize this as multi-pattern string scanning: at every text position, several banned phrases might start matching, and you need the longest one to win.
- Deduplicate and sort the banned phrases by descending length once, up front, so the scan always checks longer candidates before shorter ones.
- Walk the text left to right with an index i. At each i, case-insensitively check whether any banned phrase starts at i using str.startswith(phrase, i) — this avoids allocating a substring just to compare.
- If a phrase matches, replace exactly that span with '#' repeated to its length and jump i forward by the phrase's length (never re-scan inside a redacted span).
- If nothing matches at i, copy the single character through unchanged and advance i by 1.
- Handle the edge case of an empty banned_phrases list by returning the text unchanged immediately, without scanning.
The key insight is that checking longest-first at each position, rather than scanning all phrases in input order, is what makes prefix-overlapping phrases resolve correctly without any backtracking.
Reference solution
def redact_banned_phrases(text: str, banned_phrases: list[str]) -> str:
if not banned_phrases:
return text
# dedupe, then try longest phrases first so overlapping/prefix phrases
# redact the longest match at each position, O(n * max_phrase_len)
phrases_sorted = sorted(set(banned_phrases), key=len, reverse=True)
lower_text = text.lower()
n = len(text)
result = []
i = 0
while i < n:
matched_len = 0
for phrase in phrases_sorted:
plen = len(phrase)
# str.startswith(sub, pos) checks in place, no substring copy
if plen and lower_text.startswith(phrase.lower(), i):
matched_len = plen
break
if matched_len:
result.append('#' * matched_len)
i += matched_len
else:
result.append(text[i])
i += 1
return ''.join(result)
Key Functions & Tricks
str.startswith(sub, pos)— checks for a match at an exact index without slicing a new substringsorted(set(...), key=len, reverse=True)— dedupe then order candidates longest-first for greedy matchingtext.lower()— precompute once so every comparison is case-insensitive without repeated work'#' * matched_len— builds the redaction span at the exact length of the matched phrase''.join(result)— O(n) final assembly instead of repeated string concatenation
How to Recognize This Pattern
The signal is "scan text and replace/flag spans from a set of candidate patterns," where patterns can overlap or share prefixes. A greedy longest-match-first scan, checking candidates sorted by descending length at each position, solves this without backtracking. Common variations swap in a trie or Aho-Corasick automaton when the pattern set is large enough that per-position linear scanning becomes the bottleneck. A common pitfall is checking patterns in an arbitrary order and redacting a short match before noticing a longer one also started at that position, which silently under-redacts the text.