返回博客列表
最佳实践2026-08-0230 分钟阅读

分层风险治理总览:为什么 AI 心理陪伴需要 L0–L4 五层"安全阀"?

从一次真实危机对话回放,看 WeClaw 如何用"纵深防御"给 AI 装上一套负责任的心理危机守护系统

WeClaw_74|分层风险治理总览:为什么 AI 心理陪伴需要 L0–L4 五层"安全阀"?

系列文章第 74 篇 - 从一次真实危机对话回放,看 WeClaw 如何用"纵深防御"给 AI 装上一套负责任的心理危机守护系统


📚 专栏信息

《从零到一构建跨平台 AI 助手:WeClaw 实战指南》专栏

专栏定位:面向开发者和技术决策者的实战专栏,用真实案例和完整代码带你理解如何构建生产级 AI 应用

本文是模块八【心理健康与危机守护】第 1 篇(导读)。本模块共 9 篇,是 WeClaw 系列的全新增模块, 完整拆解 v9.0.0「心理健康陪伴大师」迭代背后的分层治理工程。本文建立全局认知: 为什么单 LLM 方案扛不住心理危机场景,以及 L0–L4 五层"安全阀"各自解决什么问题。

🧠 模块八【心理健康与危机守护】(9 篇): 74 分层总览 / 75 L0 关键词闸门 / 76 L1-L2 双重确认 / 77 RiskLevel 全序 Bug / 78 危机资源零编造红线 / 79 词表校准 / 80 L4 监护人告警 / 81 伦理边界 / 82 校准测试框架


👨‍💻 作者与项目

作者简介:翁勇刚 WENG YONGGANG 新概念龙虾-WeClaw 开发团队负责人,一群专注于跨平台 AI 应用的实践者 理念:"再复杂的技术,也能用代码讲清楚"

⚠️ 本文涉及心理危机话题。如果你或身边的人正处于危机中,请立即联系专业资源(文末附经核验热线)。 本文聚焦工程设计,不提供任何具体自伤方式的描述。


📝 摘要

本文结构概览: 本文从一次真实模拟危机对话回放切入——当用户说出高危表达时,AI 必须在几毫秒内做出反应, 而不能等 LLM 慢悠悠返回。我们先剖析单 LLM 方案在心理危机场景的三重困境(慢、贵、不可靠), 再引入"纵深防御"理念,逐层讲解 L0 内存闸门、L1 独立分析、L2 独立复核、L3 用户关怀、L4 监护人告警的职责分工, 然后展示接线工厂 wire_mental_health 如何把这些组件装配成一个"缺配置即整体 inert"的零信任系统, 最后还原一次真实事故:配置缺段导致整套系统静默失效,以及我们如何从中学到"宁可不动,不可妄动"的配置纪律。

背景:绝大多数 Agent 产品关注"任务完成效率",而 WeClaw v9.0.0 开始关注"用户这个人本身是否安全"。 这一转变让 WeClaw 区别于其它 Agent 助手产品,也带来了一道硬核工程命题。

核心问题:如何给对话式 AI 装上一套既快、又准、还负责任的心理危机守护系统?

解决方案:L0–L4 分层治理——L0 纯内存关键词闸门(<1ms 零 IO)先升级,L1/L2 双独立 LLM 复核确认, L3 用户侧关怀话术,L4 监护人邮件告警;全程"缺段即 inert",绝不在用户不知情时偷偷跑心理分析。

关键成果

  • 五层职责清晰、可独立演进的分层治理架构,检测延迟从"秒级"压到"<1ms 先升级"
  • "缺段即 inert"零信任配置纪律:零 LLM 调用、零 DB 写入,宁可不动不可妄动
  • 96 个测试用例 + 3 语料集(召回 30 / 误报 52 / 红线 22)全程 2.10s 通过
  • 真实危机对话回放逐条评估,抓出并修复 3 个生产缺口

适合读者:AI 应用开发者、心理健康数字化产品从业者、关注 AI 安全与伦理的技术决策者

阅读时长:约 16 分钟

关键词心理危机检测分层治理纵深防御L0关键词闸门双重确认AI安全伦理零信任配置


