Files
life-echo/api/tests/test_prompt_language_branching.py
Kevin ccdc4e4277 feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
  only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
  names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
  for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00

208 lines
6.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Prompt builders 在 language='en' 时返回纯 ASCIIlanguage='zh' 时含中文(防回归)。"""
from __future__ import annotations
from app.agents.chat.output_rules import (
chat_output_rules,
chat_output_rules_en,
chat_voice_style,
chat_voice_style_en,
)
from app.agents.chat.personas import AGENT_NAME_EN, AGENT_NAME_ZH, agent_name
from app.agents.chat.prompts_conversation import (
get_guided_conversation_prompt,
get_opening_prompt,
)
from app.agents.chat.prompts_profile import (
get_profile_followup_prompt,
get_profile_greeting_prompt,
)
from app.agents.memoir.prompts import (
get_creative_title_json_prompt,
get_memoir_fidelity_facts_only_prompt,
get_memoir_fidelity_system_prompt,
get_narrative_json_prompt,
)
from app.agents.stage_constants import (
chapter_category_display,
stage_display_name,
)
def _has_cjk(s: str) -> bool:
return any("\u4e00" <= ch <= "\u9fff" for ch in s)
# ── chat output rules ────────────────────────────────────────────────
def test_chat_output_rules_en_has_no_cjk() -> None:
txt = chat_output_rules_en()
assert txt.strip()
assert not _has_cjk(txt)
def test_chat_voice_style_en_has_no_cjk() -> None:
txt = chat_voice_style_en()
assert txt.strip()
assert not _has_cjk(txt)
def test_chat_output_rules_zh_unchanged_has_cjk() -> None:
assert _has_cjk(chat_output_rules())
def test_chat_voice_style_zh_unchanged_has_cjk() -> None:
assert _has_cjk(chat_voice_style())
# ── memoir narrative ────────────────────────────────────────────────
def test_memoir_fidelity_facts_only_en_branch_has_no_cjk() -> None:
assert not _has_cjk(get_memoir_fidelity_facts_only_prompt(language="en"))
def test_memoir_fidelity_facts_only_zh_default_has_cjk() -> None:
assert _has_cjk(get_memoir_fidelity_facts_only_prompt())
def test_memoir_fidelity_system_en_branch_has_no_cjk() -> None:
assert not _has_cjk(get_memoir_fidelity_system_prompt(language="en"))
def test_get_narrative_json_prompt_en_has_no_cjk() -> None:
out = get_narrative_json_prompt(
stage="childhood",
slots={"place": "the village"},
new_content="[User's oral memory this turn]\nI grew up by the river.",
language="en",
)
assert not _has_cjk(out)
assert "paragraphs" in out
def test_get_narrative_json_prompt_zh_default_has_cjk() -> None:
out = get_narrative_json_prompt(
stage="childhood",
slots={"place": "村里"},
new_content="【本段用户口述】\n我小时候住在河边。",
)
assert _has_cjk(out)
# ── memoir title ────────────────────────────────────────────────────
def test_creative_title_json_prompt_en_has_no_cjk() -> None:
out = get_creative_title_json_prompt(
stage="childhood",
emotion="warm",
slots={"place": "the river"},
language="en",
)
assert not _has_cjk(out)
assert "title" in out
def test_creative_title_json_prompt_zh_default_has_cjk() -> None:
out = get_creative_title_json_prompt(
stage="childhood",
emotion="warm",
slots={"place": "河边"},
)
assert _has_cjk(out)
# ── stage / category display helpers ────────────────────────────────
def test_stage_display_name_branches() -> None:
assert stage_display_name("childhood", language="en") == "Childhood"
assert stage_display_name("childhood", language="zh") == "童年时光"
# unknown stages pass through
assert stage_display_name("unknown", language="en") == "unknown"
def test_chapter_category_display_branches() -> None:
assert chapter_category_display("childhood", language="en") == "Childhood & Early Years"
assert chapter_category_display("childhood", language="zh") == "童年与成长背景"
# ── agent brand name (interviewer identity) ─────────────────────────
def test_agent_name_constants_aligned_with_brand() -> None:
"""中英品牌名是单一来源prompt / UI / 兜底标题统一引用此处。"""
assert AGENT_NAME_ZH == "岁月知己"
assert AGENT_NAME_EN == "Life Echo"
def test_agent_name_helper_handles_inputs() -> None:
assert agent_name("zh") == "岁月知己"
assert agent_name("en") == "Life Echo"
assert agent_name("EN") == "Life Echo"
assert agent_name(" en ") == "Life Echo"
assert agent_name(None) == "岁月知己" # type: ignore[arg-type]
assert agent_name("ja") == "岁月知己"
def test_profile_greeting_prompt_introduces_life_echo_in_en() -> None:
en = get_profile_greeting_prompt(
["birth_year", "occupation"], nickname="Sam", language="en"
)
assert "Life Echo" in en
assert "岁月知己" not in en
def test_profile_greeting_prompt_keeps_chinese_brand_in_zh() -> None:
zh = get_profile_greeting_prompt(["birth_year"], nickname="老王")
assert "岁月知己" in zh
assert "Life Echo" not in zh
def test_profile_followup_prompt_introduces_life_echo_in_en() -> None:
en = get_profile_followup_prompt(
missing_fields=["occupation"],
filled_fields={"birth_year": "1990"},
language="en",
)
assert "Life Echo" in en
assert "岁月知己" not in en
def test_profile_followup_prompt_full_basics_branch_introduces_life_echo() -> None:
en = get_profile_followup_prompt(
missing_fields=[],
filled_fields={
"birth_year": "1990",
"birth_place": "Boston",
"grew_up_place": "Boston",
"occupation": "engineer",
},
language="en",
)
assert "Life Echo" in en
assert "岁月知己" not in en
def test_opening_prompt_introduces_life_echo_in_en() -> None:
out = get_opening_prompt(
current_stage="childhood",
empty_slots_readable=["place", "people"],
language="en",
)
assert "Life Echo" in out
assert "岁月知己" not in out
def test_guided_conversation_prompt_introduces_life_echo_in_en() -> None:
out = get_guided_conversation_prompt(
current_stage="childhood",
empty_slots=["place"],
filled_slots={},
language="en",
)
assert "Life Echo" in out
assert "岁月知己" not in out