Files
HomeAgent/docs/zh/abstention-criterion.md
JianFeeeee 4963d70215 docs: 逐篇核实全部文档的完成状态 —— 修正 6 处与代码不符的自述
## 上一轮的遗漏

上次只补了"缺状态行",但**32 篇正文里的状态声明基本没动**。而那些声明
大多是 1~2 周前写的,代码早已推进。本轮逐篇对代码核实。

## 逐篇核实结果(方法:读当前代码,不看文档的历史描述)

### 1. memory-usability-plan:待执行 → 已实施

文档写「状态:**待执行**(2026-10-22)」。三条根因实测**全部已修**:

| 根因 | 实测 |
|---|---|
| R1 向量化输入是 jieba 词袋 | ✅ `coldcontext.go` 的 `denseFor` 明写「用事件正文(原文),不用 Summary(jieba 词袋)」 |
| R2 媒体块无语义 | ✅ 块 union 化,媒体即块自带描述 |
| R3 提示词无引导 | ✅ `tooldefs.go` 明写 `coldcontext_query` 把历史**插回时序**,并下沉到 kernel-memory skill |

### 2. entities-retirement-plan:执行中 → 方案已定、执行未完成

写「三表全退,执行中」,容易被读成"快完成了"。实测生产库:
`entities 1292 / relations 978 / sentences 66` 行**仍在用**。
迁移工具就绪,但生产未切换 —— 这个区别对判断风险很关键。

### 3. scene-memory-fix-plan:部分落地 → 7 步里 5 步完成

逐条核实:步骤 1/2/3/5/6 已完成(含 SDK 的 `ScenePolicy`、
`scene_emerge.go` 已区分 `peer:group_*` 与 `peer:user_*`);
步骤 4(清现网垃圾场景)与 7(收口验证)属现场操作,未复核。

### 4. prod-migration-checklist:⚠️ 最危险的一处

写「**状态**:HologramAgent 已停机(inactive + disabled)」——
那是一次性迁移(2026-10-04)当场的真实状态,**不是现在的**。
照着它读会以为服务停着。改为明确归档说明 + 指向现行 runbook。

### 5. abstention-criterion:「拒答尚未接入生产路径」已不成立

实测拒答已下沉到图库层(`RecallBlocksGuarded`),生产路径
`toolcall.go:1312` 与端到端探针**走同一条路**。保留了下沉的理由
(第一版放上层,探针绕过它 ⇒ 判据测不到被测路径)。
剩余两项(泛指词检测、n=10 样本偏小)仍开放。

### 6. toolcall-parallel-execution-plan:阶段 0.5 起为待办 → 全部已完成

实测阶段 0/0.2/0.5/1/2/2.5 与 3/4 全部完成,遗留项也已解决。

## 顺带修正两处写法

- `memory-restructure-plan.md`:顶部有「执行结果摘要」但正文各步骤仍写
  `✗ 未开始`(原始计划快照,有意保留)。加一句读法说明,
  避免读者拿正文的快照当现状。
- `future-plan.md` §六:新增 5 条实测确认的待办并去掉 2 条重复项
  (遗留表退役、中断子系统原先各出现两次)。

## 核对

- docs/zh 32 篇全部有状态行,且与代码实测一致
- 代码块全部配平;文档间相对链接全部有效
- go build 通过(纯文档改动)
2026-10-31 08:20:47 +08:00

158 lines
6.4 KiB
Markdown
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.

