DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

Prompt chaining: why three focused prompts beat one mega-prompt

The instinct with any LLM task is to write one big prompt: "read all of this, figure out the answer, and write it up." It often almost works — the output is fluent and confident — which is exactly what makes it dangerous. Under the hood the model is doing extraction, reasoning, and writing simultaneously, and attention is finite, so it drops a detail, miscounts, picks the wrong priority, or silently skips a whole sub-task. Worse, you get back one opaque paragraph with no seam to pull apart. I built an interactive demo that runs the same task both ways, and the gap is hard to unsee.

The task that breaks the mega-prompt

The demo takes five messy customer reviews and asks for one quantified executive insight: note the positives, name the number-one complaint, quantify it, and recommend a fix. The single prompt returns something plausible — "customers are happy, the main complaint seems to be slow shipping" — and it is wrong. App crashes appear in two of five reviews; shipping in one. It picked the wrong top issue, never quantified anything, and gave you no way to see where it slipped.

The idea: a pipeline of single-purpose prompts

Prompt chaining decomposes the task into a sequence of small prompts where each step's output is the next step's input. The canonical shape is extract → transform → summarize:

const rows   = await extract(rawInput);   // step 1: parse, don't think
const tally  = await transform(rows);     // step 2: reason over structure
const answer = await summarize(tally);    // step 3: present, don't compute
Enter fullscreen mode Exit fullscreen mode

It is the Unix-pipe philosophy applied to LLM calls — small tools, composed.

Extract: parse, don't think

The first link does only extraction. Turn messy text into a clean, machine-readable shape and do nothing else — no analysis, no prose. A narrow prompt like this is something even a small model gets right almost every time:

async function extract(rawReviews) {
  return llm(`For EACH review, emit one line:
    + positives  ·  - negative theme in {app,battery,shipping,price,comfort,sound}
    Reviews:\n${rawReviews}`);   // e.g. "R1 +overall · -app ..."
}
Enter fullscreen mode Exit fullscreen mode

The hard parsing now happens exactly once, and every step downstream operates on tidy rows instead of re-parsing raw text.

Transform: reason over the structure

The middle link does the actual reasoning — aggregate, count, rank — but over step 1's clean rows, never the raw input:

async function transform(rows) {
  return llm(`From these tagged rows, tally NEGATIVE mentions per theme,
             sort descending, and list POSITIVE themes separately.
             Rows:\n${rows}`);
}
Enter fullscreen mode Exit fullscreen mode

This is dramatically more reliable than reasoning inside a mega-prompt, because the model is not also fighting to parse messy text at the same time.

Summarize: present, don't compute

The last link only writes, and only from what the pipeline already computed:

async function summarize(tally) {
  return llm(`Using ONLY this tally, write one paragraph:
             overall sentiment, the #1 complaint WITH its count,
             and a concrete recommendation.\n\nTally:\n${tally}`);
}
Enter fullscreen mode Exit fullscreen mode

Separating "decide the answer" from "phrase the answer" is what stops fluent prose from quietly inventing a different top issue or forgetting a number.

The payoff: gates and error isolation

Chaining them is just the three calls in order — but because every intermediate result is a visible checkpoint, you can gate between steps, and when the answer is wrong the failure is localized instead of buried:

async function run(rawReviews) {
  const rows  = await extract(rawReviews);
  assert(rows.includes("-"), "extract produced no tagged rows");   // gate
  const tally = await transform(rows);
  assert(/\d/.test(tally), "transform produced no counts");        // gate
  const answer = await summarize(tally);
  return { rows, tally, answer };   // keep intermediates → debuggable!
}
Enter fullscreen mode Exit fullscreen mode

If the final answer is off, you open rows, then tally, then answer, and see exactly which step failed — "ah, extract missed review 4, so the count was off." A single mega-prompt has zero seams, so a bug anywhere looks the same: a bad paragraph.

When to chain — and when not to

This is not free. More calls mean more latency and token cost, and an error in an early step propagates downstream (garbage in, garbage out — so gate aggressively). Chain when the task has genuinely distinct phases and reliability or debuggability matter. For a simple single-shot task, one good prompt is faster and cheaper — building a pipeline for a one-liner is its own kind of over-engineering. Reliability compounds in your favour: one broad prompt has to nail every sub-task at once, while each narrow step has a far higher success rate on its single job.

Pick a task, flip between the single prompt and the chain, and step through the pipeline to watch state flow stage to stage:
https://dev48v.infy.uk/prompt/day35-prompt-chaining.html

Top comments (0)