一、为什么要"分层治理"?——从一次真实危机对话说起

1.1 场景重现:AI 只有几毫秒的反应窗口

想象这个场景(已脱敏的模拟危机对话回放,v9.0.0 实测):

  • 用户深夜发来一句:"最近真的好累,想从楼上飞下去。"
  • 几秒后又补了一句:"我不想麻烦任何人。"

此时 AI 面临一个生命安全级别的决策:

  1. 它必须立刻识别出这是高危信号,不能等;
  2. 它必须准确判断,不能把"今天累死了,代码写得我想跳楼"这种吐槽也当成危机(误报会摧毁信任);
  3. 它在回应时绝不能凭记忆编一个热线号码——编错的号码在危机场景等于谋财害命;
  4. 它必须在用户知情同意的前提下行动,而不是悄悄把聊天记录发给某个第三方。

问题出在哪?让我们看看如果用最直觉的"单 LLM 情感分析"方案,会遇到什么:

方案像什么?(比喻)适用场景局限性
单 LLM 情感分析每句话都请一位专家会诊低频、非紧急文本分类慢(秒级)、贵(每轮调用)、会幻觉
纯关键词匹配门口安检的金属探测门快速初筛误报高、无法理解语境
L0–L4 分层治理核电站的纵深防御体系生命安全级实时决策设计复杂,但每一层都不可替代

1.2 单 LLM 方案的三重困境

初学者常问:"直接让 LLM 判断每句话是不是危机不就行了?"

答案是:在生命安全场景,单 LLM 方案有三个无法回避的硬伤

困境一:慢。 LLM 一次往返动辄数百毫秒到数秒。而 USER_INPUT 事件是在 LLM 调用之前发射的—— 任何在事件回调里 await LLM 的订阅者,都会给每一轮对话叠加一次完整往返延迟。 危机念头不能等,普通对话更不该为心理分析买单。

困境二:贵。 如果每轮对话都调一次心理分析 LLM,token 成本会线性膨胀, 还会挤占主对话的预算。心理分析必须是"按需触发",而非"每轮必跑"。

困境三:不可靠(最致命)。 LLM 会幻觉。我们在实测中真实抓到过: 当系统处于 inert(未启用)状态时,主对话 LLM 在危机场景凭记忆生成了一个热线号码—— 这违反了心理危机资源的"零编造红线"。在生命安全场景,幻觉的代价是不可接受的。

# ❌ 反模式:在 USER_INPUT 回调里直接 await LLM
async def on_user_input(event):
    result = await llm.analyze(event.text)   # 每轮对话 +1 次完整往返延迟!
    if result.is_crisis:
        notify_guardian()                     # 单次判断即触发,误报摧毁信任

# ✅ WeClaw 做法:L0 纯内存判定 + fire-and-forget
async def on_user_input(event):
    decision = self._gate.evaluate(event.text)  # <1ms,零 IO,零 LLM
    if decision.escalate:
        # 升级才异步分析,且不阻塞主对话链路
        asyncio.create_task(self._pipeline(event.text, ...))

1.3 核心挑战:四个"既要又要"

现在我们手里有四个看似矛盾的需求:

  1. 既要快:危机念头必须在毫秒级被捕获;
  2. 又要准:不能把吐槽当危机,也不能漏掉隐晦的告别;
  3. 还要稳:用户没开启功能时,系统必须彻底安静;
  4. 更要有边界:不诊断、不处方、热线不编造、知情同意可撤回。

如何让它们像一个整体一样工作?

答案就是接下来要讲的 L0–L4 纵深防御体系——它的思想,直接来自核电站的安全工程。


二、核心概念解析 —— 用"核电站纵深防御"理解 L0–L4

2.1 什么是"纵深防御"(Defense in Depth)?

官方定义

纵深防御是一种安全设计哲学:通过设置多道独立、冗余的防护屏障, 使得任何单一屏障的失效都不会导致整体防护崩溃。

大白话解释: 不把鸡蛋放在一个篮子里。每一层都假设"上一层可能漏了",自己再守一道。

生活化比喻——核电站的安全壳:

核燃料 ─→ 燃料包壳 ─→ 压力容器 ─→ 安全壳 ─→ 应急冷却 ─→ 场外应急
         (第1道)    (第2道)    (第3道)   (第4道)    (第5道)

任何一道失效,下一道继续兜住。这就是"纵深防御"。

WeClaw 的心理危机守护,正是把这套思想搬进了对话式 AI:

用户文本 ─→ L0 内存闸门 ─→ L1 独立分析 ─→ L2 独立复核 ─→ L3 用户关怀 ─→ L4 监护人告警
            (第1道)      (第2道)       (第3道)       (第4道)       (第5道)
            <1ms 零IO      云端小模型       另一次独立推理    关怀话术注入     邮件外发
            高召回初筛      结构化评估       双critical才确认  共情+安全计划    10条前置校验

2.2 工作原理:一条消息的五层旅程

看图理解一条高危消息如何穿越五层:

┌────────────┐
│  用户输入   │  "想从楼上飞下去"
└─────┬──────┘
      │ USER_INPUT 事件 (priority=400)
      ▼
┌─────────────────────────────────────────┐
│ L0 内存闸门  L0Gate.evaluate()           │  ← 纯内存,<1ms,零 IO
│  · 长度过滤 (MIN_TEXT_LEN=8)             │
│  · 关键词命中 ("飞下去" ∈ DIRECT)         │
│  · LRU(256) 去重 + 排除表区间抵消          │
│  决策:escalate=True(升级,不下结论)      │
└─────┬───────────────────────────────────┘
      │ asyncio.create_task(fire-and-forget,不阻塞主对话)
      ▼
┌─────────────────────────────────────────┐
│ L1 独立分析  PsychAnalyzer.analyze()      │  ← 云端独立小模型
│  · glm-4.7-flash / 12s 超时 / temp 0.3   │
│  · 结构化输出 RiskLevel + confidence      │
│  · 8 维度预警信号 (IS PATH WARM)          │
└─────┬───────────────────────────────────┘
      ▼
┌─────────────────────────────────────────┐
│ L2 独立复核(另一次独立推理)              │  ← 不是复读 L1 的结果
│  · 双 critical 且 confidence≥0.85         │
│  · 才置 confirmed=True                    │
└─────┬───────────────────────────────────┘
      │ risk_level ≥ MEDIUM
      ▼
┌─────────────────────────────────────────┐
│ L3 用户关怀  MH_RISK_ESCALATED 事件        │  ← 关怀话术 + 安全计划
│  · 共情倾听 / 稳定化接地 / 危机资源引导     │
└─────┬───────────────────────────────────┘
      │ risk_level == CRITICAL and confirmed
      ▼
┌─────────────────────────────────────────┐
│ L4 监护人告警  CrisisGuard.handle_confirmed│  ← 邮件外发
│  · 10 条前置校验全过才发                   │
│  · 冷却 6h / 去重 24h / 日上限 2          │
└─────────────────────────────────────────┘

关键步骤

  1. L0 只升级、不下结论:关键词命中只是"拉响警报",判定权全部交给后面两次独立 LLM——这是核心安全反转。
  2. fire-and-forget:L0 之后用 asyncio.create_task 异步分析,主对话链路零等待。
  3. 双独立确认:L2 必须是"另一次独立推理",而非"复读 L1",两次都 critical 才算 confirmed。
  4. 逐级升档:medium+ 才进 L3 关怀,critical+confirmed 才进 L4 告警,绝不越级。

2.3 对比:单 LLM vs 分层治理

维度单 LLM 情感分析L0–L4 分层治理区别
首响延迟秒级(等 LLM)<1ms(L0 内存)危机捕获快 3 个数量级
每轮成本每轮必调升级才调普通对话零成本
误报控制单次判断双独立确认误报率显著下降
幻觉风险热线可能编造静态核验表注入零编造红线
隐私默认默认分析缺段即 inert零信任
容错性单点失效逐层兜底纵深防御

为什么选择分层治理? 因为在生命安全场景,快、准、稳、边界这四个需求无法用单一机制同时满足—— 只能用"每一层解决一个矛盾"的纵深防御来逐个击破!

📚 学术支撑:Olisaeloka 等(2026)在 GenAI 心理健康聊天机器人的安全机制研究中指出, 单一防护不足以应对生成式 AI 的风险面,需要多层冗余的安全护栏(safety guardrails)。 Brenner 等(2025)进一步提出"心理健康 AI 安全等级"(ASL-MH)框架, 主张能力越强的心理 AI 必须匹配越严格的安全约束——这正是 WeClaw"分层治理"的理论注脚。


三、实战代码详解 —— 接线工厂如何装配五层防线

3.1 装配入口:wire_mental_health

整个心理健康服务的装配,收口在一个"接线工厂"函数里。它在 GUI(gui_app.py)与 CLI(src/app.py)两个入口都被调用:

# src/core/mental_health/wire.py
def wire_mental_health(
    agent: Any,
    event_bus: EventBus,
    *,
    companion_engine: Any = None,
) -> MentalHealthService | None:
    """组装并启动心理健康服务;inert 时返回 None。"""
    global _SERVICE, _WIRE_CTX
    _WIRE_CTX = (agent, event_bus, companion_engine)

    settings = load_mh_settings()
    apply_topic_gate(companion_engine, settings.enabled)

    # ✅ 关键:总开关关闭 → 直接 inert,零 LLM 调用、零 DB 写入
    if not settings.enabled:
        logger.info("心理健康陪伴未启用([mental_health].enabled=false)")
        _SERVICE = None
        return None

    # ✅ 关键:客户端不可用 → 记一次 ERROR 后整体 inert(fail-close)
    client, reason = load_mental_health_client()
    if client is None:
        logger.error("心理健康服务进入 inert: %s", reason)
        _SERVICE = None
        return None
    ...

设计亮点

  1. 三道 inert 闸门:总开关关闭、客户端不可用、存储初始化失败——任何一道触发,整套系统返回 None,彻底安静。
  2. fail-close 而非 fail-open:客户端缺失时绝不用默认配置兜底。因为 fail-open 会把最敏感的心理文本,静默路由到一个非预期的模型提供商——这是隐私红线。

3.2 组件组装:五个对象,各司其职

通过 inert 闸门后,工厂开始组装五个核心对象:

# src/core/mental_health/wire.py(续)
storage = MentalHealthStorage()          # 持久化:mh_* 四表
snapshot = ProfileSnapshot()             # 内存画像快照(零 DB)

gate = L0Gate(                           # L0 内存闸门
    hmac_fn=storage.text_hmac,
    settings=settings,
    snapshot=snapshot,
    budget_exceeded_fn=lambda: client.is_over_budget,
)
analyzer = PsychAnalyzer(client, storage, settings, snapshot)  # L1/L2 分析

guard = CrisisGuard(                     # L4 监护人告警
    settings=settings,
    storage=storage,
    event_bus=event_bus,
    tool_registry=getattr(agent, "tool_registry", None),  # 显式注入
)

service = MentalHealthService(           # 服务编排(订阅事件、串联 L0→L4)
    event_bus, settings=settings, storage=storage,
    analyzer=analyzer, snapshot=snapshot, gate=gate, guard=guard,
)
service.start()

字段说明

  • gate(L0Gate):纯内存关键词闸门,第 75 篇详解
  • analyzer(PsychAnalyzer):L1 独立分析 + L2 独立复核,第 76 篇详解
  • guard(CrisisGuard):监护人告警 10 条前置校验,第 80 篇详解
  • service(MentalHealthService):事件订阅与 L0→L4 编排,本篇主线

为什么 tool_registry 要显式注入? 因为危机管道必须是一个独立服务,而不是某个陪伴引擎的附属品—— CompanionEngine 拿不到 tool_registry,所以这里必须从 agent 上显式取出来注入给 CrisisGuard。

3.3 服务编排:事件回调里的"零等待"纪律

MentalHealthService 的核心纪律写在它的 docstring 里——回调内只做 L0 + fire-and-forget,绝不 await LLM

