Skip to content

兼容性说明:ena-planner 在本地 Qwen3.5/Qwen3.6 模型下可能与 Jinja chat template 冲突 #46

@superfat1988

Description

@superfat1988

兼容性说明:ena-planner 在本地 Qwen3.5/Qwen3.6 模型下可能与 Jinja chat template 冲突

问题类型

兼容性说明 / 已知限制 / workaround 建议

背景

SillyTavern + LittleWhiteBoxena-planner 中,使用本地部署Qwen3.5 / Qwen3.6 系列模型时,如果模型采用较严格的内置或自定义 Jinja chat template,剧情规划请求可能失败。

这里更像是消息编排策略与部分模板约束不兼容,不一定属于严格意义上的插件 bug,所以发这个 issue 主要是为了补充兼容性信息,减少后续用户排查成本。

我这边的实际环境

  • SillyTavern: 1.17.0
  • 插件: LittleWhiteBox
  • 触发模块: modules/ena-planner/ena-planner.js
  • 模型侧: 本地部署的 Qwen3.5 / Qwen3.6 系列
  • 其中 Qwen3.5 HERETIC 已确认会触发
  • 推测相同限制也会影响采用同类严格模板约束的 Qwen3.6 本地部署方案

复现现象

触发剧情规划时,可能出现类似报错:

System message must be at the beginning

以及:

Assistant response prefill is incompatible with enable_thinking

排查结论

定位后发现,ena-planner.js 默认的消息构造方式大致是:

  1. 将多个信息块分别作为多条 role: system 发送,例如:
    • Ena system prompts
    • character card
    • worldbook
    • story outline
    • recent chat
    • story summary
    • plots
    • extra user blocks
  2. 在尾部再附加 role: assistant 的预填充内容(assistant prefill)

而部分本地部署的 Qwen3.5 / Qwen3.6 模板有更严格的格式要求,例如:

  • 只允许最前面有一条 system
  • 开启 thinking / reasoning 时,不接受 assistant prefill

因此两边会发生兼容性冲突。

我这边已经验证可行的 workaround

我本地直接修改 ena-planner.js 后,问题已解决,剧情规划可正常执行。

workaround 的核心是:

  1. 把所有 system 相关内容折叠为单条 system message
  2. 不再发送尾部 assistant prefill
  3. 如果仍需要保留原本 assistant block 的提示信息,则并入单条 system 中,例如作为 【assistant-guidance】 文本块

我本地补丁的关键改动思路

原逻辑:

  • 多次 messages.push({ role: 'system', ... })
  • 末尾 messages.push({ role: 'assistant', ... })

改为:

  • 先把所有系统信息收集到 systemParts[]
  • messages.push({ role: 'system', content: systemParts.join('\n\n') })
  • 用户输入仍保持为单独 role: 'user'
  • assistant block 改为并入 system 文本,不再作为独立 assistant message 发送

可直接参考的差异摘要

 const messages = [];
+const systemParts = [];
 
 for (const b of enaSystemBlocks) {
   const content = await renderTemplateAll(b.content, env, messageVars);
-  messages.push({ role: 'system', content });
+  if (String(content).trim()) systemParts.push(content);
 }
 
-if (String(charBlock).trim()) messages.push({ role: 'system', content: charBlock });
-if (String(worldbook).trim()) messages.push({ role: 'system', content: worldbook });
+if (String(charBlock).trim()) systemParts.push(charBlock);
+if (String(worldbook).trim()) systemParts.push(worldbook);
 
 // ... 其他 system 内容同样并入 systemParts
 
+for (const b of enaAssistantBlocks) {
+  const content = await renderTemplateAll(b.content, env, messageVars);
+  if (String(content).trim()) systemParts.push(`【assistant-guidance】\n${content}`);
+}
+
+if (systemParts.length) {
+  messages.push({ role: 'system', content: systemParts.join('\n\n') });
+}
+
 const userMsgContent = `以下是玩家的最新指令哦~:\n[${userInput}]`;
 messages.push({ role: 'user', content: userMsgContent });
-
-for (const b of enaAssistantBlocks) {
-  const content = await renderTemplateAll(b.content, env, messageVars);
-  messages.push({ role: 'assistant', content });
-}

对作者可能有帮助的具体建议

为了减少后续兼容性问题,是否可以考虑在 ena-planner 增加一个可选兼容模式,例如:

方案 A:strict single-system mode

把所有规划上下文统一压成单条 system

方案 B:disable assistant prefill for reasoning models

检测到某些本地模型 / 模板配置时,不发送 assistant prefill。

方案 C:加开关让用户自己选

例如在设置里提供类似选项:

  • Use single system message
  • Disable assistant prefill

这样既不强制改变当前默认行为,也能让本地模型用户快速绕开冲突。

为什么我认为这条 issue 值得记录

这个问题不是单纯的“本地模型不好用”,而是:

  • 插件当前的消息编排方式
  • 与部分本地 Qwen 模型模板的严格约束

在边界上发生了可复现冲突。

所以把它记录成兼容性说明 + workaround,可以显著减少作者和后续用户的重复排查成本。

谢谢。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions