WIP: memory system improvements (in progress)
Interview/chat prompt layers, reply planner, style profiles, memory injection, interview meta store, and related tests. Work not finished. Made-with: Cursor
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
访谈侧仅验证 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,
|
||||
@@ -54,6 +56,23 @@ class TestChatExperienceRegressions:
|
||||
)
|
||||
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",
|
||||
@@ -64,6 +83,19 @@ class TestChatExperienceRegressions:
|
||||
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:
|
||||
"""保护「回忆录有文笔」体验。"""
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
|
||||
|
||||
from app.agents.chat.interview_state_hints import (
|
||||
AUTOBIOGRAPHICAL_BOUNDARY_FALLBACK_ZH,
|
||||
DUPLICATE_QUESTION_GUARD_FALLBACK_ZH,
|
||||
apply_autobiographical_boundary_guard,
|
||||
apply_duplicate_question_guard,
|
||||
extract_scene_cues,
|
||||
segments_are_only_duplicate_guard_fallback,
|
||||
@@ -16,6 +18,7 @@ from app.agents.state_schema import (
|
||||
)
|
||||
from app.agents.chat.helpers import format_history_string
|
||||
from app.agents.chat.personas import normalize_interview_persona
|
||||
from app.agents.chat.output_rules import chat_output_rules
|
||||
from app.agents.chat.prompts_conversation import (
|
||||
get_guided_conversation_prompt,
|
||||
get_opening_prompt,
|
||||
@@ -217,8 +220,7 @@ def test_guided_prompt_contains_memory_section_when_evidence():
|
||||
persona="default",
|
||||
memory_evidence_text="[摘要:rolling] 1990年生于上海。",
|
||||
)
|
||||
assert "相关记忆摘录" in p
|
||||
assert "过往口述" in p
|
||||
assert "记忆线索" in p or "追问角度" in p
|
||||
assert "1990年生于上海" in p
|
||||
|
||||
|
||||
@@ -368,3 +370,92 @@ def test_format_history_string_omit_system_body() -> None:
|
||||
assert "System: <omitted total_len=16" in s
|
||||
assert "sha12=" in s
|
||||
assert "Human: hi" in s
|
||||
|
||||
|
||||
def test_guided_prompt_includes_identity_boundary_hard_rules() -> 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
|
||||
assert "真实人生传记" in p
|
||||
assert "共同回忆" in p
|
||||
assert "泛指" in p
|
||||
|
||||
|
||||
def test_guided_prompt_blocks_using_user_context_as_assistant_identity() -> 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
|
||||
assert "不能把用户的成长地答成" in p
|
||||
assert "你刚提到上海" in p or "你之前说过那段童年" in p
|
||||
|
||||
|
||||
def test_chat_output_rules_bans_assistant_autobiography() -> None:
|
||||
rules = chat_output_rules()
|
||||
assert "禁止" in rules
|
||||
assert "声称助手本人" in rules or "助手本人" in rules
|
||||
assert "共同回忆" in rules
|
||||
assert "你是哪里人" in rules
|
||||
assert "你刚提到" in rules
|
||||
|
||||
|
||||
def test_autobiographical_boundary_guard_replaces_crush_claim() -> None:
|
||||
out, touched = apply_autobiographical_boundary_guard(
|
||||
["是啊,朱丽叶就是我当时暗恋的女生。"]
|
||||
)
|
||||
assert touched is True
|
||||
assert out == [AUTOBIOGRAPHICAL_BOUNDARY_FALLBACK_ZH]
|
||||
|
||||
|
||||
def test_autobiographical_boundary_guard_replaces_childhood_claim() -> None:
|
||||
out, touched = apply_autobiographical_boundary_guard(
|
||||
["我小时候也演过这个,还挺紧张的。"]
|
||||
)
|
||||
assert touched is True
|
||||
assert out == [AUTOBIOGRAPHICAL_BOUNDARY_FALLBACK_ZH]
|
||||
|
||||
|
||||
def test_autobiographical_boundary_guard_allows_generic_empathy() -> None:
|
||||
safe = [
|
||||
"我能想象那会儿站在台上,手心里全是汗。",
|
||||
"换作很多人可能也会记很久。",
|
||||
]
|
||||
out, touched = apply_autobiographical_boundary_guard(safe)
|
||||
assert touched is False
|
||||
assert out == safe
|
||||
|
||||
|
||||
def test_autobiographical_boundary_guard_mixed_segments() -> None:
|
||||
out, touched = apply_autobiographical_boundary_guard(
|
||||
["嗯,你刚才那段我接住了。", "我小时候也演过。"]
|
||||
)
|
||||
assert touched is True
|
||||
assert out[0] == "嗯,你刚才那段我接住了。"
|
||||
assert out[1] == AUTOBIOGRAPHICAL_BOUNDARY_FALLBACK_ZH
|
||||
|
||||
|
||||
def test_autobiographical_boundary_guard_catches_iyan_role_without_demo() -> None:
|
||||
out, touched = apply_autobiographical_boundary_guard(
|
||||
["那次话剧里我演罗密欧,对手戏挺难的。"]
|
||||
)
|
||||
assert touched is True
|
||||
assert out == [AUTOBIOGRAPHICAL_BOUNDARY_FALLBACK_ZH]
|
||||
|
||||
|
||||
def test_autobiographical_boundary_guard_allows_wo_yanshi_demo() -> None:
|
||||
out, touched = apply_autobiographical_boundary_guard(
|
||||
["我演示一下这个按钮怎么点。"]
|
||||
)
|
||||
assert touched is False
|
||||
assert out == ["我演示一下这个按钮怎么点。"]
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""interview_turn_plan:轮次模式与主槽选择(服务端硬编排)。"""
|
||||
|
||||
from app.agents.chat.interview_turn_plan import (
|
||||
InterviewTurnPlan,
|
||||
apply_safe_mode_override,
|
||||
extract_anchor_snippet,
|
||||
format_interview_turn_directive_block,
|
||||
plan_interview_turn,
|
||||
primary_empty_slot,
|
||||
)
|
||||
@@ -12,11 +15,177 @@ def test_primary_empty_slot_order():
|
||||
assert primary_empty_slot("childhood", ["emotion"]) == "emotion"
|
||||
|
||||
|
||||
def test_extract_anchor_snippet_prefers_memory():
|
||||
def test_extract_anchor_snippet_prefers_user_when_long_enough():
|
||||
mem = "摘录的一段记忆\n\n[场景氛围提示"
|
||||
assert "摘录的一段记忆" in extract_anchor_snippet(
|
||||
memory_evidence_text=mem, user_message="用户说很长一句" * 3
|
||||
um = "用户说很长一句" * 5
|
||||
sn = extract_anchor_snippet(memory_evidence_text=mem, user_message=um)
|
||||
assert sn.startswith("用户说")
|
||||
assert "摘录" not in sn
|
||||
|
||||
|
||||
def test_extract_anchor_snippet_prefers_first_m_line():
|
||||
mem = (
|
||||
"【相关记忆摘录·聊天专用】\n"
|
||||
"说明行……\n"
|
||||
"[M1] 你在校园演出里饰演罗密欧。\n"
|
||||
)
|
||||
sn = extract_anchor_snippet(memory_evidence_text=mem, user_message="短")
|
||||
assert "校园演出" in sn
|
||||
assert "【相关" not in sn
|
||||
|
||||
|
||||
def test_plan_sets_memory_usage_when_evidence_present():
|
||||
p = plan_interview_turn(
|
||||
current_stage="childhood",
|
||||
empty_slots=["place"],
|
||||
normalized_user_message="嗯。",
|
||||
memory_evidence_text="【头】\n[M1] 你提过河边。",
|
||||
stage_switched_this_turn=False,
|
||||
)
|
||||
assert p.memory_usage == "allowed_with_attribution"
|
||||
|
||||
|
||||
def test_plan_marks_assistant_identity_question() -> None:
|
||||
p = plan_interview_turn(
|
||||
current_stage="childhood",
|
||||
empty_slots=["place"],
|
||||
normalized_user_message="你是哪里人,你的童年是什么样的?",
|
||||
memory_evidence_text="用户曾说:「我小时候在上海长大。」",
|
||||
stage_switched_this_turn=False,
|
||||
)
|
||||
assert p.assistant_identity_question is True
|
||||
|
||||
|
||||
def test_plan_reply_shape_ack_then_question_on_her_story_followup():
|
||||
p = plan_interview_turn(
|
||||
current_stage="education",
|
||||
empty_slots=["school"],
|
||||
normalized_user_message="那你讲讲她的故事吧。",
|
||||
memory_evidence_text="",
|
||||
stage_switched_this_turn=False,
|
||||
)
|
||||
assert p.reply_shape == "ack_then_question"
|
||||
|
||||
|
||||
def test_directive_includes_attribution_when_memory_allowed():
|
||||
plan = InterviewTurnPlan(
|
||||
mode="memoir_push",
|
||||
anchor_slot_key="place",
|
||||
anchor_slot_readable="成长的地方",
|
||||
anchor_snippet="挂钩",
|
||||
memory_usage="allowed_with_attribution",
|
||||
memory_reference_style="你之前提过",
|
||||
reply_shape="ack_then_question",
|
||||
)
|
||||
block = format_interview_turn_directive_block(plan)
|
||||
assert "线索" in block
|
||||
assert "你之前提过" in block
|
||||
assert "真实人生传记" in block
|
||||
assert "ack" not in block.lower()
|
||||
assert "承接" in block
|
||||
assert (
|
||||
"本轮追问" in block
|
||||
or "承接角度" in block
|
||||
or "本轮承接重点" in block
|
||||
)
|
||||
|
||||
|
||||
def test_directive_includes_focus_summary_when_set():
|
||||
plan = InterviewTurnPlan(
|
||||
mode="memoir_push",
|
||||
anchor_slot_key="place",
|
||||
anchor_slot_readable="成长的地方",
|
||||
anchor_snippet="挂钩",
|
||||
focus_summary="先接住「芳芳」与怕丢脸这条线",
|
||||
focus_source="llm",
|
||||
primary_focus="relationship",
|
||||
)
|
||||
block = format_interview_turn_directive_block(plan)
|
||||
assert "芳芳" in block
|
||||
assert "本轮追问" in block or "承接角度" in block
|
||||
|
||||
|
||||
def test_directive_marks_user_message_anchor_source_correctly():
|
||||
plan = InterviewTurnPlan(
|
||||
mode="memoir_push",
|
||||
anchor_slot_key="place",
|
||||
anchor_slot_readable="成长的地方",
|
||||
anchor_snippet="那年夏天我总往河边跑",
|
||||
anchor_source_kind="user_message",
|
||||
)
|
||||
block = format_interview_turn_directive_block(plan)
|
||||
assert "来自用户本轮原话摘录" in block
|
||||
assert "不是用户本轮新说的内容" not in block
|
||||
|
||||
|
||||
def test_directive_marks_memory_anchor_source_correctly():
|
||||
plan = InterviewTurnPlan(
|
||||
mode="memoir_push",
|
||||
anchor_slot_key="place",
|
||||
anchor_slot_readable="成长的地方",
|
||||
anchor_snippet="你小时候常在河边玩",
|
||||
anchor_source_kind="memory",
|
||||
)
|
||||
block = format_interview_turn_directive_block(plan)
|
||||
assert "来自检索到的用户过往口述/摘要" in block
|
||||
assert "不是用户本轮新说的内容" in block
|
||||
|
||||
|
||||
def test_directive_adds_boundary_for_assistant_identity_question():
|
||||
plan = InterviewTurnPlan(
|
||||
mode="memoir_push",
|
||||
anchor_slot_key="place",
|
||||
anchor_slot_readable="成长的地方",
|
||||
anchor_snippet="用户曾说:「我小时候在上海长大。」",
|
||||
assistant_identity_question=True,
|
||||
)
|
||||
block = format_interview_turn_directive_block(plan)
|
||||
assert "本轮用户在问助手本人" in block
|
||||
assert "你刚提到上海" in block
|
||||
|
||||
|
||||
def test_apply_safe_mode_override_blocks_emotion_downgrade():
|
||||
assert apply_safe_mode_override("emotion_first", "memoir_push", primary_focus="emotion") is None
|
||||
|
||||
|
||||
def test_apply_safe_mode_override_allows_memoir_to_emotion():
|
||||
assert (
|
||||
apply_safe_mode_override("memoir_push", "emotion_first", primary_focus="relationship")
|
||||
== "emotion_first"
|
||||
)
|
||||
|
||||
|
||||
def test_apply_safe_mode_override_clarify_blocks_memoir():
|
||||
assert apply_safe_mode_override("clarify_first", "memoir_push", primary_focus="emotion") is None
|
||||
|
||||
|
||||
def test_apply_safe_mode_override_clarify_to_emotion():
|
||||
assert (
|
||||
apply_safe_mode_override("clarify_first", "emotion_first", primary_focus="emotion")
|
||||
== "emotion_first"
|
||||
)
|
||||
|
||||
|
||||
def test_plan_clarify_first_when_ambiguous():
|
||||
p = plan_interview_turn(
|
||||
current_stage="childhood",
|
||||
empty_slots=["place", "people"],
|
||||
normalized_user_message="那种喜欢我也说不清,好像有又好像没有。",
|
||||
memory_evidence_text="",
|
||||
stage_switched_this_turn=False,
|
||||
)
|
||||
assert p.mode == "clarify_first"
|
||||
|
||||
|
||||
def test_plan_clarify_first_when_very_short():
|
||||
p = plan_interview_turn(
|
||||
current_stage="childhood",
|
||||
empty_slots=["place"],
|
||||
normalized_user_message="还好吧",
|
||||
memory_evidence_text="",
|
||||
stage_switched_this_turn=False,
|
||||
)
|
||||
assert p.mode == "clarify_first"
|
||||
|
||||
|
||||
def test_plan_memoir_push():
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import pytest
|
||||
|
||||
from app.features.memory import evidence as evidence_mod
|
||||
from app.features.memory.evidence_format import format_evidence_chunks_for_chat_prompt
|
||||
from app.features.memory.evidence import (
|
||||
EMPTY_EVIDENCE_BUNDLE,
|
||||
_facts_to_dicts,
|
||||
@@ -85,3 +86,107 @@ def test_format_helpers_empty() -> None:
|
||||
assert _facts_to_dicts([]) == []
|
||||
assert _timeline_to_dicts([]) == []
|
||||
assert _stories_to_dicts([]) == []
|
||||
|
||||
|
||||
def test_format_evidence_chunks_for_chat_prompt_reframes_and_labels() -> None:
|
||||
evidence = {
|
||||
"relevant_chunks": [
|
||||
{"id": "chunk-1", "content": "我小时候在河边长大,夏天常去玩水。"},
|
||||
],
|
||||
"relevant_summaries": [],
|
||||
"relevant_facts": [],
|
||||
"timeline_hints": [],
|
||||
"relevant_stories": [],
|
||||
}
|
||||
text = format_evidence_chunks_for_chat_prompt(evidence)
|
||||
assert "聊天专用" in text
|
||||
assert "归因" in text
|
||||
assert "[M1]" in text
|
||||
assert "用户曾说" in text
|
||||
assert "我小时候在河边长大" in text
|
||||
|
||||
|
||||
def test_slice_interview_memory_empty_bundle():
|
||||
from app.features.memory.chat_memory_injection import slice_interview_memory
|
||||
|
||||
s = slice_interview_memory(None, "你好")
|
||||
assert s.prompt_excerpt == ""
|
||||
assert s.anchor_source == ""
|
||||
assert s.planner_preview == ""
|
||||
assert s.had_retrieval is False
|
||||
|
||||
|
||||
def test_slice_interview_memory_retrieval_not_equal_inject_dismissive():
|
||||
"""有检索预览但 gating 后不进主 prompt / anchor。"""
|
||||
from app.features.memory.chat_memory_injection import slice_interview_memory
|
||||
|
||||
evidence = {
|
||||
"relevant_chunks": [
|
||||
{"id": "c1", "content": "很久以前在校园礼堂排练到很晚。"},
|
||||
],
|
||||
"relevant_summaries": [],
|
||||
"relevant_facts": [],
|
||||
"timeline_hints": [],
|
||||
"relevant_stories": [],
|
||||
}
|
||||
s = slice_interview_memory(evidence, "哈哈,早就不会了")
|
||||
assert s.prompt_excerpt == ""
|
||||
assert s.anchor_source == ""
|
||||
assert s.planner_preview.strip() != ""
|
||||
assert s.had_retrieval is True
|
||||
|
||||
|
||||
def test_slice_interview_memory_minimal_inject_when_aligned():
|
||||
from app.features.memory.chat_memory_injection import slice_interview_memory
|
||||
|
||||
evidence = {
|
||||
"relevant_chunks": [
|
||||
{"id": "c1", "content": "你在校园演出里饰演罗密欧。"},
|
||||
],
|
||||
"relevant_summaries": [],
|
||||
"relevant_facts": [],
|
||||
"timeline_hints": [],
|
||||
"relevant_stories": [],
|
||||
}
|
||||
s = slice_interview_memory(evidence, "那次排练其实挺紧张的,灯光一打我就忘词。")
|
||||
assert "记忆线索" in s.prompt_excerpt
|
||||
assert "校园演出" in s.prompt_excerpt or "罗密欧" in s.prompt_excerpt
|
||||
assert s.anchor_source
|
||||
assert s.had_retrieval is True
|
||||
|
||||
|
||||
def test_slice_interview_memory_keeps_first_person_but_marks_ownership():
|
||||
from app.features.memory.chat_memory_injection import slice_interview_memory
|
||||
|
||||
evidence = {
|
||||
"relevant_chunks": [
|
||||
{"id": "c1", "content": "我小时候在河边长大,夏天常去玩水。"},
|
||||
],
|
||||
"relevant_summaries": [],
|
||||
"relevant_facts": [],
|
||||
"timeline_hints": [],
|
||||
"relevant_stories": [],
|
||||
}
|
||||
s = slice_interview_memory(evidence, "那条河一到夏天就特别热闹,我现在都记得。")
|
||||
assert "用户曾说" in s.prompt_excerpt
|
||||
assert "我小时候在河边长大" in s.prompt_excerpt
|
||||
assert s.anchor_source.startswith("用户曾说")
|
||||
|
||||
|
||||
def test_slice_interview_memory_suppresses_long_new_topic():
|
||||
from app.features.memory.chat_memory_injection import slice_interview_memory
|
||||
|
||||
evidence = {
|
||||
"relevant_chunks": [
|
||||
{"id": "c1", "content": "旧记忆关于河边。"},
|
||||
],
|
||||
"relevant_summaries": [],
|
||||
"relevant_facts": [],
|
||||
"timeline_hints": [],
|
||||
"relevant_stories": [],
|
||||
}
|
||||
long_msg = "我今天想随便聊聊工作里的事,项目压力很大。" * 6
|
||||
assert len(long_msg) > 72
|
||||
s = slice_interview_memory(evidence, long_msg)
|
||||
assert s.prompt_excerpt == ""
|
||||
assert s.anchor_source == ""
|
||||
|
||||
112
api/tests/test_reply_planner.py
Normal file
112
api/tests/test_reply_planner.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user