# src/core/mental_health/service.py
async def _on_user_input(self, event_type: str, event: Any) -> None:
    """USER_INPUT 回调:L0 纯内存判定 + fire-and-forget。

    严禁在此 await 任何 LLM/DB 调用(emit 顺序 await 链上的固定延迟)。
    """
    self.ensure_boot_scheduled()
    text, session_id = _extract_text_session(event)
    if text:
        self._ring.append({"role": "user", "content": text[:500]})  # 环形缓冲

    decision = self._gate.evaluate(text)   # ✅ L0 纯内存,<1ms
    if decision.escalate:
        # ✅ 升级才异步分析,且不阻塞主对话链路
        task = asyncio.create_task(self._pipeline(text, session_id, decision))
        task.add_done_callback(self._pipeline_done)

代码解析

  • priority=400:订阅 USER_INPUT 时排在 CompanionEngine(priority=300)之后,确保陪伴引擎先处理。
  • self._ringdeque(maxlen=10) 内存环形缓冲,为 L2 复核提供近 10 轮上下文,全程零 DB
  • asyncio.create_task:把耗时的 L1/L2 分析丢进后台任务,主对话链路零等待。

为什么这么简单却这么重要? 因为 USER_INPUT 在 LLM 调用之前发射。如果这里 await 了 LLM, 就等于给用户的每一句话都强行插入一次心理分析的往返延迟——这是不可接受的。 "L0 禁止触碰 sqlite、禁止 await LLM"甚至被我们写进了测试断言

3.4 升级阶梯:L3 与 L4 的逐级升档

分析管道 _pipeline 拿到 L1/L2 的最终结果后,按风险等级逐级升档:

# src/core/mental_health/service.py
async def _pipeline(self, text, session_id, decision) -> None:
    final = await self._analyzer.analyze(text, session_id, list(self._ring))
    await self._emit_assessment(final)

    # L3 用户侧升级阶梯:medium+ 发 MH_RISK_ESCALATED
    if final.risk_level >= RiskLevel.MEDIUM:
        await self._bus.emit(EventType.MH_RISK_ESCALATED, MHRiskEscalatedEvent(...))

    # L4:双重确认 critical → MH_CRISIS_CONFIRMED + CrisisGuard
    if final.risk_level == RiskLevel.CRITICAL and final.confirmed:
        await self._bus.emit(EventType.MH_CRISIS_CONFIRMED, MHCrisisConfirmedEvent(...))
        if self._guard is not None:
            await self._guard.handle_confirmed(final, self._snapshot)

易错点:枚举比较的字典序陷阱

注意 final.risk_level >= RiskLevel.MEDIUM 这行——它依赖 RiskLevel 的比较运算符。 这里藏着一个我们真实踩过的生产 Bug(__lt__ 缺失导致退化为字典序),第 77 篇会完整复盘。 先看正确实现:

# src/core/mental_health/models.py
class RiskLevel(str, Enum):
    UNKNOWN = "unknown"; LOW = "low"; MEDIUM = "medium"
    HIGH = "high"; CRITICAL = "critical"

    @property
    def rank(self) -> int:
        return _RISK_RANK[self]   # UNKNOWN=0 ... CRITICAL=4

    # ✅ 四个比较运算符全部基于 rank,构成真正的全序(total order)
    def __ge__(self, other): return self.rank >= other.rank
    def __gt__(self, other): return self.rank > other.rank
    def __le__(self, other): return self.rank <= other.rank
    def __lt__(self, other): return self.rank < other.rank

最佳实践

  1. 枚举要做大小比较,要么四个运算符全定义,要么用 @functools.total_ordering永远别留半套
  2. RiskLevel(str, Enum) 继承自 str,一旦运算符缺失就会静默退化为字符串字典序——这种 Bug 不报错、难发现。

四、问题诊断与修复 —— 从"系统静默失效"到"缺段即 inert"

4.1 问题现象:监护人告警永远不触发

实测报告

"我配置了监护人邮箱,也开启了功能,但模拟了好几轮危机对话,监护人始终收不到告警邮件。 更奇怪的是——日志里一个报错都没有。"

服务日志

