Community Python Snippet
A Prompt Template With Safe Interpolation
After a customer email leaked into a system prompt and changed the model's persona, I built a 30-line template that quotes user input, fences code, and refuses unknown placeholders. Use it before every LLM call.
A Prompt Template With Safe Interpolation
After a customer email leaked into a system prompt and changed the model's persona, I built a 30-line template that quotes user input, fences code, and refuses unknown placeholders. Use it before every LLM call.
By @elisehuang
February 4, 2026
·
Updated May 20, 2026
1,093 views
10
4.4 (15)
The two failure modes I have actually hit are typos like {custmer_name} (which .format happily turns into a KeyError at request time) and customer messages that try to override the system prompt. The allowlist catches the first; fencing every value with <<<USER_INPUT ... USER_INPUT>>> markers catches the second by giving the model an unambiguous boundary to ignore instructions inside. I picked string.Formatter over Jinja or str.format_map because subclassing it lets me intercept both the field lookup and the rendering in 20 lines without a dependency. The control-character strip stops a sneaky vector where a PDF paste smuggles \x1b escape sequences that some downstream tools interpret.
The lint pass catches typos at CI time, before the template ever reaches a paying customer. I run it across every prompt file in prompts/*.txt plus the allowlist YAML, and a typo like {custmer_name} fails the build instead of the request. The runtime guard exists because templates also get loaded dynamically (A/B test variants, admin UI), and CI cannot cover dynamic loads. formatter.parse is the same Python uses internally for .format, so the lint is exactly aligned with what the runtime sees. The unused field has caught two prompts where I added a placeholder to the allowlist but forgot to wire it into the template.
The integration is the boring piece: build the system message once, run user inputs through the template, hand both to the chat client. The system prompt explicitly tells the model what the fences mean, which doubles the protection (the fences alone are not magic; you also need to teach the model to respect them). The fake client makes this snippet run offline and prints what would have been sent to the real provider. In production I log the rendered user payload at INFO when a request fails the model's safety filter, so I can replay the exact bytes that triggered it.
