Easy Data × AI:构建知识与记忆驱动的 Agent笔记3
Task 3 · P2 + D2 + I2 · 打卡笔记
🔧 D2 作业
核心演示:d2_4 精确编号场景并排对比
查询:「错误码 E-4012 的解决方案」
纯向量搜索 混合搜索(向量+全文+RRF)
------------------------------------ ------------------------------------
[1] E-4012... (0.444) [1] E-4012... (0.016)
[2] E-4011... (0.167) ← 语义相近排第二 [2] E-4011... (0.016)
[3] E-4013... (0.146) ← 差一位数字! [3] E-4013... (0.016)
📌 纯向量:三个错误码在语义空间几乎等距,排名带随机性
混合搜索:全文精确命中 E-4012 + RRF 融合,信号清晰
→ 差一位数字 = 完全不同的错误,这是"对和错"的问题,不是"好和差"
这完美印证了 P2 课程稿的核心观点:混合搜索不是可选优化,是生产级 RAG 的基本要求。
📦 vecdb.py --- D1/D2 共享向量检索层
为什么要做?
| 方案 | 平台兼容 | 首次启动 | 代价 |
|---|---|---|---|
| pyseekdb embedded | ❌ 仅 Linux + macOS arm64 | 秒开 | Windows 不可用(原生库 pylibseekdb 仅 Linux) |
| ChromaDB | ✅ 全平台 | ~10min | 首次需下载 80MB embedding 模型,15KB/s 要 2 小时 |
| TF-IDF + scipy(vecdb.py) | ✅ 全平台 | 秒开 | 只需要 numpy + scipy(已装),生产级用 ChromaDB/pyseekdb |
实现的 API(对齐 pyseekdb)
python
from vecdb import Client
db = Client(db_path="./d2_kb")
db.create_collection("d2_knowledge_base")
c = db.get_collection("d2_knowledge_base")
# 写入
c.add(ids=[...], documents=[...], metadatas=[...])
# 纯向量检索
c.query(query_texts="怎么设计用户权限", n_results=3)
# 混合检索(全文 + 向量 + RRF + 元数据过滤,一条查询搞定)
c.hybrid_search(
query={"where_document": {"$contains": "E-4012"}, "n_results": 5},
knn={"query_texts": ["错误码 E-4012"], "where": {"version": "4.2"}, "n_results": 5},
rank={"rrf": {}},
n_results=3,
)
# JSON 持久化 ------ 下次 Client() 自动加载
RAG 数据层第一步:chunk_document 切分函数
为什么值得记:RAG 的第一步不是"向量化",而是"切分"------知识库的 chunk 大小直接决定检索质量。切太大,检索时携带无关信息;切太小,丢失上下文。
python
# 来自 d2_1_ingest.py
def chunk_document(text: str, chunk_size: int = 200, overlap: int = 30) -> list[str]:
"""
固定窗口切分 + overlap 重叠区。
- chunk_size=200:每片不超过 200 字(经验值:让 embedding 模型一次能完整理解)
- overlap=30:相邻片之间留 30 字重叠,避免一句话被切断导致语义断裂
"""
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunks.append(text[start:end].strip())
start += chunk_size - overlap # ← 关键:步长 = chunk_size - overlap
return [c for c in chunks if c]
切分完整流程(d2_1 的核心逻辑):
python
raw_docs = [
{"text": "错误码 E-4012 表示数据库连接超时...", "category": "error_codes", "version": "4.2"},
{"text": "数据库查询性能优化指南...", "category": "best_practices", "version": "4.2"},
# ... 更多文档
]
collection = db.create_collection("d2_knowledge_base")
for doc_idx, doc in enumerate(raw_docs):
for chunk_idx, chunk in enumerate(chunk_document(doc["text"])):
collection.add(
ids=[f"doc_{doc_idx}_chunk_{chunk_idx}"],
documents=[chunk],
metadatas=[{"category": doc["category"], "version": doc["version"]}], # ← 保留元数据
)
混合检索完整调用(三路参数)
为什么值得记:hybrid_search 的三路参数是 pyseekdb 混合搜索的完整签名------也是 P2 讲的"广义 Retrieval 五路子通道"在代码里的集中体现:
python
results = collection.hybrid_search(
# ── 第 1 路:全文关键词检索 ──────────────────────────────────────
query={
"where_document": {"$contains": "E-4012"}, # 子串精确匹配
"n_results": 5,
},
# ── 第 2 路:向量语义检索(支持元数据过滤) ──────────────────────
knn={
"query_texts": ["错误码 E-4012 的解决方案"], # 自然语言 query
"where": {"version": "4.2"}, # ← 结构化过滤!只搜 4.2 版本
"n_results": 5,
},
# ── 第 3 路:融合算法 ────────────────────────────────────────────
rank={"rrf": {}}, # RRF = Reciprocal Rank Fusion,见下面实现
n_results=3, # 最终返回 TopK
)
三路参数对应 P2 的 RAG 六步链路:
| 参数 | 对应检索通道 | 作用 |
|---|---|---|
query.where_document.$contains |
全文/关键词 | 精确匹配专有名词、错误码、版本号 |
knn.query_texts |
向量/语义 | 理解口语化提问、同义表达 |
knn.where |
标量/过滤 | 按版本、类目、时间精确筛选 |
rank.rrf |
融合重排 | 三路结果合流,排对序 |
RRF 融合核心实现
为什么值得记:RRF 是混合搜索的"胶水"------没有它,得自己写向量分数和全文分数怎么归一化、怎么加权、怎么去重。RRF 绕开了所有这些麻烦,直接按排名给分:
python
# 来自 vecdb.py 的 hybrid_search() 方法核心片段
k = 60 # RRF 平滑常数(经验值 60 是业界常用,用来压制排名靠后的影响)
scores = {} # doc_index → 累积分数
# --- 全文分支 ---
ft_scores = backend.fulltext_scores(["E-4012"]) # 每个 doc 里 "E-4012" 出现次数
top_ft = np.argsort(-ft_scores)[:10]
for rank_pos, doc_idx in enumerate(top_ft):
if ft_scores[doc_idx] <= 0: break
# 排名第 0 的 doc 得 1/(60+1),第 1 的得 1/(60+2),以此类推
scores[int(doc_idx)] = scores.get(int(doc_idx), 0.0) + 1.0 / (k + rank_pos + 1)
# --- 向量分支(同理) ---
vt_res = backend.query(["错误码 E-4012"], n_results=10, where={"version": "4.2"})
top_vt = np.argsort(-all_sims)[:10]
for rank_pos, doc_idx in enumerate(top_vt):
if all_sims[doc_idx] <= 0: break
scores[int(doc_idx)] = scores.get(int(doc_idx), 0.0) + 1.0 / (k + rank_pos + 1)
# --- 合流:按累积分数排序,取 TopK ---
sorted_idx = sorted(scores.items(), key=lambda x: -x[1])[:n_results]
RRF 的精妙之处:
- 不需要分数归一化 :全文分支的
1/(60+0)=0.0164和向量分支的分数天然在同一量级 - 同一 doc 两路都命中 → 分数叠加 :比如 "E-4012 文档" 全文排第 0 + 向量排第 1,得分 =
1/61 + 1/62 ≈ 0.0328,自动压过只在一路排前面的 doc - k=60 压制长尾 :第 61 名的分数 =
1/121 ≈ 0.008,排名 60 之后的 doc 基本被忽略
TF-IDF 分词策略(让短文本效果到位)
- 英文:
[a-z][a-z0-9_\-]+完整单词 - 中文:2-gram
- 精确编号:
E-4012/1.2.0作为整体 token(让向量检索对版本号也有加分)
RRF(Reciprocal Rank Fusion)融合算法
python
# 两路检索结果按排名给分,相加排序
score(doc) = Σ 1/(k + rank_of_doc_in_path) # k=60 平滑常数
- 全文命中:
1/(60+0) + 1/(60+1) + ... - 向量命中:同上
- 同一 doc 两路都命中 → 分数累加 → 自动排到前面
📚 产品篇 P2 · Agentic RAG 产品设计 · 学习笔记
核心金句
"用户说'AI 答得不好',PM 的第一反应不应该是'换模型',而应该先问:R 做对了吗?数据准备对了吗?检索路径对了吗?"
"60-80% 的 RAG 问题,根因在数据层(没搜全 / 搜错了),不在模型层(幻觉)。"
一、RAG ≠ 向量检索(最核心的认知纠正)
RAG 全称是 Retrieval-Augmented Generation ,重点是第一个词:Retrieval,检索。
广义 Retrieval 包含五路子通道:
| 检索方式 | 解决什么 | 例子 |
|---|---|---|
| 全文 / 关键词 | 精确匹配专有名词、产品型号、错误码 | E-4012、OceanBase 4.4.1 |
| 向量 / 语义 | 口语化提问、同义表达、模糊需求 | "怎么设计权限" → "访问控制架构" |
| 标量 / 过滤 | 按版本、产品线、时间、权限筛选 | 只搜 version=4.2 的文档 |
| 图谱 / 关系跳转 | 多跳问题 | "这个客户 → 哪个行业方案 → 哪些成功案例" |
| API / 工具调用 | 实时数据 | 查今天的库存、股价、物流 |
只做向量检索 = 只给用户一把锤子。 真实知识库充满了版本号、错误码、产品名------这些在语义空间都很"相近",但精确匹配至关重要。
二、纯向量检索的结构性软肋(被 D2 代码完美复现)
| 查询 | 纯向量结果 | 问题 |
|---|---|---|
| "错误码 E-4012 的解决方案" | E-4012 / E-4011 / E-4013 都排前面 | 三个错误码语义几乎等距,排第二的 E-4011 和 E-4012 差一位数字=完全不同的错误 |
| "seekdb 1.2.0 版本兼容性" | seekdb 1.2.0 / 1.0.0 / 1.3.0 都排前面 | 版本号在语义空间"都是 seekdb 版本",但兼容性可能完全不同 |
结论:纯向量检索能解决语义问题,但解决不了"差一点就是另一件事"的精确问题。
三、传统 RAG vs Agentic RAG
| 维度 | 传统 RAG | Agentic RAG |
|---|---|---|
| 流程结构 | 无环 DAG,预先设计好的固定流水线 | 有闭环,系统可以自主重试、补搜、改路由 |
| 典型路径 | query → 改写 → 检索一次 → TopK → 拼 Prompt → 回答 | query → 检索 → 判断够不够 → 不够换关键词再搜 → 还是不够再查另一路 → 生成 |
| 搜索方式 | 通常单一路径(向量搜索) | 多路可选(全文/向量/过滤/工具),动态选择 |
| "更聪明"的来源 | 换更强的模型 / 加更多节点 | 流程能自我调整 |
传统 RAG:像"按固定流程查一遍资料"
Agentic RAG:像"主动在替你反复找、反复核对、反复修正"
四、混合搜索 ------ 不是高级玩法,是基础要求
混合搜索在 RAG 流程里有三层价值:
第一层:同时服务传统 RAG 和 Agentic RAG
- 一套底层支持向量 / 全文 / 过滤 → Agent 动态选搜索方式才可能实现
第二层:把复杂度内置,降低开发者负担
- 不用自己拼向量库 + 搜索引擎 + 关系库 + 应用层合并去重重排
- seekdb 这种 AI 原生数据库在引擎内部就做好了
第三层:支撑 Agentic RAG 的闭环编排
- Agent 频繁做改写 query 再搜、先全文再向量、先过滤再匹配------底层原生支持才跑得动
五、完整 RAG 六步链路
① 数据准备 ② 问题理解 ③ 搜索召回 ④ 融合重排 ⑤ 生成 ⑥ 评估反馈
(最容易低估) query改写/路由 广撒网别漏掉 RRF/去重/重排 引用+约束 RAGAS
↓
load → parse → clean → split → enrich → embed → refresh
六步对应 RAGAS 评估指标:
| 阶段 | RAGAS 指标 | 低了说明什么 |
|---|---|---|
| ③ 搜索召回 | Context Recall | 应该召回的证据漏掉了 |
| ④ 融合重排 | Context Precision | 混入了噪音,上下文不干净 |
| ⑤ 生成 | Faithfulness | 模型幻觉,编造了资料里没有的内容 |
| 用户感受 | Answer Relevancy | 答非所问,没围绕用户意图 |
六、三层归因框架(P2 最重要的工具)
当用户说"AI 答得不好"时:
① 数据层 · 内容覆盖 → 知识库里有没有正确答案?没有 → 补数据
② 数据层 · 检索策略 → 有答案但 AI 没检索到?检索偏了/排错了 → 优化检索(混合搜索!)
③ 模型层 · 幻觉 → 检索到了,答的时候加了编造信息?→ 引用设计 + 约束生成
④ 业务层 · 规则 → 事实对但不合规/风格不对?→ Prompt + 业务规则
关键比例:60-80% 是数据层问题,10-20% 是模型层幻觉,剩下少量是业务层问题。
PM 最常见的误诊:把 ①② 诊断成 ③,花大量精力换模型 → 效果没变。
💡 对比:
| 之前以为 | 现在理解 |
|---|---|
| RAG = 向量数据库 + LLM | RAG = Retrieval(多路)+ Augmented + Generation,向量只是其中一路 |
| 混合搜索是"高级优化" | 混合搜索是底线,差一位数字就是对和错的区别 |
| AI 答不好 → 换模型 | 先查数据层:知识库里有没有?检索对了吗?排对了吗?再谈模型 |
| Agentic RAG = 换更聪明的模型 | Agentic RAG = 流程能自我调整(闭环 + 自主选择搜索方式 + 补搜) |
| RAG 做一次就完 | RAG 是六步可调优的产品链路 + 评估反馈闭环(RAGAS) |