代码搜索栈迁移:Lexical、AST 与 Embedding 的影子评测和切换门禁
新代码搜索引擎的 Demo 更“聪明”,不代表它能替代现有 grep、LSP 或符号索引。本文冻结查询任务、代码快照和相关性判断,用双索引影子流量验证召回、新鲜度、权限与可回退切换。
本文要点
- 主题聚焦:AI Agent、TypeScript、DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
“查找 Invoice 的定义”适合符号索引,“哪里处理退款超时”可能需要语义检索,“所有调用这个环境变量的脚本”又更适合精确文本搜索。把一组漂亮的自然语言 Demo 当成选型证据,容易得到一个会解释代码却漏掉重命名、未提交文件或受限目录的系统。代码智能:AST + LLM介绍组合能力,本篇只治理搜索栈比较和迁移接受点。
冻结 queryPortfolio.v1
搜索 owner 发布 codeSearch.queryPortfolio.v1。每条 queryId 绑定 repository tree、query text 与语言、用户意图类别、授权主体、必须出现和禁止出现的 blob/symbol、允许等价结果、评审 owner 及难度。组合至少覆盖精确标识符、定义/引用、调用路径、配置键、错误语义、跨语言接口、重命名、删除和脏工作树,并保留“不应返回任何结果”的负向查询。
schema: codeSearch.queryPortfolio.v1
queryId: billing-refund-timeout
tree: sha256:18c...
kind: behavior-location
principal: role:payments-dev
relevant: [blob:91a...#RefundWorker, blob:77c...#retryPolicy]
forbidden: [path:legal-private/**]
相关性不能只由候选引擎自己生成。两名熟悉仓库的 reviewer 在不知道引擎身份时标注,分歧由第三人仲裁;引用图或 LSP 可以提供候选,但不能直接成为真值。Portfolio 记录未完全标注的 pool 和判断深度,避免把未审结果一律当成不相关。仓库升级后发布新 generation,旧 tree 上的高分不能证明当前代码可用。
Lexical、Tree-sitter/AST、LSP 与 Embedding 各自固定 parser、tokenizer、模型、维度、归一化、chunk 和 rerank 配置。索引 manifest 绑定 tree、submodule、生成器、ACL policy、构建时间、水位和 shard 摘要。编程 Agent 仓库地图决定提供哪些结构线索;搜索选型要证明这些线索来自同一仓库快照,而不是混用旧索引。
双索引回答同一个权威查询
影子网关把规范化 query、principal、目标 tree 和 dirty overlay digest 同时送给旧、新栈。响应保留有序结果、分数、解释类别、index generation、耗时和错误,但只把旧结果交给 Agent。精确查询比较 recall@k、MRR 和首个正确结果;探索查询报告 judged recall、nDCG 与无结果率;所有类型还要报告 p50/p95 延迟、索引成本和权限违规。
向量相似分数不能与文本 BM25 分数直接比较,跨引擎 winner 由任务指标决定。重复 query 用 event ID 去重,超时和部分 shard 失败计为 INCOMPLETE_RESULT,不能拿已返回的 top-k 假装完整。查询包含秘密或源码时不进入普通分析日志,只保留版本、类别与受控摘要。pgvector 等向量后端提供的是近邻能力,相关性、ACL 和新鲜度仍由上层合同负责。
增量更新单独验收:在 tree N 建索引后连续执行新增、修改、rename、删除、权限收紧和 force-push,记录 commit event 到 searchable generation 的延迟。Monorepo 索引新鲜度定义增量上下文水位;本次迁移把同一水位纳入新旧栈比较。脏工作树必须作为请求级 overlay 叠加,不能写进共享 canonical index。
专门测试共同缺陷和权限泄漏
故障套件注入 parser 不支持的新语法、LSP server 崩溃、Embedding 模型错代、vector shard 少一片、文件 rename 只处理新路径、删除 tombstone 丢失、ACL 缓存旧 epoch、大小写和 Unicode 标识符、符号链接越界,以及 reranker 把 forbidden path 提到首位。还要用语义相似但行为相反的函数攻击向量检索,用注释中的旧 API 名攻击 lexical 排名。
门禁要求所有 forbidden 结果返回数为 0、删除与撤权在声明水位后旧结果为 0、精确符号 recall@10 为 100%、关键行为查询 recall@20 下界达到预注册值、负向查询误报率不升、INCOMPLETE 不被记成成功、p95 延迟与单位查询成本在预算内。任何 ACL 泄漏都直接淘汰候选,不能由平均相关性抵消。RAG Embedding 迁移处理知识检索向量换代,代码栈还必须保持 blob、symbol、tree 与权限身份。
切换先双建索引并 shadow 至少一个完整发布周期,再让 5% 只读搜索按主体稳定分桶进入新栈。回滚窗口内旧栈必须持续消费 commit、ACL 和 tombstone 事件;它定期签发 readiness receipt,绑定当前 tree、ACL epoch、删除水位和完整 shard 集。alias CAS 只能切到与权威水位一致的 generation。若关键层 recall 下降、freshness p99 超标、UNKNOWN shard 超过阈值或出现一次权限泄漏,仅在旧 receipt 仍新鲜时回切;否则对当前 tree 使用受控 lexical fallback,无法证明权限或删除水位时拒绝查询。故障栈退出 serving 后可在隔离态重放,不能停止消费后把陈旧索引当回滚点。旧索引直到删除、重命名和回滚演练全部通过才清理。
Related