Applied AI Engineering
Chapter 5 · Prompt Engineering
Engineering11 July 20267 min readBerlin, Germany

Prompt Engineering Is Software Engineering

Prompt Engineering Is Software Engineering
Said Mustafa SaidAI/ML & Cloud Engineer

You open a chat window and type a request. It feels like conversation. It is not. A prompt is an interface to a machine, and like every interface it has layers, rules, and failure modes. The moment code on the other end has to read the reply, prompt engineeringDesigning that interface. Soft where a human reads the reply, software the moment code parses it. stops being writing and becomes software engineering. This post is about that line, and about crossing it on purpose.

In the last post the model was a probability machine that predicts the next token. Now we drive it.

The System Prompt Is Standing Law

Every call to a model carries messages, and messages have roles. The system promptThe standing rules for a conversation, read as higher authority than any single user turn. sets the rules: who the model is, what it must and must not do, the format it must answer in. The user promptThe request being answered, interpreted under whatever the system prompt already established. is the request the model answers under those rules. Same model, two channels, and they do not carry equal weight.

The system prompt dominates. It is read as standing law, applied to every user turn in the conversation, and the model is trained to treat it as the higher authority when the two conflict. Put "answer only in JSON" in the user turn and a clever question can talk the model out of it. Put it in the system prompt and it holds across the whole session. This is role promptingAssigning the model a role and a boundary up front so every later turn inherits it.: you assign the model a role and a boundary up front, and everything after inherits it.

The system prompt is standing law. The user prompt is one request under it, and both shape the output the model returns

Rule of thumb: stable rules go in the system prompt, the variable request goes in the user prompt. Mixing them is the first bug.

Examples Are Not Free

The cheapest prompt gives no examples. You state the task and trust the model to know the shape of a good answer. That is zero-shotAsking for the task with no worked examples, which is usually enough for common jobs., and for common tasks it is often enough.

When the task is unusual, or the format is fussy, you show it. One worked example is one-shotGiving exactly one worked example to pin down the format or the tone.. A handful is few-shotGiving a handful of examples. The most reliable way to fix a format, and you pay for them on every call.. Examples are the most reliable way to pin down a format or a tone, because the model copies the pattern it sees.

But examples ride in the context window, and every token you spend on them is a token you pay for on every call. So examples have to earn their keep. Add them when zero-shot fails on the format, not as a reflex. If two examples fix the problem, do not paste six.

Make It Show Its Work

Ask a model for the answer to a multi-step problem and it will often blurt a wrong one, because it is drawing each token from a fresh guess with no room to reason. Give it that room. Chain of thoughtTelling a model to work through a problem step by step, which lifts accuracy on anything with intermediate steps. is a single instruction, "work through it step by step," and it visibly lifts accuracy on anything with intermediate steps. The model reasons in the open, then lands the answer.

Structure buys you control too. Wrapping parts of a prompt in tags, <context>, <task>, <format>, is XML promptingWrapping parts of a prompt in tags so the boundaries between context, task and format are unambiguous., and models parse those boundaries cleanly because their training is full of them. It is the simplest form of structured promptingWriting a prompt as labelled fields rather than a paragraph, so its parts can be varied and tested.: you stop writing a paragraph and start writing fields.

One Big Prompt Is a Bad Program

The instinct is to cram the whole job into one giant prompt. Resist it. A long prompt that classifies, extracts, summarises, and formats in one pass fails in ways you cannot debug, because you cannot see which stage broke.

Split it. Task decompositionBreaking one large job into ordered smaller steps that can each be tested. breaks the job into ordered steps. Prompt chainingRunning those steps in sequence, each prompt taking the previous one output as input. runs them in sequence, each prompt's output feeding the next, so classify then extract then format become three small calls you can test in isolation. And the parts that repeat become prompt templatesA fixed prompt skeleton with slots you fill per call, the same way you would parameterise a query.: a fixed skeleton with slots you fill per call, the same way you would never inline a SQL string you could parameterise.

This is where it starts to look like code, because it is code. A prompt with slots is a function. A chain of prompts is a pipeline.

The Real Product Is Output Your Code Can Parse

Here is the line. As long as a human reads the answer, prose is fine. The instant a program reads it, prose is a liability. Your code cannot act on "Sure, the total comes to about 42 dollars, roughly." It needs {"total": 42}.

Structured outputsConstraining a model to return machine-readable data rather than prose, so code can act on it. are the answer: you constrain the model to return machine-readable data, not free text. JSON modeAsking for valid JSON. You get something parseable, but not necessarily the shape you wanted. is the blunt version, "reply with valid JSON," and it gets you parseable output but not necessarily the right shape.

For the right shape you supply a JSON SchemaAn explicit contract naming every field, its type and which are required. The shape, not just the syntax.: an explicit contract naming every field, its type, and which are required. Modern APIs take the schema and constrain the reply to conform to it, which turns "please format nicely" into an enforced structure. Then you run validationChecking parsed output against the schema before you trust it. A contract you do not check is a wish. on your side anyway, checking the parsed object against the schema before you trust it, because a contract you do not check is only a wish.

Function Calling Is the Seed of Every Agent

Structured output pointed inward: shape the answer. Turn it outward and you get the most important mechanism in this whole series. Function callingHanding the model a menu of functions and letting it reply with a structured request to call one. lets you hand the model a menu of functions your code exposes, each with a name and a JSON Schema for its arguments. Instead of answering in prose, the model replies with a structured request: call get_weather with {"city": "Berlin"}. Your code runs the real function and hands the result back.

The model never runs anything. It only asks, in a format you can parse, and your code decides. Tool callingThe same mechanism as function calling, under a different name. is the same idea under a different name. This one mechanism, structured output wired to real functions, is the seed from which every agent in the later posts grows. An agent is this loop, run until the job is done.

Assume It Will Break

The model is still a probability machine, so sometimes the JSON comes back malformed: a trailing comma, a hallucinated field, prose wrapped around the braces. ParsingReading model output into a structure your code can use. Treat it as untrusted input, because it is. the output is not a formality, it is a trust boundary, and you treat model output exactly like untrusted user input.

So you build the loop the schema demands. Parse. Validate. If it fails, do not crash, feed the error back and ask again. That is retry logicFeeding a failure back to the model and asking again rather than crashing on the first bad reply., and it is ordinary defensive engineering: the same posture you would take toward any input you did not generate yourself.

The real product is a loop: the model emits JSON, your code validates it against the schema, and a failure retries with the error instead of crashing

Prompt Engineering Ends Where Code Begins

Prompt engineering has a reputation as a soft skill, phrasing tricks and magic words. The reputation is half true and getting less true every month. The system prompt, the examples, the chain of thought: those are the interface's ergonomics, and they matter. But the product is the contract, a schema your code can enforce, a function the model can request, a retry loop for when it does not comply.

A prompt is an interface. Design the loose half well. Engineer the half that code has to read, because that half is not writing at all. It is software.

But a perfectly engineered prompt still hits one wall: the model can only answer from what it already knows. The next rung tears that wall down, handing the model your own data so it can answer from facts it was never trained on.

Check what stuck

1You need JSON on every reply. Where does that instruction belong?

2What do few-shot examples cost you?

3The model calls your get_weather function. What actually ran it?