
遇到过一个具体场景攒了七八年的老系统模块几十个代码量怎么也有几百万行。新来的同事想在新功能里复用某个老服务的逻辑翻代码翻了三个小时最后在技术群里小心翼翼问了一句“这段逻辑到底是干嘛的”结果没人说得清楚。这种场面你大概率见过因为它不是代码质量问题而是知识被埋在了项目深处。最近我在实践一个方向把它叫“项目探测”。说白了就是主动去扫描、抽取、索引老项目里的代码意图、设计决策、模块边界和历史改动把这些零散知识做成一整套AI能随时调用的外部记忆。这样AI再也不是只会写“hello world”的工具而是一个知道你这个系统为什么长成这样的“老同事”。这篇文章就把我在实操当中踩过的坑、验证过的链路以及一条可以直接照搬的最小落地路径全部摊开讲一遍。1. 项目概览给老项目做一次“知识体检”1.1 “项目探测”解决的是知识断层问题你在一个新的AI助手对话框里问“这个项目的订单状态字段到底有哪些”它如果对你这个项目一无所知只能给你一段泛泛的枚举。真正的项目知识藏在数据字典、状态机逻辑、历史代码注释还有无数个没人看的Markdown文档里而这些恰恰是模型训练时根本不可能学到的东西。项目探测要做的就是把这些散落的知识资产变成可查询的结构化索引。原理上有点像我每年会做的体检你不只是量一下体重而是把血常规、肝功能、B超这些指标全部拉出来生成一张能够判断倾向和异常的完整报告。对应到老项目上我们要探测的不是到文件数量为止而是到“这个模块为什么这样设计”“这两个服务之间的调用关系是什么”“这条历史SQL当时是解决什么场景的”为止。最终交付的产物不是一份躺在服务器里的文档而是一套可以让AI在回答问题时主动查询的知识层。1.2 “外部记忆”到底在给AI补什么大模型本身是有记忆限制的。模型参数里装了海量通用知识但关于你公司内部某个系统的具体知识基本等于零。就算用超长上下文窗口把代码一股脑塞进去算力和费用先不说信息之间的干扰也足够让模型从第一万个token开始“跑偏”。外部记忆这个概念解决的不是“让模型多记一点”而是“让模型在需要的时候能找到正确的那一小块”。你可以这样理解模型的能力是一个很强的大脑但工作台只有那么一点大外面是海量的档案库。如果你想把几百个模块的细节全部摆在工作台上桌子早塌了。外部记忆就是档案柜旁边配一个靠谱的库管员你问一句话它快速跑到柜子里把最相关的那几份文件抽出来放到工作台上。这才是真实业务环境里可行的方法。项目探测的目标就是造好这个档案柜并训练好这个“库管员”。2. 为什么不能直接“把项目喂给AI”2.1 上下文不是越大越好“泛读”会稀释注意力有一段时间我特别迷信长上下文觉得只要输入足够长AI就能把项目吃透。实测了几个月以后发现这个想法在短代码片段上成立在那几百万行的真实业务仓库里基本是灾难。原因不复杂。模型在预测下一个token时注意力会被分布在整个序列上。如果你塞进去的代码或者文档里70%都跟当前问题无关模型很容易被无关信息带偏。比如你问“支付回调重试了几次”全库里可能有十几个文件里都出现过“retry”这个单词如果把这些碎片都铺在上下文里模型很难判断哪个是支付链路的retry、哪个是消息队列的retry。我在一个真实案例里遇到模型回答问题时把“库存扣减接口”的依赖关系说成“消息通知接口”的依赖关系就是因为两个模块的描述离得太近而且上下文里几百行无关配置干扰了判断。后来我把输入改成只有高相关性的三个代码片段加一条外部索引描述回答准确率立刻上来了。2.2 Token成本与工程效率也不允许“全量投喂”假设一个项目有效代码有两百万行平均每行折算下来大约十个token那一共就是两千万token。单次全量进入上下文按照当前主流大模型的定价一次问答光输入成本都是几十块甚至上百块工程团队不可能这么玩。更要命的是延迟。你每次提问都带着十几MB的上下文去调用模型首字返回时间会拉长到让人怀疑接口挂了。而外部记忆的检索过程通常是百毫秒级加上命中片段后的小上下文调用成本和速度都友好得多。从工程角度来说“全量投喂”违反了一个根本原则不是所有信息都有同等价值。项目知识是高度长尾分布的真正影响AI回答质量的往往集中在少数几个关键的类、接口、数据表注释和改动人身上。外部记忆就是帮AI在这些长尾信息里做精确制导。2.3 老项目知识不全在代码里问答需要“多源证据”还有一个常被忽略的事实老项目里最值钱的知识往往根本不在源代码中。可能是某次线上事故后在一段注释里补了一句话可能是在某个已经被归档的Wiki页里画出了核心订单模块的状态机也可能是Git记录里某次commit message写着“修复并发导致超卖后续做幂等再想更好的方案”。这些散落在不同载体中的知识片段之间存在上下文关系。代码单独看是“做什么”文档单独看是“为什么做”而把两者关联起来才是一个完整的决策闭环。项目探测要做的不只是收集而是把“做什么为什么做谁在什么时候改了影响了什么”建模成一个可检索的知识网络。这也是外部记忆和简单全文检索之间的本质差异。3. 老项目知识采集的边界与方法3.1 七个不能漏掉的知识源我总结过自己在这个方向的几次尝试发现但凡用起来顺畅的基本都覆盖了下面这些源。这里整理成了表格方便你对照自己的项目盘点知识源典型内容常见问题源代码类名、方法签名、业务判断、命名命名不规范时语义弱代码注释意图说明、TODO、踩坑警告大量注释已过时或自说自话设计文档/架构文档系统边界、模块分层、选型理由通常跟不上代码演进Git提交记录commit message、变更文件、作者仓库历史消息质量参差Issue与PR记录现象反馈、评审讨论、最终方案可能散在多个平台不统一数据库Schema表结构、字段注释、索引和关联注释易缺失外键关系较少配置与部署文件环境差异、服务依赖、超时/重试参数常被当作运维文件忽略真正做的时候别嫌弃有些源脏脏数据可以在后面清洗。关键是一个都别漏掉因为我在实际检索时经常发现代码里没有答案的字段含义却在数据库Schema的注释里写着代码注释没说清楚的重试策略偏偏在某个年份的Issue讨论里被人拍板过。3.2 抽取的“高密度知识”长什么样一开始我也犯过很低级的错误把每个Java文件完整读一遍然后把方法签名全部抽出。结果向量化以后检索出来的内容大多是“public String getUserNameById(Long id)”这种没有上下文的碎片。后来我意识到真正对AI回答有增量价值的是下面五类高密度知识第一类是业务规则和约束。比如“优惠券每个用户最多领取三张”“超过晚上十点不允许退款”这类信息在代码里往往分散在好几个分支条件中要抽取出来拼成一句话。第二类是模块功能和边界说明。这决定了AI知道要找“A模块”而不是“B模块”。例如“库存服务只负责预占与释放不负责实际仓储调度”。第三类是历史改动与设计动机。这类知识经常只存在于Git记录和注释里比如“引入消息队列是因为老接口经常超时改异步后需要容忍一定一致性延迟”。第四类是外部依赖与副作用。老项目的坑经常在看不到的地方这个接口会回调外部系统、这个定时任务会跑一个多小时、这段代码依赖前一天某个批处理的结果。AI如果不能从外部记忆里获知这种依赖给出来的建议就很容易“只见树木不见森林”。第五类是配置和环境细节。比如“这个服务在测试环境走的Mock生产环境才是真实通道”。如果你问AI“为什么本地跑不通”它如果没有这块记忆会猜出各种离谱结论。知道要抽什么后面的清洗和切分才有章法。4. 核心实现从原始知识到AI可检索的外部记忆4.1 第一步清洗与上下文切片采集完原始素材以后紧接着要做的是能落地的切片。直接整篇文档或者整个文件去向量化是大忌。模型在把文本转成向量时会生成一个高维向量这个向量需要承载整段文本的语义。如果一段文本里涉及了多个主题最后生成的向量就会变成一个“四不像”检索时精度特别差。我现在的做法是按语义边界切而不是按字符数硬切。给源代码切片时优先按方法、函数、类、配置块的完整边界切。给文档切片时按章节标题、段落和列表结构切。切片长度需要结合实际参数做取舍。拿主流Embedding模型来算文本过长会超过模型的max tokens限制。我通常会把每片控制在一个能表达完整含义的单位内平均在四百到八百个token之间。为什么不是越小越好因为如果切得太碎“优惠券过期时间是下单时间加七十二小时”这种上下文就容易被拦腰截断。反过来如果切得太大一个类文件几千行全在一个片里检索出来的有效密度太低。一个简单示意假设有一段Java代码只取前几行来演示public class CouponValidator { // 优惠券使用规则 // 1. 每个用户最多领取三张 // 2. 活动结束后不可使用 public boolean canUse(User user, Coupon coupon) { ... } }如果按最小的代码注释块和方法签名来切片这个片段能直接变成一条“业务规则 代码入口”的知识索引。但如果整个类混在一起切模型检索时可能抓不到“最多领取三张”这个关键点。在这个阶段推荐先做一层规则过滤去掉明显没用的空注释、自动生成文件、第三方依赖包内容。这些内容不仅没有知识价值还会污染向量化结果。4.2 第二步向量化与关键词混合检索清洗切片完成后进入核心一步把每个切片转换成向量。这一步相当于给每份档案在档案柜里贴了一个语义标签。这里要注意一个问题只靠向量检索不够。老项目里有大量专业术语、缩写、类名像“SkutraceBatchJob”这种命名Embedding模型很难理解它的完整含义因为拆开看既不是标准英文句子又没有上下文。我跑的对比实验里单独使用向量检索命中率大概在60%多一旦在问题里混入缩写准确度还会再降十几个点。解决方法是做混合检索向量检索负责找语义近似的知识片段关键词检索负责精准匹配代码符号和方法名最后把两路结果做一个融合排序。我这边用的简易融合策略是先给两路检索各留前二十个结果做归一化打分然后加权合并。权重方面向量检索和关键词检索我实际调过很多组比较可靠的经验是四六开或者五五开具体要看你的项目缩写比例。缩写多、符号诡异的传统Java项目关键词权重要给高一点。下面提供一个最小可运行示意能帮你理解融合检索的过程。实际项目中你可以替换成自己的向量库和检索引擎def hybrid_search(query, top_k10): # 向量检索适合找“语义相近、说法不同”的内容 vector_hits vector_db.search(query, top_k20) # 关键词检索适合精确命中类名、方法名、缩写词 keyword_hits keyword_index.search(query, top_k20) # 双路归一化后合并 merged {} for idx, score in vector_hits: merged[idx] merged.get(idx, 0) 0.5 * score for idx, score in keyword_hits: merged[idx] merged.get(idx, 0) 0.5 * score ranked sorted(merged.items(), keylambda item: item[1], reverseTrue) return [idx for idx, _ in ranked[:top_k]]这个伪代码代表的是“先各查二十个融合后取前十”的思路。你完全可以根据自己的统计数据调整向量和关键词的融合权重比如对代码片段比重高、自然语言描述少的老项目上调keyword权重反过来对架构文档占比高的项目可以适当提高vector比重。4.3 第三步接入AI工作流外部记忆体系最终要给AI用这里有两种主流接入方式。一种是把检索做成一个工具函数让AI在需要时主动调用另一种是在主prompt里拼接系统工作流自己控制检索触发。工具调用的方式更贴近真实使用场景也更容易维护。简单画一条链路用户在对话里提问“回滚后库存扣减会不会重复”Agent发现这个问题可能涉及项目知识触发一个名为search_project_memory的函数。这个函数利用上面提到的混合检索从外部记忆库里取回前几个相关片段。Agent基于这些片段组织回答同时在回答里标注“这些结论来自哪几个代码片段”。真正落地的时候你不需要给工具起什么花哨名字直接叫query_knowledge_base就行。关键在于你要设计好工具描述让Agent清楚什么情况下该调用它。比如可以这样描述当问题涉及订单流转、库存、支付、老系统兼容逻辑等具体模块时必须调用该工具了解系统内部情况。为了控制单次进入上下文的量我一般会限制检索结果最多取五到八个切片每个切片最多五百个token这样合起来不会超过当前模型上下文窗口的一个很小比例。不要把检索到的原始文本直接用一定要让Agent在回答时做二次总结否则用户直接被塞一堆代码片段体验会变差。4.4 第四步更新与纠错闭环外部记忆不是一次性建完就永远一劳永逸。老项目每天都在变至少每周会进来新代码、新文档、新PR。按我的实践更新策略可以分两级。增量更新主要针对代码变化频繁的主干分支。可以用提交钩子或者定时流水线扫描每当有新的commit合入就对发生变更的文件重新切片和向量化再同步更新对应路径下的知识索引。周期性构建面向的是那些变化少但体量大的内容比如架构文档、历史Issue记录。每周跑一次全量索引和增量结果做合并去重就能保证知识库不至于因为信息爆炸而无限制膨胀。纠错闭环也很重要甚至可以设计一个轻量反馈打分。比如AI在回答时返回了引用来源使用者可以点击“这个回答不对”如果判断是记忆库里的原始文档本身出错就把这个切片标记成已废弃或低优先级同时记录一条人工修正内容。这个“修正后的内容”还会进知识库替代旧版本。我见过不少团队一口气把全量代码建了索引结果三个月后一半失效又没人维护最后不了了之。背后的教训是外部记忆这个系统的灵魂在更新频率和维护成本而不是最初的展示效果。5. 实操中常见的坑与排查经验5.1 高频问题与解决建议速查表这里把我实际遇到比较多的问题整理成一个表格方便你遇到问题时快速排查现象可能原因排查与解决方向检索结果明显无关切片跨语义边界碎片化严重检查切片粒度优先按方法/模块边界切精确问类名却取不到向量语义和关键词权重失衡提高keyword召回权重允许代码符号精确检索AI总爱脑补细节检索出的片段缺少结论性描述在知识库内补充“业务规则”“设计说明”类文档片段回答引用了已废弃逻辑知识库更新滞后建立增量更新机制废弃切片要标记失效老注释误导回答注释与实际代码实现不一致清洗阶段优先采用近半年更新的注释一个大文件检索命中率很低整个文件压缩为一个向量语义丢失改成按方法/单类/配置块多级切片权限或密钥被误检索出来清洗环节没做安全过滤建立敏感信息黑名单切片前先做正则脱敏5.2 传统老代码里要避开的几个大坑有些坑不只是检索策略问题而是根本性的知识陷阱。经验不够的时候我甚至被老项目的“表面事实”骗过。第一个坑是注释与代码严重脱节。老项目里很多注释具有历史惯性代码已经改掉好几轮注释还停留三年前的状态。刚开始我把注释也全部做了索引AI在回答时总爱引用那些漂亮但过时的注释把用户带沟里。后来我把注释按时间分层只保留近一年内有对应commit更新的注释并且在索引里给注释和代码绑定路径发现效果显著改善。第二个坑是重复代码和复制粘贴造成的碎片噪音。老项目里经常有一个工具类被复制到五个服务里五份实现还不完全一样。如果不对这些重复代码做识别与去重检索时会同时命中五六份相似内容模型没办法判断该用哪份。我一般会把相似度超过90%的片段压缩成一组并在知识索引里保留“该逻辑在A、B、C三处出现主版本以A为准”的描述。这样一来AI在发现改动要牵扯多个位置时也能给出更有全局性的判断。第三个坑是敏感信息的泄露风险。老项目里但凡带“密码”“密钥”“token”等字段的地方切片前必须做脱敏处理。我就是因为在初始版本里没做清洗导致一次演示时检索结果把一段线上数据库连接字符串带了回来场面非常尴尬。别等到出问题再补救直接从源头把IP、账号、密钥、鉴权头的正则规则写进清洗流程里。5.3 知识检索要做效果评估别靠感觉调参判断外部记忆系统好不好用不能只是找两个问题试一下然后说“还行”。我试过最笨但特别有效的办法是准备两百个真实工作场景下的问题保证答案肯定藏在知识库里。这些问题要覆盖几类一类是“某字段含义是什么”的定义型问题一类是“某模块的调用关系与依赖是什么”的关系型问题还有一类是“当初为什么这样设计”的决策型问题。评测时我会记录三个数字检索命中率、最终回答准确率、平均耗时。检索命中率评估的是“前五个切片里有没有正确答案所在的那份知识”。只要这个数字低于80%模型后面再怎么聪明也使不上劲。回答准确率可以靠人工抽检来打分不要只问小助手“帮我生成代码”还要问它“这个逻辑是老逻辑还是新逻辑”用类似的问题去检验它是不是真的引用了正确记忆。调参时我习惯一次只动一个变量要么动切片大小要么动向量和keyword的融合权重要么动top_k数量。多个参数同时乱调出了问题根本没法定位。这几轮下来我自己的项目是把两路合一后的准确率从初始的71%提到90%以上验证问题集就是那两百条固定的业务场景。6. 最小可行实践三天搭一个“项目记忆助手”6.1 第一天搭建数据管线我拿一个内部旧的Java服务做过实践规模不算大大概十二万行代码二十个核心接口。第一步只做两件事把代码仓库和Git历史拉下来用脚本扫出最近两年有过活跃改动的文件再跑一次文档扫描找到可用的数据库设计文档、模块说明文档。不用一次追求齐全先覆盖六个核心模块就行。6.2 第二天清洗、切片、建索引当天上午我先对原始内容做统一清洗把注释里的历史遗留信息、系统自动生成的Dockerfile片段、测试文件里的Mock数据排除掉。清洗规则我直接写在Python脚本里后端可以调用任何开源Embedding模型来生成向量。如果你是第一次试建议直接选一个比较成熟的向量库或者干脆先在本地用轻量级的向量文件组合跑通链路别一上来就把Kafka、分布式向量库、云服务全堆上。先打通最小链路后面再逐步替换组件。切片的粒度上Java代码按类和方法分别生成两层级切片文档按标题和段落切片。索引层的搭建要保持轻量。我的核心目标是验证“检索结果能不能在几十毫秒内返回并且命中率在合理范围”。上午建完索引下午用之前准备的测试问题集跑一轮命中率看哪个模块覆盖率太低再回到采集阶段去补对应知识。6.3 第三天接入对话层并做效果校准第三天做的事情是接入AI工作流。我把第一步的检索函数封装成一个工具并在系统提示词里写清楚你是一个熟悉本项目的开发助手。当用户询问模块逻辑、业务规则、历史改动原因、接口依赖关系等问题时你必须先查询本地记忆库再结合查到的内容回答。引用源码片段时要注明来源路径。我自己跑了一个很典型的问题加上了这个小记忆库以后的效果差异。在未接入外部记忆时我直接问库存扣减操作成功后如果订单退款走了老流程会不会再次调用扣减接口模型基于通用知识给出的回答是“通常会在退款时进行逆向加库存需要检查接口是否实现幂等”。这个回答放在通用场景下没错可完全没打到点子上。接入外部记忆之后再问同一个问题系统从知识库里检索到老代码的调用关系发现老退款流程会直接调用库存服务的另一个补偿接口它就能回答“会老流程会触发补偿逻辑但补偿接口带上了操作流水号重复调用会被幂等拦截所以不会导致库存为负”。这其实才是“项目记忆助手”真正让我觉得靠谱的地方它不是把话说得漂亮而是能基于你们项目里真实存在的历史逻辑来回答。6.4 后期扩展从问答到更高阶的应用当这三天的基础版本跑通以后你会发现同样的外部记忆库还可以支撑不少上层应用场景。比如在IDE插件里增加一个“解释当前方法”的按钮或者在代码评审流程里接入一条检查规则让AI去判断增加的新代码是不是改了老的业务约定。再往后可以给特定模块构建更精细的知识图谱把服务间调用链、数据依赖关系、批处理任务之间的时序关系全部做成图谱化的外部记忆。这些在未来会成为Agent自主规划的时候真正可参考的全局系统信息。不过这些是后话早先把RAG链路跑稳、把维护更新机制建好远比一次性堆很多花哨功能更重要。7. 最后再分享一点实操体会如果你准备在团队里推“项目探测”这件事我建议不要一开始就追求覆盖全部代码、全部历史、全部文档。先挑一个最核心的业务模块做试点用本文提到的“四步走”方法完成切片、索引、检索和接入让团队里技术负责人能看到AI在面对真实历史问题时确实有了明显进步。一个经常被低估的工作是后面持续的维护。外部记忆是活系统它跟代码库一样需要版本管理、评审和团队认领。我见过太多项目把“搭知识库”做成了一锤子买卖初期热情很高两周后没人更新再后来索引里的知识和真实代码越来越对不上最终整个体系被废弃。避免这个结局最有效的办法是把知识更新变成工程日常的一部分合并代码时随手挪一次索引文件开评审会的时候让记录自动沉淀进记忆库这些小事累加起来价值远超一次性的大规模搬运。另一个体会是别把外部记忆局限在“给大模型生成回答”这一个小场景。它能做的比这个多得多。比如新同学入职后想了解老系统不用再靠师傅口述直接把相关模块的历史决策和演进记录拉出来问做架构重构时靠它梳理老依赖关系排查线上问题时靠它快速定位最相关的那几个类和方法。项目探测的核心价值不在于它让AI变聪明了多少而在于它把一个团队多年积累的隐性知识真正变成了可留存、可复用、可持续进化的组织资产。