第二阶段实践:Vibe Coding 实践(简易混合检索 RAG)
以可运行的简易混合检索 RAG 为目标,使用 Codex 推进需求约束、代码实现、验证与修正,串联 Dense、Sparse、HNSW 和 Milvus 等核心能力。
开发流程
明确需求
→ 让 Codex 先分析和规划
→ 分阶段实现
→ 运行和查看结果
→ 提供完整报错上下文
→ 让 Codex 做最小修改
→ 测试和复盘
一、项目边界
1.1 实践主链路
这一部分只关注下面这条链路:
离线入库:
FAQ / 业务文档
→ 文件加载
→ 文本切分
→ BGE-M3 生成 Dense 向量
→ Milvus 内置 BM25 生成 Sparse 向量
→ 写入 Milvus
在线问答:
用户问题
→ Dense + BM25 混合检索
→ 合并候选结果
→ BGE Reranker 精排
→ 选择上下文
→ LLM 生成答案
→ 返回答案和来源
1.2 涉及的核心组件
| 组件 | 这一部分中的职责 |
|---|---|
| FastAPI | 提供GET /、GET /health和POST /ask |
| Milvus 2.5+ | 保存文档并执行向量检索 |
| HNSW | 为 Dense 向量提供近似近邻索引 |
| BM25 | 根据关键词和词频执行 Sparse 检索 |
| BGE-M3 | 将问题和文档转换为 Dense 向量 |
| BGE Reranker Large | 对候选文档进行二次相关性排序 |
| OpenAI 兼容 LLM | 根据检索上下文生成答案 |
| MySQL | 保存本案例的 active 知识库版本 |
| Docker Compose | 启动基础设施和 API |
1.3 这一部分暂时不展开的内容
下面的能力属于后续企业级章节,这一部分只保留运行所需的最小接口,不展开其内部设计:
- 多场景意图识别和网关仲裁;
- Redis 缓存;
- 多租户权限治理;
- 知识库质量门禁和回滚;
- LangSmith Trace;
- WebSocket 流式输出;
- Agent、GraphRAG 和复杂任务编排。
现有mini-rag/代码为了便于后续对照,保留了 active 版本、FAQ 直出、查询改写和查询变体等辅助能力。这一部分的主线仍然只追踪“入库、混合检索、Reranker、生成”四个核心环节。
二、先看懂最终效果
2.1 入库完成后的结果
执行入库脚本后,系统会得到两类 Milvus Collection:
FAQ Collection
每条记录包含问题、答案、source 和版本信息
Document Collection
每条记录是文档切分后的一个 chunk
每条记录的检索字段可以理解为:
text 原始文本
dense BGE-M3 生成的 Dense 向量
sparse Milvus BM25 生成的 Sparse 向量
Dense 和 Sparse 不是二选一:
- Dense 擅长理解“意思相近但用词不同”的问题;
- BM25 擅长命中明确的关键词、编号、制度名称和专有名词;
- 混合检索把两路结果合并,减少只依赖一种检索方式带来的漏召回。
2.2 在线提问后的结果
在mini-rag/static/index.html页面提问后,可以看到:
- 最终答案;
- 引用来源;
- 本次命中的路由;
- 改写后的问题;
- 查询变体;
- FAQ 和文档的命中数量;
- Reranker 是否运行;
- 每个阶段的耗时。
这些字段帮助你判断:问题有没有进入检索、检索命中了什么、Reranker 是否运行,以及慢在哪个阶段。
三、Vibe Coding 的工作方法
3.1 你和 Codex 分别负责什么
Vibe Coding 不是只写一条提示词,然后不再看代码。一个可控的分工是:
| 工作 | 主要责任 |
|---|---|
| 描述业务目标 | 你 |
| 确认技术边界 | 你和 Codex 一起确认 |
| 生成代码初稿 | Codex |
| 检查文件是否改对 | 你 |
| 运行测试和服务 | 你 |
| 根据报错提出修改方案 | Codex |
| 判断修改是否真正解决问题 | 你 |
Codex 可以提高编码速度,但不能替你判断结果是否正确。这一部分每完成一个阶段,都要留下三种证据:
修改了什么文件
执行了什么命令
命令和测试返回了什么结果
3.2 提示词必须包含什么
一条有效的 Vibe Coding 请求至少包含:
当前目标:这一轮只实现什么
项目位置:在哪个目录工作
技术约束:必须使用什么,不能引入什么
输入输出:函数或接口接收什么、返回什么
验收方式:如何判断本轮完成
修改范围:允许改哪些文件
不要只说“把 RAG 做出来”。应该明确说:
这一轮只实现 Milvus Collection 初始化。需要创建
text、dense和sparse字段,Dense 使用 HNSW,Sparse 使用 Milvus 2.5+ 的 BM25 内置函数。先阅读当前文件,再列出修改计划,暂时不要修改代码。完成后用一个不连接真实服务的测试验证索引参数。
3.3 每一轮只推进一个阶段
阶段 0:分析已有代码和目标
阶段 1:确认项目骨架和配置
阶段 2:完成文档加载、切分和入库
阶段 3:完成 Dense + BM25 混合检索
阶段 4:加入 BGE Reranker
阶段 5:连接 LLM 和问答接口
阶段 6:测试、调试和 Docker 验收
每个阶段结束后再进入下一阶段。这样出现问题时,可以知道问题是在哪一轮引入的。
四、实践一:让 Codex 先分析项目
打开一个新的工作目录,或者复制现有mini-rag/作为验证实践目录。在 Codex 中打开这个工作区,第一轮只让它阅读需求和现有代码,不要求它立即生成大量代码。
请先阅读当前项目,不要修改任何文件。
我需要完成一个简易但真实可运行的 RAG:
1. 使用 FastAPI 提供 /、/health、/ask;
2. 使用 Milvus 2.5+ 保存文本、Dense 向量和 Sparse 向量;
3. Dense 使用本地 BGE-M3;
4. Sparse 使用 Milvus 服务端 BM25 内置函数;
5. Dense 索引使用 HNSW,Sparse 使用 BM25/AUTOINDEX;
6. 使用 Weighted Hybrid Search 融合两路召回结果;
7. 使用本地 BGE Reranker 对候选结果精排;
8. 使用 OpenAI 兼容接口调用 LLM 生成答案;
9. 返回 answer、sources、route、retrieval 等诊断字段;
10. 使用 Docker Compose 启动 MySQL、etcd、MinIO、Milvus 和 API。
请输出:
- 当前项目中已经存在的相关文件;
- 每个模块负责什么;
- 一次入库的执行顺序;
- 一次在线问答的执行顺序;
- 需要新增或修改的文件;
- 分阶段实施计划;
- 每个阶段的测试和验收方法。
暂时不要修改文件,也不要输出大段代码。
检查 Codex 的方案
重点检查:
- 入库和在线问答是否分开;
- Dense 和 Sparse 是否同时存在;
- 是否先混合召回,再 Reranker 精排;
- Reranker 是否被错误地放在入库阶段;
- 是否把 Reranker 当成向量索引;
- 是否明确了 LLM 只使用最终上下文;
- 是否给出了可执行的测试方法。
如果 Codex 把所有逻辑都塞进一个文件,先要求它重新拆分方案,不要马上接受代码。
五、实践二:按阶段生成代码
5.1 阶段 1:项目骨架和配置
请根据刚才确认的方案,只完成项目骨架和配置。
要求:
- 创建 app.py、qa_core/、scripts/、static/、tests/;
- 配置从 .env 读取,不把 API Key 写死在代码中;
- 先提供 GET /health;
- 为配置解析和健康检查添加纯逻辑测试;
- 不实现混合检索和 LLM 生成;
- 不修改与本阶段无关的文件。
完成后只报告:修改文件、启动命令、测试命令、测试结果和未完成内容。
本阶段的验收重点:
python -m pytest tests -q
python -m uvicorn app:app --host 127.0.0.1 --port 8010
浏览器访问http://127.0.0.1:8010/health,确认返回 JSON,而不是只确认进程没有退出。
5.2 阶段 2:文档加载、切分和入库
请只实现离线入库流程。
输入:scenarios/enterprise_knowledge/faq.csv 和 data/ 下的业务文档。
要求:
- FAQ 转换为包含 question、answer、source 的 Document;
- 支持 Markdown、TXT、PDF、DOCX、PPTX、CSV、XLSX;
- 文档使用 RecursiveCharacterTextSplitter 切分;
- 写入 FAQ Collection 和 Document Collection;
- Milvus 记录必须包含 text、dense、sparse;
- Dense 使用本地 BGE-M3;
- Sparse 使用 Milvus 2.5+ BM25BuiltInFunction;
- Dense 索引使用 HNSW,Sparse 使用 AUTOINDEX + BM25;
- 写入 MySQL 的知识库版本记录,并将版本设为 active;
- 添加不依赖真实服务的入库元数据和索引参数测试。
先阅读现有代码并列出会修改的文件,再执行修改。
本阶段要理解的顺序是:
文件
→ Document
→ chunk
→ dense/sparse
→ Milvus Collection
→ active 版本
5.3 阶段 3:Dense + BM25 混合检索
请在现有入库代码基础上,只实现在线混合检索。
要求:
- 接收一个 query 和 top_k;
- 同时执行 Dense 检索和 BM25 检索;
- 使用 Weighted Hybrid Search 合并结果;
- 结果必须保留原文和 source;
- 对重复 chunk 去重;
- 返回文档、分数和检索耗时;
- 不加入 Reranker,不加入 LLM,不修改 API 页面。
请说明:
1. Dense 和 Sparse 两路分别解决什么问题;
2. Weighted 融合发生在哪一步;
3. 为什么不能直接比较两路未经处理的原始分数;
4. 如何测试检索函数。
可以用下面的问题观察检索结果:
新人入职需要完成哪些流程?
VPN 连不上怎么处理?
预算超过部门额度时需要谁审批?
除了看最终答案,还要展开返回的sources,确认来源内容确实与问题相关。
5.4 阶段 4:加入 BGE Reranker
请在混合检索之后加入 BGE Reranker,不要改动 Milvus 的索引和召回逻辑。
执行顺序必须是:
混合检索召回候选结果
→ 构造 (query, document) 对
→ CrossEncoder 批量打分
→ 按 Reranker 分数降序排列
→ 取最终 top_n
要求:
- 使用本地 BGE Reranker 模型;
- 模型只在在线检索阶段加载,不参与入库;
- 保留原文、source 和 Reranker 分数;
- 候选为空时返回空结果,不调用模型;
- 添加一个纯函数测试,验证排序和 top_n 截断;
- 记录 Reranker 阶段耗时。
完成后解释:
- Embedding 和 Reranker 的输入有什么区别;
- 为什么 Reranker 不能替代 Milvus 召回;
- 为什么候选数量不能无限增大。
5.5 阶段 5:连接 LLM 和问答接口
请把已经完成的检索和 Reranker 接到问答接口。
要求:
- POST /ask 接收 query、history 和 source_filter;
- 先执行混合检索,再执行 Reranker;
- 只把最终排序后的上下文交给 LLM;
- Prompt 要求答案只能依据上下文;
- 没有相关资料时明确说明资料不足;
- 返回 answer、sources、route、retrieval、timings;
- API Key 只从环境变量读取;
- 不缓存最终 LLM 答案。
本阶段的链路必须保持为:
POST /ask
→ pipeline.ask()
→ retrieval.search_many()
→ rerank()
→ select_context_docs()
→ generate_answer()
→ 返回 answer + sources
5.6 阶段 6:Docker 和说明文件
请只补齐 Docker Compose、Dockerfile、README 和运行测试。
要求:
- Docker Compose 启动 MySQL、etcd、MinIO、Milvus 和 API;
- API 通过服务名访问 Milvus 和 MySQL;
- 本地模型通过 volume 挂载到 /app/models;
- 不把 API Key 写进 Dockerfile 或 compose 文件;
- README 必须包含启动、入库、提问、测试和常见报错处理;
- 执行 docker compose config 验证配置;
- 不新增 Redis、Agent 或管理后台。
六、实践三:运行完整项目
6.1 准备模型和环境变量
当前仓库的参考代码位于:
D:\workspace\knowforge-rag-platform\mini-rag
需要准备以下本地模型目录:
models/bge-m3
models/bge-reranker-large
复制配置文件:
cd D:\workspace\knowforge-rag-platform\mini-rag
Copy-Item .env.example .env
确认.env至少包含:
DASHSCOPE_API_KEY=你的Key
EMBEDDING_MODEL_PATH=../models/bge-m3
RERANKER_MODEL_PATH=../models/bge-reranker-large
MILVUS_URI=http://127.0.0.1:19540
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3307
6.2 启动基础设施
docker compose up -d mysql etcd minio milvus
docker compose ps
确认 MySQL、etcd、MinIO 和 Milvus 已经运行后,再构建和启动 API:
docker compose up -d --build api
docker compose ps
6.3 初始化知识库
docker compose exec api python scripts/rebuild.py --reset-collections --description "vibe coding hybrid rag"
这一步会:
读取 FAQ 和业务文档
→ 文档切分
→ 创建或重建 Milvus Collection
→ 生成 Dense 向量
→ 由 Milvus 生成 Sparse BM25 数据
→ 写入 FAQ 和文档集合
→ 创建并激活 MySQL 知识库版本
6.4 发起问题
浏览器打开:
http://127.0.0.1:8010/
也可以使用命令行:
docker compose exec api python scripts/ask.py "新人入职需要完成哪些流程?"
docker compose exec api python scripts/ask.py "VPN 连不上怎么处理?" --source-filter it
检查返回结果时,按下面顺序看:
answer是否回答了当前问题;sources是否包含相关文档;retrieval.rerank是否为开启状态;retrieval.faq_hits和retrieval.doc_hits是否有命中;retrieval.timings中哪一个阶段耗时最高;retrieval.faq_rerank_ms/retrieval.doc_rerank_ms是否能反映 Reranker 精排耗时。
七、通过实验理解 Reranker
7.1 不要把三种分数混在一起
| 分数 | 来源 | 用途 |
|---|---|---|
| Dense 距离或相似度 | BGE-M3 + Milvus | 语义召回 |
| BM25 分数 | Milvus 服务端 | 关键词召回 |
| Reranker 分数 | CrossEncoder | 候选结果精排 |
这些分数的计算方式不同,不能简单地说“谁的分数最高谁就一定更相关”。混合检索阶段由 ranker 负责融合,Reranker 阶段再使用 CrossEncoder 对 query 和 document 联合判断。
7.2 用一句话理解 Reranker
可以把整个过程想成两次筛选:
Milvus:先从大量资料中快速找出可能相关的候选
Reranker:逐条阅读问题和候选内容,重新判断谁最匹配
Milvus 负责速度和召回范围,Reranker 负责候选集内的相关性排序。Reranker 不直接扫描整个知识库,所以不能替代向量数据库。
7.3 观察参数的作用
当前参考代码中的默认值是:
FAQ_TOP_K=20
DOC_TOP_K=20
RERANK_TOP_N=8
FINAL_CONTEXT_TOP_N=5
它们的含义是:
- 每路检索先召回一批候选;
- Reranker 对候选结果重新排序并保留前 8 条;
- 最后送给 LLM 的上下文最多保留 5 条。
这些是体验项目的默认实验参数,不代表所有业务都必须使用这些数值。参数调优要结合评测集和实际耗时,在后续质量评测章节再系统处理。
八、使用 Codex 修复报错
8.1 报错反馈的正确格式
遇到问题时,不要只把最后一行错误复制给 Codex。使用下面的结构:
当前目录:D:\workspace\knowforge-rag-platform\mini-rag
执行命令:
docker compose exec api python scripts/rebuild.py --reset-collections
期望结果:
FAQ 和业务文档写入 Milvus,并激活一个知识库版本。
完整报错:
粘贴从 Traceback 开始到最后一行的完整内容
请先判断错误发生在:配置、依赖、Milvus 连接、模型加载还是业务代码。
请先阅读相关文件,再给出根因和最小修改方案。
不要重写整个项目,也不要修改与这个错误无关的文件。
8.2 常见问题的定位顺序
| 现象 | 先检查什么 |
|---|---|
ModuleNotFoundError |
容器内依赖和 requirements.txt |
| 模型目录不存在 | volume 挂载和模型路径 |
| Milvus 连接失败 | docker compose ps、URI 和服务健康状态 |
| Collection 结构不匹配 | 索引参数和是否需要重建集合 |
| LLM 调用失败 | API Key、Base URL、网络和模型名 |
| 答案没有依据 | sources、上下文选择和 Prompt |
| 结果很慢 | retrieval.timings中的检索和 Reranker 耗时 |
8.3 修复完成后的验收
查看 git diff
→ 运行纯逻辑测试
→ 重新执行失败命令
→ 查看服务日志
→ 发起一个真实问题
→ 检查 answer 和 sources
“程序不再报错”只是第一步,还要确认业务结果正确。
九、只追一条代码线
不要第一次阅读就打开整个项目。先沿着下面的调用关系阅读:
9.1 入库链路
mini-rag/scripts/rebuild.py
→ mini-rag/qa_core/ingest.py
→ mini-rag/qa_core/loaders.py
→ mini-rag/qa_core/retrieval.py
→ MiniMilvusStore.add_documents()
→ Milvus
重点看:
load_faq_documents()如何把 FAQ 转为 Document;load_document_chunks()如何加载和切分文件;hybrid_index_params()如何声明 HNSW 和 BM25;MiniMilvusStore.store如何配置dense和sparse字段;add_documents()如何把文档写入 Milvus。
9.2 在线问答链路
mini-rag/app.py
→ qa_core/pipeline.py: ask()
→ qa_core/query.py: prepare_query()
→ qa_core/retrieval.py: search_many()
→ qa_core/retrieval.py: rerank()
→ qa_core/pipeline.py: select_context_docs()
→ qa_core/prompts.py: generate_answer()
→ 返回 AnswerResult
在线链路中先记住三件事:
- 混合检索负责召回候选;
- Reranker 负责候选集内的精排;
- LLM 只接收最终选出的上下文。
十、这一部分验收清单
10.1 运行验收
cd D:\workspace\knowforge-rag-platform\mini-rag
python -m pytest tests -q
docker compose config
docker compose ps
10.2 功能验收
- 能成功创建 FAQ 和文档 Collection;
- Dense 字段和 Sparse 字段同时存在;
- Dense 索引为 HNSW;
- Sparse 检索使用 BM25;
- 混合检索可以返回候选文档;
- Reranker 会重新排序候选结果;
- LLM 能根据检索上下文生成答案;
- 答案返回来源引用;
- 没有相关资料时不会编造确定答案。
10.3 Vibe Coding 验收
你还要能够回答:
- 本轮 Codex 修改了哪些文件;
- 为什么要先做混合召回,再做 Reranker;
- 为什么 Reranker 不放在入库阶段;
- 一次报错发生后,你给了 Codex 哪些上下文;
- 你用什么命令和结果确认修改有效;
- 如果答案不准确,应该先看
sources还是先改 Prompt。
十一、和后续章节的衔接
这一部分把后续项目的核心检索链路提前跑通,但有意把企业级复杂度留到后面:
| 这一部分 | 后续章节 |
|---|---|
单一enterprise_knowledge场景 |
项目阶段 05 展开多场景意图分类和路由 |
直接调用pipeline.ask() |
项目阶段 09、10 展开 QAService 和完整 Pipeline |
| Dense + BM25 + Reranker | 项目阶段 06、07、08逐项拆解检索计划、改写、变体和混合检索 |
| 简单 active 版本 | 项目阶段 13、15、16 展开版本管理、入库流程和质量门禁 |
| 基础测试 | 项目阶段 16、17 展开评测、回归和接口验收 |
| Docker Compose 能运行 | 项目阶段 17 展开离线交付、镜像和排障 |
这一部分的作用是把前文梳理的 Milvus 操作变成一条完整的应用链路,也让你提前熟悉后续阅读企业级代码时最重要的方式:先找入口,再跟调用链,最后用运行结果验证理解。
讨论
留下你的想法
评论功能尚未配置。启用 Giscus 后,这里会显示基于 GitHub Discussions 的评论区。