Skip to content

短语深度模式(Deep Phrase Mode)— §9 LLM occurrence 判断 plan

物理仓库:RB(/Users/larry/reading-browser)。纯 RB 侧、无 RVH(content-script reader 二段 + 一个 edge function + opt-in 隐私门)。 会话边界:单开 RB 会话实施(本 plan 是 bootstrap 真相源)。 创建:2026-06-26(短语功能战略转向后定为主线)。 前置真相源:phrase-noncompositionality-tiering-crossend-plan.md §9(设计原型)+ §gate-B(flash 92.9% 实测); phrase-auto-highlight-thinslice-plan.md §gate-A 复测(28%→7%,floor 已就位);backlog.md §短语 B 🧭 战略结论。

🔁 本 plan 的 floor/ceiling 两层模型已于 2026-06-28 被 phrase-unified-llm-refactor-plan.md 取代 (转为单层「AI 短语分析」:判完才上色、合并两个 judge、删 floor 静态档)。本文 §P1 落地状态 + gate-C ① + §2.1/§2.2 迭代记录保留作审计(已 ship 的 P1 是新模型的起点);后续实施走新 plan,不再按本文 floor/ceiling 推进。


✅ P1 落地状态(2026-06-26)

代码全落地 + edge function 已部署 + 实机 gate-C ① 已跑(precision 87%,下方)。实机暴露并修复 1 个 bug (judge_phrases_batch 误带 AND deleted_at IS NULL——vocabulary 共享词典无此列,修后复测无报错)。

状态锚点
Edge function judge-phrases-batch(batch 二分类,复用 _shared/llm.ts + explain-phrase 骨架,flash 单次批量)✅ 部署到 jdtbyteiwnciqnfppztzsupabase/functions/judge-phrases-batch/index.ts
Rust 命令 judge_phrases_batch(收 items → 从 pos_definitions 取主习语义 gloss 入 batch → anon_key 调 edge → graceful)✅ 注册 lib.rs,cargo check 通过src-tauri/src/commands/supabase.rs
content-script deepResolvePhrasesOnPage()(context 桶救回 + per-page 内存缓存 + data-rb-phrase-deep 标记 + cap40 + withObserverPaused 应用)✅ index.js 二段 fire-and-forget 接线(floor 后、不阻塞)src-tauri/src/content-script/features/phrase-highlight.js
opt-in 开关 phrase_deep_mode(Switch + ConfirmDialog 仿 context_disambiguation;zh/en/facade strings;init_data seed false;settings.js batch key + state.phraseDeepMode)pnpm build 通过,i18n 双守门 0 违规GeneralSettings.tsx

部署 smoke test(curl 占位验证,2026-06-26)——同一短语 occurrence 级判别正确:

  • in the black @ 财经句 → idiomatic:true ✓;@ "in the black coat" → false ✓(floor 做不到的按句判别)
  • to die for @ 甜点句 → true

gate-C ① 实机下钻(2026-06-26,单篇文学随笔 Longreads "Living in an Alive World",opted-in)

数据来源:精度侧 = DOM 实扫 23 个高亮 span(完整);召回侧 = 临时埋点 dump 拒绝集语境(已撤回)。 单页样本(gate-A 同量级),category 分布稳健,精确计数随 cap 抽样/版本略有浮动,故部分标「约」。

总结论:引擎(occurrence 二分类)本身好——precision 87% / recall ~85%、同短语跨句能分对。拖后腿的 不是 LLM,而是它上游(matcher 过度匹配)和喂进去的料(tier 质量 + gloss/prompt 边角)。下一步重心 = 「降噪 + 喂准」,非换模型。

第 1 层 · 漏斗context 候选 65 → cap40 丢 25 →(判 40)→ TRUE 23 高亮 / FALSE 17 拒

  • cap 丢 25(38%)= 隐藏 recall 漏(这 25 个从未被判,里面可能有真习语);根因 = 候选被 matcher 噪声撑爆。

第 2 层 · 高亮的 23 个(precision = 20/23 = 87%)

细类分布归因下一步
2A 高价值真习语 ✅14(61%)look up/come up/turn out/to do with×3/make up/win out/speak to/hang out/point out/set out×2/work on命中靶心,floor 此页露 0无需动(价值证明)
2B 透明低价值 PV ⚠️6(26%)slow down/speed up/get up/pass by/cut out/not so much非 LLM 错:PV 义对、但太透明=噪声。根因 = RVH tier 把高组合 PV 错放进 contextRVH demote context→literal(行动 3)
2C 硬 FP ❌3(13%)go to×2(go to a window)/push on(pushing on env)LLM 真误判,失败模式集中=位移/物理 PV 的字面位置用法prompt 外科规则 + 精准 gloss(行动 2)

