CUOTI · MATH · PIPELINE V3

错题知识点标注:五层流水线

每天几千道只有题干、没有题目 id 的数学错题,如何被自动标注上最精准的知识点——从数据治理到评测闭环的完整执行逻辑。

60.7%acc@1 主标命中
84.7%acc@3 进前三
3.1%升级 pro 比例
98%+前缀缓存命中
≈0.7分单题成本
¥0重复题成本
逐行读取 · 非数学行直接标记 「暂不支持」跳到输出 去重后的唯一题 未命中 候选列表拼入 system prompt conf ≥ 0.7 · 直接采纳(96.9%) 按指纹回填所有重复行 原列后追加 9 列结果 缓存命中 · 跳过召回与 LLM · 0 调用 ¥0 错误分析 → 裁决规则迭代 v1→v3 原子重建 读 / 写 tag_cache 窗口内知识点卡 conf<0.7 或不在候选集 升级 3.1% 采纳 pro 结果 每日错题 XLSX(只有题干+答案,无题目 id) 学科过滤:先只处理数学(subject = 3) 2 指纹计算 · 批内去重 StripHTML → 规范化:去空白 · 全角转半角 · 统一标点 ExactFP = SHA-256 · SkeletonFP(数字→N,仅存备用) 相同指纹只算一题,结果回填所有重复行 查 tag_cache 缓存 命中条件:exact_fp + prompt 版本(v3)双键匹配 3 年级窗口召回 窗口 term_id ∈ [21, 23+(年级−1)×2] ∪ {0} =该生学过的全部知识点 · 金标召回率 100% 渲染候选卡:[id] 类目路径 | 名称 | 例题(顺序稳定) 4 deepseek-v4-flash 初选 system prompt=任务说明+9 条裁决规则+候选列表 同年级逐字节一致 → 前缀缓存 98%+ · 输出 JSON:主标/次要/top3 写入 tag_cache 含骨架指纹(仅存储,禁止直接复用标签) 5 回填与置信度兜底 conf<0.7 或失败 → 「是否需人工复核」=是 单题失败不中断整批 · 失败率>10% 才非零退出 输出 XLSX 主/次知识点 · 类目路径 · 置信度 · 理由 · top3 · 来源 · 复核标记 1 数据治理(离线) 知识点 / 类目树 / 金标 XLSX 清洗垃圾数据 → 知识点卡 kb.db(SQLite 知识库) 数学知识点卡 ×594 类目树 · 金标 ×575 tag_cache 结果缓存表 原子重建(tmp+rename) deepseek-v4-pro 精排 相同 prompt 重跑,覆盖初选 评测闭环(离线回归) 金标 575 题跑 acc@1 / acc@3 分年级 → 错误分析 → 规则迭代;prompt 版本号进缓存键,升版自动全量重标
一道错题的完整旅程:绿色捷径是重复题的零成本通道;右列是离线知识库与升级精排;底部虚线框是驱动 prompt 迭代的评测闭环。五个编号对应下文五层详解。
1

数据治理层

离线一次性 · 把三份 XLSX 变成干净的知识库

输入 知识点.xlsx(4459 条)· 类型.xlsx(类目树 1190 行)· 金标.xlsx(575 条已标注题)  输出 data/kb.db

处理逻辑

  1. 解析三份源文件(全程只读,不改源数据),运行 cuoti ingest 重建 SQLite 知识库;采用「写临时文件 + rename」原子重建,任何一步失败旧库不受影响。
  2. 清洗垃圾数据:剔除 is_delete=1 的知识点与类目;剔除 term_id=20 的脏数据;剔除名字含「待删除 / 需删除」但删除位没打上的 10 个僵尸知识点——这些若混进候选会直接污染 LLM 的选择。
  3. 拼接类目路径:每个知识点沿 cid1/cid2/cid3 逐级查类目树,生成完整路径(如「数与运算 › 100以内加减法 › 巧算」)。路径是 LLM 判断"这个知识点属于哪个体系"的关键信号。
  4. 生成知识点卡:数学学科最终得到 594 张卡,每张含 id、名称、完整类目路径、例题摘录(remark 字段前 50 字,835 条有例子)。这是召回层的物料。
  5. 金标解析:金标文件的「真实标签」是名字文本而非 id,用规范化匹配还原成 knowledge_id;同名歧义(如「周期问题」对应 3 个不同 id)时把全部 id 存为 gold 集合,评测时命中任一都算对,避免冤枉模型。

设计要点

知识库是后面所有层的地基:候选列表的文本与顺序都从这里生成,稳定性直接决定第 4 层前缀缓存能否命中。

垃圾数据在最上游一次性拦掉,比在 prompt 里教模型"忽略这些"可靠得多。

实测

594 张数学知识点卡,100% 有完整类目路径

金标 575 条,100% 成功解析到 id 集合

2

指纹缓存层

没有题目 id,就用内容当身份证 · 重复题零成本

输入 当天错题行(题干 HTML/文本 + 答案)  输出 缓存命中的现成结果,或待标注的唯一题清单

处理逻辑

  1. 清理与规范化:先 StripHTML 去掉标签和实体;再删除所有空白字符、全角字符转半角、中文标点统一为英文标点。目的:同一道题不管来自哪个题库、排版差多少,规范化后完全一样。
  2. 计算精确指纹 ExactFP:对「规范化题干 + 分隔符 + 规范化答案」取 SHA-256。这就是题目在系统里的身份证。
  3. 计算骨架指纹 SkeletonFP:把连续数字替换成 N 再哈希。只存储、绝不直接复用标签——「59−43」和「59−48」骨架相同,但 48 逼近整十,该标「凑十巧算」,考点完全不同。骨架指纹留作未来"相似题提示"用。
  4. 批内去重:同一批里精确指纹相同的行只标注一次,结果回填到所有重复行。
  5. 查缓存tag_cache 表按「exact_fp + prompt 版本」双键查询。命中则直接取历史结果,跳过后面所有环节;未命中则进入召回层,标注成功后写回缓存。prompt 升级到新版本时旧缓存自动失效,全部重标。

设计要点

每天几千道题里重复率越高,这层省得越多;缓存只认精确指纹,宁可重新标注也不冒险复用相似题的标签

数学题的考点常常藏在具体数字里,这是骨架指纹被降级为"只存不用"的根本原因。

实测

冒烟二跑:8/8 全命中,0 次 LLM 调用,成本 ¥0

批内去重:9 行数学 → 8 道唯一题

3

年级窗口召回层

594 选百余 · 只把"这个年级学过的"给模型看

输入 未命中缓存的题(带 term_name 年级信息)  输出 该年级的候选知识点卡列表(文本,顺序稳定)

处理逻辑

  1. 解码学期编号:题库的 term_id 编码为 22..31 = 一年级上..五年级下(上海五年制),21 = 学前,0 = 通用。这个映射是拿金标数据反推验证出来的。
  2. 确定召回窗口:窗口 = term_id ∈ [21, 23+(年级−1)×2] ∪ {0},即「学前 + 从一年级上到该生当前学期的全部 + 通用」。语义就是:这个孩子到现在为止学过的所有知识点都是合法候选
  3. 筛卡:从 594 张知识点卡中取出窗口内的卡(垃圾知识点已在第 1 层被剔除)。
  4. 渲染候选列表:每张卡渲染成一行 [knowledge_id] 类目路径 | 知识点名 | 例:例题前50字,按固定顺序排列。顺序稳定是刻意设计:它保证同年级所有题的 system prompt 逐字节一致,这是第 4 层前缀缓存命中 98%+ 的前提。

设计要点

窗口宁大勿漏:召回层漏掉正确答案,后面再聪明也救不回来,所以用"学过的都算"这个宽松边界,把精挑细选的工作全部交给 LLM。

召回是纯规则、零成本、毫秒级——贵的判断留给下一层。

实测

金标 575 题召回率 100%(正确答案全部在窗口内)

4

LLM 级联精排层

flash 干活 · pro 兜底 · 前缀缓存把成本打下来

输入 题干+答案+候选列表  输出 JSON:primary(主标 id、置信度、理由)+ secondary(次要知识点)+ top3

