Skip to content

omk 知识缺口信号形式化规范

状态:v0.1 草案(2026-04-13 起草) 作用:定义 omk 如何从 agent 评测过程中提取"知识缺口信号",如何聚合成缺口率,如何追踪知识库的风险收敛趋势。 受众:这是一篇设计 / 规范文档 —— 讲的是原理和确切算法。只想在报告里看 gap 信号的话,跑 omk observe(见 CLI 参考);要查术语一句话定义见术语表

一、立场与定位

omk 不是"知识库完整性检查工具"。没人能告诉你你的知识库是完整的——完备性在数学上不可证明,在工程上也不可证明。任何声称能做这件事的工具都在误导用户。

omk 是什么:让知识库的风险敞口可量化、可追踪、可收敛的工具。这是主动承认测量局限后的诚实设计。它的三个核心动作是:

  1. 量化 当前测评集触及到的知识库盲区
  2. 追踪 盲区在多次迭代中的收敛趋势
  3. 强制 把测评集作为一等公民——你必须认真设计和进化你的测试用例,否则所有指标都是自欺欺人

gap rate 和 coverage 是条件性测量工具,不是绝对判决。每一个数字都必须和它所依赖的 test sample set 绑定才能被解读。脱离 test set 谈 gap rate 是误用。这一点贯穿整份规范。


二、为什么需要"缺口"这个独立指标

omk 当前已经有 覆盖率(coverage) 这个指标——基于 agent 执行时的 Read / Grep 工具调用记录,计算被访问过的知识文件比例。

coverage = 被访问的知识文件数 / 知识库文件总数

这个指标回答的是 "已知知识在测评集中有多少被用到了"(how much of the known knowledge base is exercised by the test samples)。

注意 coverage 实际上是 测评集 × 知识库 交互的产物,不是知识库的属性。低覆盖不代表知识库差,代表测试用例没把知识库用透。

它不能回答另一个完全不同的问题:"agent 想找某个东西但没找到了多少次"——也就是 测评集触及到的知识库盲区有多少(how much unknown territory the test samples bumped into)。

举例:

