fix(engine): 非中/西语轮注入 other 语言提醒——英文漂中文镜像修复(Task 11 对向)

历史护栏: 中文/西语会话纯拉丁轮不注入(护住 Task 11 场景);wall other 为
可选变体(存量快照/auto/生成器回退 DEFAULT_WALL,引擎部署即全租户生效)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-11-03 10:01:23 +08:00
parent 9b2a1ae5d0
commit 77f90d951d
5 changed files with 104 additions and 13 deletions
+19 -5
View File
@@ -75,9 +75,13 @@ _CJK_RE = re.compile(r"[一-鿿]")
_ES_FEATURE_RE = re.compile(r"[áéíóúñãç¿¡]")
LANG_NOTE_ZH = DEFAULT_WALL.reminders["language_follow"]["zh"]
LANG_NOTE_ES = DEFAULT_WALL.reminders["language_follow"]["es"]
# 2026-11-03 镜像修复(Task 11 的对向): 非中/西语方向的当轮语言强制。
# DEFAULT_WALL(jiancai 出厂墙)持有文案;租户墙无 other 键时运行期回退此常量。
LANG_NOTE_OTHER = DEFAULT_WALL.reminders["language_follow"]["other"]
def _lang_note_for(msg: str, wall: CompiledWall | None = None) -> str:
def _lang_note_for(msg: str, history: list[dict] | None = None,
wall: CompiledWall | None = None) -> str:
# C-1(终审): wall=None = 模块级默认墙(LANG_NOTE_* 常量即该默认视图,
# 旧测试锚定);manager 一律传租户墙
w = wall if wall is not None else DEFAULT_WALL
@@ -85,7 +89,16 @@ def _lang_note_for(msg: str, wall: CompiledWall | None = None) -> str:
return w.reminders["language_follow"]["zh"]
if _ES_FEATURE_RE.search(msg or ""):
return w.reminders["language_follow"]["es"]
return ""
# 历史护栏: 中文/西语会话中的纯拉丁轮(DN800/ok/型号)维持无注入,
# 历史语言自然延续——无条件注入 other 会把 Task 11 修好的中文规格
# 混排场景反向掰成英文。护栏只看客户消息(role=user)。
for m in history or []:
if m.get("role") == "user" and (
_CJK_RE.search(m.get("content") or "")
or _ES_FEATURE_RE.search(m.get("content") or "")):
return ""
note = w.reminders["language_follow"].get("other")
return note if note else LANG_NOTE_OTHER
def _cc_lang_of(msg: str) -> str:
@@ -1205,9 +1218,10 @@ class ConversationManager:
contact_note = w.reminders["contact_ask"]["web"].format(
no_contact_web=w.no_contact["web"])
# 语言跟随提醒(Task 11): 与 boundary/contact 提醒同挂载位——
# 首条消息画像为空也要生效(中文客户开场即漂英文是实测重灾区)
lang_note = _lang_note_for(latest_msg, wall=wall)
# 语言跟随提醒(Task 11 + 2026-11-03 other 方向): 与 boundary/
# contact 提醒同挂载位——首条消息画像为空也要生效(中文客户开场
# 即漂英文是实测重灾区;英文轮漂中文为其镜像)
lang_note = _lang_note_for(latest_msg, history=history, wall=wall)
if not raw_info:
# 空画像:仅返回边界/联系/语言提醒(可能为空串)
+20 -6
View File
@@ -61,6 +61,14 @@ _REMINDER_VARIANTS = {
"cc_guide": ("echo", "ask"),
"email_light": ("en", "zh"),
}
# 可选变体键(在场则同规校验,缺省合法): language_follow.other = 非中/西语
# 方向的当轮语言强制(2026-11-03 英文漂中文修复——此前该方向无任何当轮
# 提醒,中文语境+短英文查询整轮漂中文)。存量墙(auto seed/生成器产出/
# 生产 DB 快照)无此键,运行期由 _lang_note_for 回退 DEFAULT_WALL 文案;
# 绝不能并入必填——否则新代码编译旧快照即炸全部租户运行时。
_OPTIONAL_REMINDER_VARIANTS: dict[str, tuple[str, ...]] = {
"language_follow": ("other",),
}
_CC_TEMPLATE_VARIANTS = {
"ask": ("en", "zh", "es"),
"wall": ("en", "zh", "es"),
@@ -152,24 +160,29 @@ def _validate_template(template: str, label: str) -> None:
def _str_mapping(sub, label: str, required: tuple[str, ...],
*, template: bool = False) -> dict[str, str]:
*, template: bool = False,
optional: tuple[str, ...] = ()) -> dict[str, str]:
"""一层 str 映射校验(no_contact 各键/reminders 各类变体/cc_templates 各组)。
template=True 时额外干跑 .format() 校验占位符(conversation.py 运行期
回填的字段才需要;no_contact 为纯文本,不校验)。"""
回填的字段才需要;no_contact 为纯文本,不校验)。optional 键缺省合法、
在场则与必填同规校验(_OPTIONAL_REMINDER_VARIANTS 消费)。"""
if not isinstance(sub, dict):
raise _err(f"{label}: 必须为映射(缺类别或类型错误)")
legal = tuple(required) + tuple(optional)
out: dict[str, str] = {}
for key in required:
for key in legal:
val = sub.get(key)
if key in optional and val is None:
continue # 可选键缺省合法
if not isinstance(val, str) or not val:
raise _err(f"{label}.{key}: 缺非空字符串")
if template:
_validate_template(val, f"{label}.{key}")
out[key] = val
for key in sub:
if key not in required:
raise _err(f"{label}.{key}: 未知变体键(合法: {', '.join(required)})")
if key not in legal:
raise _err(f"{label}.{key}: 未知变体键(合法: {', '.join(legal)})")
return out
@@ -199,7 +212,8 @@ def compile_wall(cfg: dict) -> CompiledWall:
if not isinstance(cfg["reminders"], dict):
raise _err("reminders: 必须为映射")
reminders = {cat: _str_mapping(cfg["reminders"].get(cat), f"reminders.{cat}",
variants, template=True)
variants, template=True,
optional=_OPTIONAL_REMINDER_VARIANTS.get(cat, ()))
for cat, variants in _REMINDER_VARIANTS.items()}
extra = set(cfg["reminders"]) - set(_REMINDER_VARIANTS)
if extra: