"""面向体验的回归测试:保护「聊得下去」与回忆录文笔两个核心目标。 与 test_interview_prompts 不同,这组测试不绑定已删除的启发式分档; 访谈侧仅验证 prompt 仍包含关键行为指引。 """ from app.agents.chat.interview_turn_plan import plan_interview_turn from app.agents.chat.output_rules import chat_output_rules from app.agents.chat.prompts_conversation import ( get_guided_conversation_prompt, get_opening_prompt, ) from app.agents.memoir.prompts import ( get_creative_title_json_prompt, get_narrative_editor_system_prompt, get_narrative_json_prompt, ) from app.features.memoir import story_pipeline_sync as sps class TestChatExperienceRegressions: """保护「聊得下去」体验。""" def test_guided_prompt_encourages_flexible_followup(self) -> None: """模型自主判断追问 vs 承接,不应再出现「本轮判定」硬分支文案。""" p = get_guided_conversation_prompt( current_stage="childhood", empty_slots=["place", "people"], filled_slots={}, detected_user_stage="childhood", user_profile_context="", persona="default", ) assert "本轮追问判定" not in p assert "你自己判断" in p or "该追问" in p def test_guided_prompt_topic_switch_not_hardcoded_in_prompt(self) -> None: p = get_guided_conversation_prompt( current_stage="childhood", empty_slots=["place", "people", "emotion"], filled_slots={"daily_life": "放学后去河边玩"}, detected_user_stage="childhood", user_profile_context="", persona="default", ) assert "聊得差不多了" not in p def test_guided_prompt_intro_mentions_connect_first(self) -> None: p = get_guided_conversation_prompt( current_stage="childhood", empty_slots=["place"], filled_slots={}, detected_user_stage="childhood", user_profile_context="", persona="default", ) assert "接住" in p def test_guided_prompt_bans_assistant_autobiographical_claims(self) -> None: """避免助手把用户经历说成自己的童年/暗恋等(身份越界)。""" p = get_guided_conversation_prompt( current_stage="education", empty_slots=["school"], filled_slots={}, detected_user_stage="education", user_profile_context="", persona="default", ) assert "身份边界" in p assert "暗恋" in p assert "共同回忆" in p rules = chat_output_rules() assert "助手本人" in rules assert "共同回忆" in rules def test_opening_prompt_stays_short_task_shape(self) -> None: p = get_opening_prompt( current_stage="childhood", empty_slots_readable=["成长的地方"], user_profile_context="", persona="default", ) assert "问候" in p assert "任务" in p or "具体问题" in p def test_followup_her_story_turn_plan_stays_user_perspective_shape(self) -> None: """追问第三方故事时:编排仍锁用户视角,并倾向「承接+一问」形状(防助手自传漂移)。""" p = plan_interview_turn( current_stage="education", empty_slots=["school", "city"], normalized_user_message="讲讲她的故事吧,后来怎么样了?", memory_evidence_text="", stage_switched_this_turn=False, ) assert p.subject_owner == "user_only" assert p.forbid_first_person_experience is True assert p.reply_shape == "ack_then_question" class TestMemoirStyleRegressions: """保护「回忆录有文笔」体验。""" def test_title_prompt_allows_literary_expression(self) -> None: prompt = get_creative_title_json_prompt( stage="childhood", emotion="warm", slots={"place": "湖南老家", "turning_event": "爷爷背我过河"}, ) assert "禁止虚构" in prompt assert "平实" not in prompt.lower() def test_title_prompt_uses_facts_only_not_plain(self) -> None: prompt = get_creative_title_json_prompt( stage="childhood", emotion="warm", slots={"place": "老家"}, ) assert "优雅" in prompt or "书面语" in prompt or "文采" in prompt def test_narrative_prompt_encourages_literary_quality(self) -> None: sys_prompt = get_narrative_editor_system_prompt() assert "温度" in sys_prompt or "优雅" in sys_prompt assert "画面感" in sys_prompt or "生动" in sys_prompt def test_narrative_json_prompt_allows_emotion_rendering(self) -> None: prompt = get_narrative_json_prompt( stage="childhood", slots={"turning_event": "爷爷背我过河"}, new_content="【本段用户口述】\n那年下大雨,爷爷背我过河,鞋全湿了,他一直笑。", ) assert "文采服务于真实" in prompt or "虚构描写" in prompt def test_merge_shrink_only_on_extreme_loss(self) -> None: existing = "这是一段已有的故事正文,讲述了童年在河边的回忆。" * 20 assert len(existing) > 400 half_content = existing[: len(existing) // 2] import json raw = json.dumps( {"paragraphs": [{"content": half_content}]}, ensure_ascii=False ) out, ft = sps._apply_narrative_fallbacks( raw, "新的口述补充", existing, chapter_category="childhood" ) assert ft == "none"