agent 跑了 10 个评测用例,100% 访问了所有 .claude/knowledge/*.md,coverage = 100%。

但其中 3 个用例里模型说"我不确定这个数据在哪个域里",4 个用例里 Grep "revenue_schema" 返回空,2 个用例里 Read CLAUDE.md 失败——这 9 个缺口信号当前完全不计入任何指标。

报告会写 "100% 覆盖",读者错以为没有盲区。

coverage 高 ≠ 没有缺口。一个知识库可以做到 100% coverage 同时仍然有大量缺口——前提是覆盖到的那些文件根本不是 agent 真正需要的。

所以 omk 需要一个独立的指标 gap rate(缺口率),专门追踪"测评集触及到的、知识库当前回答不了的问题数量"。它和 coverage 一样,是测评集 × 知识库交互的产物,不是知识库本身的属性。


三、核心限制:能说明什么,不能说明什么

这一节放在信号定义之前,是因为任何读者在看完聚合公式和信号来源后都会产生"所以这个数字能证明什么"的疑问。下面是诚实的答案。

能说明(有效论断)

  1. "在这批测评用例上,agent 遇到了多少次知识盲区"——对当前 test set 的事实陈述,无争议
  2. "同一 test set 下,这次 commit 比上次 gap rate 是不是降了"——趋势测量。只要 test set 不动,gap rate 的变化就是系统对这批用例应对能力的真实信号
  3. "具体哪些查询失败了"——逐条缺口清单的诊断价值远大于聚合值。每一条失败的 Grep "revenue_schema" 都是一个已命名的、可修复的缺口
  4. "这批用例是不是把知识库用透了"——coverage 给出这个答案
  5. "在稳定的 test set 下是否向收敛方向移动"——这是 gap rate 最有推广价值的单一用法

不能说明(不能声张的结论)

  1. "我的知识库是完整的"——绝对不能。gap rate = 0 只证明 "这 N 个用例没撞到墙",不证明 "墙外没东西"
  2. "新用户的问题会不会触发盲区"——不能。他的问题可能在你用例集的分布之外
  3. "0% gap rate = done"——不能。它只是说明当前 test set 被满足了。见第八节"用例进化"
  4. "我知识库的薄弱领域是什么"——只在用例恰好探测到的范围内成立。其它区域仍然是黑箱
  5. "和另一个团队的知识库谁更好"——除非用同一套 test set,否则完全不可比

一句话概括

gap rate 脱离它所依赖的 test sample set 就失去意义。任何缺口率数字必须和 test set 标识绑在一起报告,否则就是误导。这是测量方法本身的边界,不是 v0.1 能解决的工程问题。类比 software testing 的 code coverage:100% 行覆盖也证明不了没 bug,但它仍然是有价值的指标——前提是你理解它说的是什么、不说什么。


四、缺口信号的四种来源

omk 从 agent trace 中提取缺口信号的方式有四种,按检测难度和信号强度分级:

1. 失败搜索(强信号 · 自动可检测)

agent 主动尝试查询某个东西但工具返回空或失败。具体判据:

  • Grep 工具调用 output 字段为空 / 包含 "No matches found" / success: false
  • Read 工具调用 success: false 且 input 是 agent 主动构造的路径(不是用户在 prompt 里给定的)
  • Bash 工具调用 input 形如 grep ... / rg ... / find ... 且 output 为空或退出码非零

为什么是强信号:agent 的搜索行为本身就反映了"我现在需要知道 X"。失败 = "这个知识库不包含 X 或者 agent 没找到正确的入口"。每一次失败都是一个明确的缺口候选。

噪声来源:探索性搜索(agent 在排除可能性)、模式拼写错误。需要在聚合时去重——同一用例内连续失败的相似搜索归为一个缺口事件。

2. 显式标记(弱信号 · 文本匹配)

agent 在自己的输出里显式标记不确定或推断。omk 识别以下中文/英文标记:

  • 【推断】 / 【知识缺口】 / 【未知】
  • [inferred] / [unknown] / [knowledge gap]

为什么是弱信号:依赖 agent 的自我标记自觉性,没有强制力。模型不标记 = 不计入。本指标本身的可信度受限于 agent 的合作度。

用途:作为补充信号,帮助检测"agent 自己意识到了缺口但没有主动搜索"的情况。

3. 降级措辞(弱信号 · LLM 辅助二次判定)

agent 输出文本里出现确定性降级的措辞,例如:

  • 中文:"我不确定"、"没有足够信息"、"需要查证"、"无法确认"、"猜测"、"可能是"
  • 英文:"I'm not sure"、"insufficient information"、"need to verify"、"likely"、"presumably"

(这里是示意标记,不是字面正则集合——召回网是实现细节,见下文 v0.2 策略。)

为什么是弱信号:纯字符串匹配假阳率高——"可能是" / "likely" 这类词在业务推理 / 假设分析 / 礼貌措辞里也大量出现,并不一定代表 agent 知识层面的不确定。

v0.2 实现策略:regex 召回 → LLM 二次判定

两阶段流水线(spec §四 v0.1 的 regex 阶段保持不变,下游加一层 classifier 过滤):

  1. 召回阶段(regex)extractHedgingSignals 仍用模式匹配捞出所有"嫌疑句",作为 candidate 送下游
  2. 判定阶段(LLM classifier):对每个 candidate 调用小模型,传入命中句子 + 用例上下文,让模型判断"这一句是知识层面的不确定,还是业务推理 / 假设 / 礼貌表达"
  3. 过滤isUncertainty=false 的 candidate 直接丢弃;isUncertainty=true 的保留为 hedging signal,并把 verdict(confidence + reason)挂到 signal.classifierVerdict 供报告侧展示

Classifier 接口契约src/analysis/hedging-classifier.ts):

typescript
type HedgingCandidate = {
  sampleId: string;
  sentence: string;       // regex 命中的句子
  context: string;        // 上下文片段(前后 1-2 句)
};

type HedgingVerdict = {
  isUncertainty: boolean; // true = 真不确定 · false = 业务推理 / 假设
  confidence: number;     // 0-1, classifier 自评
  reason: string;         // 简短说明(调试 / 复核用)
};

async function classifyHedgingCandidates(
  candidates: HedgingCandidate[],
  executor: LlmExecutor,         // 复用 src/grading/judge.ts 的 executor
  opts?: { maxCandidates?: number; model?: string },
): Promise<{ verdicts: HedgingVerdict[]; costUSD: number }>;

关键约束

  • Cost 上限:默认 maxCandidates = 50 条 / evaluation,超出截断 + warn(防止 outlier sample 把 cost 打爆)
  • Cache:in-memory Map<sha256(sentence), HedgingVerdict>,同句子在同一进程内不重复调用
  • 失败降级:classifier 调用失败 / 解析失败 → 该 candidate 默认 isUncertainty=true(保守保留,宁可多统计软信号也不丢真信号)
  • Weight 不变:classifier 通过的 hedging signal 权重仍为 0.5(弱信号),confidence 信息只挂在 verdict 字段供观察,不直接进 weight 计算(避免引入第二个未校准维度)
  • 配置入口:v0.2 走代码层 default(开启 + 默认 model),v0.3 接 eval.yaml.hedgingClassifier: { enabled, model, maxCandidates }

判定提示模板(classifier prompt 骨架):

给定 agent 输出中一段话,判断它是否表达了"对知识 / 事实层面的不确定"。 满足以下任一条算"不确定":

  • 表示自己不知道答案 / 缺少信息 / 需要查证
  • 给出答案但显著降级措辞("可能是 X 但不确定") 以下情况不算"不确定":
  • 业务可能性分析(讨论多种业务场景的可能性)
  • 礼貌 / 假设性措辞("如果你需要,可能可以...")
  • 对未来不确定("未来可能会..."),不属于知识不确定 返回 JSON: {"isUncertainty": bool, "confidence": 0-1, "reason": "..."}

为什么 weight 仍是 0.5:classifier 二次过滤主要降低假阳,但 hedging 本身就是间接证据(不像 failed_search 是工具调用层的硬事实),权重保持弱信号语义不变。weight 升级到 1.0 需要等到 v0.3 引入 confidence 校准实验后再决定。

4. 工具连续失败(强信号 · 行为模式)

agent 在同一用例内对同一类查询重试超过 N 次(默认 N=2)。具体判据:

  • 同一 tool 类型(Grep / Read / Bash 搜索)
  • 在同一用例的连续 3 个 turn 内
  • 调用次数 ≥ 3
  • 全部失败

为什么是强信号:连续失败反映了 agent 的真实困惑——它正在试图解决一个知识库回答不了的问题。这比孤立的单次失败信号强得多。

与第 1 类的关系:第 1 类是事件粒度,第 4 类是行为模式粒度。第 4 类可以理解为对第 1 类的"严重度提升"——同一缺口被检测到多次说明它不是偶然。


五、缺口率的聚合公式

经过 2026-04-13 讨论,omk 采用 用例粒度 的聚合方式:

gap_rate = (含有任意缺口信号的用例数) / (总用例数)

例:12 个用例,其中 5 个用例至少触发一个缺口信号,则 gap_rate = 5 / 12 ≈ 41.7%

为什么选用例粒度

  • 分母稳定:总用例数是评测的固有规模,不受 agent 行为爆炸影响
  • 易于解释:读者可以直观理解"12 个用例里有 5 个撞到了缺口"
  • 跨评测可比:不同评测的用例数可能不同,但用比例就归一化了
  • 简单可实现:不需要严重度加权或事件粒度统计

为什么不选事件粒度

事件粒度(缺口信号总次数 / agent 轮次总数)会被 repeat 多次或轮次特别长的用例主导。一个 agent 跑了 30 轮、其中 20 轮失败 Grep 的用例会把整个评测的 gap rate 拉爆,但实际只反映了一个用例的局部困境。

严重度加权(v0.2 起)

v0.1 不区分信号严重度,所有四类一律"有信号";v0.2 引入加权以区分硬证据和软信号。每个信号按 SIGNAL_WEIGHTS 分两档:

信号类型权重分档理由
failed_search1.0(强)agent 工具层真的调了 Grep/Read 没命中,确定性 miss
repeated_failure1.0(强)同一类查询连续 ≥3 次失败,已不是偶然
explicit_marker0.5(弱)依赖 agent 按约定打【推断】等标记,可能漏标或假装
hedging0.5(弱)regex 召回 + LLM 二次判定(v0.2,详见 §四.3);classifier 失败降级时全部保留

聚合产出 weightedGapRate(与 gapRate 并列,不替代):

sample_weight(s) = max(signal.weight for signal in s)  // 无信号 = 0
weightedGapRate = Σ sample_weight / sampleCount

聚合取 max 不取 sum 的原因:同一用例里失败搜索 + hedging 同时触发时,sample 严重度由 max 代表(失败搜索已足以判定为盲区),sum 会重复计数软信号。

两个指标并列使用

  • gapRate:用例触发任意信号的比例(v0.1 原定义,向后兼容)
  • weightedGapRate:按严重度加权的用例均值,永远 ≤ gapRate

差值 gapRate - weightedGapRate 反映"软信号占比":

  • 差值 < 10%:信号以硬证据为主,gapRate 结论可信
  • 差值 ≥ 10%:软信号占相当比例,gapRate 可能被 hedging / 显式标记拉高,建议对照 gap inventory 复核弱信号的真实含义

水印要求不变:两个 gap rate 都必须附带 test set 标识(§7.1),不得脱离 test set 报告任何单独数值。

样本量可信度护栏(observe 侧):gap rate / weightedGapRate 与整体健康度色带在 segment 数过小时不可信——1 段 + 一次失败搜索得到的「红」与 50 段全红没有同等意义。observe 的 SkillHealthoverall 因此带 confidence 三档(与 eval 的 UNDERPOWERED 同构):

  • underpowered:segment 数 < 5,样本太少,色带 / gap rate 仅供参考,渲染层弱化硬色带;
  • low:segment 数 < 20,只有大缺口可辨;
  • high:segment 数 ≥ 20。

这是条件性测量、不是绝对判决:低 N 不代表 skill 没问题,只代表当前观测窗口不足以下结论,需累积更多真实使用 trace 后再看。

特殊情况处理

  • 如果一个用例同时触发多个缺口信号(比如失败 Grep + 显式标记),仍只计为 1 个有缺口的用例。聚合是"是否"而非"多少"。
  • 如果一个用例因为执行失败(agent 完全跑挂)而没有完整 trace,不计入分母。gap rate 只在成功执行的用例上计算,保持与 coverage 指标的边界一致。

六、跨评测趋势

单次评测的 gap rate 是基线,真正的价值在于跨多次评测的趋势。

omk 已有 src/renderer/trends.ts 跨评测时间序列表。本规范要求在该表新增一列:

| 时间 | git commit | composite | coverage | gap_rate | 用例集 | 用例数 | 成本 |

注意必须同时展示 用例集(test set 标识,通常是文件名 + content hash 前 8 位),否则跨评测对比可能把不同 test set 的数字混在一起误读。

关键观察类型

  1. gap rate 单调递减:每次往知识库补内容 → gap rate 下降 → 健康收敛信号
  2. gap rate 平稳:补的内容没击中真实缺口 → 检查 uncovered files 优先级
  3. gap rate 上升:要么用例变难了,要么知识库被破坏了,要么测评集换了——检查用例集标识
  4. gap rate 接近 0不是庆祝时刻。见第八节"用例进化"

艺术品式的成果:一张时间序列折线图,横轴 commit、纵轴 gap rate,标记每次往知识库补充内容的事件。曲线从 60% 下降到 5% 的轨迹,是 omk 推广材料中最锋利的一张图——前提是图上清晰标注所依赖的 test set。


七、缺口率驱动什么 action

gap rate 不只是一个数字,要驱动可执行的 action。报告里至少展示四类信息:

1. 强制水印(mandatory test set watermark)

每次报告展示 gap rate 或 coverage 时,必须同时展示

  • 当前 test set 的文件路径
  • 用例总数
  • test set 内容的 SHA hash 前 8 位(用于辨识用例集是否改动过)
  • 一句明文警告:"本指标仅反映当前 test set 与知识库的交互,不代表知识库的绝对完备性"

这不是建议,是规范硬要求。没有水印的 gap rate 数字在 omk 出口被视为无效。原因:数字一旦离开上下文就会被误用,读者看到 "gap rate = 5%" 不加水印会默认它说的是"知识库 95% 完整"——这正是 omk 最想避免的误判。

2. 缺口清单(per-evaluation)

每次评测报告新增"知识缺口清单"区块,列出该次评测中每个缺口信号的具体上下文:

用例 s003 / 第 4 轮 / [失败搜索]
  Grep "revenue_schema" → no matches
  agent 自述:需要查证 revenue 的字段定义在哪个域

用例 s007 / 第 2 轮 / [显式标记]
  agent 输出:【推断】这个数据应该来自 user_profile 表

读者看完清单立刻知道"我应该补什么"——失败 Grep 的 pattern 大概率就是缺失的知识条目名称。

3. 缺口分类(per-evaluation)

按缺口信号来源(失败搜索 / 显式标记 / 降级措辞 / 连续失败)分别统计数量。让读者知道这次测评触发的缺口主要是"agent 自己说的"还是"agent 搜了搜不到的"——前者是文档没说清楚,后者是知识本身不存在。

4. CI 阈值(cross-evaluation)

CI 模式(omk eval)支持两种守护模式:

  • --max-gap-rate <number>绝对阈值。gap rate 超过 number 时 CI 失败。用于"我们设定的可接受上限"
  • --gap-rate-regression <delta>回归阈值。gap rate 相对上次评测上升超过 delta 时 CI 失败。用于"防止知识库劣化"

两种模式可同时启用。这把 gap rate 从"事后观察"提升到"前置门禁",让知识库劣化无法默默发生。


八、用例进化:gap rate 接近 0 不是终点

当一个测评集的 gap rate 连续多次跑出来都接近 0,不应该庆祝,应该扩用例

原因:gap rate 低有两种解释:

  • A:知识库确实覆盖了测试用例所问的领域
  • B:测试用例只问了知识库已经能回答的问题,回避了边界

这两种情况在 gap rate 数字上完全无法区分。唯一的破解方法是主动扩展 test set 去探测新边界

用例进化的具体做法(v0.1 可做的)

  1. 人工扩用例:评测完成后看 coverage 的 uncovered files 列表——这些文件当前没被任何用例触及,是扩用例的天然候选目标
  2. 从 gap 清单反推:缺口清单里失败的 Grep pattern(比如 revenue_schema)往往揭示知识库的真实需求,可以作为扩用例的种子
  3. 定期重置基线:每季度把 test set 换一批,防止"驯化"——你的 test set 被知识库学会了之后就失去探测价值

用例进化的判断规则

omk 规范建议(不强制):当同一 test set 的 gap rate 连续 3 次评测都 ≤ 10%,工具在报告里自动追加一行提示:

⚠ 当前测评集的 gap rate 已连续 3 次低于 10%。建议扩展测评集以探测新领域,否则 gap rate 的下降可能只反映了"用例驯化"而非"知识补齐"。

这是一个 nudge,不是 fail。目的是让读者在数字变漂亮时保持警觉,而不是陷入"看到绿就安心"的舒适区。

为什么这个规则必须在 spec 里

如果 spec 不明确这一点,gap rate 会被误读成"低 = 好"的单调指标。使用者会把"追求低 gap rate"当成目标,然后自然地选择不去挑战那些让 gap rate 变难看的用例——也就是正在发生的 overfitting to the test set。spec 必须反复强调:gap rate 是诊断工具,不是 KPI


九、v0.2 展望:主动探测(Competency Questions)

v0.1 规范定义的四类信号全部是 被动 的——只有 agent 在跑人写的用例时撞到墙,才能捕获缺口信号。这意味着 gap rate 只能反映人写用例恰好触及的边界,人没想到的领域永远是黑箱。

v0.2 将引入 主动探测(competency questions):让 LLM 读知识库结构(文件名、章节标题、CLAUDE.md 原则),主动生成"这个知识库按 scope 应该能回答但可能没覆盖到"的问题,作为补充用例源。跑这批生成用例得到的 gap rate 和人写用例的 gap rate 并列展示——后者是"我设计的测评集触到的边界",前者是"知识库自己结构里暴露出来的边界"。

为什么 v0.1 不做

  1. 工程复杂:CQ 生成 prompt 需要精心设计,评分 rubric 和人写用例不同(没有"标准答案"),接入 sample pipeline 需要新的执行路径
  2. 风险:LLM 生成的 CQ 如果跑偏(瞎编出 scope 外的问题),会用假信号污染 gap rate,反而失去可信度
  3. v0.1 的目的是先立基线:被动信号采集稳定、报告格式跑通、趋势追踪能画出图之后,再引入生成式探测。两个不确定的东西叠加会互相污染

相关文献

  • 本体工程(ontology engineering)领域的 competency question 方法
  • "Don't Hallucinate, Abstain"(Cole et al., 2024)的 LLM 协作知识边界探测

十、已知限制和未来工作

  1. agent 自我标记依赖模型配合:第 2 类信号(显式标记)对模型有要求。未来可以在 system prompt 里明确"如果你在推断或有不确定性,必须用 【推断】 / 【知识缺口】 标记",把它从可选行为变成强制约定。

  2. 信号噪声:探索性搜索(agent 在排除可能性而非找答案)会被误判为失败搜索。v0.1 接受这个噪声;v0.2 可以引入"搜索目的分类"来降噪。

  3. 跨用例去重:当前规范在用例内做去重(同一用例多次触发只计 1 次),但跨用例不去重(两个用例都搜失败 revenue_schema 计为 2 个缺口用例)。这是有意的——重复出现说明缺口频繁,应该在 gap rate 里体现。

  4. 缺口与 coverage 的并存关系:本规范不替代 coverage。两者都是 测评集 × 知识库 交互的结果,但角度互补:coverage 衡量"测评集对知识库的利用程度",gap 衡量"测评集触及到的知识库盲区程度"。报告应同时展示。一次评测同时给出 coverage = 80% / gap_rate = 25% 比给出任何一个单独指标都更有信息量。

  5. 降级措辞的假阳率:v0.1 采用字符串匹配,已知会有误判。实现后如果假阳率超过 40%(实测值),考虑在 v0.1.1 暂时禁用第 3 类信号,等 v0.2 的 LLM 辅助识别上线再启用。


附录:术语对照

中文英文一句话定义
基于测评用例的知识覆盖率Test Case Knowledge Coverage已知知识被测评用例访问过的比例(how much of the knowledge base is exercised by the test cases)
基于测评用例的知识缺口 / 缺口率Test Case Knowledge Gap / Gap Rate测评用例中触发知识盲区的用例比例(how much unknown territory the test cases bumped into)
缺口信号gap signal单次"agent 想找而找不到"的具体事件
失败搜索failed searchGrep / Read / Bash search 工具调用未命中
显式标记explicit markeragent 输出文本中的【推断】【知识缺口】等中英文标记
降级措辞hedging language"我不确定 / 需要查证 / 可能是"等确定性降级表达
连续失败repeated failure同一用例内对同一类查询重试 ≥3 次全部失败的行为模式
缺口清单gap inventory每次评测报告中缺口信号的逐条上下文展示
强制水印test set watermarkgap rate 报告时必须附带的 test set 标识 + 明文警告
用例进化sample evolution防止 gap rate 接近 0 时陷入用例驯化的主动策略
主动探测competency questionsv0.2 引入的 LLM 生成探测问题,补充被动信号