Prompt Engineering With Python: Templates, Tests, and Reuse
Prompt engineering with Python means building prompts as code: templates you can test, version, and call from an application. This guide covers template structure, output testing, library choices, and where to keep approved instructions.
Updated 6 min read
Prompt engineering with Python means treating prompts as code: templates you can version, test across inputs, and call from an application. Instead of typing an instruction into a chat interface and adjusting it manually, you write it in Python, parameterize it for different inputs, and run automated checks on the outputs. Tools like fenbs hold the approved instructions and the decisions behind them outside the codebase, so they can be read by any connected AI app without being hardcoded in a file.
This guide is for developers who want a repeatable workflow for building and maintaining prompts rather than a one-off approach. The basic Python shown here works with any model that provides an API. The principles apply whether you are using the Anthropic SDK, the OpenAI library, or another client.
Why Python fits prompt work
Python is a practical choice for prompt work because it handles the repetitive parts well. String formatting, API calls, and result parsing are all straightforward. You can run the same prompt on a batch of inputs, collect the outputs, and compare them in a script that takes a few minutes to write.
Python also integrates naturally with the testing tools developers already use: pytest for assertions, logging for recording outputs, and version control for tracking what changed and why. Treating a prompt as a Python string in a versioned file gives you the same review and rollback tools you use for code.
Building a prompt template in Python
A prompt template is a string with placeholders that you fill in before sending the request. The simplest version uses Python's f-string or .format(). For more complex templates with conditional sections, a templating library like Jinja2 gives you more control.
def build_summary_prompt(document: str, audience: str, max_bullets: int) -> str:
return (
f"Summarize the following document for {audience}.\n"
f"Format: up to {max_bullets} bullet points, each one sentence.\n"
"Constraint: include only findings, not recommendations.\n\n"
f"{document}"
)Keep the template in a separate function rather than inline in the API call. That separation makes it easier to test the template itself, swap it out without touching the application logic, and review changes in a pull request. If the prompt changes often, consider loading it from a file or a configuration source rather than hardcoding it.
from pathlib import Path
def load_prompt(name: str) -> str:
return (Path("prompts") / f"{name}.txt").read_text()Testing prompt outputs programmatically
Automated testing for prompts works differently from unit tests. You cannot assert an exact output string because model outputs vary. Instead, you assert structural properties: the output is valid JSON, it contains the required fields, it is under a certain length, or it does not contain a known failure pattern.
import json
def test_summary_output(output: str) -> None:
# Check the output contains at least one bullet point
assert output.strip().startswith("-") or output.strip().startswith("\u2022"), \
"Output should start with a bullet point"
# Check the output does not include the word "recommend"
assert "recommend" not in output.lower(), \
"Output should not include recommendations"Run each prompt template on a small fixture set: five to ten real inputs that represent the range of cases you encounter. Save the fixture inputs and the assertions in your test suite. When you change the prompt, run the tests to check whether the change broke anything.
For tasks where the output quality matters but is hard to assert automatically, log the outputs and review a sample manually. Model-based evaluation, where you ask a second AI call to judge the first output, is a useful complement to structural assertions. See prompt engineering vs. RAG vs. fine-tuning for when evaluation needs go beyond the prompt itself.
Choosing a prompt library
Several Python libraries support prompt engineering work. The right choice depends on what you need to manage.
| Library | Good for | Adds |
|---|---|---|
| Jinja2 | Complex templates with conditionals, loops, and partials. | A templating engine; install separately. No AI-specific features. |
| LangChain (prompt components) | Projects already using LangChain for chains or agents. | PromptTemplate, ChatPromptTemplate, few-shot helpers. Adds a large dependency. |
| LlamaIndex | Projects that combine prompts with document retrieval. | Prompt management tied to query pipelines. Best when retrieval is part of the task. |
| Plain Python strings | Simple templates where a function and a text file are enough. | No extra dependency. Fewer abstractions to learn or debug. |
For most projects, plain Python functions and a prompts/ directory are sufficient. Add a library when the project outgrows that: when templates become complex enough that Jinja2 conditionals are cleaner than Python string logic, or when a framework like LangChain is already in use.
Keeping approved prompts and their change history
A prompt that is approved and in production should be treated like a configuration file: changed deliberately, with a reason recorded and the change reviewed. Store production prompts in version control. The commit message should explain why the prompt changed, not just what changed.
For prompts that need to be readable and writable by non-developers or by connected AI apps, a separate store outside the codebase is practical. A developer changes the prompt, the reason is recorded, and other team members or AI tools can read the current version without opening a repository.
How to do this with fenbs
fenbs keeps a Skills and prompts library outside any codebase. A connected AI app can read current approved instructions before working, without those instructions being hardcoded in a file.
- Create a free account. Add a project for the application where these prompts are used.
- Add each approved prompt to Skills and prompts. Include the task it covers, any parameter notes, and which model version it was tested against.
- Use Decisions and rules to record why a prompt was written a certain way, such as "always request JSON output for this pipeline because the parser expects it." Future changes start from that reasoning.
- Connect Claude Code, Cursor, or another coding assistant from Integrations. Ask it to read the project context, including the approved prompts, before suggesting changes.
- When a prompt changes, add a note on what changed and why. That history is more useful to the next developer than reading the git blame.
Keep approved prompts outside the codebase
fenbs holds your Skills and prompts library alongside the decisions and context that explain them. Connected coding assistants like Claude Code and Cursor read the library before working. When a prompt changes, the reason lives in fenbs, not in a comment that may get removed on the next cleanup.
Create a free accountFree for one person · No credit card · Works with Claude, ChatGPT, Cursor and more