第 3 层 · 拒掉的 17 个(recall ~85%,最大发现在此)

细类分布归因下一步
3A matcher 过度匹配,正确清掉 ✅约 10:work on(work=名词)/key in(key=形容词)/in order(to)/mean to(means to)/listen in/good sense/time in/other side/call it/have getmatcher 按 lemma 串相等匹配、不分 POS/句法 → 把"名词 work+on"当 PV。LLM 替它兜底RB 预过滤降噪(行动 1,治本、连带救 cap 漏)
3B 真 PV 字面用法,正确拒绝 ✅约 4:look down/pull away/look out/be there + 教科书级 for the bird→"for the birds[真鸟]"deep mode 本职——同短语字面 occurrence 判掉无需动(引擎正确性正面证据)
3C 漏报 FN ❌2 清晰(on track=回正轨 / catch up="caught up in"=入迷)+ 3 边缘(let in/cut off/come to)LLM 偏保守 + 隐喻隐晦暂不单治;反向约束 2C 修法不可推它更爱拒

第 4 层 · 横切发现

  • 4A occurrence 判别生效 ✅:同短语跨句判出不同(slow down time-lapse=TRUE / "systems slow down"=FALSE;for the birds 字面=FALSE)——type 级静态标签构造上做不到,核心假设证实,premium 立论基础。
  • 4B cap 静默丢 25 = 隐藏 recall 漏 🔴:根因同 3A(噪声撑爆候选);解 = 行动 1 降噪后候选回落到 cap 内,漏自动消失。RB 侧最高优先。
  • 4C always 桶 click-confirm 挂在另一开关:单击二次确认(explain_phrase 能撤误高亮)门控 context_disambiguation,与 phrase_deep_mode 两个独立开关 → 只开深度模式的用户点 always 误高亮不被纠正。P3 评估合并。

汇总行动表(按 ROI 排序)

#行动治哪类归属优先级
1matcher 送 LLM 前预过滤(跳 basic 桶 / 功能词组前置规则)3A+4B(降噪+救 cap 漏+省钱)RBP2 最高
2prompt 外科规则「字面物理移动/按压→false」+ 精准 idiom gloss2C 三个硬 FPedge+RBP2
3透明基础 PV demote context→literal2B 六个RVH 新会话P2
4两个 AI opt-in 合并评估4C UX 缝隙RBP3
5多体裁复测(新闻/技术,验证是否文学体裁特例)全局泛化RBP2(1/2 后)

视觉:deep 高亮(蓝波浪线 rb-phrase-pv/习语样式)与 vocab/CEFR 橙点线、floor data-rb-phrase-auto 共存可区分。 已知边界(P1):deepResolve 在 vocab 高亮之后跑,被生词高亮切碎的 context 短语单节点匹配不到(召回损失,与 always 路径同源);P2 可前移捕获到 vocab 前。

行动 2 落地(2026-06-26 prompt 外科修法):见下方「§2.1 prompt 迭代记录」。

已知边界(P1,见 §4 注 + §11):deepResolve 在 vocab 高亮之后跑,被生词高亮切碎的 context 短语单节点匹配不到(召回损失,与 always 路径"必须在 vocab 前"同源);P2 可前移捕获到 vocab 前。

§2.1 prompt 迭代记录(行动 2,2026-06-26)

v1 → v2(治 2C 三个硬 FP:位移/物理 PV 字面位置用法)judge-phrases-batch SYSTEM_PROMPT 加两条—— ① 「LITERAL PHYSICAL/SPATIAL/MOTION use → false」+ 实例(go to a window / push on a surface / look down at / pull away); ② 「GUARDRAIL」反向护栏(防过度拒绝伤 recall)+ 实例(back on track / caught up in / let in → 仍 true)。 护栏实例直接取自 gate-C 实测的 FN,让同一条 prompt 同时修 FP 和保 recall。

验证(两层)

  • curl 回归(8 例 curated,新 prompt 部署后):8/8 全对——go to/push on/look down → false ✓;look up/set out/win out → true ✓;on track/catch up(原 FN)→ true ✓(护栏不仅没伤 recall,还把 2 个 FN 救回)
  • 同页实机复测(content-1 reload,真实 gloss 走 Rust)go to×2 + push on deep 高亮 归零 ✓;look up/set out×2/win out TP 保留 ✓;borderline FN let in("let in the tips…into awareness")被救回 ✓。总数仍 23(FP 出、真习语进,volume 不变质量升)。

