#!/usr/bin/env python3 """用户称呼跨会话持久化与内部 ID 隔离回归测试。""" from __future__ import annotations import asyncio import shutil import sys import tempfile from pathlib import Path import numpy as np ROOT = Path(__file__).resolve().parent.parent.parent sys.path.insert(0, str(ROOT)) from app.config import AppConfig # noqa: E402 from app.engine import ( # noqa: E402 ConversationEngine, extract_display_name, extract_memories, ) from app.identity import ManualIdentityProvider # noqa: E402 from app.llm.base import Chunk, ChunkKind # noqa: E402 from app.memory import ConfirmStatus, SQLiteMemoryStore # noqa: E402 class FakeLLM: name = "fake" last_backend = "fake" def __init__(self): self.system_prompts = [] async def stream(self, messages, *, tools=None, max_tokens=None): self.system_prompts.append(messages[0]["content"]) yield Chunk(kind=ChunkKind.CONTENT, text="好的。") yield Chunk(kind=ChunkKind.DONE, finish_reason="stop") class FakeTTS: sample_rate = 16000 def synthesize(self, _text): return np.ones(160, dtype=np.float32) * 0.1 async def consume(engine: ConversationEngine, text: str) -> None: async for _event in engine.respond_to_text(text): pass async def test_cross_session_profile() -> None: tmp = Path(tempfile.mkdtemp(prefix="profile_memory_")) store = SQLiteMemoryStore(tmp / "memory.db") await store.open() try: cfg = AppConfig() cfg.search.enabled = False cfg.memory.auto_extract_enabled = False first_llm = FakeLLM() first = ConversationEngine( cfg, first_llm, store, ManualIdentityProvider(), FakeTTS()) await first.start_session("profile_s1", user_id="u_internal") await consume(first, "你好,我叫林。") await first.end_session() assert await store.get_user_display_name("u_internal") == "林" memories = await store.list_memories("u_internal") names = [m for m in memories if m.key == "姓名"] assert len(names) == 1 assert names[0].value == "林" assert names[0].confirm_status == ConfirmStatus.CONFIRMED # 模拟 disconnect 后重新创建整个 ConversationEngine/IdentityProvider。 second_llm = FakeLLM() second = ConversationEngine( cfg, second_llm, store, ManualIdentityProvider(), FakeTTS()) await second.start_session("profile_s2", user_id="u_internal") assert second.identity.current().display_name == "林" await consume(second, "我是谁?") prompt = second_llm.system_prompts[-1] assert "林" in prompt assert "u_internal" not in prompt assert "内部 user_id" in prompt await consume(second, "知道我是谁吗?") assert await store.get_user_display_name("u_internal") == "林" await consume(second, "我叫什么?") assert await store.get_user_display_name("u_internal") == "林" await second.end_session() finally: await store.close() shutil.rmtree(tmp, ignore_errors=True) async def main() -> int: assert extract_display_name("我叫林") == "林" assert extract_display_name("你好我叫lin。") == "lin" assert extract_display_name("我的名字是小林。") == "小林" assert extract_display_name("你可以叫我老韩。") == "老韩" assert extract_display_name("我是林。") == "林" assert extract_display_name("我是程序员。") is None assert extract_display_name("我叫什么?") is None assert extract_display_name("我是谁?") is None assert extract_display_name("知道我是谁吗?") is None assert extract_display_name("我是林吗?") is None assert extract_display_name("我叫什么名字?") is None assert extract_memories("记住,我叫林。", "") == [] await test_cross_session_profile() print("用户称呼跨会话回归测试:22/22 PASS") return 0 if __name__ == "__main__": raise SystemExit(asyncio.run(main()))