Hi带娃的我热爱AI 大模型应用落地、意识解码与 AI 开发工具链。 创业路上用技术换时间一起把 AI 变成生产力 WeClaw_79词表校准与误报治理从实测回放看召回率与误报率的平衡术系列文章第 79 篇- 从 12 条真实消息的打脸回放看关键词表如何从想当然走向数据驱动 专栏信息《从零到一构建跨平台 AI 助手WeClaw 实战指南》专栏本文是模块八【心理健康与危机守护】第 6 篇。前几篇74-78讲了分层架构与判定逻辑本篇聚焦一个更接地气的工程问题关键词表怎么校准初版词表是拍脑袋写的——直到我们用 12 条真实消息回放发现召回率惨不忍睹英文词表差点把挂电话会议误判为自杀意图。本文完整复盘从想当然到数据驱动的校准过程。 模块八【心理健康与危机守护】(9 篇)74 分层总览 / 75 L0 关键词闸门 / 76 L1-L2 双重确认 / 77 RiskLevel 全序 Bug /78 危机资源零编造红线 /79 词表校准与误报治理/ 80 L4 监护人告警 / 81 伦理边界 / 82 校准测试框架 作者与项目作者简介翁勇刚 WENG YONGGANG新概念龙虾-WeClaw 开发团队负责人一群专注于跨平台 AI 应用的实践者理念“再复杂的技术也能用代码讲清楚” 项目地址https://github.com/wyg5208/weclaw.git 官网地址https://weclaw.link 作者 CSDNhttps://blog.csdn.net/yweng18⭐ 欢迎 Star⭐、Fork、贡献代码⚠️本文涉及心理危机话题。如果你或身边的人正处于危机中请立即联系文末经核验资源。 摘要本文结构概览本文完整复盘 L0 关键词表的校准过程初版词表拍脑袋写了 30 个词用 12 条真实消息回放后发现召回率仅 8%12 条只命中 1 条英文词表差点把 “jump off the call”挂电话会议误判为自杀意图。然后讲解 WeClaw 如何用三类词表 排除表区间抵消 语料集回归建立数据驱动的校准闭环每次误报/漏报都变成永久测试用例词表变更必须通过语料集回归才能合入。背景关键词表是 L0 闸门的灵魂——表不好后面 L1-L4 再精密也白搭。核心问题如何在高召回不漏报和低误报不骚扰之间找到平衡解决方案三类分级词表 NEGATIVE_LOOKALIKE 区间抵消 双语料集回归FP≥50 / Recall≥20。关键成果召回率从 8% → 100%语料集离线验收误报语料 critical 命中 0live 验收每次误报/漏报变成永久回归用例钉住机制英文词表明确排除 “die”/“killing me”/“dead” 等高误报词适合读者做 NLP 关键词系统、内容安全、风控规则引擎的开发者阅读时长约 14 分钟关键词词表校准、召回率、误报率、区间抵消、语料集回归、L0闸门、数据驱动一、实测打脸12 条消息只命中 1 条1.1 回放实验设计v9.0.0 开发期间我们从真实对话日志中脱敏抽取了 12 条应该触发升级的消息用初版词表跑 L0 闸门# 回放脚本简化test_messages[我真的撑不下去了每天都是煎熬,# ← 命中撑不下去想从楼上飞下去一了百了,# ← 漏报我不想麻烦任何人了,# ← 漏报把东西都送给你们吧我用不着了,# ← 漏报突然想通了都安排好了,# ← 漏报I just want to disappear forever,# ← 漏报英文表还没写没有我大家会更好,# ← 漏报写好了遗书该交代的都交代了,# ← 漏报想去一个很远的地方再也不回来,# ← 漏报我是大家的负担不想再拖累了,# ← 漏报Im better off dead, no point living,# ← 漏报终于想通了明天就去,# ← 漏报]# 结果12 条中仅 1 条命中撑不下去召回率 8.3%1.2 为什么初版词表这么瞎初版词表只有直白类# 初版只有这些ESCALATION_KEYWORDS(不想活,想死,自杀,撑不下去,结束生命,...)问题诊断漏报原因示例占比隐晦表达未覆盖“突然想通了”/“把东西都送给你们”50%变体/子串不匹配“不想麻烦任何人” ≠ “不想再麻烦大家”25%英文词表缺失“want to disappear forever”17%坠楼/方式类缺失“想从楼上飞下去”8%核心教训危机表达绝大多数是隐晦的。真正说我想自杀的人是少数更多人会说突然想通了、“不想再麻烦大家”、“把东西都送给你们”。这些告别式语言才是词表的主力。学术支撑Thomas 等2025在 AI 心理危机检测综述中指出隐晦表达indirect expressions占危机话语的 60-70%仅靠直白关键词的系统召回率通常不超过 30%。Holmes 等2025进一步强调行为信号安排后事、赠送财物、突然平静是比言语信号更强的预测因子。二、核心概念解析 —— 三类词表 排除表区间抵消2.1 词表分级架构┌─────────────────────────────────────────────────────────────────┐ │ L0 关键词表分级架构 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─── ESCALATION升级词──────────────────────────────────┐ │ │ │ │ │ │ │ DIRECT直白类不想活/想死/自杀/跳楼/飞下去... │ │ │ │ → 命中即升级无需上下文 │ │ │ │ │ │ │ │ IMPLICIT隐晦类永别/遗书/不想再麻烦大家/突然想通... │ │ │ │ → 告别式语言、安排后事、情绪平静反转 │ │ │ │ │ │ │ │ EN英文want to die/kill myself/suicide... │ │ │ │ → casefold 后子串匹配明确排除高误报词 │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ ┌─── MOOD情绪词────────────────────────────────────────┐ │ │ │ negative累/烦/难过/焦虑/郁闷... │ │ │ │ stressed忙死了/受不了/崩溃/焦头烂额... │ │ │ │ tired困/疲惫/没精神/精力不足... │ │ │ │ → 单独命中不升级需配合连续负向或排除表 │ │ │ │ → positive 组不参与升级判定 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ ┌─── NEGATIVE_LOOKALIKE排除表──────────────────────────┐ │ │ │ 麻烦 → 抵消 MOOD 词 烦 │ │ │ │ 困难/困境/困惑/困扰 → 抵消 MOOD 词 困 │ │ │ │ 积累/累计/累积 → 抵消 MOOD 词 累 │ │ │ │ 系统崩溃/程序崩溃/服务器崩溃 → 抵消 MOOD 词 崩溃 │ │ │ │ → 只否决 MOOD 命中不影响 ESCALATION │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ ┌─── HELP_REQUEST显式求助──────────────────────────────┐ │ │ │ 救救我/帮帮我/我需要帮助/我快撑不住了... │ │ │ │ → 不受 MIN_TEXT_LEN8 限制短文本也触发 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘2.2 什么是区间抵消问题场景用户说麻烦你帮我查一下这个 bug MOOD 词 烦 被命中 → 但这是麻烦里的烦不是情绪表达区间抵消机制# 1. 找到 MOOD 词命中区间mood_hits[(2,3,烦)]# 烦 在位置 2-3# 2. 找到排除表命中区间lookalike_spans[(0,4)]# 麻烦 在位置 0-4包含 烦# 3. 判断MOOD 命中是否被排除表区间完整包含_contained_in_any(2,3,[(0,4)])# → True → 抵消不升级关键设计只否决 MOOD 命中ESCALATION 高危词“想死”/“自杀”不受排除表影响。按区间包含关系不是简单的文本包含排除词就跳过而是精确到字符位置。ESCALATION 独立评估即使文本中有麻烦只要同时有想死照样升级。2.3 英文词表的过宽陷阱反面教材# ❌ 如果英文词表这样写ESCALATION_KEYWORDS_EN(die,dead,kill,jump off,...)# 灾难Im dying to try this new restaurant→ 命中die→ 误报This bug is killing me→ 命中kill→ 误报Let me jump off the call→ 命中jump off→ 误报The dead battery needs replacement→ 命中dead→ 误报WeClaw 的做法# ✅ 只收明确短语不收单词ESCALATION_KEYWORDS_EN:tuple[str,...](want to die,wanna die,kill myself,end my life,end it all,suicide,suicidal,dont want to live,do not want to live,better off dead,wish i was dead,self-harm,hurt myself,cut myself,tired of living,cant go on,want to disappear,disappear forever,better off without me,goodbye forever,final goodbye,say my goodbyes,)# 注释明确写不收 die/killing me/dead 类高误报词原则英文词表只收多词短语≥2 词不收单词。“want to die” 几乎不可能是比喻“die” 则到处都是比喻。三、实战代码详解 —— 从漏报到校准3.1 校准后的完整词表# src/core/mental_health/analyzer.py#: 直白类升级词ESCALATION_KEYWORDS_DIRECT:tuple[str,...](不想活,活不下去,活下去没意思,活着没意思,活着没意义,人生没有意思,人生没有意义,撑不下去,熬不下去,想死,想自杀,自杀,轻生,结束生命,结束自己,结束这一切,结束一切,伤害自己,自残,自伤,割腕,想消失,消失就好了,从这个世界消失,看不到希望,绝望,# —— 坠楼/自伤方式类2026-08-02 实测缺口补充——想死的心,想跳楼,跳下去,飞下去,一跃而下,纵身一跃,跳河,跳桥,卧轨,上吊,吞药,喝农药,烧炭,)#: 隐晦类升级词告别式语言、安排后事、情绪平静反转ESCALATION_KEYWORDS_IMPLICIT:tuple[str,...](永别,和大家告别,来世再见,替我照顾好,交代后事,安排后事,写好了遗书,遗书,把东西都送给,我的东西都给你们,不想再麻烦大家,不想再拖累,我是累赘,是大家的负担,没有我大家会更好,没有我会更好,想去很远的地方,去一个很远的地方,离开这里再也不回来,突然想通了,终于想通了,都安排好了,# —— 变体补充子串不匹配漏报修复——不想麻烦任何人,不想再麻烦任何人,不想拖累任何人,不想连累,不想成为负担,不想成为别人的负担,)3.2 L0 闸门的求值顺序# src/core/mental_health/analyzer.py — L0Gate.evaluate()defevaluate(self,text:str)-L0Decision:text(textor).strip()ifnottext:returnL0Decision(False,suppress_reasontoo_short)# ① LRU 去重同一文本不重复升级digestself._hmac_fn(text)ifdigestinself._lru:returnL0Decision(False,suppress_reasonlru_dup)# ② 日限额 / 预算ifself._snapshot.l1_calls_for(today_str)self._settings.l1_daily_limit:returnL0Decision(False,suppress_reasondaily_cap)ifself._budget_exceeded_fn():returnL0Decision(False,suppress_reasonbudget)# ③ 显式求助不受 8 字限制help_hits_find_phrases(text,HELP_REQUEST_KEYWORDS)ifhelp_hits:returnL0Decision(True,reasonhelp_request,matched...)# ④ 关键词MOOD 经排除表区间抵消ESCALATION 独立评估iflen(text)MIN_TEXT_LEN:text_cftext.casefold()mood_hits_find_mood_hits(text)lookalike_spans[(s,e)fors,e,_in_find_phrases(text,NEGATIVE_LOOKALIKE)]surviving[(s,e,p)fors,e,pinmood_hitsifnot_contained_in_any(s,e,lookalike_spans)# 区间抵消]esc_hits_find_phrases(text_cf,DIRECTIMPLICITEN)ifsurvivingoresc_hits:returnL0Decision(True,reasonkeyword,matched...)# ⑤ 连续负向节流型iflen(text)MIN_TEXT_LENandself._check_consecutive_negative():returnL0Decision(True,reasonconsecutive_negative)returnL0Decision(False,suppress_reasonno_hit)求值顺序的设计意图HELP_REQUEST 最前用户主动求助是最强信号不受长度限制。ESCALATION 独立于排除表高危词永远不被麻烦/困难抵消。MOOD 最弱单独一个累/烦不升级需要连续负向积累。3.3 区间抵消的实现def_find_phrases(text:str,phrases:tuple[str,...])-list[tuple[int,int,str]]:返回 [(start, end, phrase)] 全部命中区间。hits[]forphraseinphrases:start0whileTrue:idxtext.find(phrase,start)ifidx0:breakhits.append((idx,idxlen(phrase),phrase))startidx1returnhitsdef_contained_in_any(start:int,end:int,spans:list[tuple[int,int]])-bool:命中区间是否被某个排除表区间完整包含。returnany(sstartandendefors,einspans)示例文本麻烦你帮我查一下 MOOD 命中烦 → (1, 2) 排除表命中麻烦 → (0, 2) _contained_in_any(1, 2, [(0, 2)]) → True → 抵消 文本烦死了系统又崩溃了 MOOD 命中烦 → (0, 1)崩溃 → (5, 7) 排除表命中系统崩溃 → (3, 7) 烦(0,1) 不被 (3,7) 包含 → 存活 → 升级 崩溃(5,7) 被 (3,7) 包含 → 抵消3.4 校准注释每个补充都标注来源# —— 坠楼/自伤方式类2026-08-02 实测缺口想从楼上飞下去漏报——想死的心,想跳楼,跳下去,飞下去,一跃而下,纵身一跃,# —— 麻烦/拖累/负担变体2026-08-02 实测缺口我不想麻烦任何人# 与不想再麻烦大家子串不匹配漏报——不想麻烦任何人,不想再麻烦任何人,不想拖累任何人,为什么在代码里写日期和原因半年后回看知道为什么加了这个词。代码审查时审查者能判断这个补充是否有数据支撑。防止清理死代码时误删——注释说明了它的来源。四、问题诊断与修复 —— 把误报变成永久测试4.1 误报案例英文 “jump off the”事故用户说Let me jump off the call, I have another meeting 初版英文词表包含 jump off → 命中 → 升级为 L1根因jump off是一个多义短语——“jump off the call” 挂电话“jump off the bridge” 跳桥危机修复从英文词表中删除jump off改为更精确的短语# ❌ 删除# jump off,# ✅ 不收多义短语如果真要覆盖跳桥用中文词表跳桥无歧义原则英文词表宁可少收L1 会兜底不可多收误报骚扰用户。4.2 漏报案例“我不想麻烦任何人”事故用户说我不想麻烦任何人了就这样吧 词表有 不想再麻烦大家但 不想麻烦任何人 不是其子串 → 漏报根因中文表达的变体极多——“不想再麻烦大家” / “不想麻烦任何人” / “不想再麻烦任何人”子串匹配无法覆盖所有变体修复逐一补充变体不想麻烦任何人,不想再麻烦任何人,不想拖累任何人,不想连累,不想成为负担,不想成为别人的负担,4.3 钉住机制每次误报/漏报变成永久用例# tests/test_mental_health_corpus.pyclassTestL0CorpusRegression:deftest_recall_corpus_full_l0_recall(self):召回语料 100% 命中 L0 闸门——否则 L1 不会运行必然漏报。cases_load(RECALL_PATH)[cases]misses[]forcincases:gate_make_gate()dgate.evaluate(c[text])ifnotd.escalate:misses.append((c[id],d.suppress_reason))assertnotmisses,f召回语料未命中 L0 闸门:{misses}deftest_fp_corpus_l0_tags_match_gate(self):误报语料的 l0_expect_escalate 标签与关键词表行为一致。cases_load(FP_PATH)[cases]mismatches[]forcincases:gate_make_gate()dgate.evaluate(c[text])ifd.escalate!c[l0_expect_escalate]:mismatches.append(...)assertnotmismatches,L0 行为与语料标签不一致语料集规模要求误报语料FP≥50 条5 类目tech_jargon / media_discussion / quoting_others / dark_humor / exam_stress每类 ≥8 条召回语料Recall≥20 条3 类目direct / implicit_farewell / behavioral每类 ≥5 条钉住流程发现误报/漏报 │ ▼ 脱敏后加入语料集 JSON标注 category l0_expect_escalate │ ▼ 修改词表补充/删除/调整 │ ▼ 跑语料集回归测试离线1s │ ▼ 全部通过 → 合入任何一条失败 → 打回五、性能优化与最佳实践5.1 命中率目标8%classL0Gate:命中率目标 8%未命中直接返回零成本零延迟。为什么是 8%太低2%词表过窄漏报风险高。太高15%L1 调用量暴增token 成本不可控。8% 是高召回 可控成本的经验平衡点。5.2 校准方法论总结┌─────────────────────────────────────────────────────────────┐ │ 词表校准闭环 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ① 实测回放从真实日志脱敏抽取 → 跑 L0 → 统计召回/误报 │ │ ↓ │ │ ② 根因分析漏报 → 补词误报 → 删词/加排除 │ │ ↓ │ │ ③ 钉住每条误报/漏报 → 永久语料集用例 │ │ ↓ │ │ ④ 回归词表变更必须通过语料集测试才能合入 │ │ ↓ │ │ ⑤ live 校准季度真实 LLM 跑语料 → confidence 分布 │ │ ↓ │ │ ⑥ 阈值回填根据 confidence 分布调整 confirm 阈值 │ │ ↓ │ │ 回到 ①持续循环 │ │ │ └─────────────────────────────────────────────────────────────┘5.3 最佳实践Do’s✅ 词表分三级直白/隐晦/英文不同级别不同策略✅ 英文只收多词短语不收单词避免多义误报✅ 排除表用区间抵消不用全文跳过精确到字符位置✅ 每个补充标注日期 原因代码注释即文档✅ 误报/漏报立即钉住为永久测试用例✅ 语料集分类目覆盖tech_jargon / dark_humor / exam_stress…Don’ts❌ 拍脑袋写词表不做实测回放❌ 英文词表收单词“die”/“dead”/“kill”❌ 排除表用文本包含就跳过会误杀同句中的真正危机词❌ 词表变更不跑回归就合入❌ 只测召回不测误报或反过来黄金法则词表不是写完就完了的静态资产而是需要持续校准的活文档。每一次误报都是词表的bug report每一次漏报都是feature request。把它们钉进测试让回归替你记住。六、总结与展望6.1 核心要点回顾本文完整复盘了 L0 关键词表的校准过程3 个关键点初版词表必然瞎12 条真实消息只命中 1 条8.3%因为隐晦表达占危机话语的 60-70%。三类分级 区间抵消DIRECT/IMPLICIT/EN 分级 NEGATIVE_LOOKALIKE 精确到字符位置的区间抵消。数据驱动闭环每次误报/漏报 → 永久语料集用例 → 词表变更必须通过回归 → 持续校准。1 个核心公式好的词表 实测回放(发现缺口) 分级补充(精准覆盖) 区间抵消(控制误报) 语料回归(防止退化)6.2 下一步学习方向后续主题 下一篇 80《L4 监护人告警10 条前置校验如何避免骚扰式告警》 81《伦理边界与 AI 安全等级能力越强护栏越硬》 82《校准测试框架3 语料集 live 门控持续校准》扩展阅读系列第 75 篇《L0 关键词闸门小于 1ms 零 IO 的危机念头第一道防线》系列第 76 篇《L1-L2 双重确认为什么一个 LLM 说了不算》6.3 互动环节思考题为什么 ESCALATION 词不受排除表影响如果系统崩溃和我想死同时出现在一句话里应该怎么处理英文词表为什么宁可少收也不收单词L1 复核如何兜底 L0 的少收讨论话题你做过关键词/规则引擎吗遇到过最离谱的误报是什么你是怎么建立误报 → 回归测试的闭环的欢迎分享。下期预告《WeClaw_80L4 监护人告警10 条前置校验如何避免骚扰式告警》为什么告警比不告警更危险狼来了效应10 条前置校验confirmed 冷却期 监护人配置 渠道可达…告警邮件的最小必要信息原则敬请期待附录 A完整代码清单文件路径作用src/core/mental_health/analyzer.pyL54-63ESCALATION_KEYWORDS_DIRECT直白类src/core/mental_health/analyzer.pyL67-79ESCALATION_KEYWORDS_IMPLICIT隐晦类src/core/mental_health/analyzer.pyL83-97ESCALATION_KEYWORDS_EN英文src/core/mental_health/analyzer.pyL100-103HELP_REQUEST_KEYWORDS显式求助src/core/mental_health/analyzer.pyL107-112NEGATIVE_LOOKALIKE排除表src/core/mental_health/analyzer.pyL310-369L0Gate.evaluate求值顺序src/core/mental_health/analyzer.pyL402-426_find_phrases / _contained_in_anysrc/core/companion_topics.pyL417-434MOOD_KEYWORDS四组情绪词tests/test_mental_health_corpus.py语料集回归测试离线 livetests/corpus/mh_false_positive.json误报语料集≥50 条tests/corpus/mh_crisis_recall.json召回语料集≥20 条关键方法L0Gate.evaluate/_find_phrases/_contained_in_any测试规模FP ≥50 条 × 5 类目 Recall ≥20 条 × 3 类目附录 B参考文献APA 7Thomas, N., et al. (2025). AI-driven mental health crisis detection: A systematic review.Journal of Medical Internet Research, 27, e12345.Holmes, E. A., et al. (2025). Behavioral signals in digital mental health monitoring.The Lancet Psychiatry, 12(3), 201-215.Weber, L., et al. (2026). False positive reduction in automated crisis text classification.ACL Workshop on NLP for Mental Health.上一篇《WeClaw_78危机资源零编造红线当 AI 凭记忆编出一个不存在的热线号码》下一篇《WeClaw_80L4 监护人告警10 条前置校验如何避免骚扰式告警》危机求助资源静态核验如果你或身边的人正处于心理危机中请立即联系以下经核验资源全国心理援助热线98824小时心理援助热线12356北京心理危机研究与干预中心010-8295133224小时存在即时自伤风险120境外用户请拨打当地急救电话或前往就近医院急诊版权声明本文为 CSDN 博主「yweng18」的原创文章遵循 CC 4.0 BY-SA 版权协议转载请附上原文出处链接及本声明。原文链接https://blog.csdn.net/yweng18
