zylon-ai/private-gpt · error · ValueError

Template {template_name} does not have a source or filename.

Error message

Template {template_name} does not have a source or filename.

What it means

`PromptTemplate.get_template_str` extracts a template's text from a Jinja environment template: it prefers `template.source`, falls back to reading `template.filename` from disk, and raises `ValueError` when the template object has neither a usable `source` attribute nor a truthy `filename`. This happens with Jinja Template objects created from non-filesystem loaders or dict loaders that strip these attributes.

Source

Thrown at private_gpt/components/prompts/prompt_template.py:36

            trim_blocks=True,
            lstrip_blocks=True,
            extensions=["jinja2.ext.do"],
        )

    def get_template(self, template_name: str) -> Template:
        return self.env.get_template(template_name)

    def get_template_str(self, template_name: str) -> str:
        """Get the template string from the template name."""
        template = self.get_template(template_name)
        if hasattr(template, "source"):
            return str(template.source)
        elif hasattr(template, "filename") and template.filename:
            template_path = Path(template.filename)
            with open(template_path, encoding="utf-8") as f:
                return f.read()
        else:
            raise ValueError(
                f"Template {template_name} does not have a source or filename."
            )

    def create_prompt_template(
        self,
        template_name: str,
        **template_kwargs: Any,
    ) -> BasePromptTemplate:
        template = self.get_template(template_name)
        template_str = self.get_template_str(template_name)

        def process_value(v: Any) -> Any:
            if isinstance(v, list):
                return [process_value(value) for value in v]
            elif isinstance(v, dict):
                return {
                    process_value(key): process_value(val) for key, val in v.items()
                }

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Use the standard file-based loader so resolved templates carry `filename`
  2. Ensure the loader populates `source` on templates (e.g. wrap DictLoader values so templates keep their source)
  3. Force template compile/resolve via `env.get_template(name)` before calling `get_template_str`, since the code path already calls `get_template` first

Example fix

# before
env = Environment(loader=DictLoader({"my.prompt": "text {{x}}"}))
# template lacks source/filename -> ValueError

# after
env = Environment(
    loader=FileSystemLoader("private_gpt/components/prompts/templates")
)
Defensive patterns

Strategy: validation

Validate before calling

template = prompt_env.get_template(name)
if not (getattr(template, "source", None) or getattr(template, "filename", None)):
    raise ValueError(f"loader for {name!r} strips source/filename; use a file-based loader")

Type guard

def template_is_readable(template: Any) -> bool:
    return bool(getattr(template, "source", None)) or bool(getattr(template, "filename", None))

Try / catch

try:
    template_str = prompts.get_template_str(name)
except ValueError as e:
    if "does not have a source or filename" in str(e):
        raise ConfigurationError("prompt loader must keep source or filename") from e
    raise

Prevention

When it happens

Trigger: Loading prompts via a custom Jinja loader (e.g. DictLoader) whose Template objects lack `source`/`filename`; a template compiled from a string without source retention; calling `get_template_str`/`create_prompt_template` for a template name resolved through such an environment.

Common situations: Customizing the prompt environment or loaders; embedding prompts in code instead of files; Jinja version behavior differences in what attributes a lazily-loaded Template exposes before rendering.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/7b0a44f9c54df903. Report an issue: GitHub.