跳转至
RAG阶段实践Vibe Coding 实践

第二阶段实践:Vibe Coding 实践

用 Codex 完成一个简易混合检索 RAG

上一章:Milvus 索引机制与基本操作

下一章:意图分类

本节放在第 04 章之后学习。前面已经学习了 Milvus、HNSW、Dense 向量、Sparse 向量和混合检索,本节把这些知识放进一个可以运行的 RAG 项目中,再使用 Vibe Coding 的方式完成代码、验证和修正。

本节统一使用 Codex 演示。下面的提示词可以直接复制到 Codex 对话中执行;代码修改、命令运行和结果检查都在同一个项目工作区完成。

这里的重点不是让 Codex 一次生成所有代码,而是练习一套可以重复使用的开发过程:

明确需求
→ 让 Codex 先分析和规划
→ 分阶段实现
→ 运行和查看结果
→ 提供完整报错上下文
→ 让 Codex 做最小修改
→ 测试和复盘

本节完成什么

完成本节后,你应该能够:

  • 说清楚一个简易 RAG 的入库链路和在线问答链路;
  • 让 Codex 根据结构化需求生成项目方案;
  • 使用 Milvus 保存 Dense 和 Sparse 两类检索字段;
  • 使用 Milvus 2.5+ 完成 Dense + BM25 混合检索;
  • 理解为什么混合检索之后还需要 Reranker;
  • 使用 BGE Reranker 对候选文档进行二次排序;
  • 让 LLM 根据排序后的上下文生成答案和来源;
  • 使用测试、接口响应、检索来源和日志判断代码是否正确;
  • 遇到错误时,向 Codex 提供足够信息并完成最小修改。

本节完成的不是一个企业级平台,而是一条真实、可运行、可解释的核心 RAG 链路。

一、项目边界

1.1 本节主链路

本节只关注下面这条链路:

离线入库:
FAQ / 业务文档
    → 文件加载
    → 文本切分
    → BGE-M3 生成 Dense 向量
    → Milvus 内置 BM25 生成 Sparse 向量
    → 写入 Milvus

在线问答:
用户问题
    → Dense + BM25 混合检索
    → 合并候选结果
    → BGE Reranker 精排
    → 选择上下文
    → LLM 生成答案
    → 返回答案和来源

1.2 本节会用到的组件

组件 本节中的职责
FastAPI 提供 GET /GET /healthPOST /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 初始化。需要创建 textdensesparse 字段,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

检查返回结果时,按下面顺序看:

  1. answer 是否回答了当前问题;
  2. sources 是否包含相关文档;
  3. retrieval.rerank 是否为开启状态;
  4. retrieval.faq_hitsretrieval.doc_hits 是否有命中;
  5. retrieval.timings 中哪一个阶段耗时最高;
  6. 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 如何配置 densesparse 字段;
  • 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

在线链路中先记住三件事:

  1. 混合检索负责召回候选;
  2. Reranker 负责候选集内的精排;
  3. 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 版本 第 14、16 章学习版本管理和入库流程
基础测试 第 17、18 章学习评测、回归和接口验收
Docker Compose 能运行 第 20 章学习离线交付、镜像和排障

本节的作用是把前面学过的 Milvus 操作变成一条完整的应用链路,也让你提前熟悉后续阅读企业级代码时最重要的方式:先找入口,再跟调用链,最后用运行结果验证理解。

返回笔记开头 ↑