Commit c6944fd
refactor(prompts): centralize negative-intent + preserve user ban records in summary (Project-N-E-K-O#1318)
* refactor(prompts): centralize negative-intent in prompts_directives + preserve user ban records in summaries
迁移 + summary 指令两件事一起:
1. 把 prompts_memory.py 的 negative-intent 机制(NEGATIVE_KEYWORDS_I18N +
scan_negative_keywords + NEGATIVE_TARGET_CHECK_PROMPT + helper)整段搬到
prompts_directives.py。两者本是同一类输入("用户负面 / 回避指令")的
不同实现层(regex capture vs substring scan),同源同主题应该住一起。
行为零变化:scan_negative_keywords 接口、返回、词表完全保持不变,
evidence 系统的 _amaybe_trigger_negative_keyword_hook 调用路径不动。
import 改 app/memory_server.py 与 test_evidence_extraction.py。
2. 给 4 个 memory-compress / review prompt 加 [重要] 段:保留用户负面反馈
(祈使句"别再提 X / 不要做 Y"等),不得压缩、改写、合并、删除——下游
记忆系统据此避免再触雷。覆盖:
- RECENT_HISTORY_MANAGER_PROMPT × 7 locale
- DETAILED_RECENT_HISTORY_MANAGER_PROMPT × 7 locale
- FURTHER_SUMMARIZE_PROMPT × 7 locale
- HISTORY_REVIEW_PROMPT × 7 locale(review 路径专门防"误判冗余删除")
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(prompts_directives): normalize lang in scan_negative_keywords (coderabbit Major)
scan_negative_keywords 之前直接用 lang 查 NEGATIVE_KEYWORDS_I18N,"en-US"
/ "pt-BR" 这类完整 locale 找不到 key 会退回 zh 词表,结果用中文词表扫英文
消息——负面意图全漏。同模块其他 render 函数(render_directives_block /
render_recent_topics_block / render_regen_avoid_instruction)都通过
_norm_lang 归一化,搬迁时漏掉了,现在补齐。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test+fix: harden _norm_lang fallback + add region-locale regression test
写测试时发现 _norm_lang 依赖 ``config._runtime.normalize_language_code`` 的
运行时 resolver。在 pytest 直接跑(不经 ``install_runtime_bindings``)的环境
下,resolver 没绑定时**原样返回**输入 → "en-US" 仍是 "en-US" → lookup 漏 →
回退到 zh 词表(即我刚才修过的同款 bug 在 test env 下重现)。
修:_norm_lang 在 resolver 返回值仍带 region 后缀时手动 split("-" / "_") 兜底。
已是短码时 split 等于 no-op,零副作用。
测试同步补 ``test_negative_keywords_region_locale``(CodeRabbit nitpick):
覆盖 en-US / pt-BR 锁住归一化路径。繁体 zh-Hant 因 keyword 词表是简体("换"
vs "換" 不同字符),需要 OpenCC 才能命中,超本 PR scope 不覆盖。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(scan_negative_keywords): preserve 'unknown lang → zh' contract (codex P2)
上一版用 _norm_lang 做归一化,但 _norm_lang 服务于 i18n template rendering
策略——未知语言归 en(英文 lingua franca,模板渲染用英文兜底合理)。
scan_negative_keywords 的契约不同:docstring 写"unknown → zh"("识别不出
就当中文用户",因为中文用户基数大 / 中文关键词召回更广)。复用 _norm_lang
导致 lang='xyz' 被归到 en 词表,"别提了" 这类标准中文负面意图被漏掉。
修:scan_negative_keywords 不走 _norm_lang,只做最小本地 strip 剥 region
后缀(en-US → en / zh-CN → zh),未识别短码留给 ``.get(short, zh)`` 兜底。
两个不同策略各自走各自的路径——_norm_lang 留给 render 函数,本函数自己
管 zh-default 契约。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(scan_negative_keywords): strip + lower lang for robustness (coderabbit Minor)
NEGATIVE_KEYWORDS_I18N key 都是小写无空白;之前只做 region split,上游传
``EN-US`` / ``" en-US "`` 时 split 得到 ``EN`` / `` en``,dict miss → 错
落 zh 兜底,扫错语言。补 strip + lower 即可。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(prompts): use {master_name} placeholder in preserve-feedback clauses
之前插入的"保留负面反馈"指令用了 generic "用户/user/ユーザー/..." 等占位
词,跟同 prompt 里 ``%s`` 替换出来的 master_name 实名并列,会让 LLM 困惑
("小明 和 Neko 的对话"+"用户的负面反馈" 两个称呼指代不清)。也违反
feedback_no_dehumanizing_terms:统一用 master_name 实名,不留 generic
placeholder。
实现:clauses 里用 ``{master_name}`` 字面占位符(与 ``%s`` 错开,不冲突);
memory/recent.py 4 个 callsite 在 ``.replace("%s", ...)`` / ``% ...`` 之前补
一道 ``.replace("{master_name}", self.name_mapping['human'])``。覆盖:
- get_recent_history_manager_prompt(Stage-1 简摘)
- get_detailed_recent_history_manager_prompt(Stage-1 详摘)
- get_further_summarize_prompt(Stage-2 再压缩)
- get_history_review_prompt(Phase C review)
替换 72 个 generic-user 引用 → master_name placeholder(7 locale × 4 prompt
clauses 大致量级)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(prompts): rename {master_name} → {MASTER_NAME} for naming consistency
之前用了小写 ``{master_name}`` 占位符,但 codebase 里既有 prompt 全用大写
``{LANLAN_NAME}`` / ``{MASTER_NAME}`` 风格(见 FACT_EXTRACTION_PROMPT,
memory/facts.py:408 ``.replace('{MASTER_NAME}', ...)``)。两套大小写并存
易混且违背项目惯例——若有调用方混搭 .replace + .format 还会埋 KeyError。
统一到大写:
- config/prompts/prompts_memory.py: 4 prompt × 7 locale 的 preserve clauses
里的 72 个 ``{master_name}`` → ``{MASTER_NAME}``
- memory/recent.py: 4 个 callsite 的 ``.replace`` key 同步改大写
- tests/testbench/pipeline/memory_runner.py: 2 个 testbench callsite 之前
完全没做 ``.replace`` 替换(漏掉了),placeholder 字面会泄到 LLM;
补上 ``.replace("{MASTER_NAME}", name_mapping['human'])``。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(prompts): swap order — % formatting before {MASTER_NAME} replace (codex P2)
之前在所有 5 个 callsite 都把 .replace("{MASTER_NAME}", master_name) 放在
% formatting / .replace("%s", ...) **之前**。问题是 master_name 是
user-controlled,可能含 % 或 %s:
- 含 % → 注入后跑 % messages_text 会被当格式说明符炸 TypeError
例:"100%Real" → 后续 % messages_text 把 "%R" 当成无效 conversion
- 含 %s → 注入后被 .replace("%s", messages_text) 二次替换
例:"Test%sUser" → 变成 "Test{messages_text}User"
修复策略:先做模板自身的 %s 替换 / % formatting,把 user-controlled
master_name 注入**最后**做。.replace 不会解释 % 也不会触发 .format(),
单纯字面替换,绝对安全。
覆盖:
- memory/recent.py: compress_history(detailed + non-detailed 两支路)/
further_compress / review_history(4 个 callsite)
- tests/testbench/pipeline/memory_runner.py: 2 个 testbench callsite
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(prompts/ja): 「相処の詳細」→「やり取りの詳細」(coderabbit Minor, drive-by)
FACT_EXTRACTION_PROMPT importance=10 ja rubric 里的「相処」不是自然日语,
模型可能猜对意思但歧义会影响 ja locale 的提取稳定性。
不是本 PR 引入(commit 87746c9 时就在),但我已经在 prompts_memory.py
里改东西,drive-by 修一下。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Hongzhi Wen <cartabio.coder1@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent b6cbcbd commit c6944fd
6 files changed
Lines changed: 592 additions & 348 deletions
File tree
- app
- config/prompts
- memory
- tests
- testbench/pipeline
- unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
63 | 68 | | |
64 | 69 | | |
65 | 70 | | |
| |||
0 commit comments