处理逻辑

  1. 组装 prompt:system = 任务说明 + 9 条裁决规则 + 该年级候选列表;user = 题干和答案。同年级 system 逐字节一致,DeepSeek 前缀缓存命中后输入价约降至 1/50,实测命中率 98%+。
  2. flash 初选deepseek-v4-flash 输出结构化 JSON。并发 8 个 worker,单题 300 秒超时,429/5xx 自动退避重试 3 次。
  3. 校验输出:主标 id 必须落在候选集内——模型偶尔会幻觉出不存在的 id,越界视为不可信。
  4. 级联升级:置信度 < 0.7 输出越界时,用完全相同的 prompt 换 deepseek-v4-pro 重跑一次,采纳 pro 的结果。实测升级率仅 3.1%,是安全阀而非主力。
  5. 单题容错:一道题失败只标记这一题,不影响批内其他题。

九条裁决规则(prompt v3 的核心资产)

  • 先析结构:先分析算式/数量关系,再匹配知识点
  • 技巧优先:凑十巧算等技巧 > 应用题外壳
  • 方向一致:已知A求B ≠ 已知B求A
  • 方法一致:解题方法要与知识点声称的方法吻合
  • 具体优先:能标具体的绝不标宽泛的
  • top3 分散:前三名覆盖不同的可能性,不许扎堆
  • 应用题看算式:按算式结构定子树,不被故事情节带偏
  • 回避泛化类目:优先课程类目,回避「常见数学问题」等汇总子树
  • 计数用计数:计数题标「计数基础」子树,不标「搭配问题」

设计要点

全量 pro 实验已否决:acc@1 仅 +2 个点但成本 4 倍。结论:瓶颈是题库标注惯例,不是模型能力。

flash 的置信度校准偏差较大(0.95+ 区间仍有约 1/3 错),升级机制定位为兜底,不指望它拉精度。

实测

acc@1 60.7% · acc@3 84.7%

升级率 3.1%,575 题全量约 ¥3.8

前缀缓存命中 98%+

5

置信度兜底 + 评测闭环

不确定的交给人 · 每次改 prompt 都有数字说话

输入 每道题的标注结果或失败信息  输出 结果 XLSX(原列+9 列)· 评测报告

处理逻辑

  1. 回填:按精确指纹把唯一题的结果回填到批内所有重复行;非数学行标「暂不支持该学科」。
  2. 人工复核标记:置信度 < 0.7 或标注失败的行,「是否需人工复核」=是。
  3. 整批容错:单题失败不中断;输出和汇总先写完,失败率 > 10% 才以非零码退出。
  4. 输出 9 列:主知识点 id / 名称 / 类目路径 / 置信度 / 理由 / 次要知识点 / top3 / 来源(cache · llm · 失败)/ 是否需人工复核。
  5. 评测闭环cuoti eval 对金标 575 题跑 acc@1 / acc@3 并分年级统计。每轮错误分析产出新的裁决规则 → prompt 升版 → 版本号写进缓存键,全量自动重标:
版本改动acc@1acc@3
v1基础 prompt47.1%67.8%
v2+6 条裁决规则58.2%80.8%
v3+3 条题库惯例规则60.7% ↑13.684.7% ↑16.9
对照全量 pro(已否决)60.2%80.0%

设计要点

诚实的天花板:少数兄弟知识点(如「枚举法」vs「画树状图」)从题面无法区分,主标只能保证进 top3。

下一跳最大杠杆是更大的金标库:每个知识点配真实例题做 few-shot。

实测

复核线 0.7;升版后缓存自动失效,无需手动清理

GOLD vs MODEL · 226 CASES

分歧对比:模型主标 ≠ 金标

金标 575 题中模型主标与「真实标签」不一致的全部 226 条。每条附模型的判断理由和双方的类目路径——请逐条裁决:是模型标错了,还是金标本身有问题。判定结果存在本机浏览器里,可随时导出发我。

226总分歧数
138排序分歧(金标在 top3)
88硬分歧(金标不在 top3)
0已判定
判定进度: 金标可疑 0 模型有误 0 存疑 0 未判 226

MULTI-LABEL · SECONDARY ≥ 0.5 · 575 CASES

多标签对比:主标 + 次标(置信度 ≥ 0.5)

全量金标 575 题按多标签口径重新判定:主标即金标 = 通过;主标不是、但生效次标(置信度 ≥ 0.5)包含金标 = 合理包含;生效标签完全不含金标 = 冲突

60.7%通过率(349 题)
80.3%通过 + 合理包含率(462 题)
113合理包含(19.7%)
113冲突(19.7%)