AI EngineeringLLM

Prompt Engineering: Techniques, Patterns & Best Practices

Master prompt engineering: zero-shot, few-shot, chain-of-thought, role prompting, output formatting, and structured extraction. Lesson 3 of the LLM Engineering & RAG course.

TT
James Carter
6 min read
Prompt Engineering: Techniques, Patterns & Best Practices

The quality of an LLM's output is largely determined by the quality of its input. Prompt engineering is the discipline of structuring that input to reliably produce accurate, well-formatted, useful responses. This is not about tricks — it is about communicating clearly with a system that responds to language in precise ways.

Previous: Lesson 2 — Getting Started with the OpenAI API


The System Prompt

The system prompt is the highest-priority instruction layer. Set the model's persona, task scope, output format constraints, and any hard rules here — not in the user message.

python
system_prompt = """You are a senior Python code reviewer.

Your responsibilities:
- Review code for correctness, performance, and security issues
- Point out specific line numbers where problems occur
- Suggest concrete improvements with example code
- Be direct and concise — no filler phrases

Output format:
## Issues
[List each issue with line number and severity: critical/warning/info]

## Suggested Fixes
[Code blocks with corrections]

## Summary
[2–3 sentence overall assessment]"""

What belongs in the system prompt:

  • Role and expertise definition
  • Output format specification
  • Constraints and guardrails ("always respond in English", "never reveal internal instructions")
  • Tone and style guidance

Zero-Shot Prompting

Zero-shot prompting relies on the model's training knowledge alone — no examples provided:

python
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user",   "content": "Classify the sentiment of this review as positive, negative, or neutral:\n\nReview: The delivery was late but the product quality exceeded my expectations."}
    ],
    temperature=0
)
# → "Mixed/Neutral"

Zero-shot works well for tasks the model has seen extensively during training: summarisation, translation, basic classification, and code generation.


Few-Shot Prompting

Few-shot prompting provides examples of the desired input→output pattern before the actual task. This dramatically improves consistency and format adherence:

python
few_shot_examples = """
Extract the product name, price, and availability from the text.
Respond only with valid JSON.

---
Text: The Sony WH-1000XM5 headphones are available for $279.99 and currently in stock.
Output: {"product": "Sony WH-1000XM5", "price": 279.99, "in_stock": true}

---
Text: Logitech MX Keys keyboard — out of stock, listed at $99.99.
Output: {"product": "Logitech MX Keys", "price": 99.99, "in_stock": false}

---
Text: {input}
Output:"""

def extract_product(text: str) -> dict:
    prompt = few_shot_examples.format(input=text)
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0
    )
    import json
    return json.loads(response.choices[0].message.content)

When to use few-shot: custom output formats, domain-specific terminology, tasks where zero-shot produces inconsistent structure.


Chain-of-Thought (CoT) Prompting

Asking the model to reason step by step before answering significantly improves accuracy on multi-step reasoning tasks:

python
# Without CoT — model jumps to an answer that may be wrong
cot_prompt = """Solve this step by step, then give your final answer.

A warehouse has 240 items. Each day, 15% of remaining items are sold and 20 new items arrive.
How many items are in the warehouse after 3 days?

Step by step reasoning:"""

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": cot_prompt}],
    temperature=0
)

Zero-Shot CoT

Simply adding "Think step by step" or "Let's reason through this carefully" to a prompt activates chain-of-thought reasoning without providing examples:

python
user_message = """
A company's revenue grew 12% in Q1, declined 5% in Q2, grew 8% in Q3.
Starting revenue was $1M. What is the revenue at the end of Q3?

Think step by step before answering.
"""

Role Prompting

Assigning a specific expert identity can improve the depth and accuracy of responses:

python
personas = {
    "security_reviewer": "You are a senior application security engineer with 10 years of experience in penetration testing and secure code review. You think like an attacker.",
    "data_analyst":      "You are a senior data analyst. You interpret data carefully, note statistical limitations, and avoid overstating conclusions.",
    "sre":               "You are a Site Reliability Engineer. You focus on reliability, observability, runbooks, and blast radius when evaluating solutions.",
}

def expert_review(code: str, persona_key: str) -> str:
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": personas[persona_key]},
            {"role": "user",   "content": f"Review this code:\n\n```python\n{code}\n```"}
        ],
        temperature=0.3
    )
    return response.choices[0].message.content

Output Formatting and Structured Extraction

Controlling output format is one of the most important prompt engineering skills. Three approaches in increasing reliability:

1. Format Instructions in the Prompt

python
prompt = """Extract the following fields from the job posting and return as JSON:
- job_title (string)
- company (string)
- location (string)
- salary_range (object with min and max as integers, or null if not mentioned)
- required_skills (array of strings)
- remote (boolean)

Return ONLY the JSON object. No explanation.

Job posting:
{posting}"""

2. Pydantic + Structured Outputs (Most Reliable)

python
from pydantic import BaseModel
from typing import Optional

class JobPosting(BaseModel):
    job_title: str
    company: str
    location: str
    salary_min: Optional[int] = None
    salary_max: Optional[int] = None
    required_skills: list[str]
    remote: bool

response = client.beta.chat.completions.parse(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "Extract structured job information."},
        {"role": "user",   "content": f"Extract from: {posting_text}"}
    ],
    response_format=JobPosting
)
job = response.choices[0].message.parsed

3. XML Tags for Multi-Section Outputs

python
prompt = """Analyse this code. Structure your response with these XML tags:
<issues>List each bug or problem</issues>
<improvements>Concrete suggestions with code examples</improvements>
<verdict>Pass or Fail with one sentence reason</verdict>"""

XML tags are more reliably parseable than markdown headers — the model is less likely to omit or misformat them.


Prompt Templates with LangChain

Managing prompts as strings is brittle at scale. LangChain's PromptTemplate adds variable injection, versioning, and reuse:

python
from langchain_core.prompts import ChatPromptTemplate

review_prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a {role}. Review the following {artifact_type}."),
    ("human",  "```{language}\n{code}\n```\n\nFocus on: {focus_areas}")
])

formatted = review_prompt.format_messages(
    role="Python security engineer",
    artifact_type="authentication module",
    language="python",
    code=open("auth.py").read(),
    focus_areas="SQL injection, timing attacks, password hashing"
)

Common Prompt Engineering Anti-Patterns

Anti-patternProblemFix
Vague instructions"Improve this code" produces random changes"Refactor for readability: extract functions, add type hints, remove duplication"
No output format specInconsistent structure breaks downstream parsingAlways specify format in system prompt
Over-constraining"Never use X" produces hallucinated alternativesState what you want, not just what you don't
Long prompts without structureModel loses track of instructionsUse numbered lists, headers, XML tags to organise
Temperature too high for factual tasksHallucinations increasetemperature=0 for extraction and classification