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:
| Situation | Right tool | Why |
|---|---|---|
| One-off hardcoded call, no variables | Raw SystemMessage / HumanMessage | Zero overhead, no abstraction needed |
| Variables that change per call | ChatPromptTemplate | Validation, LCEL composition, reuse |
| Output quality needs demonstrations | FewShotChatMessagePromptTemplate | In-context examples guide format and style |
| Example bank grows or query-dependent | example_selector | Keeps token count bounded; retrieves relevant examples |
| Many chains share config (language, persona) | .partial() on ChatPromptTemplate | Lock shared config once, vary per chain |
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.
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.