A prompt builder often combines an application-owned instruction with user-provided source text. Repeated string replacement can accidentally interpret placeholders inside that source text. This example performs one substitution pass and validates the bindings before producing the request text.
Prerequisites are Python 3.12 or later and no third-party packages. The integration point is the prompt assembly stage immediately before your existing model client. This tutorial deliberately uses an offline fixture: there is no provider SDK, HTTP request or real model response.
Complete example
Save the following as prompt.py:
from string import Template
PROMPT = Template('Summarize for $audience.\nSource text:\n$text')
def render(values):
if set(values) != {'audience', 'text'}:
raise ValueError('exactly audience and text required')
if any(not isinstance(v, str) or not v.strip() for v in values.values()):
raise ValueError('nonempty strings required')
return PROMPT.substitute(values)
text = 'Price is $5; literal ${audience}; braces {stay}.'
result = render({'audience': 'editors', 'text': text})
assert result.endswith(text)
assert result.startswith('Summarize for editors.')
for values in [{}, {'audience': 'editors', 'text': ''},
{'audience': 'editors', 'text': 'x', 'debug': 'yes'}]:
try:
render(values)
raise AssertionError('accepted invalid bindings')
except ValueError:
pass
print('prompt fixture: literal text preserved; invalid bindings rejected')
Run and expected output
python3 prompt.py
prompt fixture: literal text preserved; invalid bindings rejected
The exact-key check catches a missing binding and an unexpected binding before substitution. This matters when a caller misspells a field or accidentally forwards a larger configuration dictionary. The value check rejects empty and whitespace-only strings while retaining the original contents of valid strings.
The inserted text contains a dollar amount, a placeholder-looking sequence and braces. The assertion verifies that all remain literal in the result. Template substitution processes the application template; it does not recursively substitute the strings inserted into it. Pass the returned string directly to your request builder rather than running another formatting step over it.
Edge cases and troubleshooting
Use $$ when the application-owned template itself needs a literal dollar sign. A missing template binding raises an error with substitute. Avoid silently switching to safe_substitute to hide that error: leaving unresolved fields can make a broken prompt harder to notice.
This boundary protects formatting behavior, not model instruction following. Source text can still contain adversarial instructions that a model might obey. Keep authorization and tool execution decisions in application code, and use role separation where your model interface supports it. The example makes no claim to prevent prompt injection.
When connecting a real client, add request-size limits before dispatch and record a template version with test fixtures. Do not log private source text merely to debug substitution. If a new placeholder is added to the template, update the accepted-key policy and its tests together.
Primary reference
Python documents Template substitution and its error behavior. Continue with Python tutorials and AI examples.