Skip to content

bug(tokenizers): Jinja attribute access on Undefined returns Undefined where Jinja2 raises — silently malformed prompts #416

Description

@jamesburton

Found during independent review of PR #411 (issue #399). Pre-existing — not introduced by that PR.

Problem

JinjaEvaluator.EvalMemberAccess (:272-273) returns Undefined when accessing an attribute on an Undefined value. Real Jinja2 raises UndefinedError.

{{ loop.nextitem.role }}   {# last iteration #}
  • Jinja2: UndefinedError: 'jinja2.Utils.Undefined object' has no attribute 'role'
  • dotLLM: renders ""

So a template bug that Jinja2 would surface loudly renders as an empty string here — and an empty string in a chat template is a silently malformed prompt, which then shows up as a quality regression attributed to the model or the kernels.

This is the same failure class the repo keeps paying for: #373 (unknown pre silently disabling pre-tokenization), #392 (skips counted as failures masking real ones), #261 (recurrent-state pollution silently corrupting perplexity). The pattern is always degrade quietly rather than fail loudly.

Why it hasn't bitten yet

Qwen3.8-27B's template guards both access sites via short-circuit (EvalBinary:314-318), so it is currently harmless for that template. It is a latent leniency in the evaluator generally, and templates are third-party inputs we do not control — every new model ships a new one.

Related leniency (same root)

Tuples stringify as ['k1', 1] where Jinja2 gives ('k1', 1). Cosmetic today; noted because it shares the cause — the evaluator approximates Jinja2 rather than matching it, and the divergences are undocumented.

Suggested shape

  • Attribute access on Undefined throws a JinjaException naming the expression and line, matching Jinja2's behaviour.
  • Consider an opt-out env var if any shipped template relies on the lenient behaviour — but audit first; do not assume one does.
  • A test proving the loud path fires, plus a negative control showing the old lenient path would have passed it (the Unknown tokenizer.ggml.pre silently disables pre-tokenization entirely (e.g. pixtral) #373 pattern).
  • Document in docs/TOKENIZERS.md which Jinja2 behaviours this evaluator deliberately approximates, so the next divergence is a known deviation rather than a surprise.

Evidence

Verified against reference Jinja2 3.1.6 during review of PR #411. Related: #399, #409 (parenthesized tuple literals), #373.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions