Advanced Prompting and Claude's "Personality": System Prompt, XML Tags, Letting the Model Think First, Structured Output
Skip the prompting basics and go straight to four Claude-specific habits: using a system prompt to set the role, using XML tags to organize long inputs, letting the model think before it answers, and requesting structured output.
Today's Goals
- Explain the division of labor between the system prompt and the user message, and write a reusable system prompt
- Use XML tags to organize multiple passages, instructions, and examples into a long prompt Claude can read cleanly
- Get Claude to analyze before answering, and use a JSON Schema to obtain structured output
All five days of this course are about one thing: how to work well with a teammate who is very capable but only knows what you told them. Today is about the telling. Once you have read this and finished the lab, scroll back to the top and tick off the three goals.
Plain-Language Walkthrough
The basics live in the prompting course; here we only cover Claude's four habits
Let me draw a boundary first. The generic craft of prompting — the four parts of task, context, constraints, and output format; giving a few worked examples so the model can copy the pattern (few-shot); asking the model to reason step by step (chain of thought) — is covered systematically in D1 of Prompt Engineering in 5 Days, and this course does not repeat it. If you have never practiced turning a vague request into a clear instruction, spend an hour there first and then come back.
This course has exactly one running analogy: a new teammate you have just hired. This teammate is strong — reads fast, writes fast, never complains about the hours — with one quirk: they only know what you told them, and they will guess at whatever you left out. Working well with a teammate like that has little to do with how forcefully you give orders and everything to do with how clearly you brief them. Claude as a teammate also has four specific habits, and life gets much easier once you know them:
- It is very sensitive to a business card — say who it is in the system prompt and it will stay in that role all session.
- It reads labeled material far more accurately than one wall of text, and XML tags are the delimiters it recognizes.
- It is noticeably more reliable when it thinks before it speaks — so you have to leave it somewhere to think.
- It can emit structured output that matches a JSON Schema exactly — your program consumes it directly, no regex required.
Today has six subsections: the first four take one habit each, the fifth folds all four into a reusable template set, and the last one explains how that template set gets used over the next four days. The example task that runs through the whole course also shows up today: add input validation and matching unit tests to an Express / FastAPI TODO API. Today we only use it to practice prompting; from D3 on, Claude Code will actually do the work.
System prompt: hand the teammate a business card instead of repeating their identity in every sentence
Picture taking a new colleague to meet a client. You do not preface every sentence with "as a backend engineer at our company, you…" — you hand them a business card at the door: who you are, who you represent, where today's boundaries lie. From then on, every sentence they say comes from the position on that card.
The system prompt is that card. Technically it is a separate system field in the Messages API request, distinct from the user messages in the messages array; Claude treats it as a premise for the whole conversation rather than as one turn inside it. That yields two practical differences. First, in a dense conversation, rules placed in system do not get diluted by dozens of later turns. Second, it is naturally the product side of things — persona, boundaries, and output style are maintained by you in one place, and nothing the user says in the conversation rewrites them.
A good card has three sections, always in the same order:
You are a senior Node.js backend engineer, responsible for reviewing and
completing an Express TODO API.
Boundaries:
- Only edit files under src/; do not touch migrations/ or package.json
- Do not introduce new dependencies other than zod
- If a requirement is unclear, ask first; do not guess
Output style:
- Answer in English; keep code comments in English
- For every change, first say what you are changing and why, then give the codeThe three sections answer three questions: who you are (role), what you may not do (boundaries), and how you talk (style). Note that the boundaries section lists prohibitions rather than capabilities — the teammate already knows what it can do; what you have to state is the red lines it cannot know. In code, in both languages:
import Anthropic from '@anthropic-ai/sdk'
const client = new Anthropic() // reads ANTHROPIC_API_KEY from the environment
const SYSTEM = `You are a senior Node.js backend engineer reviewing and completing an Express TODO API.
Boundaries: only edit files under src/; no new dependencies other than zod; ask when a requirement is unclear.
Output style: answer in English; for every change, say what you are changing and why before giving code.`
const res = await client.messages.create({
model: 'claude-sonnet-5',
max_tokens: 1024,
system: SYSTEM, // the card goes here, not into messages
messages: [
{ role: 'user', content: 'POST /todos validates nothing at all right now. Tell me your plan first.' },
],
})
// The reply is a list of content blocks; text blocks have type 'text'
for (const block of res.content) {
if (block.type === 'text') console.log(block.text)
}import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
SYSTEM = """You are a senior Python backend engineer reviewing and completing a FastAPI TODO API.
Boundaries: only edit files under app/; no new dependencies other than pydantic; ask when a requirement is unclear.
Output style: answer in English; for every change, say what you are changing and why before giving code."""
res = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system=SYSTEM, # the card goes here, not into messages
messages=[
{"role": "user", "content": "POST /todos validates nothing at all right now. Tell me your plan first."}
],
)
# The reply is a list of content blocks; text blocks have type "text"
for block in res.content:
if block.type == "text":
print(block.text)Where is the engineering cost? The system prompt is resent verbatim on every request, so the longer it is, the more input tokens every request carries. The card therefore has to stay lean: leave out anything derivable from the conversation, and leave out standard conventions of the language. This same criterion reappears on D3 when we write CLAUDE.md, because CLAUDE.md is essentially the system prompt of the Claude Code teammate. Another common mistake is stuffing things that change every time — the current time, the user's name — into the top of the system prompt, which completely defeats the prompt caching we cover on D2. Do not put a date on the business card.
XML tags: pack material, instructions, and examples into folders with names on them
Say you want the teammate to read a requirements doc, a chunk of existing code, and three test cases, and then write something to match. If you paste all four things together as one blob, they first have to spend effort working out which part is the requirement, which is the code, and which is the instruction to them — and getting that split wrong ruins everything downstream. Hand over four labeled folders instead and they know at a glance what each one holds.
The labels Claude recognizes are paired XML-style markers. This is not a hard syntax rule but an organizing pattern it saw a great deal during training: tags like <document>, <instructions>, and <example> separate content of different kinds cleanly, the model is measurably better at telling the boundaries apart, and it can more easily cite "item three in the document" in its answer. You can invent the tag names, as long as they are paired, semantically clear, and used consistently. The skeleton of a long input looks like this:
<context>
This is a TODO API written with Express. The routes live in
src/routes/todos.ts, and the data shape is in the schema below.
</context>
<schema>
type Todo = { id: string; title: string; done: boolean; dueAt?: string }
</schema>
<current_code>
router.post('/todos', (req, res) => {
const todo = { id: nanoid(), ...req.body }
store.push(todo)
res.status(201).json(todo)
})
</current_code>
<instructions>
1. Use zod to validate the POST /todos request body: title is required and 1-200 characters; dueAt is optional and must be an ISO date
2. On a validation failure return 400 with a body shaped like { error: string, issues: [...] }
3. Give only the functions that need to change; do not rewrite the whole file
</instructions>
<example>
Input: { "title": "" }
Expected: 400, with error set to "invalid body"
</example>Three details are worth memorizing. First, put the instructions after the material, especially when the material is long — a teammate who reads everything and then sees "here is your job" retains it better than one who reads the job first and then wades through thousands of lines; the official guidance for long documents is likewise document first, question last. Second, make it cite the tags in its answer: asking it to "quote the offending line from current_code first, then give the fix" makes the answer checkable. Third, tags are only delimiters — do not wrap every sentence to look professional. Three to six top-level tags is normal.
Someone will ask: can't Markdown headings and code fences separate sections just as well? They can, and Claude reads them fine. The difference is that XML tags are paired, with an explicit start and end, so when the material itself contains Markdown — you pasted in a README, say — nothing slides out of position. The messier the material you paste, the more this matters.
Letting the model think first: separate the analysis from the answer, so readers and programs each take what they need
Ask a teammate "should this endpoint be rate limited" and you can get two kinds of answer. One is an instant "yes." The other is "this endpoint gets three calls a second, has no authentication, and its downstream is a paid API, so yes." Even when the conclusion is identical, the second answer leaves you far more comfortable, because you can check the reasoning for anything it missed.
For a model this is not just about looking credible; it is a real accuracy difference. Making it write the analysis before the conclusion effectively gives it several more steps of computation before the answer is committed, and error rates drop noticeably on hard tasks. The mechanics are simple — give thinking and answering one tag each:
First, in an <analysis> tag, go through this item by item: which inputs in the
current code are unvalidated, what error each one can cause, and what rule you
intend to use to block it.
Then, in an <answer> tag, give only the final code; do not repeat the analysis.The output now splits naturally in two. When a person is reading, show both sections; when a program consumes it, take only the <answer> section and keep the analysis out of the downstream flow. That is what "readers and programs each take what they need" means — you do not have to choose between wanting an explanation and wanting a clean result.
Let me clear up something that gets conflated. Newer Claude models have a built-in ability to think internally before answering; in the API it is called thinking, and it is adaptively enabled by default — the model decides how much a given question deserves. It neither conflicts with nor replaces the analysis section above. Thinking is the model's internal reasoning, and you control its depth (output_config.effort) rather than its content; the analysis section is reasoning you asked it to write down for you, so it is checkable, archivable, and usable as material in later turns. Easy questions need neither; hard questions want both; anything whose process must be auditable definitely needs an analysis section.
The cost is equally plain: the analysis section is output tokens, billed at output prices, and output is usually several times more expensive than input. So write the analysis section on demand — batch-classifying a thousand easy items with an analysis section is just burning money, while reviewing a contract or auditing a piece of security-sensitive code makes it mandatory. This trade-off is one of the focal points of today's interview questions.
Structured output: use a JSON Schema to get a result your program can use directly
When the teammate finishes something, you want a filled-in form in a fixed format, not a paragraph of prose you have to pick fields out of yourself. The old approach was to write "return only JSON, do not say anything else" in the prompt and then pray it would not prefix the reply with "Sure, here is the JSON:" — the prayers failed often enough that everybody has written a regex to dig JSON out of a reply.
Claude now accepts a JSON Schema directly as a request parameter (output_config.format), and the output is constrained-decoded into JSON that strictly matches that schema: every field present, types correct, no extra chatter. Both official SDKs offer a parse method so you can define the type with zod or pydantic and get a parsed object back:
import Anthropic from '@anthropic-ai/sdk'
import { zodOutputFormat } from '@anthropic-ai/sdk/helpers/zod'
import { z } from 'zod'
const Review = z.object({
missing_validations: z.array(z.string()), // which fields are unvalidated
severity: z.enum(['low', 'medium', 'high']),
suggested_tests: z.array(z.string()), // which tests to add
})
const client = new Anthropic()
const res = await client.messages.parse({
model: 'claude-sonnet-5',
max_tokens: 1024,
messages: [
{
role: 'user',
content: `<current_code>
router.post('/todos', (req, res) => { store.push({ id: nanoid(), ...req.body }); res.status(201).json(req.body) })
</current_code>
Review the input validation in this code.`,
},
],
output_config: { format: zodOutputFormat(Review) }, // schema as a parameter, not in the prompt
})
const review = res.parsed_output // already typed as Review; no JSON.parse
console.log(review?.severity, review?.missing_validations)from pydantic import BaseModel
from typing import Literal
import anthropic
class Review(BaseModel):
missing_validations: list[str] # which fields are unvalidated
severity: Literal["low", "medium", "high"]
suggested_tests: list[str] # which tests to add
client = anthropic.Anthropic()
res = client.messages.parse(
model="claude-sonnet-5",
max_tokens=1024,
messages=[
{
"role": "user",
"content": """<current_code>
@app.post("/todos", status_code=201)
def create(body: dict):
store.append({"id": uuid4().hex, **body})
return body
</current_code>
Review the input validation in this code.""",
}
],
output_format=Review, # schema as a parameter, not in the prompt
)
review = res.parsed_output # already a Review instance; no json.loads
print(review.severity, review.missing_validations)A few boundaries to know. The schema supports the common shapes — objects, arrays, enums, constants, optional fields — but not recursive structures, numeric ranges (minimum / maximum), or string length limits; those checks belong in your own code. Structured output and citations (D2) cannot be enabled at the same time. Also, once you ask for structured output there is nowhere left to put an analysis section, so "think first" has two landing spots here: either add an explicit reasoning field to the schema and place it before the other fields (the model generates it first, which behaves much like an analysis section), or rely on the model's internal thinking. Batch extraction, classification and tagging, turning unstructured text into a table — reach straight for structured output; anything that needs a long explanation for a human still wants tagged sections.
Fold the four habits into a template set you will use every day from D2 on
That is all four habits: the business card (system prompt), the labeled folders (XML tags), thinking before speaking (the analysis section), and filling in the form (structured output). They are not four mutually exclusive techniques but four parts that can all appear in the same prompt. Together, one complete briefing looks like this:
system: the three-section card, a paragraph or two, containing nothing that changes between requests.- The
usermessage: material first (one tag per piece), then instructions, then examples; if you want to see the reasoning, the instructions ask for separate analysis and answer sections. - Request parameters: pass a schema when a program consumes the result, and skip it when nothing does.
Today's lab is to write each of those four parts as a template with blanks, then fill them in once against the example task. This template set is not homework — it is for your own use over the next four days. On D2 the prompt for reading a PDF will use "material first, instructions last." On D3, writing CLAUDE.md, you will find it is exactly a business card handed to Claude Code, and the trim-until-you-cannot criterion is identical to today's criterion for a system prompt. On D4, the "procedure" in a SKILL.md is the analysis section and the instruction section combined. And on D5, when you run CI with claude -p, what --append-system-prompt carries is precisely the card you write today.
One last thing. All four habits are about how to brief, but briefing well only solves half the problem: the teammate understood you, and whether what came out is correct still needs checking by someone. The second half of this course shifts its center of gravity from how to say things to how to make it verify its own work — that is the watershed that turns Claude from a chat tool into a production tool.
Source Reading
Hands-On Lab
This is a documentation-style lab: nothing to run, and the deliverable is four Markdown templates. The templates in starter/ have blanks left in them, and solution/ is a worked example. Read the solution once, then go back to the starter and write your own — copying the solution straight across defeats the purpose, because this template set will eventually hold the business card for your own project rather than the course's TODO API.
- Open system-prompt.md in the starter and fill in the three sections: role, boundaries, output style. Read it through once and delete any sentence the model already knows.
- Open long-input.md and put the example task's requirement, existing code, and test cases each in their own tag, with the instructions last. Count your top-level tags and check that you are in the three-to-six range.
- Open think-first.md, name the analysis and answer tags, and write one line about which section the program parses. Decide what task complexity this template actually suits.
- Open structured.md and write a minimal JSON Schema for a code review result (at least three fields, one of them an enum). Confirm the prompt body no longer describes the format.
- Run all four filled-in templates once in any Claude client or the API playground. Watch whether the output splits into sections and whether it matches the schema, and fold anything you dislike back into the template.
Interview Questions
Today's 3 questions are in the question bank below, weighted toward the responsibilities of the system prompt, why XML tags work well for Claude, and the trade-off between structured output and thinking. Expand each one and read the analysis before the key points — practicing the derivation beats memorizing bullets. The high-frequency labels for the domestic and overseas markets are there so you can pick by target market.
Checklist and Tomorrow
- Explain the division of labor between the system prompt and the user message, and write a reusable system prompt
- Use XML tags to organize multiple passages, instructions, and examples into a long prompt Claude can read cleanly
- Get Claude to analyze before answering, and use a JSON Schema to obtain structured output
- State the difference between the analysis section and the model's internal thinking, and the billing cost of each
- All 5 acceptance criteria of the lab pass
- Answer at least 2 of the 3 interview questions without looking at the key points
Tomorrow (D2) we call Claude from code for the first time: feed it a whole PDF of several dozen pages, get back a summary with page-numbered citations, and then use prompt caching to cut the cost of repeated questions by an order of magnitude. Today's "material first, instructions last" applies directly, and when we get to the prefix semantics of caching you will see exactly why "do not put a date on the business card" matters. We learn briefing before we learn feeding material because the cost of a vague briefing gets multiplied many times over once the material grows.
Interview questions
What belongs in a system prompt and what doesn't? If the model keeps ignoring one rule, what do you check first?system prompt 应该放什么、不该放什么?如果一条规则模型总是不遵守,你会先检查什么?
Common in ChinaCommon overseasBasic#system-prompt#prompt-designHow to reason about it · think before answering
- The question tests boundaries, not writing skill. Naming what to exclude, and why, is what separates a strong answer.
- Give the rule: the system prompt is a fixed premise resent on every request, so it holds only what is true for the whole conversation — role, constraints as prohibitions, output style. Anything that varies per turn belongs in the user message.
- Then the anti-patterns: obvious conventions, pasted API docs, and per-turn material dilute the important rules and also invalidate the prompt-cache prefix on every call.
- Debug order for an ignored rule: check length first and prune, then check for ambiguity or conflicting rules, and only then add emphasis. If the rule is a must-run action, move it to a deterministic gate instead of adding more words.
- Likely follow-up: can system go last? Possible but unwise — earlier instructions carry more weight and a moving prefix breaks caching.
分析过程 · 先想清楚再作答
- 这题考的是「职责边界」而不是「会不会写」。答成「放角色和要求」是及格线,能说出「不该放什么」以及「为什么」才有区分度。
- 先给一条判据:system prompt 是每次请求都重发的固定前提,所以只放整场对话都成立的东西——角色、边界(禁止项)、输出风格;每次都变的(时间、用户名、本轮材料)放 user 消息。
- 再说反面:把模型本来就知道的常识(「写干净的代码」)、大段 API 文档、每轮都不一样的材料塞进 system,只会稀释真正重要的规则,还会让 prompt caching 的前缀每次都变。
- 「规则总是不遵守」的排查顺序:先看 system 是不是太长导致规则被淹没(删到不能再删),再看规则是否含糊或与别的规则冲突,最后才考虑加强调;如果是「每次必须执行」的动作,应该改成程序层面的门禁而不是继续加规则。
- 可预期的追问:system 放最后行不行?可以但不推荐——模型对靠前的指令更敏感,且会破坏缓存前缀。
Key points
- Include role, prohibitions, and output style — premises that hold for the whole conversation
- Exclude volatile facts, common sense the model already has, and long pasted docs
- The system prompt is resent every request: longer means costlier and rules get buried
- For an ignored rule: prune first, disambiguate second, emphasize last; must-run actions become deterministic gates
答题要点
- 放:角色、边界(写禁止项)、输出风格;整场对话都成立的固定前提
- 不放:会变的信息(时间、用户名、本轮材料)、模型本来就知道的常识、大段文档
- system 每次请求重发,越长越贵,也越容易让关键规则被淹没
- 规则不被遵守先删再改再强调;「每次必须做」的动作改成程序门禁
Why does organizing long prompts with XML tags work so well for Claude, and how does it differ from using Markdown sections?为什么用 XML 标签组织长提示词对 Claude 特别有效?和用 Markdown 分段比有什么区别?
Common in ChinaCommon overseasIntermediate#xml-tags#long-contextHow to reason about it · think before answering
- The keyword is why. Citing the docs is not an answer; explain it in terms of how the model detects content boundaries.
- Breakdown: the core risk in a long prompt is mixing material, instructions, and examples. Paired tags give each part an explicit start, end, and name, so the model separates them reliably and can reference a specific section in its reply.
- Versus Markdown: headings and fences delimit but have no explicit closing marker, so pasted material that itself contains Markdown breaks the structure. XML tags are paired, nestable, and freely named, and the benefit grows with messier input.
- Add the engineering habits: consistent tag names, instructions after the material, and asking the model to cite tags in its answer. Three to six top-level tags is typical.
- Follow-ups: is there a fixed tag vocabulary? No — structure and semantics matter, consistency within one prompt matters. What if the material contains XML? Pick non-colliding names.
分析过程 · 先想清楚再作答
- 题眼在「为什么」。只答「官方推荐」等于没答;要能从「模型如何分辨内容边界」这个角度解释。
- 拆法:长提示词的核心风险是不同性质的内容(材料、指令、示例)混在一起,模型分错边界就会把材料里的句子当指令执行、或把示例当成事实。成对的标签给每一段一个明确的起止和名字,模型分辨边界的准确率更高,也能在回答里精确引用「哪一段」。
- 与 Markdown 的区别:Markdown 靠标题和围栏分段,但没有显式的结束标记;当贴进去的材料本身含 Markdown(比如一份 README)时容易串位。XML 标签成对、可嵌套、名字自定义,材料越杂优势越大。
- 补一条工程习惯:标签名前后一致,指令放在材料之后,回答时要求引用标签名。三到六个顶层标签是常态,不要过度包装。
- 可预期的追问:标签名有没有固定词表?没有,模型看的是结构和语义,但同一个提示词内要一致;另一个追问是「材料本身含 XML 怎么办」——换一个不会撞的标签名,或用 CDATA 式的转义说明。
Key points
- Long prompts mix material, instructions, and examples; paired tags give each an explicit boundary and name
- Boundary detection becomes reliable and the model can cite a specific section
- Markdown has no closing marker and breaks when pasted material contains Markdown; XML tags are paired, nestable, and freely named
- Habits: consistent names, instructions after material, ask for tag citations, three to six top-level tags
答题要点
- 长提示词的风险是材料、指令、示例混在一起;成对标签给每段明确的起止和名字
- 模型分辨边界更准,也能在回答里精确引用某一段
- Markdown 没有显式结束标记,材料含 Markdown 时会串位;XML 标签成对、可嵌套、可自定义
- 习惯:标签名一致、指令放材料之后、要求引用标签、顶层标签三到六个
When should you have the model write its analysis before answering, and when should you go straight to structured output? Can you have both?什么时候该让模型先写分析再回答,什么时候直接要结构化输出?两者能同时要吗?
Common in ChinaCommon overseasIntermediate#structured-output#reasoningHow to reason about it · think before answering
- This is a trade-off question; the criteria are who consumes the output and what an error costs. Give a decision rule, not 'it depends'.
- Chain: the analysis is output tokens, billed at output rates and adding latency, in exchange for higher accuracy on complex tasks and an auditable trace. The more complex, high-stakes, or human-reviewed the task, the more you want it; bulk, simple, machine-consumed tasks go straight to structured output.
- Can you have both? Once a JSON schema is passed the output is constrained to JSON, so free-text analysis has nowhere to go. Two options: add a reasoning field placed before the other fields, or rely on the model's internal thinking, whose depth you control but not its content.
- Production nuance: structured output fixes parsing reliability, not judgment quality; the schema cannot express numeric ranges or string lengths, so validate those yourself.
- Follow-ups: can the analysis leak into downstream code? Yes — separate analysis and answer with tags and parse only the answer. Thinking versus a written analysis: internal versus visible, depth versus content.
分析过程 · 先想清楚再作答
- 这题考取舍,判据是「谁消费输出」和「错误的代价」。答「都用」或「看情况」没有信息量,要给出可执行的判断句。
- 推导链:分析段是输出 token,按输出价计费,且会让响应变长;它换来的是复杂任务上更高的准确率与可核对的推理过程。所以任务越复杂、错误代价越高、越需要人审计,越该要分析段;批量、简单、程序直接消费的任务,直接要结构化输出。
- 「能不能同时要」:一旦传了 JSON Schema,输出被约束成 JSON,自由文本的分析段没地方放。两条路:在 schema 里加一个 reasoning 字段放在其他字段前面(模型会先生成它),或者依赖模型内部的 thinking——它是内部推理,你控制深度不控制内容。
- 生产视角:结构化输出解决的是「解析可靠性」,不是「判断正确性」;schema 不支持数值范围与字符串长度约束,这些校验要自己补。
- 可预期的追问:分析段会不会被程序误用?会,所以要用标签把分析段与答案段分开,程序只取答案段;另一个追问是 thinking 与分析段的区别——一个内部一个外显,一个控深度一个控内容。
Key points
- Written analysis costs output tokens and latency but raises accuracy and gives an auditable trace — use for high-stakes, human-reviewed work
- Structured output is consumed directly by code with zero parse failures — use for bulk, simple extraction and classification
- To combine: put a reasoning field first in the schema, or rely on internal thinking
- Structured output guarantees shape, not correctness; add range and length validation yourself
答题要点
- 分析段:输出 token 计费、更慢,但复杂任务更准、过程可核对;适合高风险、需人审的任务
- 结构化输出:程序直接消费、解析零失败;适合批量、简单、明确的抽取与分类
- 同时要:在 schema 里加靠前的 reasoning 字段,或依赖内部 thinking
- 结构化输出保证的是格式不是正确性;范围与长度校验要自己补