04 — Clarity & Specificity
Ambiguity Is the Default — Eliminate It Explicitly
Every sentence has a space of plausible interpretations. The model picks one — silently. Vague prompts don't fail because the model is dumb; they fail because you under-specified the problem and the model faithfully answered a different, equally valid reading.
# The core discipline: identify EVERY axis of ambiguity and decide explicitly.
# "Summarize this article" — the model must silently decide:
AMBIGUITY_AXES = {
"audience": None, # for whom? exec? engineer? customer?
"length": None, # 1 sentence? 3 paragraphs? 500 words?
"preserve": None, # key facts? author's argument? action items?
"tone": None, # formal? casual? technical?
"format": None, # plain text? bullets? table? markdown?
"emphasis": None, # what to highlight? what to omit?
}
# The model WILL pick a value for each. You either specify it or accept its guess.
# Production approach: treat each None as a bug.
SPECIFIED = {
"audience": "engineering team lead who needs the actionable points",
"length": "exactly 3 bullet points, max 20 words each",
"preserve": "only decisions and their rationale, not background",
"tone": "terse, declarative, no hedging language",
"format": "markdown bullet list, no preamble, no closing remarks",
"emphasis": "highlight anything blocking or at-risk; omit process details",
}
# Every value here is CHECKABLE — you or an eval can verify pass/fail.
# "concise" is not checkable. "max 20 words per bullet" is.
Complex Implementation: Vague vs. Specified
Write a product description for our new wireless earbuds.
Write a product description for our new wireless earbuds, the "Aria Pro."
Audience: existing customers on our e-commerce product page, most of whom
already own one of our older audio products and are comparison-shopping
against the previous generation.
Length: exactly 3 short paragraphs, no more than 60 words each.
Tone: confident and specific, not hype-driven. Avoid superlatives like
"revolutionary" or "game-changing." We're a brand known for understated,
engineer-focused copy.
Must mention: 30-hour battery life with the case, active noise cancellation
(new in this model, the previous generation didn't have it), and IPX4
water resistance.
Do not mention: price (set elsewhere on the page) or compare directly to
named competitor products.
Format: no title/heading, just the three paragraphs, no bullet points.
Every added clause removed one axis of ambiguity. The output from the second prompt is dramatically more likely to be usable without editing — not because the model "tried harder," but because there's almost nothing left to guess.
Anti-Pattern: Vague Constraints
<!-- ANTI-PATTERN: none of these constraints are checkable -->
Explain what a foreign key is, but keep it short and simple, for someone
who isn't very technical.
<!-- PRODUCTION: every constraint is independently verifiable -->
Explain what a foreign key is in exactly 2 sentences. Assume the reader
has never written SQL but understands what a spreadsheet is — use a
spreadsheet analogy if it helps. Do not use the words "constraint,"
"integrity," or "schema."
import re
class PromptConstraintValidator:
"""Validates that prompt constraints are checkable, not vibes.
Use during prompt development to catch vague language before it ships."""
VAGUE_TERMS = {
"concise", "brief", "short", "simple", "detailed", "thorough",
"appropriate", "nice", "good", "professional", "casual",
"several", "a few", "some", "various", "relevant",
}
def __init__(self, prompt_text: str):
self.prompt = prompt_text
self.violations: list[str] = []
def check_vague_terms(self) -> list[str]:
"""Flag vague adjectives that have no fixed meaning."""
words = re.findall(r'\b[a-z]+\b', self.prompt.lower())
found = [w for w in words if w in self.VAGUE_TERMS]
if found:
self.violations.append(
f"Vague terms found: {found}. Replace with quantifiable "
f"constraints ('exactly 3 sentences', not 'keep it short')."
)
return found
def check_quantifiable(self) -> bool:
"""Verify at least one numeric/structural constraint exists."""
has_number = bool(re.search(r'\b\d+\b', self.prompt))
has_structure = any(
term in self.prompt.lower()
for term in ["json", "table", "bullet", "list", "exactly", "at most", "no more than"]
)
if not (has_number or has_structure):
self.violations.append(
"No quantifiable constraint found. Add specific counts, "
"lengths, or format requirements."
)
return has_number or has_structure
def check_negative_framing(self) -> list[str]:
"""Flag broad negative instructions prone to ironic rebound."""
broad_negatives = re.findall(
r"don't (?:be|sound|include|mention|use) (\w+)", self.prompt.lower()
)
conceptual = {"scary", "biased", "robotic", "formal", "casual", "technical"}
risky = [n for n in broad_negatives if n in conceptual]
if risky:
self.violations.append(
f"Broad negative instructions for conceptual categories: {risky}. "
f"Name the concept → activates it (ironic rebound). "
f"Reframe as positive: describe the DESIRED behavior instead."
)
return risky
def validate(self) -> bool:
self.check_vague_terms()
self.check_quantifiable()
self.check_negative_framing()
if self.violations:
for v in self.violations:
print(f" ⚠️ {v}")
return False
return True
# Usage:
validator = PromptConstraintValidator("""
Explain what a foreign key is in exactly 2 sentences. Do not be scary
or use jargon. Keep it simple.
""")
validator.validate()
# ⚠️ Vague terms found: ['simple']
# ⚠️ Broad negative instructions for conceptual categories: ['scary']
Positive vs. Negative Instructions
<!-- ANTI-PATTERN: naming "sorry" activates the concept of apologizing -->
Don't apologize excessively in your response, and don't say "I'm sorry"
more than once.
<!-- PRODUCTION: positive target gives the model something to generate TOWARD -->
If you can't fully complete the request, state plainly what you can do
and proceed with that. Use at most one brief acknowledgment of any
limitation; do not repeat it.
# THE IRONIC REBOUND EFFECT (mechanism):
# To process "don't mention monsters," the model's internal representation
# must ACTIVATE the concept of "monsters" (to know what to suppress), then
# separately suppress it. Activating the concept raises its salience in
# context → the model is MORE likely to produce it, not less.
#
# This is structurally identical to "don't think about an elephant."
# The model has less training signal for pure, clean suppression than for
# discussing-while-claiming-to-avoid (common conversational move in training data).
# ANTI-PATTERN: naming everything you want to avoid
BAD_BEDTIME_PROMPT = """
Write a bedtime story for a 5-year-old. Do not include anything scary,
do not mention monsters, do not include any conflict or danger, and
absolutely do not have a villain.
"""
# "monster", "scary", "danger", "villain" are ALL now activated in context.
# At higher temperature, any of them can "leak" into the story despite the ban.
# PRODUCTION: describe the desired positive content directly
GOOD_BEDTIME_PROMPT = """
Write a gentle, calming bedtime story for a 5-year-old about a rabbit
who can't fall asleep and asks the moon for help. Keep the tone soothing
throughout, with a peaceful resolution where the rabbit falls asleep happy.
"""
# Nothing forbidden to accidentally activate. The desired content is fully
# and positively specified. No "don't think about the villain" tension.
# WHEN NEGATIVE INSTRUCTIONS ARE FINE: narrow, mechanical, unambiguous
# These work because they target formatting, not conceptual content:
GOOD_NEGATIVE = "Do not include a markdown code fence around the JSON output — return the raw JSON object only."
# No positive rephrasing is cleaner than this. The rule of thumb:
# negative = OK for mechanical/format constraints
# negative = BAD for broad/thematic/conceptual prohibitions
Constraint Specification Table
| Vague (uncheckable) | Specific (checkable) |
|------------------------------|------------------------------------------------------------|
| "Keep responses concise." | "2–3 sentences unless the user explicitly asks for more." |
| "Be careful with sensitive topics." | "If self-harm mentioned, provide the crisis hotline for their stated region and encourage contacting a professional." |
| "Handle errors gracefully." | "If input is missing a required field, return {\"error\": \"missing_field\", \"field\": \"<name>\"}." |
| "Format nicely." | "Markdown: H2 per section, bullets for 3+ items, table only when comparing 2+ items across 2+ attributes." |
| "Don't sound like an AI." | "Use first person ('I think', 'I'd suggest'). Never say 'as an AI' or 'I don't have access to real-time data.' Vary sentence length." |
💡 Tips & Tricks
# [Idiom] The "new teammate" test: would a new human teammate, given only
# the prompt text and no other context, produce the output you want?
# If they'd have to guess at tone, scope, length, or edge-case handling,
# so will the model — for the same reason (missing information, not skill).
# [Idiom] Quantify everything quantifiable. Any time you're about to write
# "brief", "detailed", "several", "a few", or "appropriate" — pause and
# ask if there's a number you have in mind. If there is, write the number.
# If there truly isn't, say so: "length should scale with complexity."
# [Debug] Define domain terms inline. If your prompt uses a term with a
# specific business meaning ("Sev1", "at-risk customer", "qualified lead"),
# restate your definition in the prompt every time precision matters.
# The model does NOT share your internal vocabulary.
# [Idiom] Write constraints as if they'll be graded. Imagine handing the
# output to a strict reviewer with a checklist. If a constraint couldn't
# become a checklist item ("is this concise?" → no fixed threshold),
# rewrite it until it could.
# [Performance] Positive-frame first, negative-frame as backup. When you
# catch yourself writing "don't do X", ask what you want INSTEAD, and
# phrase that first. Reserve the negative form for narrow mechanical
# prohibitions where no clean positive equivalent exists.
⚠️ Edge Cases & Gotchas
# [Gotcha] Over-specification can backfire. An extremely long, rigid list
# of constraints can cause the model to prioritize surface compliance with
# the FORMAT of your rules over the SUBSTANCE of the task. Specificity ≠
# maximalism. Goal: "no remaining ambiguity on axes that matter," not
# "the longest possible prompt."
# [Gotcha] A precise-sounding number can be arbitrary and get treated as
# a hard target anyway. "summarize in exactly 3 sentences" when the content
# doesn't compress cleanly → the model forces one unnaturally long,
# comma-spliced "sentence" to hit the count, or drops content to fit.
# If the number is a soft target, say "approximately 3" or "2-4 sentences."
# [Gotcha] Ambiguity compounds in multi-turn. A single-turn prompt lets you
# catch and fix an ambiguous phrase. In multi-turn, "can you make it
# shorter?" inherits ALL the ambiguity of everything before it (shorter
# than what — the whole response or one section?). See Chapter 8 for
# disambiguation strategies in multi-turn conversations.
# [Gotcha] "Don't" instructions about the model's own behavior/identity
# are especially unreliable. "Don't reveal you're an AI" fights both the
# ironic-rebound effect AND the model's training toward honesty about its
# nature. These specific categories of "don't" are worth flagging as
# unusually unreliable compared to ordinary content-based negatives.
# [Safety] Vague constraints tested only on "easy" inputs look fine until
# they don't. "handle edge cases sensibly" might work on your test inputs
# (which weren't edge cases) and fail unpredictably in production. Test
# vague-looking constraints specifically against inputs designed to be
# ambiguous, not just happy-path examples. See Chapter 19.
🧠 Spot the Bug
Draft a reply to this customer email. Don't be too formal, but don't be
too casual either. Don't make it too long, but make sure it fully
addresses their question. Don't sound robotic or like a template.
Output quality is wildly inconsistent from one email to the next. What's the structural issue?
Answer
Every constraint is a negatively-framed midpoint on a spectrum with no anchor for where the target point is. "Not too formal, not too casual" describes an entire range, not a specific target. The model has no positive description of the desired tone, length, or style — it invents one from scratch on every call, and since generation involves sampling, it lands in a different part of that wide, underspecified middle range each time.
None of these constraints could become a checklist item: "is this too formal?" has no fixed threshold. The fix is to specify the positive target: "Write in a warm, professional tone — like a knowledgeable colleague helping another team member. Address the customer by first name. Keep it to 3-5 sentences. Use contractions naturally. Never use phrases like 'I apologize for the inconvenience' — instead, acknowledge the issue directly and state the fix."
Key Takeaways
"""
Clarity & specificity — the highest-leverage skill in prompt engineering.
"""
# 1. Ambiguity is the default state of natural language. The model resolves
# it SILENTLY. Your job: identify every axis and decide explicitly.
# vague: "keep it short" → the model picks its own definition of "short"
# exact: "max 3 sentences" → checkable, verifiable, no guessing
# 2. Positive framing beats negative for conceptual constraints.
# "don't be scary" → activates "scary" → ironic rebound risk
# "keep it gentle" → no forbidden concept activated → no leak path
# Exception: narrow mechanical negatives ("no code fences around JSON") are fine.
# 3. Quantify everything quantifiable.
# "brief" → "exactly 2 sentences"
# "a few" → "at most 3"
# "appropriate" → "scales with complexity: 1-2 sentences for simple, 4-5 for multi-part"
# 4. Constraints must be checkable — by you, by an eval (Chapter 19), or by
# downstream code. If you can't write a pass/fail test for it, it's not
# a constraint, it's a wish.
# 5. Over-specification ≠ better. The goal is "no remaining ambiguity on
# axes that matter," not "the longest possible prompt." A rigid 15-section
# format spec can cause the model to prioritize format compliance over
# substance, or truncate content to fit the mandated structure.