Skip to content

Commit c6944fd

Browse files
wehosHongzhi Wenclaude
authored
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/memory_server.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@
6060
CHAT_HOLIDAY_CONTEXT,
6161
MEMORY_RECALL_HEADER, MEMORY_RESULTS_HEADER,
6262
PERSONA_HEADER, INNER_THOUGHTS_DYNAMIC,
63+
)
64+
# Negative-intent prompts/scanner 已迁到 ``prompts_directives``(与 ban-topic
65+
# regex 同源——同是"用户负面 / 回避指令"的语义层)。``prompts_memory`` 保留
66+
# fact/persona/reflection/summary 等纯 memory-业务 prompt。
67+
from config.prompts.prompts_directives import (
6368
get_negative_target_check_prompt,
6469
scan_negative_keywords,
6570
)

0 commit comments

Comments
 (0)