八道题覆盖全书,按章节顺序排列。每题给出涉及章节、难度(★ 入门 / ★★ 进阶 / ★★★ 综合)与验收标准;每题先给提示、再给参考要点。做完 C-8,你就把第 9 章的循环在自己仓库上真正转起来了。
chunker.py(3.2)把带装饰器的定义当 decorated_definition 处理时,装饰器行可能落入孤儿块。改造 visit:让被 @decorator 修饰的函数/类正常进入函数/类 chunk,装饰器行随定义走。
验收:对一个含 @app.route(...) 装饰器的文件切分,chunk.text 首行是装饰器、无孤儿碎片;对 chunker.py 自举运行不报错。
提示:decorated_definition 的子节点结构是 decorator* + definition。遍历到它时,取其最后一个 definition 类型的子节点(或按 child_by_field_name 思路找到内层定义)按原逻辑切成 chunk;把 decorated_definition 从孤儿分支的条件里挪走,避免定义与装饰器分离。
4.2 的增量更新器调用了 store.remove_by_id(gone),而 4.1 的 MiniVectorStore 没有这个方法。实现"标记删除 + 定期压实"版:remove_by_id 只记入待删集合,compact() 才真正重排矩阵;search 跳过待删行。
验收:删 3 个 chunk 后 search 不再返回它们;compact 后 len(meta) 与矩阵行数一致;总行数变化前后各跑一次 5.1 的 hybrid_search 结果一致(对齐未被破坏)。
提示:待删集合存行号会有"删 A 后 B 的行号变了"的错位问题——存 meta 里的 id 更稳(search 时按 id 过滤)。compact 用布尔掩码:mask = [i for i, m in enumerate(meta) if m["id"] not in dead]; vecs = vecs[mask]——7.2 的 update 正是这么压实的,可对照。
把 rrf_fuse 的 k 取 1 / 10 / 60 / 600,对同一个查询的两路排名分别融合,比较四个结果的排序差异。
验收:用文字解释:k→1 时什么文档占优(单路第一)、k→∞ 时融合退化成什么(名次和排序);给自己的场景选一个 k 并说明理由(没有标准答案,有理由即可)。
提示:算三个量感受差距:排名第 1 与第 2 的贡献差 1/(k+1) - 1/(k+2),在 k=1 时是 1/6、k=60 时是 ~1/3744——k 越小,"排前"的溢价越猛,单路冠军越容易霸榜。
用第 7 章的 ask.py --rerank {0,10,20,50} 对同一批查询各跑一遍,记录每档的 top-5 手感质量(或接 C-8 的评测集看 MRR)与端到端耗时。
验收:一张四行的 depth/质量/耗时表;指出你观察到的收益饱和点;解释为什么 depth 超过某个值后纯烧钱(5.2 的 O(depth) 代价 + 融合精度上限)。
提示:计时用 time.perf_counter() 包住 retriever.search(9.3 的 evaluate 已内置)。看质量时分别记"第一位就对"的查询数(MRR 的直觉)与"top-5 含正确"的查询数(Recall 的直觉)——两者对 depth 的响应曲线不同。
6.1 的 repo_map.py 做的是文件级 PageRank。改成符号级:图的节点是 identifier,边是"引用文件 → 定义符号",迭代出每个符号的权重;渲染时文件按其符号权重之和出场。
验收:对同一仓库输出新旧两张地图,找一例"文件级地图排在后面、符号级排到前面"的文件并解释原因;token 预算下符号级地图的信息密度(每行符号数)应不低于文件级。
提示:骨架完全复用:build_graph 里把 refs_out[f][target] 换成 refs_out[f][sym](引用文件→符号),pagerank_files 的迭代公式一行不用改(它只认"节点→出边邻居")。渲染时按 def_site 把符号权重聚合回文件。
给 6.3 的 search_code 加 mode="lexical" 参数:走纯 BM25 一路(跳过嵌入调用),返回格式不变。同步更新工具 schema 与 description(写明何时模型该选 lexical:指名道姓的查询)。
验收:mode="lexical" 时查询 kill_session 第一位命中且不调用 embed(用计数器或打点验证);schema 的 description 里写清了两模式的选用条件。
提示:hybrid_search 里向量一路的调用包在 if mode != "lexical" 里,rrf_fuse 只喂一路排名也能工作(退化成单路名次)。工具层验证 embed 未被调用:给 embed 包一层计数器函数(calls = 0; def counted(texts): nonlocal calls; ...)。
给 codeqa 加 --lang js:装 tree-sitter-javascript,为 JS 写 DEF_QUERY((function_declaration name: (identifier) @def)、(method_definition name: (property_identifier) @def)、(class_declaration name: (identifier) @def)),切分与建库全链路跑通。
验收:对一个 JS 仓库建索引并用 ask.py 检索;标注出至少一处 Python 与 JS 查询语句法的差异(对照 3.2 的查询写法);说明 chunker.py 里哪些逻辑是语言无关的、哪些要按语言分派。
提示:差异点举例:JS 的类方法节点叫 method_definition(Python 里是类体内的 function_definition);箭头函数赋值 (variable_declarator value: (arrow_function)) 没有名字,需要用变量名兜底。语言分派建议做成 {lang: (parser, query)} 的注册表,而不是 if-else 链。
用 9.2 的 weak_label.py 从自己的仓库造 50 条评测集候选,人工复核成 30~50 条正式评测集(三级金标);跑 evaluate.py 出基线;然后按 9.3 的 playbook 做两轮"一次一变量"实验(建议:先 recall、再 bm25_w:vec_w)。
验收:交付三样——评测集 JSONL、含基线与两轮实验的 history.jsonl、一页纸结论(每个参数的取值决定与理由,指标数字支撑)。全程遵守纪律:一次一变量、全量评测、holdout 不动。
提示:弱标注复核的快速判据:commit 首行是"做了什么"(add retry to upload)就是好查询候选,是"改了哪里"(fix typo、bump version)就丢。金标别贪多:每题 1~3 个 chunk、等级拿不准就先不加(宁缺毋滥——金标错比漏标伤害大,9.2 的原话)。