Skip to content

bug(search): the tracked OKF export is two and a half months stale, so the local SAG index covers 194 of 411 lessons #2185

Description

@Ikalus1988

事实

data/okf/lessons.jsonl 是被跟踪的文件(75,659 字节 / 194 行),最后一次写入是
2026-07-07(ae3fc5a2f),此后没有任何 workflow 触碰过它:

$ grep -rn "export_okf" .github/workflows/*.yml        # 没有任何调用点
$ git log -1 --format=%ad data/okf/lessons.jsonl       # 2026-07-07

它的写者是 scripts/export_okf.py(手工跑)。同一棵树里现在重新导出:

$ python3 scripts/export_okf.py --output /tmp/okf-fresh
$ wc -l data/okf/lessons.jsonl /tmp/okf-fresh/lessons.jsonl
    194 data/okf/lessons.jsonl
    411 /tmp/okf-fresh/lessons.jsonl

411 行里有 187 行(45%)的课程是 2026-07-07 之后才出现的,所以那份被跟踪的导出在结构上就不可能包含它们。

影响:本地 SAG 搜索少 45% 语料,而且它优先于完整的 BM25

data/sag.db 由 scripts/build_sag_index.py 从上面那个 jsonl 构建,且被 gitignore
(.gitignore:data/sag.db,2026-07-01 c040ca439 移出跟踪)。所以每个新 clone 都要自己构建,
而构建的输入就是那份 2.5 个月前的导出。

misakanet/server/handlers/search.py 在没有任何索引时给出的提示恰好只让用户跑这一步:

"error": "Search engine unavailable — index not built",
"hint": "Run: python3 scripts/build_sag_index.py to enable BM25/SAG search",

build_sag_index.py 发现 data/okf/lessons.jsonl 存在就不会报错(它的 "Run export_okf.py first"
只在文件缺失时出现)——于是照着文档做的人拿到一个 194 篇的索引。

而搜索路径是:

if HAS_SAG and not explain:          # SAG 优先
    results = sag_search(SAG_DB, query, domain=domain, top=fetch_n)
elif HAS_BM25:                       # 完整语料(直接读 lessons/)

实测(同一台机器,只换索引):

$ python3 - <<'EOF'   # 用一条只存在于新导出里的课程做探针
stale index: 194 lessons
fresh index: 411 lessons
lessons first-seen after 2026-07-07: 187 of 411
probe: 'Accidental __pycache__ artifacts committed'
  stale (194)  -> 0 hits  []
  fresh (411)  -> 1 hits  ['accidental-pycache-commit.md']
EOF

也就是说:新用户按提示构建索引 → 搜索召回比不构建更差(SAG 覆盖 47%,还会遮住完整的 BM25 路径)。

为什么现在才发现

  • 这个文件不在任何门禁的视野里:sync_lesson_count.py 管的是课程数、update_lessons_json.py 管
    data/lessons.json,没有一条规则说"导出必须比语料新";
  • 它看起来很新——文件在仓库里、格式合法、build_sag_index.py 能读,没有任何信号说它旧;
  • 唯一一次被纠正是在 PR fix(mcp): the search surface returned unreadable results, leaked drafts, and crashed on real error text #1999 里(作者为了让 status: draft 进入索引而重新导出,407 行)——
    那是手工的,下次还会烂回去。

建议

  1. 给它一个写者,和 data/lessons.json 同等待遇:挂在每日的 update-lessons.yml 里跑
    export_okf.py(它已经读过 411 篇),或者明确声明"这个文件是手工的"并从仓库里移除、让
    build_sag_index.py 自己去 lessons/ 导出;
  2. 加一条新鲜度关系(而不是一个数字):data/okf/lessons.jsonl 的行数/时间戳必须与语料一致——
    不一致就让 --check 变红。这类"派生物比源旧"的缺陷在 chore(docs): two documents that call themselves automatic stopped being true #2082 里已经有过一次了;
  3. 顺手收紧那句提示:hint 应该写成"先 export_okf.py 再 build_sag_index.py",或者在
    build_sag_index.py 里检测"导出比 lessons/ 里最新的课程还旧"并拒绝构建。

第 3 条是成本最低、能立刻阻止"照文档做反而更差"的一半。


This repo is using Opire - what does it mean? 👇
💵 Everyone can add rewards for this issue commenting /reward 100 (replace 100 with the amount).
🕵️‍♂️ If someone starts working on this issue to earn the rewards, they can comment /try to let everyone know!
🙌 And when they open the PR, they can comment /claim #2185 either in the PR description or in a PR's comment.

🪙 Also, everyone can tip any user commenting /tip 20 @Ikalus1988 (replace 20 with the amount, and @Ikalus1988 with the user to tip).

📖 If you want to learn more, check out our documentation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds-ac

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions