Chapter 7 · AI-Curious Developer

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 4

Common 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.