2026-08-02 16:11 | mental_health.wire | INFO | 心理健康陪伴未启用([mental_health].enabled=false)

奇怪:用户明明说"开启了功能",为什么 wire 读到的是 enabled=false?而且整套系统安静得像不存在!

4.2 根因分析:本地配置文件缺了整段

排查步骤

1️⃣ 检查配置模板config/default.toml.example 第 432-436 行确实有 [mental_health] 段,enabled = true

2️⃣ 检查本地实际配置:用户的 config/default.toml 里——根本没有 [mental_health] 这一段

3️⃣ 发现问题链路

default.toml.example(模板源,有段)
        │  用户升级时未同步
        ▼
default.toml(本地实际,缺段)
        │  load_mh_settings() 用 get+默认值
        ▼
settings.enabled = False(默认值)
        │  wire_mental_health 第一道闸门
        ▼
返回 None → 整体 inert(零 LLM、零 DB、零告警)

根本原因:本地配置文件缺少 [mental_health] 段,读取侧用"get+默认值"兜底为 enabled=False, 触发了第一道 inert 闸门——这其实是系统按设计在工作,但对用户来说就是"功能莫名失效"。

4.3 修复方案:同步配置 + 理解 inert 哲学

修复 1:从 example 同步整段到本地 default.toml

# config/default.toml(末尾追加)
[mental_health]
enabled = true                  # 总开关
guardian_alert_enabled = true   # 监护人告警二级独立开关
l1_daily_limit = 40             # L1 日调用上限(第一道熔断)
consecutive_negative_window_hours = 4
consecutive_negative_threshold = 3
alert_cooldown_hours = 6        # 告警冷却
alert_daily_cap = 2             # 告警日上限
alert_dedup_hours = 24          # 组合桶去重窗口
assessment_ttl_days = 90        # 评估记录 TTL
confirm_confidence = 0.85       # 双重确认置信阈值

修复 2:理解"为什么默认是 false"——这是刻意的零信任设计

# ✅ 读取侧一律 get+默认值,缺段即整体 inert
@dataclass
class MHSettings:
    enabled: bool = False                # 默认关闭
    guardian_alert_enabled: bool = False  # 默认关闭
    ...

这不是 Bug,而是安全设计:心理分析涉及用户最敏感的隐私文本。 如果默认开启,就等于在用户不知情的情况下偷偷分析其心理状态——这违背知情同意原则。 宁可让功能"看起来失效",也绝不在用户不知情时妄动。

验证结果

✅ 同步配置后:load_mh_settings() 读回 enabled=True
✅ wire_mental_health 返回有效 service 实例
✅ 危机对话回放:L0 升级 → L1/L2 确认 → L3 关怀注入正常
✅ 监护人告警链路恢复(仍需通过 10 条前置校验)

4.4 经验教训:学到了什么?

Checklist

  • 本地 default.toml 是否与 default.toml.example 的关键段保持一致?
  • 新增配置段时,读取侧是否一律用 get+默认值(而非强依赖段存在)?
  • 默认值是否倒向"更安全"的一侧(心理分析默认 false)?
  • inert 状态是否有明确的一次性日志(便于排查"为什么没生效")?

避坑指南

  1. 配置模板与本地配置脱节:example 是唯一模板源,本地 default.toml 停止跟踪(见项目 SCM 规范),升级时需主动同步新段。
  2. fail-open 的诱惑:客户端/配置缺失时,绝不用默认值"让功能跑起来"——心理文本路由错提供商是隐私事故。
  3. 静默失效最难排查:inert 设计虽好,但一定要留一条 INFO 日志说明"为什么 inert",否则用户和开发者都会一头雾水。

五、性能优化与最佳实践

5.1 性能瓶颈分析

心理危机守护的性能命门只有一个:绝不能拖慢主对话。我们来看各层的实测开销:

L0  L0Gate.evaluate()        : <1ms    (纯内存:长度+关键词+LRU+排除表)
L1  PsychAnalyzer L1 分析     : ~800ms  (云端小模型,仅升级时触发)
L2  独立复核                  : ~800ms  (另一次独立推理)
L3  关怀话术注入              : ~0ms    (prompt 组装时附带)
L4  CrisisGuard 告警          : ~200ms  (邮件外发,仅 critical+confirmed)

结论:L0 是唯一运行在每轮对话主链路上的层,必须做到 <1ms 零 IO; L1-L4 全部是"升级才触发"的后台任务,对普通对话零开销。

5.2 优化策略

策略 1:L0 纯内存,禁止触碰 sqlite

# ✅ L0 只做纯内存判定
decision = self._gate.evaluate(text)   # 长度/关键词/LRU/排除表/内存快照

# ❌ 反模式:L0 里查数据库
# decision = self._gate.evaluate_with_db(text)  # 每轮对话叠加一次磁盘 IO!

代价:画像数据需在启动时预热到内存快照(ProfileSnapshot)。 收益:L0 单条评估 <1ms,主对话链路零额外延迟。

策略 2:fire-and-forget 异步分析

# ✅ 升级才创建后台任务,主链路不等待
task = asyncio.create_task(self._pipeline(text, session_id, decision))
task.add_done_callback(self._pipeline_done)   # 异常隔离,不污染主链路

代价:分析结果异步到达,需要事件机制(MH_* 事件)解耦。 收益:即便 L1/L2 各耗时 800ms,用户也完全无感知。

策略 3:LRU 近重复去重 + 预算熔断

# LRU(256) 缓存:相同文本不重复升级
# 预算熔断:L1 日调用上限 40 次 + 金额预算,超限短路
if decision.suppress_reason in ("daily_cap", "budget"):
    # 短路也落 tier=L0,unknown 记录,保证审计链完整
    asyncio.create_task(self._record_l0_suppressed(...))

代价:需要维护 LRU 缓存与持久化日计数。 收益:防止恶意刷屏触发海量 LLM 调用,成本可控。

5.3 最佳实践总结

Do's(推荐做法):

  • ✅ 主链路只做纯内存判定,重活全部 fire-and-forget
  • ✅ 配置读取一律 get+默认值,默认值倒向更安全的一侧
  • ✅ 降级绝不向"更容易外发"的方向倒(超时/失败 → unknown,不外发)
  • ✅ 每一次短路/抑制都落审计记录,保证链路可追溯

Don'ts(避免做法):

  • ❌ 在事件回调里 await LLM 或 sqlite
  • ❌ 用关键词单独判定 critical(关键词只升级,不下结论)
  • ❌ 配置/客户端缺失时 fail-open 兜底
  • ❌ 让心理分析挤占主对话的 token 预算

黄金法则

在生命安全场景,快靠内存、准靠复核、稳靠 inert、边界靠红线—— 四个矛盾,四层解决,谁也别替谁背锅。


六、总结与展望

6.1 核心要点回顾

本文讲解了 WeClaw 心理危机守护的分层治理架构

3 个关键点

  1. 单 LLM 方案扛不住生命安全场景:慢、贵、会幻觉三重困境,必须分层。
  2. L0–L4 纵深防御:L0 内存闸门(快)→ L1/L2 双独立确认(准)→ L3 关怀(暖)→ L4 告警(兜底),每层解决一个矛盾。
  3. 缺段即 inert 的零信任纪律:宁可不动,不可妄动;默认关闭是对用户隐私的尊重。

1 个核心公式

负责任的心理守护 = 内存级速度(L0) + 双重独立确认(L1/L2) + 零编造红线(L3) + 知情同意告警(L4)

6.2 下一步学习方向

前置知识

  • ✅ Python asyncio 事件循环与 create_task
  • ✅ 事件总线(EventBus)发布订阅模型
  • ✅ Enum 比较运算符协议

后续主题(模块八导读地图):

  • 📖 下一篇 75:《L0 关键词闸门:<1ms 零 IO 的危机念头第一道防线》
  • 🔜 76:《L1–L2 双重确认:为什么一个 LLM 说了不算?》
  • 🔜 77:《RiskLevel 全序 Bug:一个缺失的 __lt__ 如何让告警链路静默失效》
  • 🔜 78:《危机资源零编造红线:当 AI 凭记忆"编"出一个不存在的热线号码》
  • 🔜 79:《词表校准与误报治理:从实测回放看召回率与误报率的平衡术》
  • 🔜 80:《L4 监护人告警:10 条前置校验如何避免骚扰式告警》
  • 🔜 81:《伦理边界与 AI 安全等级:能力越强护栏越硬》
  • 🔜 82:《校准测试框架:3 语料集 + live 门控持续校准》

