teachyou.ai academy
← All posts
Prompt EngineeringClaudeAnthropic APILLM DevelopmentAI Agents

Prompt Engineering for Modern Claude Models

Pramod Dutta · Jun 24, 2026 · 13 min read

Prompt engineering for Claude is the practice of shaping instructions, context, and examples so that a Claude model (Opus, Sonnet, or Haiku) produces reliable, on-format output on the first try instead of the fifth. It is not about magic phrases. It is about giving the model a clear role, unambiguous instructions, the right amount of context, and a predictable output contract. Get those four things right and most "the model is being weird" problems disappear. This guide walks through the techniques that actually move the needle, with runnable code you can paste into a script today.

Why Prompt Engineering for Claude Is Different From "Just Asking Nicely"

Claude models are trained to be helpful, careful, and to follow instructions closely, but they are still statistical systems that respond to the literal text you send. Vague prompts get vague answers. A prompt that says "summarize this" gets a different quality of output than one that says "summarize this in exactly 3 bullet points, each under 15 words, focused on financial risk." The gap between those two prompts is prompt engineering.

Three things make Claude specifically worth understanding, rather than treating it like any other model:

  • It responds very well to explicit structure, especially XML-style tags, because a large share of its training and fine-tuning used that format for delimiting instructions from content.
  • It supports extended thinking (sometimes called "thinking mode") on capable models, which lets Claude reason step by step before producing a final answer, and you can prompt around that behavior.
  • It has strong instruction-following fidelity at the level of small details, meaning if you say "never" or "always" in a system prompt, Claude tends to actually respect that, so sloppy absolute language in your prompt will bite you.

Once you internalize that Claude takes your instructions literally and rewards structure, the rest of prompt engineering is mostly craft, not tricks.

Start With the System Prompt, Not the User Turn

The single highest-leverage lever in Claude prompting is the system prompt. It sets persistent behavior: role, tone, constraints, and output format. Put stable rules there, and put only the task-specific content in the user turn.

A weak pattern crams everything into one long user message:

Write a product description for a running shoe, keep it under 100 words, don't use exclamation marks, write in a confident but not salesy tone, and format it as a single paragraph.

That works, but it re-explains the rules every single call. A stronger pattern separates identity and rules (system) from the task (user):

import anthropic

client = anthropic.Anthropic()

MODEL_NAME = "your-claude-model"  # e.g. a current Opus, Sonnet, or Haiku model id

SYSTEM_PROMPT = """
You are a product copywriter for an athletic footwear brand.
Rules you must always follow:
- Keep descriptions under 100 words.
- Never use exclamation marks.
- Tone is confident, not salesy.
- Output a single paragraph, no headings, no bullet points.
"""

response = client.messages.create(
    model=MODEL_NAME,
    max_tokens=300,
    system=SYSTEM_PROMPT,
    messages=[
        {"role": "user", "content": "Product: trail running shoe with a rock plate and wide toe box."}
    ],
)

print(response.content[0].text)

Now every call to this endpoint inherits the same rules, and the user turn stays short and swappable. This is exactly how you would build a real feature: system prompt owns behavior, user turn owns the specific request. It also makes prompt caching more effective, since the stable system prompt can be cached across many requests while only the user turn changes.

Structure Instructions and Content With XML Tags

Claude was trained extensively on XML-tagged data, and it reliably uses tag names as semantic anchors. When you have multiple distinct chunks of information in one prompt (instructions, source document, examples, output schema), wrapping each in its own tag reduces ambiguity dramatically.

prompt = """
<document>
{{document_text}}
</document>

<instructions>
Read the document above and extract every action item.
For each action item, identify the owner if one is named.
</instructions>

<output_format>
Return a numbered list. Each line: "Action - Owner (or 'unassigned')".
</output_format>
"""

This matters most once your prompts grow past a couple of paragraphs. Without tags, Claude has to infer where the "document" ends and the "instructions" begin, which is exactly the kind of ambiguity that produces inconsistent output across runs. With tags, that boundary is explicit and machine-parseable, and you can reference it later in the same prompt: "using only the text inside <document>, answer the following."

A few conventions that hold up well in production prompts:

  • Use lowercase, descriptive tag names: <context>, <examples>, <constraints>, <output_format>.
  • Keep tag names consistent across your prompt templates so you can build reusable functions around them.
  • Nest tags for structured examples, such as <example><input>...</input><output>...</output></example>.
  • Ask Claude to echo your tag structure back in its answer when you need machine-parseable output, for example <answer>...</answer>, then parse that tag out in code instead of parsing free text.

Give Claude Room to Think

For tasks that involve multi-step reasoning, math, code review, or anything with a "trick" in it, letting the model reason before answering improves accuracy substantially. There are two ways to do this with Claude.

