Prompting Techniques
|
This content was generated with the assistance of AI and should be verified against the official documentation of each tool and framework before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Examples on this page target Python 3.12 with the current anthropic SDK, plus one Java variant using the
Spring AI 2.0 ChatClient, checked against Anthropic’s and Spring AI’s own documentation as of 2026-09-28. The
running example throughout is DocsAssistant, the same assistant used
elsewhere in this documentation set that answers questions about this site’s own AsciiDoc pages (see
Vector Databases & RAG for its fuller retrieval-augmented setup) — here the focus is narrower:
how the prompt itself is structured and iterated on, independent of retrieval, to get a more reliable answer
out of a single underlying model.
Chain-of-thought prompting
Chain-of-thought (CoT) prompting asks the model to lay out intermediate reasoning steps before its final answer, instead of jumping straight to a conclusion. Wei et al. showed this alone — with no change to the model’s weights — substantially improves accuracy on tasks that require multi-step arithmetic, logic or common-sense reasoning (see References). The classic form adds an instruction like "think step by step" (or a handful of worked examples that themselves show the reasoning) to a prompt that would otherwise ask only for the final answer.
Manual CoT prompting matters less with a reasoning model (Reasoning Models), because those models already generate an internal reasoning stream by default — adding "think step by step" on top of a model that already thinks adaptively is usually redundant, and can even conflict with the model’s own trained reasoning process. The practical rule: reach for explicit CoT instructions on a non-reasoning or budget-constrained model, and prefer the model’s own thinking/effort controls once a reasoning model is in play.
A minimal explicit-CoT prompt for DocsAssistant asks for the reasoning inside one tag and the final answer inside another, so the caller can log the reasoning and show only the answer:
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
response = client.messages.create(
model="claude-haiku-4-5", # explicit CoT suits a smaller, non-thinking model
max_tokens=500,
messages=[{
"role": "user",
"content": "<context>\nTop-p sampling keeps the smallest set of highest-probability tokens whose "
"cumulative probability reaches p, renormalizes, and samples from that set.\n</context>\n\n"
"Using only the context, explain what top-p = 0.9 does to a distribution where one token "
"has probability 0.95. Think step by step inside <thinking> tags, then give the final "
"answer inside <answer> tags.",
}],
)
print(response.content[0].text)
See the chain-of-thought prompting paper for the technique, and the Anthropic prompting best practices — "Leverage thinking & interleaved thinking capabilities" for when to prefer the model’s own adaptive thinking instead.
Prompt chaining and task decomposition
Prompt chaining breaks one large, unreliable task into a sequence of smaller prompts, each handling one stage, with the output of one step feeding into the input of the next as a separate API call. This trades latency (multiple round trips instead of one) for reliability: each individual step is easier for the model to get right than the combined task would be in a single pass, and each step’s output can be logged, validated or branched on independently.
For DocsAssistant, a two-step chain separates extracting the facts relevant to a question from answering using only those facts — this keeps the answering step from having to also decide what in a long page is relevant, and makes the extracted facts independently inspectable (a wrong final answer is much easier to debug when you can see whether the extraction step already went wrong):
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
page_text = """
= Sampling & Decoding
...
Top-p sampling (nucleus sampling) takes the smallest set of highest-probability tokens whose cumulative
probability reaches a threshold p, renormalizes, and samples from that set.
"""
def extract_facts(question: str, page_text: str) -> str:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=300,
messages=[{
"role": "user",
"content": f"Extract only the sentences from this page that are relevant to the "
f"question below. Output them verbatim, one per line, with no commentary.\n\n"
f"Question: {question}\n\nPage:\n{page_text}",
}],
)
return response.content[0].text
def answer_from_facts(question: str, facts: str) -> str:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=300,
messages=[{
"role": "user",
"content": f"Using only these extracted facts, answer the question in one paragraph. "
f"If the facts do not answer it, say so.\n\nFacts:\n{facts}\n\n"
f"Question: {question}",
}],
)
return response.content[0].text
question = "What does top-p sampling do?"
facts = extract_facts(question, page_text)
answer = answer_from_facts(question, facts)
print(answer)
See the Anthropic prompting best practices — "Chain complex prompts" for this pattern, including the self-correction variant (generate a draft, review it against criteria, refine it) as the most common chaining shape.
Task decomposition is the same idea applied before any prompting happens at all: instead of chaining generic steps, the sequence of steps is itself designed around the task’s actual structure (for example, "find the relevant page" → "extract the relevant section" → "answer" → "cite the section" for DocsAssistant), so each prompt in the chain has a narrow, well-defined job rather than being a generic "do part of the work" instruction.
Self-consistency
Sampling & Decoding already introduced self-consistency as a majority-vote variant of best-of-N: sample several independent completions at a temperature above 0 and take the most common final answer, instead of trusting any single sample. Wang et al. found this consistently outperforms plain greedy decoding on tasks with a single correct answer, precisely because CoT reasoning at a non-zero temperature can wander down a wrong reasoning path on any given sample, but a wrong path is unlikely to be the same wrong path across several independent samples (see References).
The following extends that idea into a small, complete voting loop: it samples N completions to a factual
yes/no-style DocsAssistant question and returns the majority answer, along with the vote breakdown so a caller
can see how close the vote was:
import collections
import re
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
def normalize_answer(text: str) -> str:
match = re.search(r"\b(yes|no)\b", text, flags=re.IGNORECASE)
return match.group(1).lower() if match else "unclear"
def self_consistency_answer(question: str, page_excerpt: str, n: int = 7) -> tuple[str, dict]:
answers = []
for _ in range(n):
response = client.messages.create(
model="claude-haiku-4-5", # sampling params such as temperature need an earlier model
max_tokens=200,
temperature=0.8,
messages=[{
"role": "user",
"content": f"<context>\n{page_excerpt}\n</context>\n\n"
f"Using only the context above: {question} Think it through briefly, then end "
f"your reply with exactly one word, 'Yes' or 'No'.",
}],
)
answers.append(normalize_answer(response.content[0].text))
votes = collections.Counter(answers)
majority_answer, _ = votes.most_common(1)[0]
return majority_answer, dict(votes)
page_excerpt = (
"Temperature 0 makes decoding greedy, but hosted APIs do not guarantee bit-for-bit identical "
"output across calls: batching and floating-point non-determinism can still change the result."
)
question = "Does the sampling page say temperature 0 is bit-for-bit reproducible on a hosted API?"
answer, votes = self_consistency_answer(question, page_excerpt)
print(answer, votes) # e.g. "no", {"no": 6, "yes": 1}
See the Wang et al. self-consistency paper (References) for the underlying
method; the code above is an ordinary application of the messages.create call already covered on the
Sampling & Decoding page, sampled N times.
Tree-of-thought
Tree-of-thought (ToT) prompting generalizes chain-of-thought from a single linear reasoning path into a search over several candidate reasoning paths: at each step the model (or a controller loop around it) generates multiple possible "next thoughts", evaluates or scores each partial path, and expands only the most promising ones — backtracking away from a path that a later step judges unlikely to lead anywhere, much like a search algorithm exploring a tree of states rather than committing to the first branch taken. Yao et al. introduced this as a way to solve problems where a single reasoning chain is prone to getting stuck (planning puzzles, tasks with multiple valid-looking but ultimately dead-end approaches) by giving the model an explicit way to compare and prune alternative approaches instead of best-of-N’s simpler post-hoc pick-the-best-completion (see References). For DocsAssistant, a ToT-style approach could apply to answering an ambiguous question that plausibly matches several different pages: generate one candidate answer per plausible source page, have the model (or a lightweight scoring prompt) rate how well each is actually supported by its cited page, and keep only the best-supported candidate rather than committing to whichever page was retrieved first.
Because ToT needs the surrounding search/evaluation loop as much as it needs the prompt text, it is usually implemented as an explicit orchestration loop around several prompts rather than a single prompt — the loop itself is out of scope for this page, but the prompting piece is: each "generate a candidate" and "score a candidate" step is an ordinary, focused prompt of the kind covered elsewhere on this page (task decomposition for the generation step, self-critique below for the scoring step).
A minimal, one-level version of that loop for DocsAssistant generates one candidate per plausible page, scores each candidate against its page, and keeps only the best-supported one (a full ToT search would repeat the generate-and-score step at every level of the tree and prune low-scoring branches):
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
def ask(prompt: str, max_tokens: int = 300) -> str:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=max_tokens,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text
def tree_of_thought_answer(question: str, pages: dict[str, str]) -> str:
scored = []
for name, text in pages.items():
candidate = ask(f"<context>\n{text}\n</context>\n\nUsing only the context, answer: {question}")
score = ask(
f"<context>\n{text}\n</context>\n<answer>\n{candidate}\n</answer>\n\n"
"On a scale of 1 to 10, how fully does the context support the answer? Reply with the number only.",
max_tokens=5,
)
scored.append((int(score.strip() or 0), name, candidate))
best_score, best_page, best_answer = max(scored)
return f"{best_answer}\n(source: {best_page}, support {best_score}/10)"
See the tree-of-thoughts paper (References) for the full search formulation, including breadth-first and depth-first expansion of the candidate tree.
Self-critique and reflection
Self-critique (also called reflection) asks the model to evaluate its own prior output against explicit criteria, then produce an improved version informed by that critique — typically as two or three separate calls: generate an answer, critique it (does it cite a real section, does it actually answer the question asked, is anything unsupported by the retrieved page), then revise based on the critique. Unlike self-consistency (which trusts a majority across otherwise-equal samples), self-critique relies on the model being a reasonably reliable grader of its own or another model’s output, which holds up well for checklist-style criteria (did it cite something, is it a complete sentence) and less well for judging its own factual accuracy on ambiguous questions, where a critique pass can just as easily "correct" a right answer into a wrong one.
For DocsAssistant, a lightweight reflection pass over a drafted answer might ask specifically: "Does this answer cite an actual heading from the source page? Does it avoid claiming anything the page doesn’t say?" — narrow, checkable questions the model can grade far more reliably than an open-ended "is this a good answer?" A minimal draft, critique and revise chain for that pass takes three separate calls:
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
def ask(prompt: str) -> str:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=400,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text
page = "<context>\n== Top-p sampling\nTop-p keeps the smallest set of tokens whose cumulative probability reaches p.\n</context>"
question = "What does top-p sampling do?"
draft = ask(f"{page}\n\nUsing only the context, answer: {question}")
critique = ask(
f"{page}\n<draft>\n{draft}\n</draft>\n\nCritique the draft: does it cite an actual heading from the "
"context? Does it claim anything the context does not say? List each problem, or reply 'OK'."
)
final = draft if critique.strip() == "OK" else ask(
f"{page}\n<draft>\n{draft}\n</draft>\n<critique>\n{critique}\n</critique>\n\n"
"Rewrite the draft so it fixes every problem in the critique."
)
print(final)
See the Anthropic prompting best practices — "Chain complex prompts" for this self-correction chain, and the Self-Refine paper for the iterative feedback-and-refine method it is based on.
Metaprompting / prompt generators
Metaprompting uses the model itself to draft or improve a prompt, rather than a human iterating on the prompt text by hand: given a task description (and, ideally, a handful of example inputs/outputs or failure cases), the model proposes a candidate prompt, which is then tested against real inputs like any other prompt. This is especially useful as a starting point when there is no first draft yet — Anthropic ships a metaprompt notebook in the Claude Cookbooks for exactly this (see References) — and as an iteration tool once a prompt is already failing on specific cases: feeding the model the failing input, the current prompt, and the wrong output, and asking it to propose a revised prompt, turns prompt debugging into another chained step rather than manual trial and error.
A minimal metaprompting call for that second use case sends the current prompt, a failing input and the wrong output, and asks for a revised prompt:
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
current_prompt = "Extract only the sentences from this page that are relevant to the question."
failing_input = "Question: What does top-p do?\nPage: ...(a page where top-p is defined under 'Nucleus sampling')..."
wrong_output = "(no sentences extracted)"
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=800,
messages=[{
"role": "user",
"content": f"<current_prompt>\n{current_prompt}\n</current_prompt>\n"
f"<failing_input>\n{failing_input}\n</failing_input>\n"
f"<wrong_output>\n{wrong_output}\n</wrong_output>\n\n"
"Propose a revised prompt that would have produced a correct output for this input "
"without breaking inputs the current prompt already handles. Return only the revised "
"prompt inside <revised_prompt> tags.",
}],
)
print(response.content[0].text)
See Anthropic’s metaprompt notebook in the Claude Cookbooks for a complete prompt-generator metaprompt, and the automatic prompt engineer paper for model-driven prompt search as a research method.
Metaprompting still needs the same evaluation discipline as any other prompt change: a generated or revised prompt is a candidate, checked against a held-out set of real questions (Managing Prompts covers testing a prompt against a golden set) before it replaces a working prompt in production.
Role prompting
Role prompting assigns the model a persona or area of expertise, most naturally through the system
parameter rather than folded into the first user message, since the system prompt applies to the whole
conversation rather than reading as one more instruction embedded in the user’s question. A role does not
grant the model new knowledge or capabilities it does not already have, but it does reliably shift tone,
level of detail and default assumptions — a role like "an experienced technical writer reviewing internal
documentation for accuracy" nudges DocsAssistant’s answers toward being terser and more willing to flag when a
page’s wording is ambiguous, compared to a generic "helpful assistant" framing with no role at all.
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=300,
system="You are a meticulous technical editor reviewing this site's AsciiDoc documentation for "
"accuracy. You only state what a cited page actually says, and you explicitly flag any "
"question a page does not clearly answer.",
messages=[{
"role": "user",
"content": "<context>\n== Determinism, seeds, and why T = 0 is not fully deterministic\n"
"In practice, T = 0 plus a fixed seed is low-variance, not bit-for-bit reproducible, "
"on a hosted, multi-tenant API.\n</context>\n\n"
"Using only the context, does the sampling-and-decoding page say temperature 0 "
"guarantees a deterministic response on a hosted API?",
}],
)
print(response.content[0].text)
See the Anthropic
prompting best practices guide (References) for role prompting via the system parameter alongside the
other general techniques it covers.
The same role prompt in Java uses the Spring AI 2.0 ChatClient fluent API, where .system(…) sets the role
and .user(…) carries the question and its context:
import org.springframework.ai.chat.client.ChatClient;
public class RolePromptingExample {
private final ChatClient chatClient;
public RolePromptingExample(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build(); // model configured via spring.ai.anthropic.* properties
}
public String ask(String pageExcerpt, String question) {
return chatClient.prompt()
.system("You are a meticulous technical editor reviewing this site's AsciiDoc documentation "
+ "for accuracy. You only state what a cited page actually says, and you explicitly "
+ "flag any question a page does not clearly answer.")
.user("<context>\n" + pageExcerpt + "\n</context>\n\nUsing only the context: " + question)
.call()
.content();
}
}
See the Spring AI ChatClient reference for
ChatClient.Builder, the prompt().system(…).user(…).call().content() chain and its other options.
Prefilling
Prefilling supplies the beginning of the assistant’s own turn (instead of only a user message) and has the
model continue generating from that partial text, which is a direct way to force an output to start in a
particular shape — for example, opening a JSON object so the model has no room to add introductory prose
before it.
This technique has a real, current caveat that changes which model an example can target: as of 2026-09-28,
Anthropic’s own migration guidance states that prefilled responses on the last assistant turn are no longer
supported starting with the Claude 4.6 model generation onward — which includes claude-sonnet-5 — and a
request that prefills the last assistant turn against one of those models returns a 400 error. Anthropic’s
stated reasoning is that current models follow direct formatting instructions and Anthropic’s own Structured
Outputs feature reliably enough that most of prefill’s former use cases (forcing JSON, skipping a preamble) no
longer need it. The example below therefore targets
claude-haiku-4-5, an earlier generation that still accepts prefill, and DocsAssistant’s own prompts should
prefer explicit formatting instructions or structured outputs over prefill wherever the target model is
claude-sonnet-5 or later:
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=300,
messages=[
{"role": "user", "content": "List the sampling parameters DocsAssistant's sampling page "
"covers, as a JSON array of strings."},
{"role": "assistant", "content": "["}, # prefill: forces the reply to continue a JSON array
],
)
print("[" + response.content[0].text)
See the Anthropic prompting best practices — "Migrating away from prefilled responses" (References) for the exact models affected and the recommended replacements (structured outputs, direct system-prompt instructions, or tool calling) on current-generation models.
ReAct
ReAct (Reason + Act) interleaves reasoning steps with tool calls and their results, rather than doing all of a task’s reasoning up front and only then invoking a tool: the model reasons about what it needs, calls a tool to get it (a search, a lookup, a calculation), observes the result, and reasons again with that new information before either calling another tool or producing a final answer. Yao et al. showed that interleaving reasoning and acting this way reduces the compounding errors that come from committing to a full plan before any real information has come back from a tool (see References). For DocsAssistant, a ReAct-style loop reasons about which page a question is likely answered on, calls a page-lookup tool, reads what came back, and only then either answers or reasons that a different page needs to be looked up next — rather than guessing the single right page once and never revisiting that guess.
With the Messages API, a minimal ReAct loop gives the model a lookup_page tool, runs the tool whenever the
response stops with stop_reason == "tool_use", sends the result back as a tool_result block, and repeats until
the model answers:
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
PAGES = {"sampling-and-decoding": "Top-p keeps the smallest set of tokens whose cumulative probability reaches p."}
tools = [{
"name": "lookup_page",
"description": "Return the text of one DocsAssistant documentation page by its slug.",
"input_schema": {
"type": "object",
"properties": {"slug": {"type": "string", "description": "Page slug, e.g. sampling-and-decoding"}},
"required": ["slug"],
},
}]
messages = [{"role": "user", "content": "What does top-p sampling do? Look the page up before answering."}]
while True:
response = client.messages.create(model="claude-sonnet-5", max_tokens=800, tools=tools, messages=messages)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
break
results = [
{"type": "tool_result", "tool_use_id": block.id,
"content": PAGES.get(block.input["slug"], "No such page.")}
for block in response.content if block.type == "tool_use"
]
messages.append({"role": "user", "content": results})
print(next(block.text for block in response.content if block.type == "text"))
See the Anthropic tool use overview for
the tools parameter and the tool_use / tool_result loop, and the ReAct
paper (References) for the reason-act-observe pattern itself.
ReAct as covered here is the prompting/reasoning pattern itself: an alternating reason-act-observe loop over one or more tools. How that loop is actually operated in practice — the agent runtime that drives the loop, tracks tool results across turns, and decides when to stop — is covered in more depth on the planned AI Agents sub-section rather than repeated here.
Before/after prompt fragments
The following table gives one deliberately weak prompt fragment against one improved fragment for each technique above, all phrased against the same kind of DocsAssistant question:
| Technique | Weak prompt fragment | Improved prompt fragment |
|---|---|---|
Chain-of-thought |
"What does top-p sampling do?" |
"What does top-p sampling do? Think through the definition step by step before giving your final answer." |
Prompt chaining / task decomposition |
"Read this page and answer the question." (one call, one giant instruction) |
"Step 1: extract only the sentences relevant to the question. Step 2 (separate call): answer using only those extracted sentences." (two focused calls) |
Self-consistency |
"Does temperature 0 guarantee a deterministic response?" (single sample at temperature 0.8, trusted as-is) |
Sample the same question 7 times at temperature 0.8 and return the majority "Yes"/"No" across the 7 samples. |
Tree-of-thought |
"Which page answers this question? Answer immediately." (commits to the first page considered) |
"List every page that plausibly answers this. For each, draft a candidate answer, then rate how well the page actually supports it, and keep only the best-supported candidate." |
Self-critique / reflection |
Return the drafted answer directly, unreviewed. |
"Before finalizing: does this answer cite an actual heading from the source page? Does it avoid claiming anything the page doesn’t say? Revise if not." |
Metaprompting / prompt generators |
Hand-write and hand-tune the extraction prompt from scratch. |
"Here is the task, three example questions, and two cases where the current prompt produced a wrong extraction — propose a revised prompt that would have gotten those two right." |
Role prompting |
No |
|
Prefilling |
Ask for JSON and hope the reply has no leading prose, on any model. |
On a pre-4.6 model: prefill the assistant turn with |
ReAct |
Reason about which page answers the question once, then answer without ever consulting the page. |
Reason → call the page-lookup tool → observe the returned section → reason again → answer (or look up a different page if the section returned does not actually answer it). |
References
-
Wei et al., Chain-of-Thought Prompting Elicits Reasoning in Large Language Models
-
Wang et al., Self-Consistency Improves Chain of Thought Reasoning in Language Models
-
Yao et al., Tree of Thoughts: Deliberate Problem Solving with Large Language Models
-
Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models
-
Anthropic Claude Cookbooks: Metaprompt notebook (as of 2026-09-28)
-
Anthropic: Prompting best practices — "Chain complex prompts" (as of 2026-09-28)
-
Anthropic: Prompting best practices — "Migrating away from prefilled responses" (as of 2026-09-28)