Ask a model to “write a rate limiter” and you get a tutorial: plausible code with the wrong error class, print for logging, a broad except and a clock you can’t control in tests. The model isn’t careless. It has no idea how your codebase works unless the prompt shows it. This guide covers the prompt patterns that close that gap, in the order the model documentation recommends, and then the part most guides skip: an automatic acceptance check, because a better prompt raises the odds but doesn’t guarantee anything.
⚡ TL;DR
- Show before you ask: put your relevant files at the top of the prompt and the task at the end. Anthropic’s guidance says queries at the end can improve response quality by up to 30 percent in its tests on long, multi-document inputs.
- Give 3 to 5 examples from your own codebase, wrapped in
<example>tags so the model can tell them apart from instructions.- State constraints as what to do (“raise
RateLimitError“), not only what to avoid.- Ask for reasoning before code on tricky problems, and for one fenced block (or structured output) so the result is easy to parse.
- Accept code only after it compiles, passes AST checks for your house rules, and passes behavior tests. Run that in a sandbox.
What this is based on: Anthropic’s prompting best practices for Claude (the general principles apply to other models too), and a harness that was run on two candidate outputs, shown below.

Pattern 1: Show your code first, ask last
The single biggest improvement is context: the error class you use, how middleware is written, how logging works. Anthropic’s guide recommends putting long documents near the top of the prompt, above the query, instructions and examples, and wrapping each in <document> tags with <source> and <document_content>. Choose files that show the patterns the new code must follow (a sibling module, the error definitions, a test), not the whole repository.
Earlier versions of this post recommended putting constraints first because “the model parses them first”. That isn’t what the model docs say, so the layout below puts the documents first and the task last.
Pattern 2: Tagged examples from your own code
Examples are one of the most reliable ways to steer format and style. The guidance is to make them relevant (mirror your real use case), diverse (cover edge cases, so the model doesn’t copy accidental details), and structured (each in <example> tags, all of them in <examples>), with 3 to 5 examples for best results. For code, a good example is a short, real function from your repository that shows the conventions, paired with the request that would have produced it.
Pattern 3: Constraints that say what to do
The same guide recommends telling the model what to do instead of what not to do. “Raise app.errors.RateLimitError when a request is refused” is clearer than “don’t use generic exceptions”, and it names the thing to use. Make constraints checkable, because the next section checks them. Avoid vague ones such as “production quality”.
"""Build a code-generation prompt: your code first, examples tagged, positive constraints, the task last."""
import tiktoken
enc = tiktoken.get_encoding("o200k_base")
def build(task: str, context_files: dict[str, str], examples: list[tuple[str, str]],
constraints: list[str], budget: int = 6_000) -> str:
docs, used = [], 0
for i, (path, text) in enumerate(context_files.items(), 1): # most relevant first
block = f'<document index="{i}">\n<source>{path}</source>\n<document_content>\n{text}\n</document_content>\n</document>'
n = len(enc.encode(block))
if used + n > budget:
break # drop whole files, never half a function
docs.append(block); used += n
ex = "\n".join(f"<example>\n<request>{q}</request>\n<code>\n{a}\n</code>\n</example>" for q, a in examples)
rules = "\n".join(f"- {c}" for c in constraints)
return (f"<documents>\n{chr(10).join(docs)}\n</documents>\n\n<examples>\n{ex}\n</examples>\n\n"
f"<constraints>\n{rules}\n</constraints>\n\n<task>\n{task}\n</task>\n"
"Return one Python module in a single fenced python code block and nothing else.")
CONSTRAINTS = [
"Raise app.errors.RateLimitError(key, retry_after) when a request is refused.",
"Log through the logger argument passed to the constructor.",
"Take the current time as a parameter (now: float) so tests can control it.",
"Annotate every function and method with types.",
"Catch only the specific exceptions you expect, by name.",
]The builder adds files whole, in order of relevance, until a token budget is reached, so the model never sees half a function. Then come the examples, the constraints and finally the task, with an explicit output format.
Pattern 4: Reasoning before code, for the hard parts
For concurrency, algorithms and debugging, ask the model to answer specific questions first: where state lives, what the race condition is, what happens when a dependency is down. Reviewing the reasoning is quicker than finding the flaw in the code, and wrong assumptions show up before they’re encoded. For debugging, ask for ranked hypotheses, each with a cheap way to test it. Many current models also have a built-in thinking mode (Anthropic now documents adaptive thinking for its latest models); check your model’s documentation for how that interacts with reasoning you ask for in the prompt.
Pattern 5: A role, and a format you can parse
Setting a role in the system prompt (“You are a senior Python reviewer for a payments service”) focuses tone and priorities, but it doesn’t replace context: a role without your files still produces generic code. For the output, ask for exactly one fenced block, or use structured outputs when you need several files with metadata (see structured outputs that compile).
One more instruction worth adding for agentic coding, from the same guide: ask for a general solution that works for all valid inputs, and say not to hard-code values to pass specific tests. Models sometimes optimize for the visible tests otherwise.
The acceptance check
A prompt raises the odds; a check makes the decision. This harness extracts the code block, parses it, applies house rules as AST checks (no print, no broad except, type hints everywhere, no direct clock reads), runs it, and tests behavior:
"""Accept generated code only if it compiles, follows the house rules (checked by AST) and passes behavior tests."""
import ast
import re
import types
def extract(reply: str) -> str:
m = re.search(r"`{3}python\n(.*?)`{3}", reply, re.S)
if not m:
raise ValueError("no fenced python block in the reply")
return m.group(1)
def house_rules(tree: ast.AST) -> list[str]:
problems = []
for node in ast.walk(tree):
if isinstance(node, ast.Call) and getattr(node.func, "id", "") == "print":
problems.append(f"line {node.lineno}: print() instead of the logger")
if isinstance(node, ast.ExceptHandler) and (node.type is None or getattr(node.type, "id", "") == "Exception"):
problems.append(f"line {node.lineno}: broad except")
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
if node.returns is None or any(a.annotation is None for a in node.args.args if a.arg != "self"):
problems.append(f"line {node.lineno}: {node.name} is missing type hints")
if isinstance(node, ast.Call) and getattr(getattr(node.func, "value", None), "id", "") == "time":
problems.append(f"line {node.lineno}: reads the clock directly; take now as a parameter")
return problems
def behavior(mod) -> list[str]:
class Log:
def __init__(self): self.lines = []
def info(self, *a, **k): self.lines.append(a)
warning = info
b = mod.TokenBucket(capacity=2, refill_per_sec=1.0, logger=Log())
errs = []
if not (b.take("u1", now=0.0) and b.take("u1", now=0.0)):
errs.append("first two requests should pass")
try:
b.take("u1", now=0.0); errs.append("third request should raise RateLimitError")
except mod.RateLimitError as e:
if not 0 < e.retry_after <= 1.0:
errs.append(f"retry_after {e.retry_after} should be within (0, 1]")
if not b.take("u1", now=1.0):
errs.append("a token should refill after 1 second")
if not b.take("u2", now=0.0):
errs.append("keys must not share a bucket")
return errs
def accept(reply: str, errors_module_src: str) -> dict:
try:
src = extract(reply)
tree = ast.parse(src)
except (ValueError, SyntaxError) as e:
return {"accepted": False, "problems": [str(e)]}
problems = house_rules(tree)
mod = types.ModuleType("generated")
exec(compile(errors_module_src, "app/errors.py", "exec"), mod.__dict__) # provides RateLimitError
try:
exec(compile(src.replace("from app.errors import RateLimitError", ""), "<generated>", "exec"), mod.__dict__)
problems += behavior(mod)
except Exception as e: # the harness may catch broadly
problems.append(f"runtime: {type(e).__name__}: {e}")
return {"accepted": not problems, "problems": problems}from prompt_builder import build, CONSTRAINTS
from acceptance import accept
FENCE = "`" * 3 # a code fence, spelled out so this listing doesn't contain one
ERRORS = '''class RateLimitError(Exception):
def __init__(self, key: str, retry_after: float):
super().__init__(f"{key} rate limited; retry after {retry_after:.2f}s")
self.key, self.retry_after = key, retry_after
'''
prompt = build("Write TokenBucket(capacity, refill_per_sec, logger) with take(key, now) -> bool, one bucket per key.",
{"app/errors.py": ERRORS, "app/middleware/auth.py": "def require_user(req, logger): ..."},
[("a cache with expiry", "class TTLCache:\n def get(self, key: str, now: float) -> str | None: ...")],
CONSTRAINTS)
print(prompt[:120].replace("\n", " | "), "...", prompt[-160:].replace("\n", " | "))
tutorial = FENCE + '''python
import time
class TokenBucket:
def __init__(self, capacity, refill_per_sec, logger):
self.capacity, self.rate, self.buckets = capacity, refill_per_sec, {}
def take(self, key, now=None):
try:
now = now or time.time()
tokens, last = self.buckets.get(key, (self.capacity, now))
tokens = min(self.capacity, tokens + (now - last) * self.rate)
if tokens < 1:
print("rate limited", key)
return False
self.buckets[key] = (tokens - 1, now)
return True
except:
return True
''' + FENCE
constrained = FENCE + '''python
import logging
from app.errors import RateLimitError
class TokenBucket:
def __init__(self, capacity: int, refill_per_sec: float, logger: logging.Logger) -> None:
self.capacity, self.rate, self.log = capacity, refill_per_sec, logger
self.buckets: dict[str, tuple[float, float]] = {}
def take(self, key: str, now: float) -> bool:
tokens, last = self.buckets.get(key, (float(self.capacity), now))
tokens = min(float(self.capacity), tokens + (now - last) * self.rate)
if tokens < 1.0:
retry_after = (1.0 - tokens) / self.rate
self.log.warning("rate limited", extra={"key": key, "retry_after": retry_after})
raise RateLimitError(key, retry_after)
self.buckets[key] = (tokens - 1.0, now)
return True
''' + FENCE
for name, reply in (("tutorial-style", tutorial), ("constrained", constrained)):
r = accept(reply, ERRORS)
print(f"{name}: accepted={r['accepted']}")
for p in r["problems"]:
print(" -", p)<documents> | <document index="1"> | <source>app/errors.py</source> | <document_content> | class RateLimitError(Exception): | ... refill_per_sec, logger) with take(key, now) -> bool, one bucket per key. | </task> | Return one Python module in a single fenced python code block and nothing else.
rate limited u1
rate limited u1
tutorial-style: accepted=False
- line 3: __init__ is missing type hints
- line 5: take is missing type hints
- line 15: broad except
- line 7: reads the clock directly; take now as a parameter
- line 11: print() instead of the logger
- third request should raise RateLimitError
- a token should refill after 1 second
constrained: accepted=TrueThe tutorial-style output fails seven checks. Two are worth a closer look. now = now or time.time() treats a time of 0.0 as “no time given”, so in the test it silently used the real clock, which is why the refill test failed: a bug that’s hard to see in review and obvious to a test. And the broad except: return True would have let every request through if anything went wrong. The constrained output passes. The two rate limited u1 lines at the top were printed by the tutorial version itself, which is the print problem in action.
Run this in a sandbox. The harness executes generated code. In CI that’s a container with no credentials and no network; never run it on a developer laptop with cloud credentials loaded.
Worked example: the retry loop
- First attempt: the model returns the tutorial version. The harness rejects it with seven problems.
- The problems go back to the model once, verbatim: “line 11: print() instead of the logger”, and so on.
- The second attempt passes. A human reviews the diff, which is now about logic, not style.
- If the second attempt had failed too, the harness stops and a human takes over. Endless retry loops cost tokens and hide a prompt that’s missing context.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Code ignores your conventions | No examples or context files | Add the sibling module and error definitions as documents |
| Model copies irrelevant details from an example | Examples too similar to each other | Use 3 to 5 diverse examples, including an edge case |
| “Don’t do X” instructions are ignored | Negative phrasing with no alternative | Say what to do instead |
| Code passes tests but fails on new input | Optimized for the visible tests | Ask for a general solution; add hidden tests |
| Can’t parse the reply | Prose around the code | Ask for one fenced block, or use structured outputs |
FAQ
Do these patterns work with models other than Claude?
The principles (context, examples, clear constraints, checks) are general. Exact effects such as the “query at the end” improvement were measured by Anthropic on its models, so measure on yours.
How many context files should I include?
The few that show the patterns the new code needs. More isn’t better: it costs tokens and dilutes the signal.
Should I keep prompts in the repository?
Yes. Version them next to the acceptance checks, and review changes to either like code.
Is the AST check better than a linter?
Use both. A linter covers general style; AST checks encode rules specific to your codebase, like “use the injected logger”.
Official documentation
- Anthropic: prompting best practices (examples, XML tags, long context, roles)
- Python ast module
- Amazon Bedrock prompt engineering guidelines
Last updated on October 10, 2026
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- REST API Design Best Practices: The Patterns That Make APIs a Joy to Use
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
