Commit Graph
6 Commits
Author SHA1 Message Date
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
ylhan c37a968548 Phase 1 spike:Pipecat Flows 兼容性验证,7 项全通过
按 P1-Pipecat Flows Agent化与记忆改造交接.md 的 Phase 1 要求,
只做独立 spike,不改生产入口。

新增
- p1/flows_spike.py:探索脚本,带请求体抓取与逐项诊断
- app/tests/test_flows_spike.py:断言版,复用探索脚本的检查项避免漂移

验证结果(9 个断言全过)
- DeepSeek 在 Flows 中流式输出中文(14–22 个 token 级分块)
- 工具调用参数聚合完整、无重复拼接;回灌后能出最终回答
- worker.cancel() 能立即终止 LLM 流
- role_message 生效(模型自称"桌面陪伴小助手")
- context 里只有一条 system 消息

三条最重要的结论
1. **不要装 pipecat-ai-flows**。Pipecat 1.10 内置 Flows(pipecat.flows),
   装了会报 _warn_if_standalone_flows_installed;且独立包 1.4.0 会把
   pipecat 从 1.10.0 降到 1.4.0。requirements.txt 已加说明。
2. **必须用 PipelineRunner().run(worker)**。直接 worker.run() 时 runner
   没挂上,工具执行失败并抛 "runner is not set; call attach() first",
   该异常被转成给 LLM 的通用错误串,handler 从不执行,默认日志看不到。
3. **await flow.initialize(node)**。它是协程但签名显示 -> None,
   漏 await 不报错,只是 role_message/task_messages/工具全部静默失效——
   极易误判成"框架不支持这些特性"。

另两条
- respond_immediately 默认 True,initialize 会立刻触发一轮回复,
  做归因测试时必须清空观察窗口,否则会归因错。
- role_message(走 system_instruction)与 task_messages 各产生一条
  system 消息,两者不要同时用;切回本地 vLLM 时会 400。

LLMService 选择:复用 Pipecat 的 DeepSeekLLMService。它自带
DeepSeekLLMAdapter 处理 reasoning_content 回传约束(我们 P0 踩过的坑),
thinking 默认 disabled,与既有结论一致。代价是失去本地 vLLM 降级
(Flows 要求 LLMService 实例,FallbackProvider 不能直接用;且当前
本机 vLLM 已停用,fallback 实际无可用目标)。

环境:NLTK punkt_tab 本地也需预装(服务器已有),spike 加了 fail-fast
检查与拷贝命令提示。
2026-09-16 13:13:39 +08:00
ylhan 20cfaa6ef7 基线:姓名档案修复、疑问句防误写、搜索与传输测试
这是进入 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/覆盖现有改动。此提交即该基线。
2026-09-16 12:48:18 +08:00
ylhan 719fc13e48 修复网页对话并接入 SearXNG 搜索 2026-09-16 11:17:41 +08:00
ylhan c3d2c7f3a8 修复 Pipecat 实时链路并补齐网页测试 2026-09-16 09:50:38 +08:00
ylhan 35e107c290 助手本体:会话引擎、LLM 路由、记忆、身份、语音适配
从 Obsidian 笔记库剥离为独立仓库。规划文档与 P0 报告留在笔记库,
代码(助手本体、TTS 服务、P0 基准脚本、ESP32 固件)在此。

架构:服务器是助手,本地是客户端
- 助手本体跑在 cadadmin-server,N5095 只做 USB 外设桥与网络桥接
- 起因:实测 N5095 无 AVX2,跑不动可用的语音模型

当前技术栈
- LLM:DeepSeek deepseek-flash(云端,必须关思考)
- TTS:Qwen3-TTS 1.7B(服务器 3090,独立 HTTP 服务)
- STT:Zipformer 中英双语 int8(CPU,中文识别率未测)

实测:端到端 16/16 通过,首音延迟 1545ms
(对比:本地 LLM + vits-zh-ll 是 179ms,但音色不满意——音质与延迟
在此硬件上此消彼长)

设计要点
- MemoryStore 接口按"可能远程"设计(全 async、可超时、可失败)
- user_id / session_id / track_id 三者分开,人脸只作候选不自动切身份
- 记忆查询强制带 owner_user_id,无按 id 单独取的口子
- 自动抽取的记忆是"候选",默认不参与检索
- 异步写记忆绑定创建时的身份,不读"当前用户"
- 引擎与传输解耦,只用文件音频即可端到端测试

模型与音频产物不进版本管理(1.1GB),由 download_models.sh 恢复
2026-09-15 11:05:09 +08:00