Skip to content

# [Bug] 记忆内容含 {{hl|}} 等模板语法时整轮失败:malformed prompt variable reference "{{hl|}}" in context "memory" #40

Closed

Description

@x12xx-CNp

环境

  • DSH: 0.1.0-rc.7(Web GUI,Windows)
  • 插件: @modusensus/dsh-mneme 0.7.2(npm 最新 0.7.3 同样存在,代码未变)
  • 相关核心包: @deepseek-ai/dsh-system-prompt 0.1.0-rc.8

问题描述

当注入的记忆(context memory / user-settings)内容中包含任何 {{...}} 形式的文本时,整轮对话直接失败,而不是正常降级。

实际报错(来自记忆库 llm_audit_logs / 运行日志):

本轮运行失败 malformed prompt variable reference "{{hl|}}" in context "memory" (variable names match /^[a-z][a-z0-9_]*$/)

触发场景:用户消息/记忆里包含 MediaWiki 模板语法(如灰机 wiki 的 {{hl|}}、{{黑幕}}),或中文占位符(如学习平台知识点里的 {{关键词}}、{{挖空}})——这些都是合法业务文本,但一旦被 mneme 记忆捕获并注入系统上下文,整个回合就崩了。

复现步骤

  1. 安装并启用 mneme(默认配置,autoInject: true)。
  2. 让模型记住一条含模板语法的内容,例如用户指示"你应该学习使用 {{hl|}} 这个模板"(或任何含 {{...}} 的文本),使其进入记忆库。
  3. 在下一轮对话中触发系统上下文渲染(注入 "memory" context)。
  4. 整轮运行失败,报 malformed prompt variable reference ... in context "memory"。

根因分析

变量识别的校验不在 mneme 里,而在 DSH 核心 dsh-system-prompt 的 interpolate()(lib/index.js):

const VARIABLE_NAME = /^[a-z][a-z0-9_]*$/;
const GROUP_AT = /^\{\{([^{}]*)\}\}/;

// 第 118 行:任何 {{...}} 组都会先校验变量名,不合法直接 throw
if (!VARIABLE_NAME.test(name)) throw new Error(
  `malformed prompt variable reference "{{${name}}}" in ${kind} "${input.name}" (variable names match ${String(VARIABLE_NAME)})`
);

即:任何注入 context 中出现的 {{...}} 都会被当成 prompt 变量引用做严格校验(名字必须匹配 /^[a-z][a-z0-9_]*$/,且必须是已注册变量 provider/model/cwd 等)。{{hl|}} 因含 |、{{挖空}} 因含非 ASCII 字符,都会触发 throw。

而 mneme 的注入(lib/inject.js)通过 ctx.systemPrompt.context({ name: "memory" | "user-settings", ... }) 原样透传记忆/画像/规则文本,没有任何转义或降级处理。记忆内容属于不可控的用户/助手文本,天然可能包含 {{...}},于是任意一条这样的记忆都会让后续所有回合失败。

影响面:不只是 {{hl|}}——任何含 {{...}} 的记忆(模板语法、中文标记、代码示例、正则文本等)都会触发;且错误是 throw 而非降级,renderPrompt/renderContextSnapshot 直接中断。

期望行为

  • mneme 侧:注入系统上下文的记忆/画像/规则文本应当始终按字面渲染,不被当作变量识别;含 {{...}} 的内容不应导致运行失败。
  • DSH 侧(上游):dsh-system-prompt 的 interpolate() 对"看起来不像合法变量名"的 {{...}}(无法通过 VARIABLE_NAME)应当按字面文本处理而非 throw,只有合法变量名但未注册/无值才报错——这样任何插件的动态文本都不会被模板语法误伤。

建议修复

  1. mneme(推荐,已本地验证):在注入边界转义花括号——{{ → \{\{,}} → \}\}。转义后 \{\{/\}\} 不含 {{/}} 子串,interpolate 不再扫描到,内容保持可读且幂等(不会二次转义)。可加配置开关(如 escapePromptVariables,默认开)。
  2. 上游 dsh-system-prompt:区分"非法变量名"与"合法但未知变量":VARIABLE_NAME 不匹配的 {{...}} 视为字面文本(不 throw),仅对匹配变量名但未注册/无值的情况报错。这样对所有插件一劳永逸。

本地临时补丁(供参考)

已对 mneme lib/inject.js(及 src/、.ignored/ 副本)打补丁:新增 escapeVars() 并应用于记忆块 render()、短期上下文 renderHotContext()、用户设置 renderUserSettings() 三处输出。验证:语法检查通过,使用 DSH 真实 interpolate 逻辑复测,{{hl|}}/{{挖空}}/{{关键词}} 均正常渲染不再抛错。注意:修改 node_modules 下的文件会被插件升级覆盖,建议在本仓库正式修复。

Activity

  1. Anans-Amarre commented on Aug 30, 2026

    @Anans-Amarre
    Collaborator

    感谢非常详细的报告和根因分析!这个 bug 已确认,很有价值。

    复现确认:我核实了 lib/inject.js,renderUserSettings()(用户画像/规则)和 memory 块的 render() / renderHotContext() 输出确实是原样透传到 ctx.systemPrompt.context({ name: "memory" | "user-settings", text }),没有任何花括号转义。配合 autoInject: true,任何含 {{...}} 的记忆/规则文本都会在 DSH 核心 interpolate() 里被当成 prompt 变量校验,一旦名字不匹配 /^[a-z][a-z0-9_]*$/ 就 throw——不只是 {{hl|}},中文标记、代码示例、正则文本都会中招,而且是整轮直接失败而非降级。

    修复方案(与你第 1 条建议一致,推荐):

    • mneme 侧在注入边界做花括号转义:{{ → \{\{、}} → \}\}
    • 覆盖三处输出:memory 块 render()、短期上下文 renderHotContext()、用户设置 renderUserSettings()
    • 加配置开关 escapePromptVariables(默认开),极少数需要字面渲染模板的场景可关闭
    • 转义后内容保持可读、幂等(不会二次转义),interpolate() 不再扫描到,无需动上游

    关于上游 dsh-system-prompt 的修复(建议 2):认同"非法变量名按字面处理、仅合法但未注册变量才报错"的思路,但这属于核心包改动、影响所有插件,需要谨慎设计(要避免静默吞掉真正配置错误的情形)。mneme 侧先做转义兜底,上游改进作为后续跟进项。

    这个 bug 优先级高(任意含 {{...}} 的业务文本就能让整个回合崩溃),我会尽快安排修复并发布。感谢报告,尤其是那个本地补丁的验证思路,非常专业!

  2. added 2 commits that reference this issue on Aug 31, 2026
  3. Anans-Amarre commented on Aug 31, 2026

    @Anans-Amarre
    Collaborator

    已在 v0.7.4 修复并发布 ✅:注入边界改为 run-based 花括号转义(连续 2+ 个花括号之间插 \\,{{a}}→{\\{a\\}}),奇数连续如 {{{a}}} 也不会残留字面 {{——interpolate() 不再扫到非法变量名。覆盖 memory 块 / 短期上下文 / 用户设置三处注入出口,并新增配置 escapePromptVariables(默认开,关闭时原样透传)。已加回归测试(含 {{{a}}} 奇数括号场景,全套 782 全绿)。请升级 @modusensus/[email protected] 验证,感谢你的详细根因分析和本地补丁思路!

  4. x12xx-CNp commented on Sep 4, 2026

    @x12xx-CNp
    Author

    感谢!

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions