Menu

LLMs course · Lesson 5 of 12

Prompt Engineering and Structured Output: Getting Reliable Answers from LLMs

How to write prompts that work and get reliable JSON from an LLM: clear instructions, examples, schemas, validation and retries, with a runnable Python example.

  • Beginner
  • 5 min read
  • Updated Oct 2026
On this page
  1. What a good prompt contains
  2. System prompts
  3. Structured output in practice
  4. Prompting techniques that help
  5. Test prompts like code
  6. Common mistakes
  7. Key takeaway

A prompt is everything you send to a model: instructions, context, examples and the user’s input. Prompt engineering is the practice of writing prompts that give useful, reliable results, and then testing and improving them like code. For applications, the most important skill is getting structured output, such as JSON, that your program can use without guessing.

What a good prompt contains

  1. The task: one clear sentence. “Extract support data from this customer email.”
  2. The context: what the model needs to know. Who the user is, which product, which rules apply.
  3. The output format: exactly what to return. For programs, a JSON object with named fields and allowed values.
  4. Rules for edge cases: what to do when information is missing. “Use an empty string if there is no order number.” Without this, models tend to guess.
  5. Examples when the task is unusual: one to three input and output pairs, called few-shot examples.

Put long documents after the instructions, separated clearly, for example with a heading or tags, so the model can tell instructions from data.

System prompts

Most APIs accept a system prompt: instructions that apply to the whole conversation, set by the developer. Use it for the role, tone, rules and format. Keep it focused, and do not rely on it for security: sensitive rules, such as which records a user may see, must be enforced in your code.

Structured output in practice

Models are good at following a format, but not perfect. A reliable pattern has three parts:

  1. Describe the schema in the prompt, with allowed values.
  2. Validate the reply in code: is it valid JSON, are all fields present, are the types and values allowed?
  3. Retry once with the error message if validation fails, or fall back to a safe default.

Many providers also offer a JSON mode or a structured output feature that constrains the model to a schema. Use it when available, and still validate, because a valid shape can contain wrong values.

The example below runs that pattern. A small stand-in function plays the model, so the code runs anywhere. Its first reply contains two common mistakes: extra text around the JSON, and an issue type that is not allowed.

import json

SCHEMA = {"order_id": str, "issue": str, "refund_requested": bool}
ISSUES = {"late", "damaged", "wrong_item", "other"}

def build_prompt(email):
    return (
        "You extract support data from customer emails.\n"
        "Return ONLY a JSON object with these keys:\n"
        '  "order_id": string, like "A-1042" (use "" if missing)\n'
        '  "issue": one of "late", "damaged", "wrong_item", "other"\n'
        '  "refund_requested": true or false\n\n'
        f"Email:\n{email}"
    )

def validate(text):
    """Return (data, None) if the model output matches the schema, else (None, reason)."""
    try:
        data = json.loads(text)
    except json.JSONDecodeError as e:
        return None, f"not JSON: {e.msg}"
    for key, typ in SCHEMA.items():
        if not isinstance(data.get(key), typ):
            return None, f"'{key}' missing or not {typ.__name__}"
    if data["issue"] not in ISSUES:
        return None, f"issue '{data['issue']}' not allowed"
    return data, None

# Stand-in for a model API call, so this example runs anywhere. Its first reply has a common mistake.
replies = iter([
    'Sure! Here is the JSON: {"order_id": "A-1042", "issue": "broken", "refund_requested": true}',
    '{"order_id": "A-1042", "issue": "damaged", "refund_requested": true}',
])
def call_model(prompt):
    return next(replies)

email = "Hi, order A-1042 arrived with a cracked screen. I'd like my money back please."
prompt = build_prompt(email)
for attempt in (1, 2):
    reply = call_model(prompt)
    data, error = validate(reply)
    print(f"attempt {attempt}: {'OK ' + str(data) if data else 'rejected (' + error + ')'}")
    if data:
        break
    prompt += f"\n\nYour last answer was invalid ({error}). Return only the JSON object."
attempt 1: rejected (not JSON: Expecting value)
attempt 2: OK {'order_id': 'A-1042', 'issue': 'damaged', 'refund_requested': True}

The first reply was rejected before anything used it, and the retry message told the model exactly what was wrong. In a real application, replace call_model with your provider’s API call, keep the validation, and count how often retries happen: a rising retry rate is an early sign that a prompt or model change made things worse.

Prompting techniques that help

  • Few-shot examples: show the exact format and judgement you want. Use varied examples, because the model copies their patterns, including mistakes.
  • Step by step: for reasoning tasks, ask the model to work through steps before the final answer, and put the final answer in its own field.
  • Give a way out: “If the answer is not in the text, say you don’t know.” This reduces made-up answers.
  • Fewer, clearer rules: long lists of rules conflict with each other. Prefer a few precise ones.

Test prompts like code

Keep prompts in version control. Build a set of real inputs with expected outputs, and run it every time you change the prompt or the model. Look at the failures by hand: most improvements come from noticing a pattern in what went wrong, not from clever wording.

Common mistakes

  1. Parsing free text with string tricks instead of asking for JSON and validating it.
  2. No instruction for missing information, so the model invents values.
  3. Changing several things at once, so you cannot tell what helped.
  4. Trusting the format but not the content: a valid JSON object can still have a wrong order number.

Key takeaway

Good prompts state the task, context, exact output format and edge-case rules. For applications, ask for structured output, validate it in code, and retry or fall back when it is wrong. Then measure prompts on real examples before every change. Next: letting the model use your functions, in tool calling.

By , Data Engineer and Founder of DataDank · Last reviewed Oct 2026 · Applies to any LLM API. The example uses a stand-in function in place of a real model call so it runs anywhere; swap in your provider's client to use it for real. Output shown is real.

Progress is saved in this browser only. No account needed.

Search
Filter by type