Skip to content

Commit 65a799c

Browse files
wehosHongzhi Wenclaude
authored
refactor(prompts-chara): tighten wording + handle in-place L10N drift (Project-N-E-K-O#1116)
Wording / consistency fixes in `_L10N` and the prompt template: - `客气` → `客套`(zh / zh-TW / ja `遠慮は不要` → `他人行儀は不要`);en/ko/ru 原本就是 formality 语义,无需改动 - en `char_setting` 补成双形 `settings/character setting`,与其它 locale 对齐 - Avatar Overlay / Memory Integrity 从第二人称改回第三人称,整段保持口吻一致 - 修 `shares an screen capture` → `shares a screen capture` typo Normalizer 之前的 `_normalize_default_prompt_text` 只能吃下整行新增 / 整行 删除,无法处理 in-place 字句修改。今天这批改动里有 5 处都是就地改字 (`无需客气` / `無需客氣` / `遠慮は不要` / `"character setting"` / `an screen capture`), 任意一处不兜住,所有今天之前装过 Xiao8 的用户其 stored prompt 在比对时 都会被判非默认 → `is_default_prompt` 返 False → 切语言时不再自动本地化。 引入 `legacy_text_replacements` 元组,比较前先把旧字串规范化到当前字串。 后续再做 wording tweak,往该元组加一行即可。 顺手把 `added_context_lines` 同步到 Memory Integrity 新文案,并保留旧文案 以兼容上一版 stored prompt。 测试加了两条覆盖 in-place wording drift 与 typo 兜底,全 7/7 通过。 Co-authored-by: Hongzhi Wen <cartabio.coder1@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f6539e2 commit 65a799c

2 files changed

Lines changed: 64 additions & 15 deletions

File tree

config/prompts_chara.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,43 +12,43 @@
1212

1313
_L10N = {
1414
'zh': {
15-
'relationship': '{MASTER_NAME}是{LANLAN_NAME}的亲人,{LANLAN_NAME}与{MASTER_NAME}之间无需客气。',
15+
'relationship': '{MASTER_NAME}是{LANLAN_NAME}的亲人,{LANLAN_NAME}与{MASTER_NAME}之间无需客套。',
1616
'language_style': '可以根据需要使用中文、English或日本語等多种语言,但一定是简洁的口语化表达。',
1717
'no_servitude': '不要询问"我可以为你做什么",除非对方主动提出。禁止反复询问"有什么好玩的/新鲜事儿可以和我聊聊/说说"这类话。',
1818
'no_repetition': '不要重复已经说过的片段。语言一定要简洁。',
1919
'char_setting': '设定/人设',
2020
},
2121
'zh-TW': {
22-
'relationship': '{MASTER_NAME}是{LANLAN_NAME}的親人,{LANLAN_NAME}與{MASTER_NAME}之間無需客氣。',
22+
'relationship': '{MASTER_NAME}是{LANLAN_NAME}的親人,{LANLAN_NAME}與{MASTER_NAME}之間無需客套。',
2323
'language_style': '可以根據需要使用中文、English或日本語等多種語言,但一定是簡潔的口語化表達。',
2424
'no_servitude': '不要詢問「我可以為你做什麼」,除非對方主動提出。禁止反覆詢問「有什麼好玩的/新鮮事兒可以和我聊聊/說說」這類話。',
2525
'no_repetition': '不要重複已經說過的片段。語言一定要簡潔。',
2626
'char_setting': '設定/人設',
2727
},
2828
'en': {
2929
'relationship': '{MASTER_NAME} is {LANLAN_NAME}\'s close family. There is no need for formality between {LANLAN_NAME} and {MASTER_NAME}.',
30-
'language_style': 'May use multiple languages as needed, including English, 中文, 日本語, etc., but always in concise colloquial expressions.',
30+
'language_style': 'May use multiple languages as needed, including English, 日本語, etc., but always in concise colloquial expressions.',
3131
'no_servitude': 'Do not ask "what can I do for you" unless the other party brings it up first. Never repeatedly ask things like "anything fun/new to chat about".',
3232
'no_repetition': 'Do not repeat what has already been said. Language must be concise.',
33-
'char_setting': 'character setting',
33+
'char_setting': 'settings/character setting',
3434
},
3535
'ja': {
36-
'relationship': '{MASTER_NAME}は{LANLAN_NAME}の身近な家族です。{LANLAN_NAME}と{MASTER_NAME}の間に遠慮は不要です。',
37-
'language_style': '必要に応じて日本語、中文、Englishなど複数の言語を使えるが、必ず簡潔な口語表現で。',
36+
'relationship': '{MASTER_NAME}は{LANLAN_NAME}の身近な家族です。{LANLAN_NAME}と{MASTER_NAME}の間に他人行儀は不要です。',
37+
'language_style': '必要に応じて日本語、Englishなど複数の言語を使えるが、必ず簡潔な口語表現で。',
3838
'no_servitude': '相手から言い出さない限り「何かできることある?」と聞かないこと。「何か面白いこと/新しいこと話して」のような言葉を繰り返し聞くのは禁止。',
3939
'no_repetition': '既に話した内容を繰り返さないこと。言葉は必ず簡潔に。',
4040
'char_setting': '設定/キャラ設定',
4141
},
4242
'ko': {
4343
'relationship': '{MASTER_NAME}은(는) {LANLAN_NAME}의 가까운 가족입니다. {LANLAN_NAME}와(과) {MASTER_NAME} 사이에 격식은 필요 없습니다.',
44-
'language_style': '필요에 따라 한국어, 中文, English, 日本語 등 여러 언어를 사용할 수 있지만 반드시 간결한 구어체로.',
44+
'language_style': '필요에 따라 한국어, English, 日本語 등 여러 언어를 사용할 수 있지만 반드시 간결한 구어체로.',
4545
'no_servitude': '상대방이 먼저 말하지 않는 한 "뭐 도와줄까"라고 묻지 말 것. "재밌는 거/새로운 거 얘기해줘" 같은 말을 반복해서 묻는 것은 금지.',
4646
'no_repetition': '이미 말한 내용을 반복하지 말 것. 언어는 반드시 간결하게.',
4747
'char_setting': '설정/캐릭터 설정',
4848
},
4949
'ru': {
5050
'relationship': '{MASTER_NAME} — близкий родственник {LANLAN_NAME}. Между {LANLAN_NAME} и {MASTER_NAME} нет нужды в формальностях.',
51-
'language_style': 'Может использовать несколько языков по необходимости, включая русский, 中文, English, 日本語 и т.д., но всегда в лаконичной разговорной форме.',
51+
'language_style': 'Может использовать несколько языков по необходимости, включая русский, English, 日本語 и т.д., но всегда в лаконичной разговорной форме.',
5252
'no_servitude': 'Не спрашивать «чем могу помочь», если собеседник сам не попросит. Запрещено повторно спрашивать вроде «расскажи что-нибудь интересное/новенькое».',
5353
'no_repetition': 'Не повторять уже сказанное. Речь должна быть лаконичной.',
5454
'char_setting': 'настройки/образ персонажа',
@@ -74,9 +74,9 @@
7474
7575
<Context Awareness>
7676
- System Info: The system periodically sends some useful information to {LANLAN_NAME}. {LANLAN_NAME} can leverage this information to better understand the context.
77-
- Visual Info: If {MASTER_NAME} shares an screen capture/camera feed, react to it naturally. There may be a delay. {LANLAN_NAME} should NOT make ungrounded assumptions before seeing actual images. Visual information is a very important and useful source of conversation topics.
78-
- Avatar Overlay: If you see a small overlaid annotation on a screenshot reading something like "This is {LANLAN_NAME}'s virtual avatar on the desktop, ...", it's internal metadata marking your on-screen avatar position — ignore it, never repeat it, never bring it up.
79-
- Memory Integrity: Respect your memories about {MASTER_NAME}. NEVER fabricate facts about {MASTER_NAME} (e.g. hobbies, occupation, experiences, preferences). If you don't know or don't remember, just say so honestly instead of making things up.
77+
- Visual Info: If {MASTER_NAME} shares a screen capture/camera feed, react to it naturally. There may be a delay. {LANLAN_NAME} should NOT make ungrounded assumptions before seeing actual images. Visual information is a very important and useful source of conversation topics.
78+
- Avatar Overlay: If {LANLAN_NAME} sees a small overlaid annotation on a screenshot reading something like "This is {LANLAN_NAME}'s virtual avatar on the desktop, ...", it's internal metadata marking her on-screen avatar position — ignore it, never repeat it, never bring it up.
79+
- Memory Integrity: {LANLAN_NAME} should respect her memories about {MASTER_NAME} and NEVER fabricate information about {MASTER_NAME} (e.g. hobbies, occupation, experiences, preferences, past events, etc.). If {LANLAN_NAME} doesn't know or doesn't remember, just say so honestly instead of making things up.
8080
</Context Awareness>
8181
8282
<WARNING> {LANLAN_NAME} replies in CONCISE spoken language. </WARNING>
@@ -118,12 +118,14 @@ def _build_lanlan_prompt(lang: str) -> str:
118118

119119

120120
def _normalize_default_prompt_text(prompt_text: str) -> str:
121-
"""Normalize legacy default prompts so added/removed lines don't break matching.
121+
"""Normalize legacy default prompts so wording drift doesn't break matching.
122122
123-
Strategy: strip away lines that differ between old and new defaults so both
124-
versions reduce to the same canonical form.
123+
Three classes of drift are handled so old stored prompts still reduce to the
124+
same canonical form as the current default:
125125
- Removed lines (e.g. old ``- Skills:``): dropped from ``<Characteristics>``.
126126
- Added lines (e.g. new ``- Memory Integrity:``): dropped from ``<Context Awareness>``.
127+
- In-place wording edits (e.g. 无需客气 → 无需客套): rewritten via
128+
``legacy_text_replacements`` so old wording canonicalizes to current wording.
127129
"""
128130
allowed_characteristic_prefixes = (
129131
"- Identity:",
@@ -137,12 +139,30 @@ def _normalize_default_prompt_text(prompt_text: str) -> str:
137139
"- Skills: versatile, proactive and capable of using external tools when available.",
138140
"- Skills: versatile, proactive, and capable of using external tools when available.",
139141
}
142+
# In-place wording changes: (old_text, current_text). When wording in _L10N or
143+
# _LANLAN_PROMPT_TEMPLATE is edited in place (not added/removed as a whole line),
144+
# add an entry here so users with the previously-shipped default still match.
145+
legacy_text_replacements = (
146+
# zh / zh-TW Relationship: 客气 → 客套
147+
("无需客气", "无需客套"),
148+
("無需客氣", "無需客套"),
149+
# ja Relationship: 遠慮 → 他人行儀
150+
("遠慮は不要", "他人行儀は不要"),
151+
# en char_setting (used in <IMPORTANT> via {_char_setting})
152+
('"character setting"', '"settings/character setting"'),
153+
# template typo fix
154+
("shares an screen capture", "shares a screen capture"),
155+
)
140156
# Lines added in newer defaults that old stored prompts won't have.
141157
# We strip them during comparison so both old and new match.
142158
# Must be exact strings (not prefixes) to avoid stripping user-customised variants.
143159
added_context_lines = {
160+
# Memory Integrity — kept across rewrites so users with old stored prompts still match.
144161
"- Memory Integrity: Respect your memories about {MASTER_NAME}. NEVER fabricate facts about {MASTER_NAME} (e.g. hobbies, occupation, experiences, preferences). If you don't know or don't remember, just say so honestly instead of making things up.",
162+
"- Memory Integrity: {LANLAN_NAME} should respect her memories about {MASTER_NAME} and NEVER fabricate information about {MASTER_NAME} (e.g. hobbies, occupation, experiences, preferences, past events, etc.). If {LANLAN_NAME} doesn't know or doesn't remember, just say so honestly instead of making things up.",
163+
# Avatar Overlay — kept across rewrites so users with old stored prompts still match.
145164
"- Avatar Overlay: If you see a small overlaid annotation on a screenshot reading something like \"This is {LANLAN_NAME}'s virtual avatar on the desktop, ...\", it's internal metadata marking your on-screen avatar position — ignore it, never repeat it, never bring it up.",
165+
"- Avatar Overlay: If {LANLAN_NAME} sees a small overlaid annotation on a screenshot reading something like \"This is {LANLAN_NAME}'s virtual avatar on the desktop, ...\", it's internal metadata marking her on-screen avatar position — ignore it, never repeat it, never bring it up.",
146166
}
147167
normalized_lines = []
148168
in_characteristics = False
@@ -179,7 +199,10 @@ def _normalize_default_prompt_text(prompt_text: str) -> str:
179199
if in_context_awareness and stripped in added_context_lines:
180200
continue
181201
normalized_lines.append(line)
182-
return "\n".join(normalized_lines).strip()
202+
normalized = "\n".join(normalized_lines).strip()
203+
for old_text, new_text in legacy_text_replacements:
204+
normalized = normalized.replace(old_text, new_text)
205+
return normalized
183206

184207

185208
# ============================================================================

plugin/tests/unit/test_prompt_and_router_compat.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,32 @@ def test_is_default_prompt_rejects_custom_memory_integrity_line() -> None:
5353
assert is_default_prompt(customized_prompt) is False
5454

5555

56+
def test_is_default_prompt_accepts_legacy_in_place_wording() -> None:
57+
"""Old shipped defaults that differ only by in-place wording edits must still be
58+
classified as default — otherwise existing users lose auto-localization on
59+
every cosmetic prompt tweak."""
60+
cases = {
61+
"zh": ("无需客套", "无需客气"),
62+
"zh-TW": ("無需客套", "無需客氣"),
63+
"ja": ("他人行儀は不要", "遠慮は不要"),
64+
"en": ('"settings/character setting"', '"character setting"'),
65+
}
66+
for lang, (current, legacy) in cases.items():
67+
current_prompt = get_lanlan_prompt(lang)
68+
assert current in current_prompt, f"{lang}: current wording absent from template"
69+
legacy_prompt = current_prompt.replace(current, legacy)
70+
assert is_default_prompt(legacy_prompt) is True, f"{lang}: legacy wording not normalized"
71+
72+
73+
def test_is_default_prompt_accepts_legacy_screen_capture_typo() -> None:
74+
"""Old shipped defaults had ``an screen capture`` typo — fixed to ``a screen capture``.
75+
Users who stored the typo'd version should still be classified as default."""
76+
current_prompt = get_lanlan_prompt("zh")
77+
legacy_prompt = current_prompt.replace("a screen capture", "an screen capture")
78+
assert legacy_prompt != current_prompt
79+
assert is_default_prompt(legacy_prompt) is True
80+
81+
5682
def test_agent_router_exports_openclaw_availability_proxy() -> None:
5783
paths = {
5884
path

0 commit comments

Comments
 (0)