扩展阅读

  • 系列第 60 篇:《能力越强,护栏越硬:AI 自主进化的安全边界工程与核能类比》
  • 系列第 63 篇:《确定性约束引擎:从 Prompt 引导到代码级强制的范式转变》
  • 系列第 21 篇:《主动陪伴引擎实战:五大组件如何协力决策何时和说什么》

6.3 互动环节

思考题

  1. 为什么 L0 关键词命中只能"升级"而不能"下结论"?如果让关键词直接判定 critical,会引入什么风险?
  2. "缺段即 inert"让功能可能"看起来失效",你认为这是否值得?如何在"安全默认"与"开箱即用"之间权衡?

讨论话题

如果你的 AI 助手也遇到"用户深夜发来高危表达"这个场景,你会怎么设计检测与响应链路? 单 LLM、纯关键词、还是分层治理?欢迎在评论区分享你的方案与顾虑。


下期预告:《WeClaw_75|L0 关键词闸门:<1ms 零 IO 的危机念头第一道防线》

  • 三类词表分工:DIRECT / IMPLICIT / MOOD 如何各司其职
  • NEGATIVE_LOOKALIKE 区间抵消:为什么"我不想死"不能简单命中"想死"
  • 中文子串匹配 vs 英文 casefold:全大写"I WANT TO DIE"如何被捕获

敬请期待!


附录 A:完整代码清单

文件路径作用
src/core/mental_health/wire.py接线工厂:装配 + inert 闸门 + 热重连
src/core/mental_health/service.py服务编排:事件订阅 + L0→L4 管道
src/core/mental_health/analyzer.pyL0 闸门 + L1/L2 云端分析
src/core/mental_health/models.py领域模型:RiskLevel 全序 + 评估契约
src/core/mental_health/security.pyCrisisGuard:监护人告警 10 条校验
src/core/mental_health/storage.py持久化:mh_* 四表
config/default.toml[mental_health] 配置段(11 键)

关键方法wire_mental_health / _on_user_input / _pipeline / L0Gate.evaluate / RiskLevel.__lt__ 测试用例:96 passed + 3 skipped(live 门控),全程 2.10s


附录 B:参考文献(APA 7)

  1. Brenner, G. H. (2025). Toward a framework for AI safety in mental health: Artificial Intelligence Safety Levels for Mental Health (ASL-MH). NeuroModec.
  2. Holmes, et al. (2025). Applications of large language models in suicide prevention: A scoping review. ScienceDirect, S1438887125001098.
  3. Olisaeloka, L., et al. (2026). Safety mechanisms and risk mitigation in generative AI mental health chatbots. MDPI Healthcare, 14(10), 1395.
  4. Thomas, et al. (2025). LLM performance vs human expert crisis text lines. Nature Scientific Reports, s41598-025-22402-7.
  5. Weber, et al. (2026). Suicide- and crisis-risk detection using LLMs in mental-health chatbots. medRxiv, 2026.01.12.26343914.
  6. 上一篇:《WeClaw_73|向量库重建实战:维度变更、10000 字符截断陷阱与文件锁》
  7. 下一篇:《WeClaw_75|L0 关键词闸门:<1ms 零 IO 的危机念头第一道防线》

🆘 危机求助资源(静态核验) 如果你或身边的人正处于心理危机中,请立即联系以下经核验资源:

  • 全国心理援助热线:988
  • 心理援助热线:12356
  • 北京心理危机研究与干预中心:010-82951332
  • 境外用户:请拨打当地急救电话或前往就近医院急诊

这些号码与 WeClaw 代码中 data/mental_health/crisis_resources.json 保持一致,季度核验。


版权声明:本文为 CSDN 博主「yweng18」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接https://blog.csdn.net/yweng18