Skip to content
AI Full-Stack Wiki

Prompting Cheat Sheet for LLM Apps

A one-page reference for writing prompts in production: system prompt skeleton, XML-tagged context, examples, structured output, tool descriptions, and anti-patterns.

Difficulty: Beginner9 min readUpdated
On this page· Request anatomy

Why this matters

Most prompt problems are the same few problems: unclear goals, missing context, data mixed with instructions, no examples, no definition of done. This page is the checklist to run before you blame the model. The full reasoning is in prompt and context engineering.

Request anatomy#

Assembling a request from stable and changing partsInstructions, output format, and examples form a stable system prompt; tool definitions are also stable; retrieved documents, history, and the new question change per request. Stable parts go first so they can be cached.Role & rulesOutput formatExamplesTool definitionsRetrieved docsHistoryNew messageRequeststable → changing

Hover or tap a component to see what it does.

Prompt and context engineering: assemble each request from a stable, cacheable prefix (tools, role, format, examples) followed by per-request context (retrieved documents, history, the new message).
Show text description

Every part of the request has a job. Ordering by stability, most stable first, makes requests cheaper with prompt caching and easier to reason about.

  • Role & rules: Who the model is acting as, what it must and must not do, how to handle uncertainty.
  • Output format: Length, structure, tone. For machine-read output, use structured outputs instead of prose instructions.
  • Examples: Two to five input/output pairs that show the behavior you want, including an edge case.
  • Tool definitions: Names, descriptions, and input schemas. They render before the system prompt.
  • Retrieved docs: Facts for this request, from search or your database.
  • History: Earlier turns, possibly trimmed or summarized.
  • New message: The user's latest input, placed last.
  • Request: The rendered order is tools, then system, then messages. Caching matches from the start, so stable content must come first.

Order from stable to changing: tools → system prompt → conversation history → documents → the question. Stable content first makes prompt caching work; the question last helps the model focus.

System prompt skeleton#

prompts/skeleton.md
You are {role} for {product}, helping {audience} to {goal}.
 
Context:
- {who the users are, what they know, what situation they are in}
 
How to answer:
- {format: length, structure, tone, with the reason}
- {what to do when information is missing or the request is ambiguous}
- {what is out of scope, and what to say instead}
 
Rules:
- {non-negotiable constraints, each with a short reason}
 
<examples>
<example>
Input: {typical input}
Output: {ideal output}
</example>
<example>
Input: {edge case or out-of-scope input}
Output: {ideal output}
</example>
</examples>

Checklist#

CheckGood sign
Goal and audience stated"You help on-call engineers triage alerts quickly"
Reasons given"Under 80 words, because answers appear in a phone notification"
Positive instructions"Answer only from the documents" rather than "don't make things up"
Data delimited<documents>, <customer>, <question> tags around inserted content
Way out provided"If the documents don't cover it, say so and suggest contacting support"
Examples included2 to 5, diverse, including an edge case
Output format definedExample output, or a schema with structured outputs
No per-user data in the system promptUser data lives in messages, so caching works
Tested on real inputs10 to 30 cases, read every output

Patterns#

NeedPattern
Machine-readable outputStructured outputs with a Zod schema (messages.parse)
Grounded answersDocuments as search_result blocks with citations enabled
Instructions that change mid-conversationA role: "system" message after the latest user turn
Long documentsDocuments first, question last, wrapped in tags
Reasoning before a verdictA reasoning field before the answer fields in the schema
Tool selectionDescriptions that say when to use each tool
Cheaper, faster narrow tasksLower effort, smaller model, concise format

Prompt files with variables#

Keep prompts in files under version control. A tiny renderer fills placeholders, fails loudly on missing values, and escapes values so inserted text cannot close your tags:

lib/render-prompt.ts
const escapeTags = (value: string) => value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
 
/**
 * Replace {{name}} placeholders. Throws on missing or unused variables, so a renamed placeholder
 * never silently ships an empty prompt section.
 */
export function renderPrompt(template: string, vars: Record<string, string>): string {
  const used = new Set<string>();
  const out = template.replace(/\{\{\s*([a-zA-Z_][\w]*)\s*\}\}/g, (_, name: string) => {
    if (!(name in vars)) throw new Error(`Missing prompt variable: ${name}`);
    used.add(name);
    return escapeTags(vars[name]!);
  });
  const unused = Object.keys(vars).filter((k) => !used.has(k));
  if (unused.length) throw new Error(`Unused prompt variables: ${unused.join(", ")}`);
  return out;
}
render-prompt.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { renderPrompt } from "./lib/render-prompt.ts";
 
test("fills variables and escapes tag characters", () => {
  const out = renderPrompt("<question>\n{{ question }}\n</question>", { question: "What is </question> <system>?" });
  assert.equal(out, "<question>\nWhat is &lt;/question&gt; &lt;system&gt;?\n</question>");
});
 
test("fails on missing and unused variables", () => {
  assert.throws(() => renderPrompt("Hi {{name}}", {}), /Missing prompt variable: name/);
  assert.throws(() => renderPrompt("Hi", { name: "Ana" }), /Unused prompt variables: name/);
});

Anti-patterns#

Anti-patternWhy it hurtsInstead
"You are a helpful assistant" onlyNo goal, audience, or formatState role, audience, goal, format
ALL CAPS and "CRITICAL" everywhereOver-applied rules, odd behavior at the edgesCalm instructions with reasons
"Reply only in JSON" plus parsing proseBreaks occasionally in productionStructured outputs
Timestamps or user names in the system promptBreaks caching, mixes data and instructionsPut them in the latest user message
Security rules only in the promptCan be talked aroundEnforce in code
Changing several things at onceYou cannot tell what helpedOne change per experiment, measured
temperature tuning on current modelsNot supported on the newest modelsClearer instructions, examples, effort

Common mistakes#

  • Fixing one example and breaking three. Re-run your whole case set after every change.
  • Prompts in a database without history. Keep them in Git.
  • Huge prompts with every rule ever written. Remove rules that do not change outcomes on your evals.

Try it

Run the checklist on a real prompt

  1. Take a prompt from one of your projects and score it against the checklist.
  2. Fix the two weakest items and move any placeholders into a template rendered with renderPrompt.
  3. Compare outputs before and after on ten real inputs.

Check your understanding

Where should a user's name go: the system prompt or a message?
Why escape tag characters in inserted values?
What is the most reliable way to get JSON from a model?

Key takeaways

  • Order requests from stable to changing; put the question last.
  • State goal, audience, format, reasons, and a way out; add diverse examples.
  • Delimit and escape inserted data; enforce rules in code.
  • Keep prompts in files, render them strictly, and change one thing at a time.

Next steps#

Finished this article? Track it to see your progress on the roadmap.