"""上下文组装(架构 §7.2 步骤③)的纯逻辑测试:画像注入与历史截尾。""" from __future__ import annotations from packages.agent.context import ( PROFILE_SUMMARY_MAX_CHARS, build_context_messages, build_profile_message, ) from packages.llm.types import Message class TestBuildContextMessages: def test_profile_first_then_history(self) -> None: history = [ Message(role="user", content="第一句"), Message(role="assistant", content="第一答"), ] messages = build_context_messages( profile_summary='{"dimensions":[]}', history=history, history_turns=10 ) assert len(messages) == 3 assert messages[0].role == "system" assert '{"dimensions":[]}' in messages[0].content assert [m.role for m in messages[1:]] == ["user", "assistant"] assert [m.content for m in messages[1:]] == ["第一句", "第一答"] def test_no_profile_degrades_to_history_only(self) -> None: """无画像用户(FR-AGT-10 降级):不注入画像消息,历史照常携带。""" history = [Message(role="user", content="hi")] messages = build_context_messages(profile_summary=None, history=history, history_turns=10) assert len(messages) == 1 assert messages[0].role == "user" def test_empty_profile_string_treated_as_absent(self) -> None: messages = build_context_messages(profile_summary="", history=[], history_turns=10) assert messages == [] def test_history_tail_kept_by_turns(self) -> None: """一轮 = user+assistant 两条;N=2 时只留最近 4 条,截头不截尾。""" history: list[Message] = [] for i in range(5): history.append(Message(role="user", content=f"u{i}")) history.append(Message(role="assistant", content=f"a{i}")) messages = build_context_messages(profile_summary=None, history=history, history_turns=2) assert [m.content for m in messages] == ["u3", "a3", "u4", "a4"] def test_zero_turns_drops_history(self) -> None: history = [Message(role="user", content="u0")] messages = build_context_messages(profile_summary=None, history=history, history_turns=0) assert messages == [] def test_history_message_content_capped(self) -> None: history = [Message(role="user", content="x" * 5000)] messages = build_context_messages(profile_summary=None, history=history, history_turns=10) assert len(messages[0].content) == 2000 class TestBuildProfileMessage: def test_wraps_summary_as_system_message(self) -> None: message = build_profile_message("{}") assert message.role == "system" assert message.content.endswith("{}") def test_budget_constant_within_token_budget(self) -> None: """画像摘要预算 ≤ 500 token(README 预算表),按 ~1.6 字符/token 折算。""" assert PROFILE_SUMMARY_MAX_CHARS <= 500 * 1.6