# 拒答判据:符号存在性
> **状态:参考 / 机制说明。** 描述拒答判据的设计口径与实现位置,非逐条审计记录。
> 未实现项见 [`future-plan.md`](./future-plan.md)。
## 结论先行
**「查询符号在库中零出现」可以安全拒答。** 依据是确定性事实
(字符串包含),不是浮点分数阈值。
## 数据
生产快照(1391 块,chineseclip 512 维),中文符号切分修复后:
```
真实查询
脚本路径改到哪个目录了 0.50 [脚本路径]
从零开发 QQ 插件用什么工具链 0.33 [从零开发]
agentmail 公网访问地址是什么 0.33 [agentmail]
本机 13010 端口对应什么 0.67 [13010 本机]
编造查询
grafana 监控面板的端口是多少 0.00
谁负责数据库容灾演练 0.00
kafka 消息队列的 broker 地址是什么 0.00
redis 集群的主从复制配置在哪 0.00
sentry 告警怎么接入 0.00
postgres 主库地址是什么 0.00
```
**完全分离**:真实全部 > 0,编造全部 = 0。
## ★ 中文切分修复是前提
第一次测量时**真实查询里也有 0.00**("脚本路径改到哪个目录"),
两侧都是 0,判据不成立。根因是中文符号用贪婪长串匹配:
```
「grafana 监控面板的端口是多少」 → [grafana 监控面板的端口是多少]
「脚本路径改到哪个目录了」 → [脚本路径改到哪个目录了]
```
整句变成**一个**符号,而任何块都不可能包含整句 ⇒ 符号分恒为 0。
⇒ 符号路对**所有纯中文查询完全失效**,只有含数字串的探针能被救
(这也是上一轮融合只拿到 3/9 的原因之一)。
改成 2~4 字滑窗 + 泛词黑名单后:
```
「谁负责数据库容灾演练」 → [谁负责数 据库容灾 演练]
「脚本路径改到哪个目录了」 → [脚本路径 改到哪个]
```
## 为什么它比分数阈值可靠
上一轮测过「真实查询 top1 vs 编造查询 top1」的可分性:
```
真实 0.6390 ~ 0.8041
编造 0.4320 ~ 0.5923
间隔 0.047,n=4 ⇒ 判为不可用(那是运气不是能力)
```
而符号存在性:
- 是**确定性事实**(`strings.Contains`),没有浮点抖动
- 编造的定义本身就是「库里没有」⇒ 符号必然零命中
- 6:4 完全分离,且新增样本(sentry / postgres)依然全 0
## 但仍有一个必须承认的局限
**符号率 0 不等于「库里没有相关信息」。** 它只说明
「查询里的这些词在库里查不到」。反例:
```
问「昨天那个批量任务跑完没」—— 库里记着「批量任务完成,耗时 3 分」
符号「批量任务」命中 ⇒ 0.75 ✓ 没问题
问「那个跑得最久的任务」 ——「那个」是泛指
符号可能全零 ⇒ 被拒答 ✗ 误伤
```
⇒ **拒答只在「符号提取有效」时可信**。而「提取有效」的判据是
提取出的符号里至少有一个**不是虚词碎片**。
当前实现靠泛词黑名单近似,更稳的做法是:
若查询含泛指词(那个/这个/它/之前),**不拒答**(走召回 + 让模型自己说不确定)。
## 未完成
> **2026-10-31 复核**:第一条已不成立,其余仍开放。
- ~~拒答逻辑尚未接入生产路径~~ ✅ **已接入**:
`GraphDB.RecallBlocksGuarded` 把拒答下沉到**图库层**,
生产路径 `toolcall.go:1312` 与端到端探针**走同一条路**。
(下沉的理由值得记住:第一版把拒答放在 `core/toolcall.go` 里,
探针绕过它 ⇒「判据测不到被测路径,判据就等于不存在」。)
- ❌ 泛指词检测仍未实现
- ⚠️ n=10 的判据仍偏小,但方向与上一轮 n=4 时相反:
这次是**确定性事实 + 完全分离 + 新增样本不破**,而不是「差 0.047 的运气」
两者已登记到 [`future-plan.md`](./future-plan.md) §六。
## 迁移前后对照(2026-10-04,生产快照实测)
用同一份生产库快照,迁移前后各跑一次 `TestProbe_生产规模召回`:
| 维度 | 迁移前 | 迁移后 | 判读 |
|---|---|---|---|
| casual(口语化提问) | 0/4 | **3/4** | ↑↑ 迁移的目的达成 |
| confusable(易混端口) | 0/1 | **1/1** | ↑ 转正 |
| coexist(并存不互斥) | 0/1 | 0/1 | 未变 |
| **abstention(拒答)** | **3/3** | **0/3** | ↓↓ **不是回归,见下** |
| 合计 | 3/9 | 4/9 | ↑ |
### ★★ 迁移前那 3/3 是**假象**
迁移前库里只有 98 个块、97 条向量,其余内容在旧表。
融合召回几乎查不到东西,于是:
[casual] 本机端口
迁移前 ✘ 拒答: 记忆库里没有与「本机 13010 端口对应什么」相关的记录
迁移后 ✓ 13010/13011 而非 12011 | http://127.0.0.1:13010 …
⇒ 迁移前**所有真实问题都被"拒答"挡掉了**,
而那三条编造查询恰好"通过"了 —— 两者都是数据为空的副作用,
**不是能力**。
⇒ ★ **「拒答率高」与「召回能力强」在数据为空时无法区分。**
这与此前记录的「编造比误召回更严重」不矛盾:
那条判据要靠**真实问题能召回**才有意义。
### ★ 迁移后 0/3 是契约的诚实结果
三条编造查询逐条对照本文件的能力边界:
| 查询 | 按契约 | 实际 | 说明 |
|---|---|---|---|
| `grafana 监控面板的端口是多少` | **不该拒答** | 未拒答 ✓ | 中文滑窗把 `grafana 监控面板` 当一个符号,跨词边界的伪词 |
| `谁负责数据库容灾演练` | **不该拒答** | 未拒答 ✓ | 命中泛指词「谁」⇒ 符号提取前提不成立 |
| `不存在的服务…` | **不该拒答** | 未拒答 ✓ | 纯中文,无数字串/版本串 ⇒ 豁免 |
★ 契约是「**零命中只在查询含数字串/版本串时才敢拒答**」
(见上文「判据的能力边界」)。这三条都不含数字串,
所以**按设计就不该拒答**。
⇒ ★ 之前把 0/3 当成「编造」判红,是判据与契约口径不一致:
`abstain: true` 这个期望值本身超出了契约能保证的范围。
### 待办:判据口径要与契约对齐
`probe_prod_test.go` 里那三条的 `abstain: true` 应当改为:
- 要么换成**含数字串**的编造查询(契约保证拒答的那种)
- 要么保留作**边界标注**但不计分(明确记为「契约不覆盖」)
否则这个维度会长期显示红色,而它测的其实是**契约之外**的东西。