结论:prompt 外科修法成立——同页 precision 13% 硬 FP 清零、且顺带救回 ≥2 个 FN。待多体裁复测(行动 5)验证泛化。

§2.2 行动 1 落地(2026-06-26 candidate 分批不丢,治 4B 隐藏 recall 漏)

先证伪:原设想「送 LLM 前按 basic 桶/tag 预过滤降噪」被数据否决(rb_db_query)——is_basic 不区分伪/真匹配: 8/10 伪匹配非 basic(滤不掉)、5 个真高价值(look up/come up/make up/hang out/point out)是 basic(会误杀), work on 真+伪同 PK。伪/真纯 occurrence 级,无 phrase 级干净信号,只能 LLM(或没有的 POS)判 → 伪匹配交 LLM 兜底即可(precision/recall 不受影响),不预过滤。

真正改动deepResolvePhrasesOnPage 候选超批不丢、分批并行判——DEEP_PHRASE_CAP=40 静默丢DEEP_PHRASE_BATCH=40(每批 ≤ edge MAX_ITEMS=60)滚入下一批 Promise.all 并行调,单页硬顶 DEEP_PHRASE_MAX=120(3 批)才丢 + log。4B 隐藏 recall 漏闭合(密页 65 候选全判、dropped→0),成本仍可忽略(密页 2-3 次 flash ≈ 0.1-0.2¢)。任一批失败 graceful(该批不补,其余照常)。

验证:build 通过 + edge gold 27/27;实机 judged 40→~65 / dropped→0 待下次 /phrase-eval 复测(落地时 dev app 卡在启动网络 fetch,未当场核;逻辑经 inspection 确认)。剩 2B 透明 PV(行动 3,RVH)+ 多体裁复测(行动 5)未动。


0. 为什么做(战略定位)

静态 idiomaticity 三档收敛不到 0——残留 FP(all along/in a fix/for the bird)全是 occurrence 级问题 (同一短语在不同句有时习语、有时字面),用 type 级标签(与上下文无关)解,构造上漏长尾。继续 retag = 边际递减。

正确架构 = floor + ceiling 两层(综合评估 2026-06-26 敲定):

是什么状态
floor(免费/默认)本地 always 桶:即时、离线、零外发、~93% 干净(gate-A 7% FP)✅ 已就位
ceiling(premium/opt-in)本 plan = §9 LLM occurrence 判断:救回 context 桶(recall 2789→6000+)+ 洗掉 always 残留 FP(→近 0)🚀 本 plan

ceiling 解决 floor 解不了的两件事

  1. recall 大增:floor 只露 2789 个 always;context 桶 3450 条短语的习语用法全漏。deep mode 在它们真的是习语义时救回(gate-B 实测 0 漏报)。
  2. precision 到顶:opted-in 时 LLM 复判 always 桶,把 all along 这类 occurrence 陷阱按句降掉 → FP 近 0。

约束是隐私不是成本:flash ≈ 0.056¢/篇(重度 50 篇/天 ≈ $0.84/月)可忽略、可定价进 premium;opt-in 的真正理由 = 整篇候选正文片段外发第三方,需显式知情同意(沿 disambiguate-sense 同口径)。


1. 架构总览(两段式,不发整页)

进入 reader 模式(或 live 页)+ phrase_deep_mode = on

【第一段·本地·即时】highlightPhrasesOnPage()  ← 现有,floor,不变
  · always 桶本地秒出(绿/彩高亮)

【第二段·LLM·异步补出】deepResolvePhrasesOnPage()  ← 新增
  1. 本地 matcher 返回 context 桶候选(+opted-in 可含 always 复判集),带 char 偏移 + 所在句
  2. 去重 + 组 batch(含候选的句子,不发整页)→ 一次 edge function 调用
  3. flash 逐条判「习语义 vs 字面/组合义」
  4. fit=true → 高亮(context 桶新增);always 复判 fit=false → 移除该 always 高亮(FP 清理)
  5. 结果种入 per-page 内存缓存(word+sentence 键),翻页/重扫免重调

关键纪律

  • always 桶永远本地先到(deep mode 只做"补"和"减",不阻塞首屏高亮)。
  • 只发含候选短语的句子(matcher 已知偏移 → 切句),不发整页正文(token + 隐私双控)。
  • 失败/超时/null → graceful:context 桶就不补、always 桶就不减(floor 体验不退化)。

2. Edge Function(batch occurrence judge)

