"""reply_planner:JSON 合并与安全边界。""" import json from app.agents.chat.interview_turn_plan import InterviewTurnPlan from app.agents.chat.reply_planner import merge_reply_planner_json_into_turn_plan def _base_plan(**kwargs) -> InterviewTurnPlan: defaults = dict( mode="memoir_push", anchor_slot_key="place", anchor_slot_readable="成长的地方", anchor_snippet="河边", memory_usage="allowed_with_attribution", reply_shape="flexible", memory_reference_style="你之前提过", ) defaults.update(kwargs) return InterviewTurnPlan(**defaults) def test_merge_does_not_upgrade_memory_from_none_to_allowed(): plan = _base_plan(memory_usage="none") raw = json.dumps( { "memory_usage": "allowed_with_attribution", "reply_shape": "ack_then_question", "memory_reference_style": "你说过", "forbid_first_person_experience": True, } ) merged = merge_reply_planner_json_into_turn_plan(plan, raw) assert merged.memory_usage == "none" assert merged.reply_shape == "ack_then_question" assert merged.memory_reference_style == "你说过" def test_merge_allows_downgrade_memory_usage_to_none(): plan = _base_plan(memory_usage="allowed_with_attribution") raw = json.dumps({"memory_usage": "none"}) merged = merge_reply_planner_json_into_turn_plan(plan, raw) assert merged.memory_usage == "none" def test_merge_invalid_json_returns_original(): plan = _base_plan() merged = merge_reply_planner_json_into_turn_plan(plan, "not json") assert merged == plan def test_merge_ignores_non_dict_json(): plan = _base_plan() merged = merge_reply_planner_json_into_turn_plan(plan, "[1,2]") assert merged == plan def test_merge_trims_memory_reference_style(): plan = _base_plan() raw = json.dumps({"memory_reference_style": " 你刚讲到 "}) merged = merge_reply_planner_json_into_turn_plan(plan, raw) assert merged.memory_reference_style == "你刚讲到" def test_merge_sets_focus_and_summary(): plan = _base_plan() raw = json.dumps( { "primary_focus": "relationship", "focus_summary": "先接住用户提到的在场关系与面子压力", "forbid_first_person_experience": True, } ) merged = merge_reply_planner_json_into_turn_plan(plan, raw) assert merged.primary_focus == "relationship" assert "关系" in merged.focus_summary assert merged.focus_source == "llm" def test_merge_mode_override_memoir_to_emotion_when_focus_supports(): plan = _base_plan(mode="memoir_push") raw = json.dumps( { "mode_override": "emotion_first", "primary_focus": "identity", "forbid_first_person_experience": True, } ) merged = merge_reply_planner_json_into_turn_plan(plan, raw) assert merged.mode == "emotion_first" assert merged.focus_source == "llm" def test_merge_rejects_unsafe_mode_override_emotion_to_memoir(): plan = _base_plan(mode="emotion_first") raw = json.dumps( { "mode_override": "memoir_push", "primary_focus": "memoir_gap", "forbid_first_person_experience": True, } ) merged = merge_reply_planner_json_into_turn_plan(plan, raw) assert merged.mode == "emotion_first" def test_merge_omitted_secondary_focus_unchanged(): plan = _base_plan() raw = json.dumps({"reply_shape": "ack_only", "forbid_first_person_experience": True}) merged = merge_reply_planner_json_into_turn_plan(plan, raw) assert merged.reply_shape == "ack_only" assert merged.focus_source == "rule"