Files
ai-companion/app/agent/llm_service.py
T
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

60 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Pipecat LLMService 工厂。
Phase 1 spike 的选型结论固化在这里:**复用 Pipecat 自带的 `DeepSeekLLMService`**,
不自写 `FallbackProvider → LLMService` 适配器。
理由(详见笔记库 `P1-Flows兼容性Spike结论.md`):
1. 它自带 `DeepSeekLLMAdapter`,处理"思考模式下必须把 reasoning_content
原样回传"这个约束——**正是本项目 P0 阶段踩过的坑**,框架已经做了。
2. `thinking` 默认就是 disabled,与本项目既有结论一致。
3. 支持自定义 `base_url`,将来要接本地 vLLM 也能用。
4. Flows 硬要求 `LLMService` 实例,而 `pipecat.flows` 的 `LLMAdapter`
**只管摘要生成**,不是 provider 适配器——想保留 `FallbackProvider`
必须自己写 `LLMService` 子类,成本不划算。
**代价**:失去 `FallbackProvider` 的本地 vLLM 降级。当前本机 vLLM 已停用,
fallback 实际没有可用目标,所以先只用 DeepSeek。将来若需双后端,
Pipecat 有 `LLMSwitcher` 可用,不必自写。
"""
from __future__ import annotations
import logging
from ..config import LLMConfig
log = logging.getLogger(__name__)
def create_llm_service(cfg: LLMConfig):
"""按配置造 Pipecat LLMService。
目前只支持 DeepSeek(OpenAI 兼容)。其它后端返回 None,调用方应当
回退到 legacy 路径,而不是带着一个坏 service 继续。
"""
if cfg.name != "deepseek":
log.warning("Flows 路径暂不支持后端 %r,请继续使用 legacy", cfg.name)
return None
from pipecat.services.deepseek.llm import DeepSeekLLMService
# **必须显式覆盖模型名。**
# Pipecat 的默认是 `deepseek-v4-flash`,该模型不存在,用默认值会 400。
# 见 P0-DeepSeek验证报告.md:真实清单只有 deepseek-flash 与 deepseek-v4-pro。
settings = DeepSeekLLMService.Settings(
model=cfg.model,
# 显式关闭思考。Pipecat 的默认恰好也是 disabled,但写出来更安全:
# 将来默认值变化时不会悄悄改变行为,也不至于把思考内容送进 TTS。
thinking=DeepSeekLLMService.ThinkingConfig(type="disabled"),
max_tokens=cfg.max_tokens,
)
return DeepSeekLLMService(
api_key=cfg.api_key,
base_url=cfg.base_url,
settings=settings,
)
__all__ = ["create_llm_service"]