2.1 The Four Prompting Techniques
Production prompts are not guesswork. There are four techniques you apply systematically, depending on the symptom.
Diagnosis table — match the symptom to the missing technique:
| Symptom | Missing technique |
|---|---|
| Output is off-topic, too casual, or wrong persona | System prompt (role + constraints not set) |
| Instructions mixed up with user content, model confuses data for directives | XML tags (structure not applied) |
| Format wrong, model doesn't know what "good" looks like | Few-shot examples |
| Output is prose when you need JSON; inconsistent structure | Output constraints (schema or tool use) |
Treat these as independent levers. You can apply all four simultaneously.
2.2 System Prompts
The system prompt sets the operator-level context that persists across the conversation. It is where you define:
- Role and persona ("You are a senior code reviewer…")
- Capabilities and constraints ("You have access to the customer database via tools. Do not reveal raw SQL.")
- Output format requirements ("Always respond in JSON matching this schema…")
- Guardrails ("Never discuss competitor products. If asked, redirect politely.")
Structure matters. A long system prompt should use headers and XML tags internally:
<role>
You are a senior code reviewer for a financial services company.
Your job is to review Python code for correctness, security, and compliance with PCI-DSS.
</role>
<constraints>
- Never approve code that transmits raw card numbers outside the system
- Flag any use of MD5 or SHA-1 for password hashing as a critical issue
- All findings must be categorised: critical / major / minor
</constraints>
<output_format>
Respond in JSON:
{"findings": [{"severity": "...", "line": N, "description": "..."}], "approved": true/false}
</output_format>
System prompts are operator-level instructions — they cannot be overridden by user messages. But they are not a security boundary. A determined user can manipulate Claude's behaviour through the conversation. Authorization and access control must be enforced in your application code, not in system prompt instructions.
2.3 XML Tags
XML tags solve the "mixed content" problem: when instructions, examples, documents, and user input all appear in the same prompt, Claude can confuse which text it should treat as a directive and which as data.
Use XML tags to separate content types:
<document>
{{contract_text}}
</document>
<instructions>
Review the above contract for any clauses that would restrict our ability to use the software for AI-powered features. List each problematic clause with a plain-English explanation.
</instructions>
Custom tag names. You choose the tag names. Use descriptive names that match your domain:
<user_query>...</user_query>
<retrieved_context>...</retrieved_context>
<task>...</task>
XML tags are especially valuable when user content could contain text that looks like an instruction. Wrapping user input in <user_message> tags tells the model "treat this as data, not as a directive" — a first line of defence against prompt injection.
2.4 Few-Shot Examples
Few-shot examples show the model what "good" looks like rather than describing it in words. They are most effective when:
- The output format is complex or unusual
- The model needs to apply a reasoning pattern you can demonstrate
- The edge cases are important to get right
Example construction rules:
- Diversity over volume. Three examples covering different cases (normal, edge, tricky) beat ten examples of the same pattern.
- Cover the hard cases. Easy examples add no signal. Include examples where a naive approach would fail.
- Format consistency. The format of every example must match the format you expect in production.
- Negative examples. For classification tasks, include examples of each class, including the hard-to-distinguish ones.
<examples>
<example>
<input>Review: "Product stopped working after 2 days. Very disappointed."</input>
<output>{"sentiment": "negative", "confidence": "high"}</output>
</example>
<example>
<input>Review: "Does what it says, nothing more."</input>
<output>{"sentiment": "neutral", "confidence": "medium"}</output>
</example>
<example>
<input>Review: "Love it! Would be 5 stars but shipping took 2 weeks."</input>
<output>{"sentiment": "positive", "confidence": "medium"}</output>
</example>
</examples>
2.5 Output Constraints and Structured Output
For machine-readable output, don't ask for JSON in prose — enforce it structurally.
Method 1: Tool use for structured output. Define a tool that describes your schema. The model calls the tool instead of writing free text. This is the most reliable approach:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=[{
"name": "submit_classification",
"description": "Submit the sentiment classification result",
"input_schema": {
"type": "object",
"properties": {
"sentiment": {"type": "string", "enum": ["positive", "neutral", "negative"]},
"confidence": {"type": "string", "enum": ["high", "medium", "low"]},
"reasoning": {"type": "string"}
},
"required": ["sentiment", "confidence", "reasoning"]
}
}],
tool_choice={"type": "tool", "name": "submit_classification"},
messages=[{"role": "user", "content": f"Classify: {review}"}]
)
Setting tool_choice to a specific tool name forces the model to call exactly that tool — guaranteed structured output.
Method 2: Prefill the assistant turn. Start the assistant response with { to constrain the output to JSON: