Phase 4:补齐通用记忆闭环(验收 9/9 通过)

按 P1-Pipecat Flows Agent化与记忆改造交接.md 的 Phase 4 完成。
新增 p1/phase4_acceptance.py:跑真实服务端验 7 条,每条之间查数据库。

实测结果(2026-09-16)
1. "记住我不吃香菜"        → confirmed 落库 ✅
2. **新连接** "我不吃什么?" → "你不吃香菜。" ✅ 连接开始时加载档案生效
3. "我今天吃了香菜"        → 不新增 confirmed,模型还追问了是否口味变了 ✅
4. "其实我现在吃香菜了"    → 同键仍只一条 confirmed,值已更新 ✅
5. "忘掉我的饮食偏好"      → 已删除 ✅
6/7. 访客与跨用户隔离       → 由单测覆盖(客户端不支持切身份)

实现:会话开始时加载稳定档案
- load_stable_memories():取已确认、未过期记忆(身份未确认返回空)
- _conversation_node 改用 build_system_prompt,与 legacy 共用同一套
  人格与记忆注入格式;记忆进**同一条** system 消息,仍不用 role_message

修掉一个真实缺陷:Flows 路径误用了语音的 max_tokens
- 症状:一句"忘掉我的饮食偏好",服务端日志
  "Failed to parse function call arguments: {"query": "饮食"(JSON 被截断),
  回复为空、记忆没删掉,且**没有任何报错**
- 根因:Flows 路径沿用了语音的 max_tokens=200(为"回复要短"而设)。
  原始 API 对照实测:max_tokens=200 时模型 finish=stop、根本不调工具;
  600 时正常产生 tool_calls 且参数 JSON 完整
- 修法:新增 AgentConfig.max_tokens(AI_AGENT_MAX_TOKENS,默认 600),
  Flows 路径用它,语音路径仍用 200

新增 client/test_client.py 能力(为验收所需)
- RTVIReplies:收服务端 RTVI 文本消息(bot-output / bot-transcription)
- 打印 BOT_REPLY 供验收脚本断言

回归:test_agent_tools 45/45、test_flows_runtime 24/24、
test_memory 30/30、test_identity 19/19

服务器已改回 AI_AGENT_RUNTIME=legacy(改默认是 Phase 7 的事)
This commit is contained in:
ylhan
2026-09-16 14:54:42 +08:00
parent 4f621dc95b
commit 98bbba93d0
7 changed files with 283 additions and 26 deletions
+12 -3
View File
@@ -99,7 +99,9 @@ def test_runtime_switch():
def test_node_shape():
print("\n=== 2. 节点配置形态 ===")
identity = ManualIdentityProvider("u_a")
identity = ManualIdentityProvider()
# 称呼来自 IdentityDecision.display_name(不是构造参数)
identity.switch("u_a", display_name="林")
store = FakeStore()
bridge = FR.FlowsBridge(rtvi=FakeRTVI(), tts=FakeTTS(),
cfg=AppConfig.load())
@@ -117,7 +119,11 @@ def test_node_shape():
"properties": {"query": {"type": "string"}},
"required": ["query"]}}},
]
node = FR._conversation_node(display_name="林", tool_schemas=schemas,
from app.memory.base import Memory as M, MemoryKind as MK, ConfirmStatus as CS
memories = [M(memory_id=1, owner_user_id="u_a", kind=MK.PREFERENCE,
key="饮食偏好", value="不吃香菜", confirm_status=CS.CONFIRMED)]
node = FR._conversation_node(identity_decision=identity.current(),
memories=memories, tool_schemas=schemas,
ctx_factory=ctx_factory)
check("节点名是 conversation", node["name"] == "conversation")
@@ -126,7 +132,10 @@ def test_node_shape():
check("用 task_messages 承载人格",
len(node["task_messages"]) == 1
and node["task_messages"][0]["role"] == "system")
check("人格里含称呼", "林" in node["task_messages"][0]["content"])
sysmsg = node["task_messages"][0]["content"]
check("人格里含称呼", "林" in sysmsg, sysmsg[-40:])
check("**不含内部 user_id**(验收第 7 条)", "u_a" not in sysmsg)
check("稳定记忆已注入", "香菜" in node["task_messages"][0]["content"])
check("respond_immediately=False(不自动开口)",
node["respond_immediately"] is False)
check("工具已注册", len(node["functions"]) == 1)