从零搭一个能读懂项目上下文的AI编程助手,我的RAG落地全过程
写代码这几年,我一直有个痛点:IDE自带的代码补全只认语法,不认业务。你让它在某个模块里自动补全,它根本不知道这个项目里已有的工具函数、数据模型和接口约束,经常给出一些"语法全对、语义离谱"的垃圾建议。后来大模型火了,很多人直接把代码库喂给模型,让模型当助手,但很快就会发现一个尴尬的问题——模型上下文窗口有限,一个中等规模的项目几十万行代码,怎么塞都塞不进去,而且塞进去之后,模型容易"淹"在无关信息里,抓不住重点。
直到我自己动手做了个基于RAG(检索增强生成)的AI编程助手,这些问题才算真正解决。这篇文章不是什么高深论文,就是我从设计到落地的一条完整路线,包括我踩过的坑、调过的参、改过的设计,尽量把每个环节都讲清楚。标题说"从入门到精通",我不敢保证看完你就精通,但至少能让你从零开始,把一个真正可用的版本跑起来,并且知道每一步为什么要这么做。
先说清楚这套东西到底能干什么:它可以理解你当前正在编辑的文件,结合项目里已有的代码逻辑,给你补全提示、代码评审建议、接口调用方式,甚至可以帮你按照项目现有风格生成新函数。它的核心不是"懂代码",而是"懂你的项目"。
1. 先把需求和方案想明白:RAG到底解决什么问题
1.1 直接喂全量代码给大模型,为什么不行
很多人第一反应是:直接把整个代码库打包成上下文丢给模型,然后用模型理解项目。这个思路听起来直接,但实操里全是问题。第一个问题是上下文长度,主流模型几万到几十万的token窗口看起来很大,但真实项目里光是一个大型模块的源码就可能十几万token,加上依赖配置、文档说明,很快就爆了。第二个问题是信噪比,模型拿到一坨几十万行的代码,它根本不知道你现在关心的是哪个函数、哪个变量,注意力被大量无关代码稀释,回答质量显著下降,幻觉也会增多。第三个问题是成本,每次都把所有代码塞给模型,token消耗非常惊人,跑上几天,费用和延迟都让人崩溃。
所以在设计阶段我就确定了一个原则:绝不把整个项目塞给模型,而是要做一个"按需检索 + 精准上下文"的方案。也就是RAG的核心思路。它分三步——先把代码库拆成小块并建立索引,然后根据你当前的需求实时检索最相关的代码片段,最后把检索结果和你当前的文件内容拼装成上下文,交给模型生成回答。这样模型每次只看到它真正需要的那部分代码,既控制成本,又提升准确度。
1.2 上下文感知的真正含义:不是粘一段代码那么简单
编程助手和通用问答机器人最大的区别,在于它需要感知"你正在做什么"。同样是问"帮我写一个配置文件解析函数",如果助手知道你当前项目是用某个轻量级配置库而不是标准库里那些重量级模块,它给出的方案会完全不同。如果它知道你的项目里已经有一个公共工具函数,它就应该直接建议调用,而不是重新造一个轮子。
这就引出了"上下文感知"的三个层次:第一层是文件级上下文,也就是你正在编辑的文件里有哪些已有的导入、函数和变量,这决定了新代码的语法风格和依赖关系;第二层是项目级上下文,也就是你整个代码库里有哪些相关模块、公共函数、数据模型和接口约束,这决定了新代码该跟谁对接;第三层是历史上下文,也就是你最近改过什么、提交过什么,这决定了建议的演进方向。RAG方案就是围绕这三个层次来设计检索和拼接策略的。
1.3 能力边界:它能做什么,不能做什么
我也得泼一盆冷水,RAG编程助手有很多事情做不好。它不太擅长处理需要全局推理的问题,比如"我这个服务在集群部署时为什么偶发超时",这类问题往往涉及跨模块、跨进程、甚至跨机器的综合判断,简单的检索拼接很难覆盖。它也不太擅长处理完全没有既有代码依据的新功能开发,检索不到相关代码,RAG就退化成普通的通用问答,效果会明显下降。它更不会自动学习你项目的所有规则,除非你把规则写进文档或者代码注释里,否则它感知不到。
认清这个边界很重要,因为很多人栽在这里——期望着一个"开箱即用的万能助手",结果用起来发现很多场景不灵,然后就放弃了。但其实把使用场景限定在它擅长的事情上,比如代码生成、接口调用、重构建议、报错排查,它就能发挥非常大的价值。下文讲的所有实现,都是围绕这些它能做好的场景来设计的。
2. 技术选型:每个组件凭什么选它
2.1 整体架构的四个组成部分
一套完整的RAG编程助手,本质上是一条数据流水线,四个组成部分缺一不可。第一部分是代码解析器,负责把代码库拆成可检索的块,这一步做得好不好直接决定后续所有效果;第二部分是向量数据库,负责存储这些代码块的向量表示,并在检索时按相似度返回结果;第三部分是嵌入模型,负责把文本变成向量,也就是让计算机能"理解"代码语义;第四部分是大语言模型,负责根据检索到的上下文生成最终的答案或代码。
2.2 嵌入模型选型:代码专用还是通用模型
刚开始做的时候,我图省事直接用了通用文本嵌入模型,效果很差。代码和自然语言的差异比想象中大得多——代码有严格的语法结构、长距离的函数调用关系、各种缩写和专有名词,通用模型往往抓不住这些特征。后来换成了在代码语料上专门训练过的嵌入模型,效果立刻上了一个台阶。
我强烈建议选支持较长序列的嵌入模型,因为单个代码文件有时候比较长,太短的输入会让模型丢失信息。另外要考虑的是向量维度和本地部署的可行性,嵌入模型是要部署在你的环境里的,如果机器性能不够,就选小一点的。这里给个经验值:向量维度在768到1024之间,效果和资源开销相对均衡,太低的维度信息不足,太高了存储和计算成本涨得快。
2.3 向量数据库选型:轻量优先
向量数据库的选择不要纠结太久。我的建议很简单:单机个人用,优先ChromaDB或者FAISS这种轻量级方案,部署简单,无需单独起服务,跟你的Python环境直接集成。团队协作或多用户场景,才需要考虑独立的向量数据库服务。
这里要注意一个细节:向量数据库管的是"相似度搜索",它不懂代码的语义结构,不懂什么叫函数调用,它只知道两个文本片段在向量空间里挨得近不近。所以在检索之前,你需要在代码解析阶段就把结构信息提取出来,把"语义检索"和"结构检索"结合起来,这样效果才好。如果你只靠向量搜索,很容易出现检索出一堆表面语义相似但实际毫无关联的代码片段的情况。
2.4 大语言模型的接入策略:本地部署还是远程API
大语言模型的选择直接关系到你的使用体验和代码安全。代码是很敏感的东西,很多项目根本不允许把代码发到外部API。如果你做的是个人玩具项目,用远程API完全没问题,省心省力;但如果你的代码打了保密标记,那还是老老实实本地部署开源模型更稳妥。
我个人的实践是两手准备:做一个中间层,屏蔽底层模型差异,在底层可以灵活切换本地模型和远程API。这样既保证开发阶段调试方便,又能在需要时切换到本地模型。说到这里我要提一个最大的坑:很多人在这个环节过多纠结"哪个模型最强",其实对于RAG助手来说,模型只是最后一个生成步骤,前面检索质量的好坏才是决定性的。检索搞不好,再强的模型也生成不出好东西;检索搞好了,一个中等规模的模型就够用了。
3. 环境准备:把地基打牢
3.1 基础依赖清单
动手之前先把环境搭好。我这里用Python为主语言,因为生态最成熟,几乎每个环节都有现成库可以用,省去大量底层实现的功夫。你要准备的依赖如下:
- Python 3.9及以上版本
- 代码解析库tree-sitter(含对应语言的语法包)
- 嵌入模型推理库(只要支持运行ONNX格式即可)
- 向量数据库ChromaDB或FAISS
- 大模型接口库,无论是调用远程API还是加载本地模型都要有对应客户端
- 一个支持异步IO的HTTP框架,用于启动本地服务
安装的时候有一个值得注意的地方:tree-sitter的版本兼容性比较敏感,不同语言语法包的版本号必须跟核心库匹配,否则会出现解析崩溃或者解析结果为空的情况。我在第一次装的时候就吃过这个亏,所以建议你安装时先查看官方文档的版本对应关系,不要无脑装最新版。
3.2 目录结构设计:从一开始就做好分层
代码的组织方式会影响后续的维护效率。我建议把整个项目拆成几个独立模块,每个模块单一职责:
ai-coding-assistant/ ├── indexer/ # 代码解析、索引构建 ├── retriever/ # 检索逻辑(语义检索 + 结构检索) ├── context/ # 上下文组装与压缩 ├── llm/ # 模型接入层(本地/远程切换) ├── server/ # API服务 ├── config/ # 配置文件 └── data/ # 索引存储、日志分层的核心思路是解耦。索引模块不需要知道检索模块的实现,检索模块不需要知道模型层用的是什么模型,这样以后任何一个环节要升级或替换,都只动一个模块。我见过很多人把所有代码写在一个大文件里,一开始挺爽,到后面每改一个功能都要小心翼翼,这种苦头真的没必要吃。
4. 实现第一步:代码库索引——这一步决定成败
4.1 代码切分策略:比想象中更需要讲究
代码切分是整个系统效果好坏的第一道关口。切得太粗,一块里面包含太多内容,检索时返回的片段有大量无关信息,模型容易跑偏;切得太细,又容易把一个完整的函数或者类拆得七零八落,检索时上下文不完整,模型压根看不出来这是个啥。
我的实践是采用三级切分策略:工程级别不切,整个项目就是一棵目录树,用来定位文件路径;文件级别按语法树来切,以函数、类、方法为基本单元,而不是像文档RAG那样按固定字数来切;函数级别还要额外存储它的依赖关系、调用关系、所在文件路径和签名信息。这样检索的最小单位是"一个函数或一个类",但返回结果时可以把整个文件的结构信息也带上,让模型有足够的上下文来理解这个函数在项目里的位置和作用。
4.2 用语法树做结构化解析
这里就轮到tree-sitter登场了。tree-sitter是一个增量解析器,可以精确地把源码解析成语法树,而且速度非常快,上千个文件的索引构建只需要几分钟。用它可以做到按语法单元切分代码,而不是盲目按行数去切,同时还能提取关键的语义信息,比如函数名、参数列表、返回值类型、装饰器、类的继承关系等。
以Python代码为例,伪代码的思路是这样的:
import tree_sitter_python as tsp from tree_sitter import Language, Parser # 关键:按语法节点提取函数和类 def extract_functions(source_bytes, tree): root = tree.root_node functions = [] def walk(node): if node.type == "function_definition": name_node = node.child_by_field_name("name") params_node = node.child_by_field_name("parameters") functions.append({ "name": node_text(name_node), "params": node_text(params_node), "start_line": node.start_point[0], "end_line": node.end_point[0], "signature": extract_signature(node), "dependencies": resolve_imports_and_calls(root, node) }) for child in node.children: walk(child) walk(root) return functions这里有个很实用的点:解析的时候要同时收集两个结构——代码树的层次结构,以及函数之间的调用关系图。有了这两个结构,你检索一个函数时,可以顺着调用关系把跟它相关的上下游函数也找出来,这样模型看到的上下文就更完整了。很多入门教程只讲了切分和存储,没讲提取结构关系,这是他们效果不好的一个重要原因。
4.3 补充元数据:让检索更精准
在存储向量之前,我给每个代码块准备了一套完整的元数据:文件路径、代码块的开始和结束行号、代码块类型(函数/类/方法/常量)、源码内容、它引用了哪些符号、哪些符号引用了它、以及它的上一级代码块的信息。这套元数据就像给每个代码块贴了标签,检索的时候,可以根据这些标签做精确的过滤。
举个例子,当你正在编辑文件A时,助手应该优先检索同一目录或同模块下的代码,而不是跑到另一个完全无关的模块里去翻。有了文件路径和模块字段,这个过滤就非常方便了。在向量数据库中,这些元数据可以作为过滤器条件传给查询接口,先做结构过滤,再做语义检索,效率和准确率都高很多。
4.4 向量化与入库:批量处理时的经验
代码块准备好了,接下来就是向量化和入库。这里我碰到几个坑,一个个说。
第一个坑是向量化太慢。最初我是一条一条调用嵌入模型API去算向量,处理一个小项目还好,项目大了以后慢得离谱。后来改成批量推理,一次传入几百个片段,速度立刻快了几十倍。所以嵌入模型的使用,一定不要逐个调用,务必走批量推理。
第二个坑是向量库的集合设计。我一开始把所有语言的代码都放在同一个集合里,检索时按语言过滤,结果发现有的时候过滤条件配置错误,会把不同语言的代码混在一起返回。后来改成每个语言一个集合,就彻底避免了这个问题。如果项目规模不大,按语言分集合的收益还是很明显的。
第三个坑是去重。代码库里经常有重复或者高度相似的代码,比如自动生成的文件、各模块里近似复制的工具函数。索引的时候不去重,检索的结果就可能全是同一段代码的变形,毫无参考价值。我加了一道简单哈希去重,以代码内容去重后只保留一个副本,检索效果回归正常。
5. 实现第二步:混合检索——语义和关键词双管齐下
5.1 为什么纯向量检索不够
只靠向量检索的体验是:有些明显应该命中的代码检索不到。原因在于,向量检索依赖嵌入模型的语义理解,如果模型没能理解代码里的专业缩写或者生僻命名,很可能就检索不到。而且代码里大量的符号名称都是短文本,比如变量名uid、函数名get_user_info,它们的语义表达极其依赖上下文环境,单独算向量时效果并不好。
我测试下来最明显的例子是:项目里有一个函数叫cache_fetch,专门用来做带缓存的HTTP请求。用向量检索查"获取网络请求结果",这个函数经常排不上号。但用BM25关键词检索(标注化方法)fetch、cache、request,它立刻就能命中。反过来,有些代码表面关键词不匹配、但语义相关的情况,靠纯关键词检索也不行。所以最终的方案是混合检索:一半向量,一半关键词,两路结果合并去重后再按相关度排序。
5.2 混合检索的具体实现
混合检索的实现没有想象中那么麻烦,我的做法是利用向量数据库内置的关键词检索能力,再加上向量相似度查询,两者各给一个得分,然后用加权合并。合并的权重不是随便定的,我调试了很久才找到一个比较可靠的区间:向量得分占0.6到0.7,关键词得分占0.3到0.4,如果代码库中命名很规范、语义很清晰,可以适当提高向量权重。
具体流程可以拆成三路并行查询:第一路按向量相似度查TopK;第二路按关键词匹配查TopK;第三路按代码结构关系查TopK(比如你正在编辑的文件的邻近代码、同模块的高频调用函数)。三路结果合并之后,统一按一个综合得分排序,再截取最终要用的片段列表。这里的关键是第三步,它是上下文感知真正区别于普通RAG的地方。
5.3 重排序:检索的最后一道关口
检索完后如果不做重排序,直接拼接上下文,效果会打个折扣。原因是最初检索出来的TopN结果里,很可能混着一些表面上相似、实际上对当前问题没用的片段。我的方案是在混合检索之后,加一个精排层。
精排层可以用一个专门的交叉编码器模型,它对每对"查询-文档"对直接打分,比嵌入模型的双塔结构要精准得多。代价是速度慢,所以只对第一轮检索回来的前几十个候选做精排。综合实测下来,加了这个精排层之后,最终上下文里有效代码的比例从六成提升到了九成左右,这个提升非常可观。
6. 实现第三步:上下文组装——感知当前状态
6.1 把你正在编辑的文件的"状态"提取出来
上下文感知的一个关键动作,是从你正在编辑的文件里提取"当前状态"。听起来玄乎,其实内容很具体:文件里已经import了哪些模块、已经定义哪些类和函数、当前光标位置的上文是什么、下文有什么未完成的代码。这些信息是模型决定"接下来该写什么"的最直接依据。
实现上我是这么做的:当你在编辑器里触发助手(比如按下快捷键),插件会把当前文件内容和光标位置发到后端。后端先调用语法解析器把当前文件解析一遍,提取出上述信息。然后根据光标位置,把上文最后几行和当前行截出来作为"当前编辑目标"。这一步看起来简单,但非常管用——模型看见了"你正在写什么",给出的建议才能贴合思路。
6.2 把检索到的代码组织成模型看得懂的上下文
检索完成之后,不能把一堆原始代码直接扔给模型,那样模型分不清谁是谁,也不知道你当前要干什么。我设计了一套上下文模板,每次组装请求时都按照固定结构来,模型的输出质量比其他方式稳定得多。
模板的结构大概是这样:开头是任务说明,明确告诉模型你当前在做什么;然后是当前文件信息,包括语言、文件路径、当前符号定义、光标位置附近的代码;再往下是检索到的相关代码片段,每个片段注明来源路径、行号范围、是什么类型的代码块;最后是所有代码库层面的约束规则,比如"优先使用已有的工具函数""不要修改接口签名""遵循文件的命名风格"等。
这里有个细节值得说说:检索到的相关代码片段要往上限控制。刚开始我把Top10的片段全部塞进去,结果模型输出开始废话变多,甚至被无关片段带偏。后来把上限控制在5至6个片段,同时对片段的长度也做了限制,超长的代码块先做截断。你要记住一个原则:上下文不是越多越好,而是相关的越多越好,无关信息多了,质量一定下降。
6.3 上下文压缩:让模型聚焦重点
即使是过滤后的检索结果,也可能因为代码太长而超出门控。这时候就需要上下文压缩。我的做法是:如果某个代码片段太长,就提取它的"要点"——签名、函数头、关键变量、以及它的调用依赖列表,正文部分可以缩略成一个占位符。这个压缩操作要在保证信息不丢失的前提下进行。
还有一个很重要的技巧:对检索结果做去重时,要保留信息量最大的那个版本。有时候同一个函数在项目中的多个位置被引用,检索结果里可能有好几段相似内容。这时候不是简单地删掉重复项,而是要综合所有引用位置,把一段加上了"被谁调用"信息的聚合描述放进去,这样模型的上下文里包含了使用场景,它给出的建议会更符合你现有代码的调用习惯。
7. 跑通服务:把助手接到编辑器里
7.1 服务接口设计
后端服务我设计成了一个轻量级的HTTP服务,暴露三个核心接口:查询接口,接收当前文件内容和光标位置,返回建议或代码补全;问答接口,接收用户的自然语言问题,返回针对当前项目的回答;索引更新接口,在文件保存时增量更新索引。这三个接口基本覆盖了编程助手的日常使用场景。
这里要特别注意增量更新的设计。如果不做增量更新,每次改一个文件就全量重建索引,项目大了根本扛不住。我自己的做法是监听文件保存事件,只对改动过的文件重新做解析和入库,同时删掉旧版本对应片段的索引。这个说起来简单,实现的时候要注意一点:不同代码块可能会因为代码变更而改变行号,需要及时更新它们的位置信息。
7.2 编辑器插件的最小实现
编辑器端我需要做一件事:把用户编辑操作变成对后端服务的请求。以常见的编辑器插件为例,核心就是监听文件保存和光标移动事件,在特定操作(比如暂停输入后触发补全)时,把当前文件内容和光标位置POST到后端。
我没有去实现特别复杂的交互,先做了一个最小可用的版本:用户在编辑器里选中代码或者把光标放在目标位置,触发一个快捷键,界面上会显示一个对话框,把后端返回的建议展示出来,用户可以一键插入到编辑器里。这种交互方式看起来简单,但非常好用,而且实现成本低,很容易上手。
8. 效果调优:参数怎么调、效果怎么改
8.1 检索参数调试经验
混合检索里最关键的参数是各路召回的TopK值、重排序后最终保留的片段数、向量和关键词的加权系数。我的默认配置是:向量召回20条,关键词召回10条,结构关系召回10条,合并去重后精排取前6条。这个配置不是拍脑袋来的,是我在多个项目上对比测试后得到的一个相对均衡的取值。
调试的时候有个技巧:先固定最终片段数,然后逐步调大各路的召回数,观察效果的变化。如果你发现某个问题在召回路数增加后效果变好了,说明之前漏召回了;如果增加后效果没有变化,说明问题出在重排序或者上下文组装环节,不要再盲目加大召回量了。
8.2 提示词调优:约束模型行为
大模型生成阶段,提示词的作用被很多人低估。同一个模型,提示词写得差和写得好,生成质量差距非常大。我的提示词实践原则有几点:第一,明确输出格式,如果要求返回代码,就说清楚只要代码块并附上关键的模块依赖;第二,给出约束规则,明确告诉模型不要修改现有接口、不要引入额外的依赖库、命名风格保持一致;第三,提供"反面示例",这个效果比讲空洞的原则好得多。
一个我非常推荐的技巧是:提示词里加一段"项目风格指南"。它是从代码库里自动提取的风格规则,比如命名约定、缩进风格、是否使用类型标注、异常处理偏好等。每次组装请求时自动附带,模型输出就非常贴近你项目本身的味道。这个做法一开始是我灵光一现想到的,结果成了整个系统里效果提升最大的单项改动。
8.3 评估方法:怎么知道有没有变好
我自己做了一个简单的评测集,包含了十五个典型开发场景,比如"实现一个带超时重试的HTTP请求""给现有类增加一个序列化方法""修复某个函数的边界条件"。每次改动系统参数后,就跑一遍评测集,给结果人工打分,观察总分变化。这个办法朴素但非常有效,远比"感觉差不多"靠谱。
打分的时候重点看几个维度:建议的代码是否可以直接使用(可用性)、是否和项目现有代码风格一致(一致性)、是否漏掉了关键依赖或约束(完整性)、以及是否出现了幻构的API或函数(幻觉率)。如果你的改动导致得分下降,就回滚这次改动。长期积累下来,评测集会成为你最宝贵的调优财富。
9. 踩坑记录:这些问题我花了很长时间才解决
9.1 代码片段检索不精准,它给你翻出无关文件
这是我早期最头疼的问题。检索出来的相关代码里经常混着一些名字看起来很匹配、实际上完全无关的代码。后来排查下来原因在于:只用向量检索,而向量模型对代码符号的理解不如对自然语言准确。解决方法是加了关键词和结构关系的混合检索,以及最后的重排序。这两步加上去之后,问题基本消失。
9.2 生成建议风格突然漂移
有时候模型一开始生成的代码风格很贴合项目,但用着用着就开始偏离。后来发现是因为它在长对话中慢慢遗忘了初始的风格约束,或者被某些检索片段里的特殊写法带偏了。解决方案是在每次请求时都重新组装完整的上下文,永远不加历史记忆对话,同时把项目级硬性规则放在提示词最前面靠前的位置,确保压过其他信息的影响。
9.3 增量更新导致索引不一致
文件保存触发增量更新的时候,有时候旧索引没有被正确删除,导致同一个函数在向量库里同时存在新旧两个版本,检索时拿到的可能是旧版本,给出的建议也就过时了。这个问题的根源在于增量删除的逻辑不严谨。我的修复方案是给每个代码块加一个全局唯一的版本号,入库前先按版本号清理旧数据再插入新数据,保证一致性。
9.4 本地模型和远程API的结果差异
如果你在本地模型和远程API之间切换,你会发现对同一个检索结果,两者生成的质量差异可能很大。这不是bug,而是模型能力差异带来的正常现象。我的建议是不要把不同模型的调用逻辑混在一起,而是单独为每个模型调一套提示词模板和参数配置。本地模型通常需要更详细更明确的规则和示例,远程API模型可以给更开放的任务描述。
10. 进阶扩展:从能用做到好用
10.1 加入行为感知能力
目前的实现是"被动感知"——用户触发时才会去检索。进阶一点的方案是加入行为感知逻辑:不仅看你当前的光标位置,还看你最近的编辑行为。比如你已经连续改了某个模块里好几个函数,接下来你创建的代码很大概率还会涉及这个模块。系统可以根据这些行为特征,预加载相关的代码片段到缓存中,触发助手时几乎零延迟返回建议。
10.2 支持多语言代码库
如果你的项目是多语言混合的,比如前端加后端、Python加TypeScript,那么每个语言的检索策略应该独立配置。不同语言的代码结构差异很大,Python的函数定义和Java的类定义在语法树上的表现形式完全不同,切分策略和元数据提取也必须不同。这里的关键是抽象出一套语言无关的代码块接口,让底层语言解析器适配不同语言,这个抽象层可以让你在后续新增语言支持时,只需要写一个解析适配器。
10.3 结合测试与运行反馈
更高阶的做法是让助手看到运行反馈。比如在测试执行后,把失败的测试用例和错误堆栈喂给助手,让它分析失败原因并给出修复建议。这已经超出了一般编程助手的范畴,属于"可观测性辅助编程"的方向了。不过这个扩展并不难:在后端增加一个接口接收测试运行结果,让检索模块去检索相关代码,然后交给模型生成分析和修复建议。我给这个方向留下的扩展点,在系统设计阶段就预留好了。
11. 一些最后想说的心得体会
做了这个项目之后,我对"AI编程助手"这件事的认识有了很大变化。第一阶段我以为核心是大模型的能力,折腾了半天发现,如果检索和上下文组装不行,再强的模型也白瞎。第二阶段我以为检索是最重要的,但做到后面发现,真正决定系统上限的其实是"对代码库结构的理解程度"和"对用户状态感知的精细度"。
如果你想在自己的项目上复刻一套,我建议你按顺序来:先老老实实把代码索引做扎实,然后做混合检索,再然后才是上下文组装和模型提示。每一步都要有可评测的指标,不要凭感觉去调。这个项目最让我有成就感的地方,还不是最终效果有多好,而是我完全理解了每个环节的工作原理——哪怕是让我从头再写一遍,我也有信心写得更快更好。
希望这篇带实操细节的记录,能帮你少踩一些我踩过的坑。如果过程中有哪个环节卡住了,回头看看对应的章节,大部分问题都能找到答案。