跳到正文
DEV Community· Pratham Shettigar·· 3 小時前AI 評分39

Stop Hardcoding Prompts in Your Python Files: Meet PromptFill

Stop Hardcoding Prompts in Your Python Files: Meet PromptFill (best balance of curiosity and search terms)

AI 導讀

PromptFill 是一個零依賴 Python 套件,可將提示詞模板存放在獨立的 Markdown 或純文字檔案,再於執行時填入變數並回傳字串。它使用標準庫的 string.Template 處理 $ 佔位符,缺少變數時會以 ValueError 指明名稱,檔案路徑錯誤則會觸發 FileNotFoundError。

正文

Open any LLM-powered Python project that's been alive for a few weeks and you'll probably find the same thing: a function with a prompt inside it. Then the prompt grows a few more instructions, a formatting rule, an example. Before long, the logic is buried under a wall of text and nobody wants to touch the file.

That's the itch behind PromptFill, a small, zero-dependency Python package that lets you keep prompts in their own files and fill in the variables at runtime.

pip install promptfill

This post covers what it does, how it works under the hood (it's short enough to read in a minute), and where it fits.

The problem with prompts living inside code

Prompts aren't really code, but we treat them like code because they happen to be strings. That causes predictable problems:

  • Readability suffers. A long prompt in the middle of a function makes the actual logic hard to follow.
  • Reuse means copy-paste. Need the same prompt in another script? You copy it, and now you have two versions to keep in sync.
  • Missing values hide until runtime. Forget one variable and you may not notice until a half-formed prompt reaches a model.

Think of a form letter. Nobody retypes the whole letter for each recipient. You keep one template with blanks like Dear ___, and fill in the names when it's time to send. Prompts deserve the same treatment: one template file, blanks filled in by your code.

What PromptFill does

PromptFill loads a prompt template from a text file, replaces the placeholders with values you pass from Python, and returns a plain string. That's the whole job, and it's deliberately small.

  1. Write your prompt in a .md or .txt file.
  2. Add placeholders wherever you need dynamic values.
  3. Call load_prompt() and supply those values.

What you get:

  • External prompt files in Markdown or plain text
  • Dynamic formatting with values supplied from Python
  • Reusable templates across scripts and projects
  • Missing variable detection with a clear ValueError
  • A clear FileNotFoundError when the path is wrong
  • No third-party dependencies, only the standard library
  • Provider-agnostic output: the result is a string you can hand to Gemini, OpenAI, or anything else

A quick walkthrough

1. Write the template

Create reviewer.md:

You are an expert $role.

Review the following code written in $language for bugs,
security vulnerabilities, and performance issues.

Code to review:

$code_snippet

Provide your findings and suggest improvements.

Placeholders use a $ prefix: $role, $language, $code_snippet.

2. Load and fill it

from promptfill import load_prompt

prompt = load_prompt(
    file_path="reviewer.md",
    role="Senior Security Engineer",
    language="Python",
    code_snippet="print('Hello, World!')"
)

print(prompt)

3. Get the result

You are an expert Senior Security Engineer.

Review the following code written in Python for bugs,
security vulnerabilities, and performance issues.

Code to review:

print('Hello, World!')

Provide your findings and suggest improvements.

The return value is a regular Python string, so it drops straight into whatever client you already use.

How it works under the hood

The whole implementation fits on one screen:

from pathlib import Path
from string import Template
from typing import Union

def load_prompt(file_path: Union[str, Path], **kwargs) -> str:
    path = Path(file_path)

    if not path.is_file():
        raise FileNotFoundError(f"Prompt file not found at: {file_path}")

    content = path.read_text(encoding="utf-8")
    template = Template(content)

    try:
        return template.substitute(**kwargs)
    except KeyError as e:
        raise ValueError(f"Missing prompt variable for placeholder: {e}")

Here's what happens when you call it:

  1. It checks the file exists. If the path doesn't point to a file, you get a FileNotFoundError that includes the path, so a typo is easy to spot.
  2. It reads the file as UTF-8 text.
  3. It builds a string.Template from the contents. This is the standard library's built-in $placeholder substitution, which is why placeholders look the way they do.
  4. It calls substitute() with your keyword arguments. Every $name in the template is replaced with the matching value.
  5. If a placeholder has no matching value, it raises a ValueError. Template raises a KeyError internally, and PromptFill catches it and re-raises it as a ValueError with a message that names the missing variable.

I chose string.Template over f-strings or str.format() on purpose. It only cares about $, so curly braces in your prompt (a JSON example, say) aren't treated as placeholders. And since it's part of the standard library, there's nothing extra to install, audit, or keep updated.

Reusing one template for many jobs

The real payoff comes when one prompt serves several cases:

from promptfill import load_prompt

template_path = "reviewer.md"

python_prompt = load_prompt(
    file_path=template_path,
    role="Python Developer",
    language="Python",
    code_snippet="print(10 + 20)"
)

javascript_prompt = load_prompt(
    file_path=template_path,
    role="JavaScript Developer",
    language="JavaScript",
    code_snippet="console.log(10 + 20)"
)

No duplicated text and no drift between copies. Improve the wording in reviewer.md and every caller benefits.

Catching missing variables early

Say your template expects $role, $language, and $code_snippet, but you forget language:

from promptfill import load_prompt

prompt = load_prompt(
    file_path="reviewer.md",
    role="Developer",
    code_snippet="x = 1"
)

You get:

ValueError: Missing prompt variable for placeholder: 'language'

It fails loudly, and it names the exact variable you forgot. That beats sending an incomplete prompt to a model and wondering why the output is strange.

Good to know

Because PromptFill builds on string.Template, a few of its standard behaviors carry over:

  • Literal dollar signs need escaping. To include a real $ in a prompt (for example, a price), write $$ in the template file.
  • ${name} also works. Use it when a placeholder sits right next to other text, like ${language}_review.
  • Extra keyword arguments are ignored. Passing a value the template doesn't use won't raise an error.

Plugging it into an LLM call

PromptFill only formats prompts. It doesn't call any model itself, so you're free to use whichever provider you like. Here's an example with an OpenAI-compatible client:

from promptfill import load_prompt
from openai import OpenAI

client = OpenAI()

prompt = load_prompt(
    file_path="reviewer.md",
    role="Python Expert",
    language="Python",
    code_snippet="print('Hello, World!')"
)

response = client.chat.completions.create(
    model="YOUR_MODEL_NAME",
    messages=[{"role": "user", "content": prompt}]
)

print(response.choices[0].message.content)

The OpenAI SDK is installed separately (pip install openai) and only for this example. PromptFill itself stays dependency-free.

A project layout that scales

Once prompts are files, organizing them is easy:

my-ai-project/
├── prompts/
│   ├── reviewer.md
│   ├── summarizer.md
│   └── chatbot.txt
├── main.py
├── requirements.txt
└── README.md

One file per prompt, named for its purpose. It also makes prompt changes easy to review: a diff on prompts/summarizer.md reads like a diff on a document, not a tangle of string literals.

Who it's for

PromptFill is aimed at anyone who keeps prompts in Python projects:

  • AI engineers building LLM-powered apps
  • RAG developers maintaining retrieval and Q&A prompts
  • Agent developers managing a different prompt per agent or task
  • Python developers who just want cleaner code

A note on security

PromptFill is a text formatter. It doesn't make prompts or inserted values safe on its own, so a few habits still apply:

  • Don't put API keys or passwords in prompt files.
  • Treat user-provided values as untrusted input.
  • Review formatted prompts before using them in sensitive applications.
  • Remember that sending a prompt to an external provider sends its contents to that provider.

What's next

PromptFill is young and the roadmap is open. Ideas I'm considering:

  • Better error messages for invalid templates
  • Support for nested prompt directories
  • Template validation
  • Prompt version management
  • Integration examples for LangChain and other frameworks
  • Unit tests and CI
  • Support for additional template formats

If one of these matters to you, tell me. Feedback shapes what gets built first.

Try it

pip install promptfill

If you try it, I'd love to hear how it goes. Open an issue, suggest a feature, or send a pull request. And if it saves you from one more 80-line string in a Python file, a star on the repo goes a long way.

How do you manage prompts in your projects: inline strings, files, or something else? Let me know in the comments.

來源:DEV Community · dev.to