The first is a manual chain-of-thought instruction, which works on any Claude model:

Before giving your final answer, work through the problem step by step inside <reasoning> tags. Then give your final answer inside <answer> tags. Only the content inside <answer> will be shown to the user.

The second, on models that support it, is enabling extended thinking directly through the API, which gives the model a dedicated reasoning budget separate from its final response:

response = client.messages.create(
    model=MODEL_NAME,
    max_tokens=2000,
    thinking={"type": "enabled", "budget_tokens": 1024},
    messages=[
        {"role": "user", "content": "A train leaves station A at 60 mph, another leaves station B (180 miles away) at 90 mph toward A. When and where do they meet?"}
    ],
)

for block in response.content:
    if block.type == "thinking":
        print("REASONING:", block.thinking)
    elif block.type == "text":
        print("ANSWER:", block.text)

Extended thinking is not something you need to prompt for with clever phrasing, it is a parameter you set, and the model uses that budget to reason before writing the visible answer. Reserve it for genuinely hard tasks. For a simple classification or a short rewrite, forcing extra reasoning steps just adds latency and cost without improving quality.

Show, Don't Just Tell: Few-Shot Examples

Description is good, demonstration is better. If you want a specific output shape, tone, or edge-case handling, give Claude two or three examples of exactly that, formatted the way you want the real output formatted.

prompt = """
<instructions>
Classify each support ticket as "billing", "technical", or "account".
Respond with only the category, nothing else.
</instructions>

<examples>
<example>
<ticket>I was charged twice for my subscription this month.</ticket>
<category>billing</category>
</example>
<example>
<ticket>The app crashes every time I upload a PDF.</ticket>
<category>technical</category>
</example>
<example>
<ticket>I can't remember which email I signed up with.</ticket>
<category>account</category>
</example>
</examples>

<ticket>My invoice shows a charge for a plan I already cancelled.</ticket>
<category>
"""

Notice the prompt ends mid-tag, with <category> open. This is a small but powerful trick: it primes Claude to continue directly into the expected format rather than adding preamble like "Sure, here's the category:". Few-shot examples do more work than adjectives ever will. If you find yourself piling on words like "concise," "professional," or "friendly but not too casual," replace them with one clean example that embodies all three.

Prompting Claude for Tool Use and Agentic Workflows

When Claude is calling tools, whether that's a search function, a database query, or a code execution sandbox, prompt engineering shifts from "produce good text" to "make good decisions about when and how to act." A few practices matter here specifically.

Describe each tool's purpose precisely in its schema description, not just its parameters. Claude decides whether to call a tool largely based on how well the description matches the user's intent.

tools = [
    {
        "name": "search_knowledge_base",
        "description": "Search the internal knowledge base for policy documents, refund rules, and product specs. Use this before answering any question about company policy or product details. Do not use it for general knowledge questions.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "The search query."}
            },
            "required": ["query"],
        },
    }
]

response = client.messages.create(
    model=MODEL_NAME,
    max_tokens=1024,
    tools=tools,
    messages=[
        {"role": "user", "content": "What's our refund policy for digital courses?"}
    ],
)

for block in response.content:
    if block.type == "tool_use":
        print("Tool call:", block.name, block.input)

In the system prompt, spell out the decision boundary explicitly rather than trusting the model to infer it: "If the user asks about policy, pricing, or product specifics, always call search_knowledge_base first. Never answer policy questions from memory." That single sentence eliminates most of the hallucinated-policy failure mode.

For multi-step agentic loops, also tell Claude what "done" looks like. An agent without a stated stopping condition will either quit too early or loop past the point of usefulness. A line like "once you have enough information to answer confidently, stop calling tools and respond to the user" gives the model a concrete exit condition instead of leaving it to guess.

Controlling Output Format Reliably

A recurring real-world need is getting Claude to return output your code can parse without babysitting it, usually JSON or a fixed structure. Three techniques stack well together.

First, describe the exact schema in the prompt, including field names and types:

Return only valid JSON matching this shape, no other text:
{
  "title": string,
  "priority": "low" | "medium" | "high",
  "tags": string[]
}

Second, use a prefill on the assistant turn to force the response to start exactly where you want it, which is one of the most underused Claude-specific techniques:

response = client.messages.create(
    model=MODEL_NAME,
    max_tokens=500,
    messages=[
        {"role": "user", "content": "Summarize this ticket as JSON: 'Payment failed twice, user is frustrated, needs urgent follow-up.'"},
        {"role": "assistant", "content": "{"},
    ],
)
print("{" + response.content[0].text)

