系列:100 天系统学习 AI Agent 开发
当前阶段:RAG、知识库与工具边界
今日目标:Embedding 把文本映射到向量空间,向量检索用相似度找出最相关的片段。
搜到"意思差不多",离答对还很远
关键词搜索擅长找相同字词。用户问"Agent 断线后怎么接着跑",资料里写的是"checkpoint 支持恢复执行",两边没有完全相同的表达,关键词可能错过。Embedding 的用处,是把文本编码成一组数字,让语义接近的文本在向量空间里更靠近。
但"靠近"只是统计意义上的相似,不代表事实正确,更不代表片段足以回答问题。部署文档和卸载文档会共享大量词;旧版本与新版本也可能非常相似。向量检索解决的是候选召回,不是最终判断。
从文本到候选片段
#mermaid-svg-zf5176q1k2Fu2KMx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zf5176q1k2Fu2KMx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zf5176q1k2Fu2KMx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zf5176q1k2Fu2KMx .error-icon{fill:#552222;}#mermaid-svg-zf5176q1k2Fu2KMx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zf5176q1k2Fu2KMx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zf5176q1k2Fu2KMx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zf5176q1k2Fu2KMx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zf5176q1k2Fu2KMx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zf5176q1k2Fu2KMx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zf5176q1k2Fu2KMx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zf5176q1k2Fu2KMx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zf5176q1k2Fu2KMx .marker.cross{stroke:#333333;}#mermaid-svg-zf5176q1k2Fu2KMx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zf5176q1k2Fu2KMx p{margin:0;}#mermaid-svg-zf5176q1k2Fu2KMx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zf5176q1k2Fu2KMx .cluster-label text{fill:#333;}#mermaid-svg-zf5176q1k2Fu2KMx .cluster-label span{color:#333;}#mermaid-svg-zf5176q1k2Fu2KMx .cluster-label span p{background-color:transparent;}#mermaid-svg-zf5176q1k2Fu2KMx .label text,#mermaid-svg-zf5176q1k2Fu2KMx span{fill:#333;color:#333;}#mermaid-svg-zf5176q1k2Fu2KMx .node rect,#mermaid-svg-zf5176q1k2Fu2KMx .node circle,#mermaid-svg-zf5176q1k2Fu2KMx .node ellipse,#mermaid-svg-zf5176q1k2Fu2KMx .node polygon,#mermaid-svg-zf5176q1k2Fu2KMx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zf5176q1k2Fu2KMx .rough-node .label text,#mermaid-svg-zf5176q1k2Fu2KMx .node .label text,#mermaid-svg-zf5176q1k2Fu2KMx .image-shape .label,#mermaid-svg-zf5176q1k2Fu2KMx .icon-shape .label{text-anchor:middle;}#mermaid-svg-zf5176q1k2Fu2KMx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zf5176q1k2Fu2KMx .rough-node .label,#mermaid-svg-zf5176q1k2Fu2KMx .node .label,#mermaid-svg-zf5176q1k2Fu2KMx .image-shape .label,#mermaid-svg-zf5176q1k2Fu2KMx .icon-shape .label{text-align:center;}#mermaid-svg-zf5176q1k2Fu2KMx .node.clickable{cursor:pointer;}#mermaid-svg-zf5176q1k2Fu2KMx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zf5176q1k2Fu2KMx .arrowheadPath{fill:#333333;}#mermaid-svg-zf5176q1k2Fu2KMx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zf5176q1k2Fu2KMx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zf5176q1k2Fu2KMx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zf5176q1k2Fu2KMx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zf5176q1k2Fu2KMx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zf5176q1k2Fu2KMx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zf5176q1k2Fu2KMx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zf5176q1k2Fu2KMx .cluster text{fill:#333;}#mermaid-svg-zf5176q1k2Fu2KMx .cluster span{color:#333;}#mermaid-svg-zf5176q1k2Fu2KMx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zf5176q1k2Fu2KMx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zf5176q1k2Fu2KMx rect.text{fill:none;stroke-width:0;}#mermaid-svg-zf5176q1k2Fu2KMx .icon-shape,#mermaid-svg-zf5176q1k2Fu2KMx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zf5176q1k2Fu2KMx .icon-shape p,#mermaid-svg-zf5176q1k2Fu2KMx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zf5176q1k2Fu2KMx .icon-shape .label rect,#mermaid-svg-zf5176q1k2Fu2KMx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zf5176q1k2Fu2KMx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zf5176q1k2Fu2KMx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zf5176q1k2Fu2KMx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 文档片段
Embedding 模型
向量 + 原文 + metadata
用户问题
同一 Embedding 模型
查询向量
相似度搜索
过滤/重排后的 Top K
交给模型基于证据回答
向量数据库实际保存的不应只有向量。至少还要有原始文本、chunk_id、来源、版本、权限和更新时间。否则即使搜到了,也无法引用、过滤或删除。
常见相似度可以直观理解为"方向是否接近"。下面只是数学演示,不负责生成真实 Embedding:
python
from math import sqrt
def cosine_similarity(a: list[float], b: list[float]) -> float:
if len(a) != len(b) or not a:
raise ValueError("向量维度必须相同且不能为空")
dot = sum(x * y for x, y in zip(a, b))
norm_a = sqrt(sum(x * x for x in a))
norm_b = sqrt(sum(y * y for y in b))
if norm_a == 0 or norm_b == 0:
raise ValueError("零向量没有可用方向")
return dot / (norm_a * norm_b)
真实系统必须使用同一套、同一版本的 Embedding 生成文档向量和查询向量。更换模型后直接混用旧索引,维度可能不同;即便维度碰巧相同,空间含义也不保证一致,通常需要重建索引或维护明确的索引版本。
五段学习笔记怎么准备
| chunk_id | 笔记主题 | 关键词 | 是否独立成块 | 理由 |
|---|---|---|---|---|
| d006-loop | Agent Loop 与最大轮数 | loop、停止条件、observation | 是 | 能独立回答循环控制 |
| d007-retry | 可重试错误与幂等 | timeout、retry、idempotency | 是 | 与参数缺失策略不同 |
| d007-input | 缺参数时如何追问 | missing field、ask user | 是 | 不应和自动重试混为一谈 |
| d008-memory | 短期/长期记忆 | scope、TTL、source | 是 | 有完整的概念边界 |
| d009-cli | CLI 与安全计算器 | router、allowlist、trace | 可按标题再拆 | 代码和设计理由检索意图不同 |
这里我会保留标题路径,例如"Day 007 > 错误分类 > 需要补充",而不是只存正文。很多短片段脱离标题后会失去"它在讨论什么"的信息。
向量、关键词还是混合检索
| 查询特点 | 更合适的起点 |
|---|---|
| 精确错误码、函数名、订单号 | 关键词/倒排检索 |
| 同义表达、自然语言问题 | 向量检索 |
| 既有专有名词又有语义描述 | 混合检索 |
| 强版本或权限约束 | 先做 metadata 过滤,再检索 |
只用向量检索的一个典型反例是错误码。"E_CONN_1042"几乎不需要语义联想,精确匹配更可靠。工程上经常把关键词和向量候选合并,再用规则或重排模型排序。
我会怎样比较,而不是凭感觉
准备五个问题,为每题预先标注应命中的 chunk_id,再记录 Top 1、Top 3 是否包含目标:
json
{
"query": "网络超时可以自动重试吗?",
"expected_chunk_ids": ["d007-retry"],
"retrieved_chunk_ids": [],
"hit_at_1": null,
"hit_at_3": null,
"notes": "真实运行后填写"
}
列表保留空值,是为了明确本文没有运行向量模型。之后改变 chunk 大小、Embedding 模型或过滤条件时,用同一批问题复跑,才知道提升发生在哪里。
今天留下的判断
Embedding 像一张语义地图,向量数据库像管理这张地图的检索系统;它们都不是事实裁判。真正可靠的 RAG 还需要切分、元数据、过滤、重排、引用和拒答。下一篇研究 Chunking,正是因为"地图上的一个点代表多大一段文本"会直接影响能否找对。
面试官会追问:换一个 Embedding 模型,怎么证明更好?
不能只看某个问题"搜得更像"。同一批查询要固定语料、切分和 top-k,比较 Recall@k、MRR、索引体积、构建时间与单次检索延迟;中文、英文、代码和缩写查询还要分桶统计。向量维度更大可能提升表达能力,也会增加存储、内存和 ANN 搜索成本。
我会准备三类难例:同义改写、精确编号、跨语言表达。若向量检索擅长同义改写但漏掉产品编号,就用 BM25 与向量检索做混合召回,而不是逼一个 Embedding 模型同时解决全部问题。
面试时最重要的一句是:Embedding 选型是检索系统实验,不是模型排行榜抄答案;判断标准来自自己的语料和查询分布。
今日检查清单
- 文档向量和查询向量来自同一模型版本
- 每个向量能追溯到原文、来源、版本和权限
- 精确标识符没有强行只走语义检索
- 用预标注问题计算命中,不用一条漂亮回答下结论
- 更换 Embedding 模型时有索引迁移或重建计划