DEV Community

Emam
Emam

Posted on

DynaPrompt: A Cleaner Way to Manage Prompts in LLM Apps


Most **LLM **apps start with a few hardcoded prompts. Then the project grows, the logic spreads across files, and prompt handling becomes a mix of strings, environment checks, and scattered config.

That is exactly the problem DynaPrompt is trying to solve: a dynamic prompt management and configuration library for LLM applications, with lazy loading, Jinja2 templates, and Pydantic schema support.

GitHub: mohamed-em2m/dynaprompt

Why prompt management gets messy

When prompts live directly inside application code, they become harder to test, harder to version, and harder to reuse. DynaPrompt takes the opposite approach: keep templates separate from business logic, and treat prompts like configuration instead of inline strings.

Here is the kind of code that gets ugly fast:

import os
import json

user_name = os.getenv("USER_NAME", "Guest")

SYSTEM_PROMPT = f"""
You are a helpful assistant.
Current User: {user_name}
Format your output according to this schema:
{json.dumps(MySchema.model_json_schema(), indent=2)}
"""

if os.getenv("ENV") == "production":
    model = "gpt-4"
    temperature = 0.2
else:
    model = "gpt-3.5-turbo"
    temperature = 0.7
Enter fullscreen mode Exit fullscreen mode

What DynaPrompt changes

DynaPrompt lets you load prompts from files, render them at runtime, and keep environment-specific config outside your app logic.

A cleaner version looks like this:

from dynaprompt import DynaPrompt

prompts = DynaPrompt(settings_files=["prompts/"])

rendered = prompts.system.render(user_name="Emam")

response = llm_client.generate(
    prompt=rendered.text,
    model=rendered.config["model"],
    temperature=rendered.config["temperature"],
    response_format=rendered.response_schema,
)
Enter fullscreen mode Exit fullscreen mode

Markdown prompts with YAML frontmatter

One of the nicest parts of DynaPrompt is that prompts can live in Markdown files with YAML frontmatter at the top, letting you set model settings and schema metadata right next to the prompt text.

---
model: gpt-4o
temperature: 0.2
max_tokens: 1000
response_schema: AnalysisSchema
---

You are an expert code analyzer.

Please review the following code snippet from {{ developer_name }}:

    {{ code_snippet }}

Analyze it and return the result strictly matching the schema.
Enter fullscreen mode Exit fullscreen mode

And then render it like this:

from dynaprompt import DynaPrompt

prompts = DynaPrompt(settings_files=["prompts/"])

rendered = prompts.analyzer.render(
    developer_name="Emam",
    code_snippet="print('hello')",
)

print(rendered.config["model"])
Enter fullscreen mode Exit fullscreen mode

Environment layering for dev and prod

The library also supports environment-based layering. You can define a base prompt config with a production override, switching behavior without rewriting code.

[default.summarizer]
template = "Summarize this: {{ text }}"
model = "gpt-3.5-turbo"
temperature = 0.7

[production.summarizer]
model = "gpt-4-turbo"
temperature = 0.1
Enter fullscreen mode Exit fullscreen mode

Usage:

from dynaprompt import DynaPrompt

prompts = DynaPrompt(settings_files=["prompts.toml"], env="development")
print(prompts.summarizer.config["model"])

with prompts.using_env("production"):
    print(prompts.summarizer.config["model"])
Enter fullscreen mode Exit fullscreen mode

Validation and hooks

DynaPrompt also includes validation and hooks, useful when you want guardrails around prompt rendering. The example below shows a validator that blocks prompts that are too long, plus a hook that injects the current date into every prompt before rendering.

from dynaprompt import DynaPrompt
from dynaprompt.validator import PromptValidator

class TokenLimitValidator(PromptValidator):
    def validate(self, node, rendered) -> None:
        if len(rendered.text.split()) > 2000:
            raise ValueError(
                f"Prompt '{node.name}' exceeds the maximum allowed length!"
            )

prompts = DynaPrompt(
    settings_files=["prompts/"],
    validators=[TokenLimitValidator()],
)

def inject_date(node, kwargs):
    from datetime import datetime
    kwargs["current_date"] = datetime.now().strftime("%Y-%m-%d")
    return kwargs

prompts.add_hook("before_render", "inject_date", inject_date)

rendered = prompts.system.render(user_name="Emam")
Enter fullscreen mode Exit fullscreen mode

Why this is useful

DynaPrompt is a good fit for LLM apps that need structure, reusable templates, and clean separation between prompt text and application code. It also includes lazy loading, auto-exporting to TOML, and inspection helpers for tracking how prompts were loaded and merged.

Installation

pip install dynaprompt
Enter fullscreen mode Exit fullscreen mode

Or, if you use uv:

uv add dynaprompt
Enter fullscreen mode Exit fullscreen mode

Final thoughts

If your prompt logic is starting to feel like spaghetti, DynaPrompt is worth a look. It gives you a more organized way to manage prompts, environment overrides, schemas, and validation without stuffing everything into Python strings. The project is MIT licensed and written in Python.

➡️ GitHub: mohamed-em2m/dynaprompt



Enter fullscreen mode Exit fullscreen mode

Top comments (0)