Skip to content

bug: JinjaTemplateChatWrapper drops generation-prompt text after the assistant header #634

Description

@JoshuaWise

Issue description

JinjaTemplateChatWrapper (and therefore resolveChatWrapper/"auto" when it falls back to it) does not reproduce the part of the chat template's generation prompt that comes after the assistant role header. Any static text the template emits between the role header and the model's content -- most importantly a pre-filled reasoning tag like <think> is silently dropped.

Expected Behavior

If a model's jinja template explicitly emits <think>, the JinjaTemplateChatWrapper class should respect that.

Actual Behavior

The <think> tag is stripped out, inadvertently breaking the expected reasoning behavior of the model.

Steps to reproduce

The bug can be reproduced like this:

import { JinjaTemplateChatWrapper } from "node-llama-cpp";

// A chat template that pre-fills "<think>\n" in its generation prompt,
// as several reasoning-model GGUF templates do:
const template =
  "{%- for m in messages %}{{- '<|im_start|>' + m.role + '\\n' + m.content + '<|im_end|>\\n' }}{%- endfor %}" +
  "{%- if add_generation_prompt %}{{- '<|im_start|>assistant\\n<think>\\n' }}{%- endif %}";

const wrapper = new JinjaTemplateChatWrapper({ template });

const { contextText } = wrapper.generateContextState({
  chatHistory: [
    { type: "user", text: "Hi" },
    { type: "model", response: [] }, // i.e. generating the assistant turn now
  ],
  availableFunctions: {},
});

console.log(JSON.stringify(contextText.toString()));

Expected: "<|im_start|>user\nHi<|im_end|>\n<|im_start|>assistant\n<think>\n"

Actual: "<|im_start|>user\nHi<|im_end|>\n<|im_start|>assistant\n"

My Environment

Dependency Version
Operating System macOS 24.5.0 (arm64)
CPU M4
Node.js version 24.18.0
Typescript version 5.9.3
node-llama-cpp version 3.19.1

npx --yes node-llama-cpp inspect gpu output:

OS: macOS 24.5.0 (arm64)
Node: 24.18.0 (arm64)
TypeScript: 5.9.3

node-llama-cpp: 3.19.1
Prebuilt binaries: b10068
Cloned source: b10068

Metal: available

Metal device: Apple M4 Max
Metal used VRAM: 0% (464KB/96GB)
Metal free VRAM: 99.99% (96GB/96GB)
Metal unified memory: 96GB (100%)

CPU model: Apple M4 Max
Math cores: 12
Used RAM: 81.49% (104.32GB/128GB)
Free RAM: 18.5% (23.68GB/128GB)
Wired RAM: 2.91% (3.74GB/128GB)
Used swap: 0% (0B/0B)
Max swap size: dynamic
mmap: supported

Additional Context

Many reasoning GGUFs (various Qwen3 fine-tunes/merges) pre-fill <think> in their add_generation_prompt block and rely on it to start reasoning. Under JinjaTemplateChatWrapper they never receive the prefill and so never reason, even though the wrapper is meant to faithfully use the model's own template. The result is that these models, which are supposed to be reasoning models, can't do reasoning in node-llama-cpp. A good example of one is Qwen3.6-27B-Fable-Fusion which is currently #3 on HuggingFace.

Relevant Features Used

  • Metal support
  • CUDA support
  • Vulkan support
  • Grammar
  • Function calling

Are you willing to resolve this issue by submitting a Pull Request?

Yes, I have the time, and I know how to start.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingrequires triageRequires triaging

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions