案例库建好以后,下一步并不是把全部历史塞进提示词,而是回答一个检索问题:当前任务发生时,系统应该把哪几条过去经验交给新的模型?
这次实现先选择了一个很朴素的基线。错误描述会被去掉文件路径、行号、引号和裸数字,再转成小写模式;关键词提取会拆分 camelCase 和 snake_case,移除常见停用词,并把数量限制在十五个以内。查询与历史案例之间使用 Jaccard 相似度,默认取前五条,再压缩进一个最多一千五百字符的历史案例区块。
它没有向量库,也不调用额外模型。问题是:这样一个可解释的关键词基线,什么时候算够用,什么时候只是在制造“已经有召回”的错觉?
一条完整的召回链路其实包含四次压缩:原始任务被压成模式,模式被压成关键词集合,全部历史被压成 top-k,最终 top-k 又被压进字符预算。任何一次压缩都可能丢掉决定根因的细节。
算法是否简单不是关键,关键是每一次丢失是否可见、是否可以被样本证明。
先把机械过程做成可检查的
Jaccard 的优点不是“先进”,而是每一步都能展开。
假设一次错误包含文件名、行号和属性名,归一化后会尽量留下稳定的错误类型与动作词。两个关键词集合的交集越大、并集越小,相似度越高。当前实现把 0.4 以上标为高相关,0.2 到 0.4 标为中等相关,默认最低阈值是 0.15。
这套规则可以直接解释为什么某条案例被召回。出现误召回时,也能看到究竟是哪些词造成了重合,而不是只能接受一个不可读的向量分数。
已有测试能确认几件机械事实:归一化会移除路径和行号;驼峰与下划线词可以拆开;停用词不会进入关键词;同一输入会得到一致签名;关键词数量不会无限增长。
例如,两条只在属性名、文件名和行号上不同的 TypeError,经过处理后可能落到相近模式。这有利于把偶然差异合并成同类问题。召回结果还会携带历史根因、修复摘要、改动文件和结果状态。下一次任务看到的不是一段完整聊天,而是一份被压缩的工程案例。
但当前测试没有覆盖“给定查询,正确案例是否排在前面”。它们也没有模拟案例数量增长、同名模式冲突、字符截断和跨来源检索。
机械不变量是必要条件,不是检索质量证据。
当前实现最明显的五个边界
第一个边界是中文。
关键词提取目前只按英文字母、数字和下划线切词。对于以 TypeError、函数名和代码标识符为主的错误,这种策略有现实价值;但如果任务描述主要是中文,关键词可能为空,或者只剩夹在中文里的少量英文标识符。
这不是线上统计得出的结论,而是代码规则本身暴露出的限制。它意味着“错误召回”与“任务召回”可能是两套不同难度的问题:前者通常有稳定的英文错误词,后者更依赖中文语义。
第二个边界是表面相似不等于根因相同。两个案例都包含 undefined、request 和 state,一个可能是异步初始化未完成,另一个可能是字段契约已经变更。Jaccard 会看到词的重合,却看不到因果差异。
第三个边界来自去噪。移除数字、文件名和路径通常能减少偶然差异,但某些数字其实是错误码,某些文件名代表模块边界。统一删除会提高泛化,也可能删掉区分问题所需的证据。
第四个边界是字符预算。最多一千五百字符可以控制上下文,却也会让后面的案例被截断。当前格式优先保留相似度排序靠前的案例,但没有证明“前五条”或“一千五百字符”是最佳配置。
第五个边界是案例更新语义。当前设计会按来源和归一化模式合并重复案例,后一次结果可以更新前一次结果。这能避免同一个错误无限复制,却可能把“同样表象、不同时间、不同根因”的历史压成一行。若成功与失败交替出现,只保留最新状态还会失去环境变化信息。
这个问题不是 Jaccard 本身造成的,但会直接影响它能检索到什么。
被召回,不等于帮助过任务
案例表记录了命中次数。这个数字只能说明某条案例被检索出来过,不能说明它让任务更快,也不能说明模型采用了正确修复。
一个失败案例可能因为词很常见而频繁命中;一个真正关键的案例也可能因为描述方式不同而从不出现。只看 hit count,很容易把“容易被算法找到”误写成“对后续任务有价值”。
因此,检索效果至少需要两类证据。
第一类是离线标注:给定一批真实查询,由人工标出哪些历史案例拥有相同根因或可复用修复,再计算 Precision@3、Recall@5 和 MRR。正例标准应该是根因与修复可迁移,而不是错误文本看起来相似。
第二类是在线结果:记录任务是否看到了召回内容、是否引用了其中的修复、最终验证是否通过,以及召回是否造成错误锚定。只有这条链存在,才能判断历史经验是否实际参与了下一次任务。
还需要一组负样本。检索系统不能只证明“相似问题能找到”,还要证明无关问题不会因为几个通用词被强行关联。对于低于阈值的查询、关键词为空的中文描述和根因明显不同的同类报错,预期结果可以是零命中。
能够承认“没有合适案例”,比稳定返回五条看似相关的内容更重要。
为什么仍然先保留这个基线
在没有标注集、没有足够案例量、也没有真实对照实验时,直接上 embedding 只会把未知变得更难解释。
Jaccard 至少提供了一个可以被证伪的起点。它没有外部模型依赖,结果可以逐词检查,失败样本也容易归因。更重要的是,它迫使当前系统先回答数据问题:案例描述是否一致、根因是否填写、中文比例有多高、哪些字段真正区分问题。
下一步可以先补一个小型标注集,同时统计空关键词率、中文查询占比、误召回类型和被截断案例数量。标注时需要保存判断理由:是同一错误文本、同一根因、同一修复,还是仅仅属于同一模块。否则,不同的人会用不同标准定义“相关”。
然后再比较三条路线:现有关键词基线、加入中文分词或字符 n-gram 的本地基线、向量召回。比较必须使用同一批查询、同一案例快照和同一字符预算。除了相关性,还要记录延迟、可解释性、离线依赖和错误分析成本。
只有当更复杂的方案在相同样本上稳定提高相关性,并且收益覆盖复杂度,升级才有证据。
《我删掉了一套数学上正确、工程上无效的学习闭环》解决的是历史经验能否穿过会话边界。这个实验关心的是另一件事:当通道已经存在,送过去的究竟是不是正确的那段历史。