LangChain/Prompts & Output
★ OverviewIntermediate14 min

Prompts That Work

Every production LLM bug traces back to a prompt decision made without thinking about tokens, injection, or testability. This article covers when to use prompt templates, how to budget few-shot examples, how to defend against injection, and how to build a prompt you can actually evaluate.

Quick Reference

  • ChatPromptTemplate.from_messages() — variables, LCEL composition, partial(), validation
  • MessagesPlaceholder(variable_name, optional=True, n_messages=20) — inject and cap conversation history
  • FewShotChatMessagePromptTemplate — fixed examples; switch to example_selector when the bank exceeds ~10
  • prompt.partial(language='English') — lock shared config, return a new template object
  • Escape literal braces with double braces: {{literal}}
  • System message carries the strongest instruction weight — put directives there, not in HumanMessage
  • Stable system prefix + MessagesPlaceholder = cache-friendly — prefix hits Anthropic prompt cache after first call

When to Use Prompt Templates (and When Not To)

The first engineering decision is whether to use a template at all. Raw message objects are often the right answer:

SituationRight toolWhy
One-off hardcoded call, no variablesRaw SystemMessage / HumanMessageZero overhead, no abstraction needed
Variables that change per callChatPromptTemplateValidation, LCEL composition, reuse
Output quality needs demonstrationsFewShotChatMessagePromptTemplateIn-context examples guide format and style
Example bank grows or query-dependentexample_selectorKeeps token count bounded; retrieves relevant examples
Many chains share config (language, persona).partial() on ChatPromptTemplateLock shared config once, vary per chain
Don't over-engineer a one-shot call

If you're writing a CLI tool that calls an LLM once with a hardcoded prompt, skip ChatPromptTemplate. The abstraction costs more than it saves when there are no variables to inject and no chain to compose.

Does the prompt haveruntime variables?NoRaw MessagesSystemMessage / HumanMessageone-off, no reuse neededYesChatPromptTemplatevariables, LCEL composition,partial(), validationDoes output quality needexample demonstrations?NoDone — ship itYesFewShotChatMessagePromptTemplatefixed examples ≤ 5, fits in contextbudget is predictableExample bank > 10 or grows over time?NoDone — ship itYes →use example_selector (vector similarity)

Pick the simplest tool that solves your problem — add complexity only when the simpler option runs out of capability

Once you have variables or need to compose with LCEL, raw strings break down: no validation, no escaping, no partial application. That's when ChatPromptTemplate earns its keep.