Constrained Decoding › Regex Constraints
Language AI HandbookAll chapters Constrained Decoding › Regex ConstraintsOne section · Sections in this chapter6 / 13 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Regular expressions provide a flexible way to constrain generation when you need pattern matching without full grammar complexity. Common use cases include:
- Phone numbers:
\d{3}-\d{3}-\d{4} - Email addresses:
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,} - Dates:
\d{4}-\d{2}-\d{2} - Custom identifiers:
[A-Z]{2}\d{6}
Regex to DFA Conversion
Regular expressions can be converted to deterministic finite automata (DFAs), which then guide generation exactly like grammar-based FSMs.
import re
class RegexConstraint:
"""
Constrain generation to match a regular expression.
Uses a simplified simulation approach for demonstration.
Real implementations compile to DFA for efficiency.
"""
def __init__(self, pattern: str):
self.pattern = pattern
self.regex = re.compile(f"^{pattern}$")
self.partial_regex = re.compile(f"^{pattern[:]}")
def get_valid_tokens(
self, current: str, vocabulary: list[str]
) -> list[str]:
"""
Find tokens that could extend current string toward a match.
Args:
current: String generated so far
vocabulary: List of possible next tokens
Returns:
List of valid next tokens
"""
valid = []
for token in vocabulary:
candidate = current + token
# Check if candidate could be prefix of a valid match
if self._could_match(candidate):
valid.append(token)
return valid
def _could_match(self, text: str) -> bool:
"""Check if text could be extended to match the pattern."""
# Simplified check: see if any extension could work
# Real implementations use DFA state tracking
for extension in ["", "a", "0", "@", ".", "-"]:
test = text + extension * 20 # Pad with potential matches
if self.regex.match(test):
return True
# Also check if text itself is a valid prefix
# by trying the pattern with .* appended conceptually
try:
partial = re.compile(f"^{re.escape(text)}")
# If we can find any completion that matches, text is valid
return True # Simplified - assume prefixes are valid
except re.error:
return FalseRegex constraint: \d{3}-\d{3}-\d{4}
Valid next characters at each step:
(empty) → valid: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-']
"5" → valid: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-']
"55" → valid: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-']
"555" → valid: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-']
"555-" → valid: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-']
"555-12" → valid: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-']
"555-123-" → valid: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-']
"555-123-456" → valid: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-']The regex constraint ensures that partial outputs always remain on a path toward completing the pattern. Once you've generated "555-", only digits are valid for the next three positions, followed by a mandatory hyphen, then four more digits.

Combining Regex with Semantic Guidance
A powerful technique combines regex constraints with the model's semantic knowledge. The regex ensures format correctness while the model contributes meaningful content.
import numpy as np
def generate_with_regex_constraint(
model_probs: dict[str, float],
current: str,
regex_pattern: str,
vocabulary: list[str],
) -> str:
"""
Sample next token using both model preferences and regex constraints.
Args:
model_probs: Model's probability distribution over tokens
current: Generated string so far
regex_pattern: Pattern the final output must match
vocabulary: List of possible tokens
Returns:
Selected token
"""
constraint = RegexConstraint(regex_pattern)
valid_tokens = constraint.get_valid_tokens(current, vocabulary)
if not valid_tokens:
raise ValueError(f"No valid continuation from '{current}'")
# Filter model probs to valid tokens only
filtered_probs = {t: model_probs.get(t, 0) for t in valid_tokens}
total = sum(filtered_probs.values())
if total == 0:
# Uniform over valid tokens if model gives no probability
return np.random.choice(valid_tokens)
# Normalize and sample
normalized = {t: p / total for t, p in filtered_probs.items()}
tokens, probs = zip(*normalized.items())
return np.random.choice(tokens, p=probs)Model digit preferences (from training data): '5': 15% '1': 12% '7': 11% '2': 10% '6': 10% Generating phone number with constraint + model semantics: Generated: 063582472021 Note: Digits reflect model preferences, hyphens are structurally required.