By seeding the assistant turn with {, you remove the possibility of Claude prepending "Here's the JSON you requested:" before the object. This works for any fixed prefix, not just JSON, code blocks and specific opening phrases benefit the same way.

Third, if your SDK or workflow supports structured output validation, parse the response and retry with the validation error appended to the prompt on failure, rather than trying to write a single perfect prompt that never fails. Treat output format as something you engineer for reliability across many calls, not something you get right once and forget.

Iterating on Prompt Engineering Like an Engineer

Treat prompts as code that needs a test suite, not as a one-off message you tune by feel. A practical loop:

  1. Write down 8 to 15 representative inputs, including edge cases you expect to be hard (ambiguous requests, adversarial phrasing, empty input, very long input).
  2. Run your current prompt against all of them and save the outputs.
  3. Grade each output against a rubric you define in advance, not against vibes in the moment.
  4. Change one thing in the prompt at a time: reorder sections, add an example, tighten a constraint.
  5. Re-run the same test set and compare, not just the failing cases but the ones that were passing before, since a fix for one case can regress another.

This is where prompt engineering for Claude stops being guesswork. A prompt that looks clean and well-written can still fail on 2 out of 15 test cases, and you only find that by running the set, not by rereading the prompt one more time. If you're building anything user-facing, keep this test set in version control next to the prompt itself, and re-run it whenever you touch the system prompt or swap model tiers.

It also helps to separate concerns explicitly when you iterate: is the failure a knowledge gap (the model doesn't have the information), an instruction gap (you never told it the rule), or a format gap (it knows the answer but expresses it wrong)? Each has a different fix. Knowledge gaps need retrieval or more context, instruction gaps need a clearer system prompt, and format gaps usually need an example or a prefill, not a longer explanation.

Common Prompt Engineering Mistakes

  • Burying the real instruction in a wall of context. If the actual task is one sentence, put that sentence first or last, the two positions Claude weighs most heavily, and let supporting context sit in the middle inside its own tag.
  • Using soft language for hard constraints. "Try to keep it short" produces inconsistent length. "Maximum 50 words, no exceptions" produces consistent length.
  • Asking for too much in one turn. A single prompt asking Claude to research, draft, critique, and finalize a document in one pass produces worse results than breaking that into sequential calls where each step's output feeds the next.
  • Forgetting negative examples. Telling Claude what to avoid, with a short example of the bad output right next to the good one, closes gaps that positive instructions alone leave open.
  • Re-explaining stable rules in every user turn. That's what the system prompt is for; move anything that doesn't change between requests there.
  • Ignoring temperature. For extraction, classification, and structured output, set temperature low (near 0) for consistency. For brainstorming or creative writing, a higher temperature is appropriate. Leaving it at a default value for every task type is a common source of "why did it change the answer this time" confusion.

FAQ

Does prompt engineering matter less as Claude models get smarter? No, it shifts rather than disappears. Smarter models need less hand-holding on basic instruction-following, but they still need clear task definitions, output contracts, and tool descriptions. The floor rises, the ceiling for what good prompting unlocks rises with it, especially for agentic and multi-step work.

Should I use Opus, Sonnet, or Haiku for prompt-heavy workflows? Match the model tier to the task's reasoning depth, not to habit. Haiku-class models are well suited to high-volume, low-ambiguity tasks like classification or short extraction. Sonnet-class models are a strong default for most application logic. Opus-class models earn their cost on tasks with real ambiguity, long multi-step reasoning, or high-stakes correctness requirements. The prompting techniques in this guide apply across all three; what changes is how much you can lean on the model's own judgment versus how explicit you need to be.

How long should a system prompt be? As long as it needs to be to remove ambiguity, and no longer. A good test is whether every sentence in the system prompt would change the model's behavior if removed. If a sentence is generic filler ("You are a helpful assistant"), cut it. If it encodes a real rule ("never reveal internal ticket IDs to the end user"), keep it, and keep it near the top.

Do XML tags actually beat Markdown headings for structuring prompts? For separating distinct blocks of content, especially when a prompt is generated programmatically and content might contain Markdown itself, XML tags are more reliable because they are less likely to collide with formatting inside the content. Markdown headings work fine for prompts you hand-write once and don't template.

Is few-shot prompting still worth it if I have a detailed instruction list? Yes. Instructions describe rules, examples demonstrate the rules in context, including edge cases you didn't think to write a rule for. The two are complementary, not substitutes, and a prompt combining a short instruction list with two or three sharp examples usually outperforms either one alone.

How do I stop Claude from adding conversational filler before structured output? Use an assistant-turn prefill that starts the response exactly where you want it, such as seeding the assistant message with { for JSON or `python for a code block. This is more reliable than asking politely, because it removes the option entirely rather than hoping the model chooses not to add a greeting.