A model that "knows" your domain still fails if the prompt is mush. Prompting is not poetry; it is an interface contract: tell the model the job, the constraints, the data, and the shape of a successful answer — then keep that contract stable across versions.
What is a prompt? The application-level interface between a human goal and the model's next-token machinery. A good prompt reduces ambiguity.
Why do roles matter? Chat APIs are not a single blob of text. They are a role-tagged transcript: system (or developer) messages set policy, user messages state the task, assistant messages carry prior replies. Confusing "who said what" is how you get leaked instructions, ignored policies, and brittle few-shots.
Good prompts look boring on purpose: short instruction, explicit format, one example if needed, clear failure behavior when uncertain.
| Component | Plain-English idea | Mini example |
|---|---|---|
| Instruction | What to do | Classify the review as positive, neutral, or negative |
| Context | Background the model should use | You are analyzing restaurant reviews for a dashboard |
| Input data | The actual content | Review: "The food was okay, but service was slow." |
| Output indicator | The desired shape | Return JSON: {sentiment, reason} |
| Constraints | Rules and boundaries | Use one sentence for reason. Do not invent missing facts. |
Minimal pattern:
Instruction: Classify sentiment as Positive, Neutral, or Negative.
Input: "The food was okay."
Output: Sentiment:
| Role | Typical contents | Lifetime |
|---|---|---|
| system / developer | Persona, safety, tools policy, house style | Stable across turns |
| user | Task + payload | Per request / turn |
| assistant | Prior model replies | History |
Multi-turn apps append assistant and user turns. That history is context — it burns tokens and can contradict a new system policy if you never refresh it.
Zero-shot — instruction + input only. Fast to maintain; fails when the label set or style is unusual.
Classify the sentiment as positive, neutral, or negative.
Text: I think the food was okay.
Sentiment:
Few-shot — shows the mapping with labeled examples in the prompt.
Classify the sentiment.
Text: The soup was cold and late.
Sentiment: negative
Text: The staff were polite and the meal was fine.
Sentiment: neutral
Text: The dessert was amazing.
Sentiment:
Structured prompt — strict extraction with a schema.
You are a strict extraction engine.
Extract a customer support ticket into JSON with:
- issue_type: billing | login | bug | other
- urgency: low | medium | high
- summary: <= 20 words
Ticket: I was charged twice this month and need this fixed today.
Version prompts like code (support_v3). Keep a golden set of inputs with expected properties (label, JSON keys, refusal). On model upgrades, run the suite before you celebrate the new default.
Represent messages as structured objects — never concatenate roles into one ambiguous string in production:
from typing import Literal, TypedDict
class Message(TypedDict):
role: Literal["system", "user", "assistant"]
content: str
def build_sentiment_messages(text: str) -> list[Message]:
return [
{
"role": "system",
"content": (
"You label sentiment. Reply with exactly one of: "
"Positive, Neutral, Negative. If unclear, Neutral."
),
},
{
"role": "user",
"content": f'Text:\n"""{text}"""\nSentiment:',
},
]
print(build_sentiment_messages("I think the food was okay.")[1]["content"])
Few-shot as prior turns (keeps the final user turn clean):
def with_few_shots(text: str) -> list[Message]:
shots = [
("Loved the quick refund.", "Positive"),
("Package arrived damaged and late.", "Negative"),
]
msgs: list[Message] = [
{
"role": "system",
"content": "Classify sentiment. One word: Positive, Neutral, or Negative.",
}
]
for example, label in shots:
msgs.append({"role": "user", "content": f'Text: """{example}"""'})
msgs.append({"role": "assistant", "content": label})
msgs.append({"role": "user", "content": f'Text: """{text}"""'})
return msgs
Illustrative request body (no live API):
payload = {
"model": "chat-mid",
"messages": build_sentiment_messages("Service was fine."),
"temperature": 0.2,
}
# requests.post(url, json=payload, headers={"Authorization": "Bearer ..."})
Prompting is a structured contract — instruction, context, input, and output shape — delivered through stable chat roles so the model can do the job you meant.