Organizing and Versioning Prompts
Keeping prompts in their own file, such as prompts.py, lets the instructions be named and referenced rather than embedded in function bodies.
A Function That Hides Its Instructions
Most small programs that call a language model start the same way. The author writes the instruction text directly inside the code that makes the call. This works well for a first attempt, but it quietly ties two different kinds of material together: the logic that calls the model, and the words that tell the model what to do. This article starts from a concrete function and asks what changes when those two parts are kept apart.
What do you think happens?
When you read a function that sends a request to a model, where would you look to find out what the model is being asked to do?
Reveal answer
The instructions are not an argument. In the source's example they are read from a named constant, GPT4o_ENTITY_EXTRACTION_PROMPT, which is the name that appears in the system message. The function never receives the instructions as input, so a reader has to look elsewhere to learn what the model is asked to do.
The parameter the caller supplies is only the text that changes from call to call. The fixed instructions live somewhere the function refers to by name.
Tracing a Query Function Step by Step
A model query in this style is built from a list of messages. Each message has a role and some content. A system message carries the standing instructions, and a user message carries the text that changes from one call to the next. The function below is a generated illustration of that principle. It pulls the message-building step into its own small function so you can inspect the result without calling a model. The instructions are not typed here; they are referred to by the name GPT4o_ENTITY_EXTRACTION_PROMPT, which will be defined in another file.
- Line 1 imports the named constant. The instruction text enters the application here.
- The
user_promptparameter is the only input that changes from call to call. - The system message refers to the constant by name rather than containing its text.
- The user message carries the caller's text in the role labeled user.
Trace the call build_messages('Ada Lovelace visited London.'). Python first runs the import on line 1, so the name GPT4o_ENTITY_EXTRACTION_PROMPT is available. Then it builds the list. The system entry takes its content from the constant. The user entry takes its content from the argument. The function returns the list, and the model query would send that list. Every step reads a value; none of them contains the instruction text itself.
Moving Instructions Into a Named Constant
The source recommends placing prompts in their own file, named prompts.py, and referring to them when a model query is created. The application file imports the prompt it needs by name, and the constant itself lives in prompts.py as a value such as GPT4o_ENTITY_EXTRACTION_PROMPT. The wording of the instructions has a single home, and the application only refers to it.
def build_messages(user_prompt):
return [
{'role': 'system', 'content': 'Extract the names of people, places, and organizations from the text. Return them as a comma-separated list.'},
{'role': 'user', 'content': user_prompt},
]The instruction text is embedded in the function body.
from prompts import GPT4o_ENTITY_EXTRACTION_PROMPT
def build_messages(user_prompt):
return [
{'role': 'system', 'content': GPT4o_ENTITY_EXTRACTION_PROMPT},
{'role': 'user', 'content': user_prompt},
]The function refers to a named constant defined in prompts.py.
Notice what this does to the function. The query function still uses the same name in its system message, so the logic of the call is not rewritten. What has changed is where the text lives. If the instructions need revising, you open prompts.py rather than searching through application logic. The function's behavior is unchanged, but its dependence on the wording is now visible in one import line.
Reusing One Prompt Across Applications
The first advantage the source names is reuse. Once a prompt is a named value in its own module, any application that needs it can import it. The wording is written once and referenced from several places, so it is not copied into each program. Several applications can import the same prompt, and the wording lives in a single location.
Read the diagram as a question about edits. If the instruction text changes in prompts.py, each application that imports the constant picks up the new wording without its own copy being edited. The applications themselves do not need to change, because they only refer to the name. This is the practical meaning of writing the wording once.
Testing Code and Prompts Separately
The second advantage is that code and prompts can be tested separately. The source gives a concrete form of this: code can be tested with different prompts. When the prompt is embedded in the function, every test of the function is also a test of that particular wording. When the prompt is a separate value, a test can supply other wordings and check how the code behaves around each one.
- The first test checks the code's handling of the changing input, and does not depend on the wording of the instructions.
- The second test checks that the system message is taken from the named constant.
Isolating the cause of a wrong result
A run of the application returns poor entity lists. Is the cause the code or the prompt?
- 1Hold the code fixed
Keep
build_messagesand the calling code exactly as they are. - 2Vary the prompt
Replace the constant in
prompts.pywith a revised wording and rerun the same inputs. - 3Compare results
If the output improves, the wording was the cause. If it does not, the wording was not the only problem.
- 4Hold the prompt fixed
If the code had been changed instead, keep GPT4o_ENTITY_EXTRACTION_PROMPT unchanged and vary only the code to see its effect.
Separation makes each experiment possible without rewriting the other half every time.
Who Benefits on a Team
The last advantage the source names concerns people rather than machines. Separating prompts from code allows subject matter experts to collaborate and help devise prompts without being distracted by code. Someone who knows the domain can open prompts.py, read and revise the instructions, and never need to follow the logic of the query function. Developers, in turn, can keep working in the application file without having the wording of every instruction in view.
The source also says that separation makes both files easier to read. Each file now has one main purpose, so a reader can tell at a glance whether a file is about behavior or about wording.
Mistakes Beginners Make With Prompt Files
Common mistake Keeping a copy of the instruction text inside the function as well as in prompts.py
What it looks likeThe system message is typed out again inside build_messages, even though the constant already exists.
Why it failsTwo copies of the wording can drift apart, and edits to
prompts.pywill silently fail to reach the function.Do this insteadRefer to the constant by name in the function and remove the inline copy.
Common mistake Passing the instructions in as a parameter when they are fixed
What it looks likebuild_messages(instructions, user_prompt) is called with the same instruction text every time.
Why it failsThe parameter list now reflects a choice that never varies, which hides the fact that the instructions are a fixed part of the program.
Do this insteadKeep user_prompt as the only input that changes from call to call, and import the fixed instructions by name.
Common mistake Assuming a test of the function proves the wording is good
What it looks likeA test passes because the function returns the right structure, and the author concludes the instructions are effective.
Why it failsThe test checked the code's behavior, not the quality of the instructions that the model received.
Do this insteadTest the code with its handling of inputs, and evaluate prompt wording as a separate question by varying only the prompt.
Common mistake Putting unrelated helper functions in prompts.py, which blurs the one-purpose separation the source describes
Keeping Prompt Changes Traceable
Because the wording now lives in one named place, that place is also the natural point to record how the instructions have changed over time. The source material for this article concentrates on why separation helps and does not describe a specific versioning method, so this section does not prescribe one. What the separated structure does give you is a single file whose history answers the question of what wording a given program used. Keep the instruction text in one place, change it deliberately, and make each change reviewable on its own.
Checking Your Understanding of the Separated Design
A colleague has a function that embeds a long instruction string directly in its body, and two other scripts contain nearly identical copies of that same string. Describe how you would move the wording into prompts.py, which parts of the code you would change, and which parts you would leave alone. Then explain which of the three copies would disappear and what each application would import.
Hints
- Start by naming the constant, then identify the one line in each application that would change.
- Think about which input still changes from call to call, since that input should remain a function parameter.
- Consider what a change to the wording would require after the move.
Key Takeaways
- Embedding instructions inside a function ties the wording to the logic that calls the model, so the instructions are hidden from anyone reading the function.
- Keeping prompts in their own file such as
prompts.pylets the application refer to a named constant, so the function's code is unchanged while the wording has a single home. - Several applications can import the same named prompt, so the wording is written once and reused rather than copied.
- Code and prompts can be tested and revised separately, and subject matter experts can work on prompts without reading the code.