Prompt engineering with string transforms
A prompt is just a string. The whole of "prompt engineering" is, at the code level, building that string carefully and consistently — and the Python skill underneath it is the string work you already know: f-strings, .format(), joining lines with \n.
Good prompts share a shape: a clear role ("You are a…"), an explicit task, and the context the model needs, slotted into a fixed template. Using a template instead of hand-writing each prompt means every request comes out structured the same way — which makes the model's behaviour far more predictable.
Three building blocks cover most of it:
- f-strings — drop variables straight into the text:
f"Task: {task}". .format()/.format_map()— fill{key}placeholders in a reusable template string..format(**d)and.format_map(d)both take a dict.\n— newlines give the prompt structure (one instruction per line reads better to the model than one long run-on).
The string you build here is exactly what you'd send to the model. Everything else in this chapter is about handling what comes *back*.
Syntax
prompt = f"You are a helpful assistant.\nTask: {task}\nContext: {context}\nResponse:"
# Reusable template + a dict of values:
template = "Reply in a {tone} tone about {topic}."
template.format(tone="friendly", topic="otters")
template.format_map({"tone": "friendly", "topic": "otters"})Worked examples
Build a structured prompt from a template
def build_prompt(task, context):
return f"You are a helpful assistant.\nTask: {task}\nContext: {context}\nResponse:"
print(build_prompt("Summarise this", "The sky is blue"))
# Output:
# You are a helpful assistant.
# Task: Summarise this
# Context: The sky is blue
# Response:Fill a reusable template with .format()
template = "Reply in a {tone} tone about {topic}."
filled = template.format(tone="friendly", topic="otters")
print(filled)
# Output:
# Reply in a friendly tone about otters..format(**d) and .format_map(d) — both take a dict
data = {"name": "Sam", "level": 4}
print("{name} is on level {level}".format(**data))
print("{name} is on level {level}".format_map(data))
# Output:
# Sam is on level 4
# Sam is on level 4Common mistakes & gotchas
Forgetting the f on an f-string
Without the leading f, "Task: {task}" keeps the literal braces — the model gets {task} instead of the value. The f is what turns the braces into slots. A plain template string is the opposite: it *should* keep the braces, because you fill them later with .format().
A missing key crashes .format_map()
If the template has {topic} but your dict has no "topic" key, .format_map() raises KeyError. Make sure every placeholder in the template has a matching key — or validate the dict before filling.
Letting raw user/external text into the prompt unchecked
Whatever you concatenate into a prompt, the model reads as instructions — that's the root of "prompt injection". At minimum, guard the obvious: reject empty prompts, cap the length, and screen for marker strings (if "<secret>" in prompt: return False). Treat the assembled prompt as untrusted before you send it.
Why it matters
Prompting is mostly disciplined string-building. A consistent template makes the model's output consistent, makes bugs reproducible, and gives you one place to tighten safety checks — long before you touch any model SDK, this is pure Python text work you already own.