Prompts as code Required
Keep prompts in files
Store every prompt as a template file in version control, one file per prompt, with an ID and a version. Never build prompts from strings scattered through the code.
How to do it
Keep prompts in one folder, such as ai/prompts/. Give each file front matter with an ID, a version number and the output schema it expects. Fill variables with a template engine that fails on a missing variable, such as Jinja2 with StrictUndefined or a small function of your own. Load the file in the model gateway. Raise the version on every change, so logs and evals show which text produced which answer.
Example
---
id: return-assistant
version: 7
schema: ReturnAnswer
---
You answer questions about returns for Fernwood Bikes.
Use only the policy text inside <policy>. If it does not answer the question, say so.
<policy>
{{policy}}
</policy>
export function renderPrompt(template: string, vars: Record<string, string>): string {
return template.replace(/\{\{(\w+)\}\}/g, (_, name: string) => {
if (!(name in vars)) throw new Error(`Missing prompt variable: ${name}`);
return vars[name];
});
}
from jinja2 import Environment, FileSystemLoader, StrictUndefined
prompts = Environment(loader=FileSystemLoader("ai/prompts"), undefined=StrictUndefined, autoescape=False)
def render_prompt(prompt_id: str, **variables: str) -> str:
return prompts.get_template(f"{prompt_id}.md").render(**variables)
How to check
A test collects every prompt ID passed to the gateway in the code and finds a prompt file for each. The gateway accepts only a prompt ID, never prompt text. Every prompt file has an ID and a version, and a test renders each one with sample variables without error.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers ai features playbook: https://agentwranglers.se/playbook/ai-features/prompts-as-code/prompts-in-files.