复用 explain-phrase 骨架(已存在,FITS/COMPOSITIONAL 选择式 + _shared/llm.ts 计量/限额/llm_call_log)。 现 explain-phrase单条(一次一个 phrase+sentence+candidates,on-click 用)。deep mode 要批量才能命中 §9 的 0.056¢/篇(~3000 in / ~500 out,一页一次调用)。两选一:

  • 方案 A(推荐)新建 judge-phrases-batch:收 { items: [{ id, phrase, sentence, gloss }] } → 返回 { verdicts: [{ id, idiomatic: bool }] }精简 prompt(二分类,不返回 sense_index)——deep-mode 高亮只需 true/false; 弹窗的 sense 仍走现有 on-click explain_phrase(已工作,不动)。token 最省。
  • 方案 B:给 explain-phrase 加 batch 模式。复用度高但 prompt 携 candidates 更重(token 高)。

选型建议 = A:高亮决策是二分类,不需要每条带 candidates 全 glosses;轻 prompt 把每页压到 ~3000 token。 复用 explain-phrase 的 system prompt 中「COMPOSITIONAL 判定」那段语义,去掉 sense 选择部分。

prompt 要点(gate-B 教训):

  • 每条候选的 per-sense 精准 gloss(不是泛化义)——gate-B 的 2 个误报全是 work in 配泛化 gloss "to insert or include something" 被多义干扰。matcher 取该短语主习语义 glosspos_definitions 第一条 idiom 义)入 batch。
  • 低置信可选择性升 pro 二判(gate-B 实测 pro 同错 → 先改 gloss 更值,pro 兜底 P2)。

secret:复用全局 LLM_PROVIDER/LLM_API_KEY/LLM_MODEL(与 explain-phrase/disambiguate-sense 同)。 modeldeepseek-v4-flash(gate-B 坐实,pro 无增益)。部署/edge-deploy(project-ref jdtbyteiwnciqnfppztz)。


3. Rust 命令

  • judge_phrases_batchcommands/ 短语相关模块):收候选 items → anon_key 调 edge function → graceful CommandResult<Vec<Verdict>>(错误返回空/null,不抛)。镜像现有 explain_phrase 的调用范式。
  • matcher 复用match_phrases_in_node_textsquery.rs)已返回 idiomaticity + 偏移 + phrase。第二段从 perNode 里筛 idiomaticity === 'context'(及 opted-in 时 'always')作候选,无需新 SQL
  • 切句:content-script 已有 getSentenceAround/sentenceContaining(cloze/popup 复用),deep mode 第二段同款取候选所在句。

4. Content-script(reader 二段路径)

新增 deepResolvePhrasesOnPage()phrase-highlight.js),在 highlightPhrasesOnPage() 之后异步触发:

  1. 复用第一段已 match 的 perNode 结果(避免二次 match_phrases_in_node_texts);从中取 context 桶候选(m.idiomaticity === 'context')+ 各自所在句。
  2. opted-in always 复判(可选 Phase 2):把已高亮的 always span([data-rb-phrase-auto])连句一起加入 batch。
  3. 去重(同 word+sentence 只判一次)+ 查 per-page 内存缓存(命中跳过)。
  4. invoke('judge_phrases_batch', { items }) → verdicts。
  5. 应用
    • context fit=true → wrapPhrasesInTextNode(新增高亮,标 data-rb-phrase-deep="1" 区分来源 + 便于埋点/回收)。
    • always fit=false → 移除该 span(unwrap,FP 清理)。
  6. 缓存 verdicts(word+sentence 键,仅当页、仅正向 + 负向都存,规避瞬时故障钉死)。
  7. 视觉:判定期可给一个克制的"• AI"脉冲(沿 disambiguation 既有提示),结果平滑补出/淡出。

作用域findMainContentRoot()(已落地,提取为共享 helper 复用)。reader 模式优先(overlay-scoped、最干净);live 页 Phase 2。 选区路径 highlightPhrasesInSelection 不变(仍露全部,保留手动捞回)。


5. opt-in 隐私门(沿 context_disambiguation 同款)

  • 新 setting key phrase_deep_modeinit_data.sql seed 'false'(默认关)。
  • GeneralSettings.tsx:Switch + 开启前 ConfirmDialog(文案:"深度短语模式会把你正在读的、含候选短语的句子发送给第三方 AI 服务做习语判断,以更准地识别习语用法。是否开启?")。
  • settings.ts getter/setter;zh/en locale(走 src/lib/strings/,禁 JSX 中文字面量,/arch-check S19)。
  • content-script 第二段先查开关(invoke 读 phrase_deep_mode,关 → 直接 return,floor 照常)。
  • context_disambiguation 关系:两者都是"正文片段外发"opt-in。可独立;亦可评估合并成一个"AI 语境增强"总开关(实施时定,倾向独立 = 语义清晰 + 各自可灰度)。

