基线:姓名档案修复、疑问句防误写、搜索与传输测试
这是进入 Pipecat Flows 改造前的回滚点。 提交时已核对本地与线上代码 md5 一致(engine.py 7e97bad8、 bot_server.py 1cf543ae),所以这个提交就是当前线上状态。 内容(此前一直在线上运行但未提交的改动): - 姓名档案:用户自报"我叫林"同步写 confirmed 姓名,重连后恢复 - 疑问句防误写:"谁吗"之类的疑问句不再被覆盖成姓名 - 新增 test_profile_memory.py(姓名档案专项回归) - 新增 test_profile_live.py(需要真实 LLM 的现场测试) - 搜索、身份、记忆测试的相应补充 背景:下一步要按 P1-Pipecat Flows Agent化与记忆改造交接.md 渐进引入 Pipecat Flows,交接文档明确要求先保护基线、不得 reset/checkout/覆盖现有改动。此提交即该基线。
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
#!/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()))
|
||||
Reference in New Issue
Block a user