There is a second kind of prompt: not one static block, but one built per request from user data. The temptation there is to build the string wherever it happens to be needed, and to write the AI-facing wording inline while you do.
That splits one contract across two concerns. The wording that shapes generated content ends up scattered through assembly code, and the counts and labels that the rest of the application also needs get written twice.
So the copy lives with the schema, and the builder only assembles it:
public const COUNTS = [
'services' => 3,
'testimonials' => 3,
'faqs' => 6,
];
public const ITEM_FIELDS = [
'testimonials' => [
'paragraph' => 'Write a convincing testimonial review (max 200 characters)
from a satisfied client. Include: 1) specific problem solved,
2) measurable result or outcome, 3) positive personal touch.',
'name' => 'Generate a realistic first and last name for this satisfied client.',
],
];
And the service does no authoring at all:
public function generateTestimonials(): string
{
$fields = ContentSchema::ITEM_FIELDS['testimonials'];
return self::generateContent(range(1, ContentSchema::COUNTS['testimonials']), fn () => <<<TESTIMONIAL
{
"paragraph": "{$fields['paragraph']}",
"name": "{$fields['name']}"
}
TESTIMONIAL);
}
Now "how many testimonials" is one number, read by the prompt that requests them and by the layer that validates them. Change it in one place and both follow. That is the same single-source-of-truth argument as the last chapter, applied to the wording rather than the structure — and it is why the prompt copy sits in the schema class rather than in the prompt builder.
Bind an Instruction to the Value It Is About
Here is a bug that is pure prompt, with no code path involved at all.
When a merchant supplies their own list of services, each one gets a generated description. The original instruction was, reasonably: describe this service with specific benefits for clients.
Hair salons got descriptions about skincare.
The model had the service name in the JSON structure it was filling in, several lines above, and a generic instruction in the field it was filling. Given a general instruction and a nearby-but-not-adjacent value, it drifted toward the sector average rather than the specific item — and "beauty salon" averages out to something broader than "haircut."
The fix was to put the value inside the instruction:
'description_provided' =>
'Describe "{service}" specifically — this description must be about "{service}"
and nothing else (max 450 characters, benefit-focused, explain the value/outcome).
Do not describe a different service.',
Interpolated at build time, so the model reads Describe "Balayage" specifically — this description must be about "Balayage" and nothing else.
The general rule: an instruction that refers to "this" is weaker than an instruction that names the thing. If a field's value is available when you build the prompt, put it in the instruction rather than relying on the model to look up two lines. Repetition that would be redundant to a human reader is load-bearing here.