6. 成本 / 延迟 / 鲁棒护栏

  • 成本_shared/llm.ts 已有 llm_call_log + 预算闸;deep mode 调用计入。每页一次批量调用 ≈ 0.056¢。
  • 页内候选上限:cap(如 >40 候选句则截断 + log 丢弃数,不静默——沿"no silent caps"纪律),防超长页 token 爆。
  • 延迟:flash ~1-3s(高峰波动)。always 桶已先到,deep 异步补——不卡首屏。给加载脉冲。
  • 缓存:per-page 内存(word+sentence),翻页/重扫免重调。不持久化(§9 + 消歧 follow-up 同结论:隐私含用户句 + 须按 VOCABULARY_SEED_VERSION 失效,投入产出不成立)。
  • graceful:任何错误/超时/null → context 不补、always 不减,floor 不退化。

7. Premium / 定价(架构留口,v1 不做 paywall)

  • deep mode = 天然的 premium 锚点(成本 + 隐私同意都绑定到"愿意为质量付费 + 授权"的用户)。
  • v1 = opt-in 免费(验证期,收集真实使用 + gate-C 准确率);paywall 基建(订阅/额度)独立后续,不阻塞本 plan。
  • 免费用户始终有 floor(本地 always 桶,即时/私密/离线)——分层不剥夺基础体验。

8. 验证(gate-C)

复用 gate-A 同法(Claude 读 data-rb-sentence 逐句判 + rb-debug 驱动),但分两个口径分开验(契约 §8 验证分层纪律,不混算):

  1. context 桶 precision/recall(deep mode 新增高亮):opted-in 开 deep mode,文学/叙事体裁取样——
    • precision:新增的 data-rb-phrase-deep 高亮里,真习语义占比(目标 ≥90%,对齐 gate-B)。
    • recall:抽查正文里 context 桶短语的真习语用法,deep mode 有没有救回(对比 floor 漏的)。
  2. always 桶 FP 清理(Phase 2):all along/for the most part 等 floor 残留,opted-in 后是否按句被移除。
  3. 延迟/鲁棒:always 先到、deep 后补的体感;断网/超时 floor 不退化。

判据:context precision ≥90% + recall 明显高于 floor + always FP 清理生效 → deep mode 成立,评估 premium 化。


9. 分阶段

Phase范围阻塞关系
P1opt-in 开关 + judge-phrases-batch edge function + Rust 命令 + reader 二段 context 桶救回 + 内存缓存 + gate-C ①验证核心,先做
P2always 桶复判 FP 清理 + per-sense 精准 gloss 优化(治 work in 类)+ live 页路径P1 后
P3premium/paywall 基建(订阅/额度)+ 与 context_disambiguation 开关合并评估独立、不阻塞体验

并行非阻塞(floor 侧,RVH):1 次廉价 demote-only(all along + 同模式 homonym 长尾 + §11.4 话语子集 no matter what/pay attention/for the most part 降 literal)把 floor FP 推向 ~0,之后冻结静态治理(见 backlog 🧭 战略结论)。RVH 新会话,与本 plan 无依赖。


10. 文件锚点

文件改动
supabase/functions/judge-phrases-batch/index.ts新建(复用 explain-phrase 骨架 + _shared/llm.ts,batch 二分类)
supabase/functions/explain-phrase/index.ts参考骨架(不必改;on-click 弹窗仍用它)
src-tauri/src/commands/.../judge_phrases_batch 命令(镜像 explain_phrase
src-tauri/src/commands/vocabulary/query.rsmatcher 已返回 idiomaticity + 偏移,无需改(第二段筛 context)
src-tauri/src/content-script/features/phrase-highlight.js新增 deepResolvePhrasesOnPage();复用 findMainContentRoot()/getSentenceArounddata-rb-phrase-deep 标记
src/components/.../GeneralSettings.tsxphrase_deep_mode Switch + ConfirmDialog(仿 context_disambiguation
src-tauri/.../init_data.sqlseed phrase_deep_mode='false'
src/lib/strings/zh/en 文案(开关标题/隐私确认)

11. 不做 / 边界

  • 不发整页正文——只发含候选短语的句子。
  • 不做持久缓存(隐私 + 版本失效,§6)。
  • 不动 floor(always 桶本地路径)——deep 只做"补 context + 减 always FP",不替换首屏。
  • 不动选区路径(手动捞回保留)。
  • literal 桶不送 LLM(§2 路由:跑了也是浪费)——三档分类在此兑现"省钱省延迟路由器"价值。
  • v1 不做 paywall(opt-in 免费验证先行)。