Commit Graph
2 Commits
Author SHA1 Message Date
ylhan 4f621dc95b Phase 3:Flows 接管网页文字路径(默认仍 legacy)
按 P1-Pipecat Flows Agent化与记忆改造交接.md 的 Phase 3 完成。
**语音路径保持 legacy**,文字路径可由 AI_AGENT_RUNTIME=flows 切换。
服务器实测通过工具、姓名、记忆三项验收,随后按文档要求改回 legacy。

新增 app/agent/flows_runtime.py
- FlowsBridge:LLM 文本帧 → RTVI 消息 + 分句 TTS,产出与 legacy 的
  _run_turn 一致(bot-llm-started → 逐 token bot-llm-text → stopped
  → bot-transcription / bot-output)
- UserTextRecorder:截下用户原话供策略层判定语义等级
- build_flows_parts / make_flow_manager:拆成两段,解决
  "FlowManager 要 worker、worker 要等管线" 的先后依赖
- 节点只用 task_messages,**不用 role_message**(避免两条 system 消息)
- respond_immediately=False(不自动开口)
- handler 用闭包捕获 ctx_factory,不用模块级全局(并发会话会串身份)

新增 app/agent/llm_service.py
- 固化 Phase 1 spike 的选型:复用 DeepSeekLLMService,显式覆盖模型名
  (Pipecat 默认的 deepseek-v4-flash 不存在)

配置 app/config.py
- 新增 AgentConfig + AI_AGENT_RUNTIME,**默认 legacy**

改造 services/bot_server.py
- EngineProcessor 加 owns_text:flows 模式下语音仍归它,文字帧往下传
- runtime_deps() 暴露 memory/identity/session/tts,两条路径共用同一份资源
  (避免各建一份 SQLite 连接或各加载一次 TTS)
- **_run_task 改用 PipelineRunner**:直接 task.run() 时 runner 没挂上,
  Flows 的工具执行会失败("runner is not set; call attach() first"),
  异常被转成给 LLM 的通用错误串,默认日志看不到。这正是 Phase 1 spike
  记录的坑 2,集成时又踩了一次。

新增 app/tests/test_flows_runtime.py(22 项,不依赖网络)

新增 deploy.sh
- **不用 --delete**、一次只同步一个目录、显式排除清单。
  起因见下。

客户端 client/test_client.py 加 --text 模式(RTVI send-text)
- 信封字段实测校正两次:**是 id 不是 msg_id**,**data.content 不是 text**;
  写错会被服务端以 "Invalid RTVI transport message" 拒掉且客户端无感知
- 等待条件改为"等到非静音音频",不能数音频块——WebRTC 空闲时也在推静音

服务器实测(2026-09-16)
- legacy 文字:11.2s 音频 / 25 个有声段
- flows 文字:9.3s 音频 / 18 个有声段,首音约 2.8s
- flows 记忆闭环:只说"记住我不吃香菜" → 工具写入 confirmed 记忆
  → 数据库确认 #4 [confirmed] preference 饮食偏好=不吃香菜
- 称呼"林"从档案恢复

已知问题(留给 Phase 4/7)
- Pipecat 报 "Both system_instruction and an initial system message in
  context are set" 警告。来源未查明(我只用了 task_messages)。
  切回本地 vLLM 时可能 400,因为该后端拒绝多条 system 消息。
- Flows 路径暂不暴露 web_search(交接文档要求分阶段,搜索仍走 legacy)
2026-09-16 14:13:32 +08:00
ylhan 4fe2755fd6 Phase 2:抽出工具与策略层
按 P1-Pipecat Flows Agent化与记忆改造交接.md 的 Phase 2 完成。
legacy 路径行为不变,只是改为复用新工具层。

新增 app/agent/
- memory_policy.py:记忆写入/删除的裁决层。模型只提议,策略决定。
  不依赖 LLM/网络/真实存储,可纯单元测试。
  - 身份未确认 → 一律 reject,不碰私人记忆
  - 模型传 owner_user_id/scope 等越权字段 → reject(不静默忽略)
  - 只有用户明确说"记住/以后/别忘了/我的偏好是"才写 confirmed
  - "记住我今天吃了香菜"这类一次性事件 → 降级 candidate
  - 不采信模型自称的"用户明确要求"
  - 模糊/多条删除 → needs_confirmation,不猜目标
- tools.py:工具 schema 与执行器,legacy 与 flows 共用。
  - 身份由 ToolContext 注入,**不在工具参数里**
  - 身份未确认时不暴露任何记忆工具(比暴露后拒绝更干净)
  - 写入结果如实回报:candidate 明确告知"尚未生效",
    删除如实返回"已删除/没有找到/需要确认",不把"请求已收到"说成"已做到"
  - 工具失败转成给模型的文本,不拖垮本轮对话
- llm_service.py:Pipecat LLMService 工厂,固化 Phase 1 spike 的选型结论
  (复用 DeepSeekLLMService;必须显式覆盖模型名为 deepseek-flash,
  因为 Pipecat 默认的 deepseek-v4-flash 不存在)

改造 app/engine.py
- _execute_tool 改为委托 app/agent/tools.execute(),删除重复实现
- 本轮用户原话挂到实例上,供策略层判定语义等级
- 移除不再使用的 SearchError 导入

新增 app/tests/test_agent_tools.py:45 项断言,**不依赖 LLM/网络/SQLite**
(用 fake store)。覆盖交接文档 §十 的清单:越权参数、未确认身份、
明确记住 vs 推断、同 key 幂等、模糊删除、精确删除、跨用户隔离。

回归:test_agent_tools 45/45、test_memory 30/30、test_identity 19/19、
test_search 12/12、test_transport 4/4、test_profile_memory 22/22,无退化。
2026-09-16 13:25:46 +08:00