> ## Documentation Index
> Fetch the complete documentation index at: https://www.yuan111.asia/doc/llms.txt
> Use this file to discover all available pages before exploring further.

# 08 · 向量化与向量库：让机器理解"意思相近"

# 08 · 向量化与向量库：让机器理解"意思相近"

> **本片目标**：解决"怎么找到最相关的块"。用 Embedding 把文本变成向量，用向量相似度检索——这是 RAG 的召回引擎。
> **新增规定性：8**（Embedding 接口 + Chroma 库协议 + Retriever 转换）
> **数据字典**：Embeddings / HuggingFaceEmbeddings / Chroma / VectorStoreRetriever。
> **进程线程模型**：embedding 本地推理（CPU 计算）；Chroma 本地 sqlite+HNSW。
> **网络模型**：embedding 模型首次需从 HF 下载（\~100MB）；之后纯本地。

***

## 1. 上集回顾

第 07 篇把新人培训手册切成了 163 块。现在问题：用户问"转正需要什么条件"，**怎么找到最相关的块？**

**关键词搜索**：`if "转正" in chunk` —— 但用户问"我多久能转正"，里面没有"条件"二字；问"答辩要几个评委"，块里写的是"评委人数：4 位以上评委打分"。字面对不上，但**意思相近**。

**向量搜索**：把每句话变成一个"语义坐标"（向量），语义相近的文本向量也相近。用户问句的向量，和"转正"块的向量挨得很近——就能命中。

***

## 2. 数据字典：Embedding 接口

### 2.1 `Embeddings`（抽象接口，langchain\_core）

| 方法                                  | 签名                                                       | 返回值        |
| ----------------------------------- | -------------------------------------------------------- | ---------- |
| `embed_query`                       | `embed_query(text: str) -> list[float]`                  | 单条文本 → 向量  |
| `embed_documents`                   | `embed_documents(texts: list[str]) -> list[list[float]]` | 批量文本 → 向量组 |
| `aembed_query` / `aembed_documents` | 异步版                                                      | —          |

**铁律**：`embed_query` 和 `embed_documents` **必须用同一个模型**——否则查询向量和文档向量在不同空间，相似度无意义。

### 2.2 `HuggingFaceEmbeddings`（本地模型，本系列主力）

```python theme={null}
from langchain_huggingface import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-small-zh-v1.5",   # 中文语义模型，512 维
    encode_kwargs={"normalize_embeddings": True},  # 归一化 → 余弦相似度 = 点积
)
v = embeddings.embed_query("就业优先战略是什么")
# v: list[float]，实测长度 512
```

| 参数              | 类型     | 说明                                     |        |
| --------------- | ------ | -------------------------------------- | ------ |
| `model_name`    | `str`  | HF 模型名。`bge-small-zh-v1.5` 是中文检索主流小模型  |        |
| `encode_kwargs` | `dict` | `{"normalize_embeddings": True}` 归一化向量 |        |
| `cache_folder`  | \`str  | None\`                                 | 模型缓存目录 |
| `model_kwargs`  | `dict` | 传给底层模型的参数（如 device）                    |        |

**为什么归一化**：归一化后向量的余弦相似度 = 点积（一维 `sum(x*y)` 就能算），又快又直观。

**实测相似度**（2026-08）：

```
「就业优先战略」 vs 「促进高质量充分就业」 = 0.69  ← 意思相近，分高
「就业优先战略」 vs 「今天天气怎么样」     = 0.24  ← 意思无关，分低
```

**下载注意**：首次运行模型自动从 HuggingFace 下载（\~100MB）。国内需镜像：

```python theme={null}
os.environ.setdefault("HF_ENDPOINT", "https://hf-mirror.com")  # 放在 import 之前
```

***

## 3. 数据字典：Chroma 向量库

### 3.1 概念模型

```
一个向量库目录 (data/chroma_db) = 一个"数据库"（sqlite + HNSW 索引）
  ├── collection "training_manual"   ← 一个文档/主题一个 collection
  ├── collection "employment_15_5"   ← 类比 SQLite 的"表"
  └── collection "education_15_5"
```

每个 collection 里存的是：**块文本 + 向量 + metadata + id**。

### 3.2 构造与方法

```python theme={null}
from langchain_chroma import Chroma

vector_store = Chroma(
    collection_name="training_manual",   # collection 名
    embedding_function=embeddings,       # 建库和检索必须同一个 embedding 模型
    persist_directory="data/chroma_db",  # 一个目录 = 一个库
)
```

| 方法                             | 签名                                                             | 返回值              | 说明                        |
| ------------------------------ | -------------------------------------------------------------- | ---------------- | ------------------------- |
| `add_texts`                    | `add_texts(texts, metadatas=None, ids=None) -> list[str]`      | `list[str]`      | 文本入库，返回 id                |
| `similarity_search`            | `similarity_search(query, k=4, filter=None) -> list[Document]` | `list[Document]` | **语义检索**，返回最相近的 k 块       |
| `similarity_search_with_score` | `(query, k=4) -> list[tuple[Document, float]]`                 | `(块, 相似度)`       | 带分数的检索（第 10 篇精排用）         |
| `similarity_search_by_vector`  | `(vector, k=4) -> list[Document]`                              | `list[Document]` | 直接拿向量检索                   |
| `as_retriever`                 | `as_retriever(search_kwargs={"k": 3}) -> VectorStoreRetriever` | 检索器              | **转为 Runnable**（第 09 篇关键） |
| `delete_collection`            | `delete_collection()`                                          | —                | 只删自己的 collection          |

***

## 4. 数据字典：Retriever（检索器）

```python theme={null}
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
results = retriever.invoke("转正需要什么条件？")
# results: list[Document]  ← 3 个最相关的块
```

| 属性/方法           | 说明                                     |
| --------------- | -------------------------------------- |
| `search_type`   | `"similarity"`（默认）或 `"mmr"`（最大边际相关，去重） |
| `search_kwargs` | `{"k": 3}` 取回块数                        |
| `invoke(query)` | `-> list[Document]`                    |

**为什么需要 Retriever（规定性 8 的收尾）**：VectorStore 本身**不是 Runnable**（没有 invoke）。Retriever 包一层，让它变成 Runnable——这样就能用 `|` 管道接进 LCEL 链（第 09 篇）。**这是"向量库"通向"RAG 链"的桥。**

***

## 5. 进程线程模型

```
查询链路（第 08 篇）：
  问句 → embed_query(本地 CPU ~30ms) → 512 维向量
       → Chroma HNSW 索引相似度搜索(本地 ~ms) → top-k 块

入库链路（建库时）：
  9 块 → embed_documents(本地批量 ~百ms) → 9×512 向量 → sqlite + HNSW 索引
```

* **embedding 是本地 CPU 推理**（sentence-transformers + torch）——不联网、不阻塞网络（首次下载模型除外）。
* **Chroma 是本地嵌入式数据库**（sqlite + HNSW）——单进程内运行，不联网。

***

## 6. 网络模型

```
首次运行：HuggingFace Hub 下载 bge-small-zh-v1.5 (~100MB)
          GET https://hf-mirror.com/.../BAAI/bge-small-zh-v1.5/...   ← 仅一次
之后：    完全本地，0 网络请求
```

**对比第 01\~06 篇**：对话模型的调用每次都要联网（云端推理）；embedding 用本地小模型——**快、免费、隐私安全**。生产常见组合：本地 embedding + 云端对话模型。

***

## 7. 验证：跑起来

配套代码 `code/08_embed_search.py`：

1. embed\_query 看 512 维向量；
2. 亲手算余弦相似度（验证"意思相近分高"）；
3. 新人培训手册 163 块入库 Chroma；
4. 语义检索 vs 关键词搜索对比；
5. **InMemoryVectorStore**：纯内存向量库，零磁盘（测试/原型神器）。

```powershell theme={null}
cd enterprise-rag-course\code
python 08_embed_search.py
```

**预期输出（节选）**：

```
===== ① 文本 → 向量 =====
「就业优先战略是什么」→ 向量维度 512

===== ② 语义相似度 =====
就业优先战略 vs 促进高质量充分就业 = 0.69   ← 意思相近
就业优先战略 vs 今天天气怎么样     = 0.24   ← 无关

===== ③ Chroma 入库 + 语义检索 =====
入库 163 块
问「转正需要什么条件？」→ 命中: ## 第十三章：转正要求...
问「日总结什么时候发？」→ 命中: | 17:30~18:00 | 写日总结，发邮件 |...

===== ④ 关键词 vs 语义 =====
关键词搜索「答辩」命中 3 块
语义搜索问「转正要考什么？」→ 命中: ## 第十三章：转正要求...
   （问句里没有"答辩"两个字，但意思相近，被命中了！）

===== ⑤ InMemoryVectorStore =====
问「多久能转正？」→ 命中: 转正需累计至少 60 篇日总结
向量库类型: InMemoryVectorStore，无需 persist_directory、无 sqlite 文件
```

***

## 7.5 补充：InMemoryVectorStore——纯内存向量库（测试/原型神器）

Chroma 要 `persist_directory`（落盘到 sqlite）。但**测试和原型**场景，你经常只是"临时塞几个句子进去查一下"——用 `InMemoryVectorStore` 更省事：

```python theme={null}
from langchain_core.vectorstores import InMemoryVectorStore

imvs = InMemoryVectorStore(embedding=embeddings)   # ← 没有 persist_directory！
imvs.add_texts(
    ["转正需累计至少 60 篇日总结", "答辩平均分 75 分以上", "生产数据库只能 SELECT 查询"],
    ids=["r1", "r2", "r3"],
)
imvs.similarity_search("多久能转正？", k=1)
# → [Document(page_content='转正需累计至少 60 篇日总结')]   ← 语义命中
```

**为什么有用**：

* **接口和 Chroma 完全一致**（`add_texts` / `similarity_search` / `as_retriever`）——测试时用它，上生产换 Chroma 只改构造那行；
* **零磁盘、零配置**——不用建目录、不用删旧库、进程结束即消失，测试之间天然隔离；
* **第 13 篇全地图**里它被列为"测试神器"，就是说的这个用途。

**适用边界**：纯内存、进程重启就丢、不适合多进程共享——只用于测试/原型/单元测试，生产用 Chroma（单机）或 Milvus（大规模）。

***

## 8. 边界

* **embedding 模型 ≠ 对话模型**——两者独立选择。换 embedding 模型 = 向量空间全变，**必须重新建库**。
* **相似度高分 ≠ 答案对**——检索只是"召回"，质量由模型判断。召回不准时用第 10 篇重排序。
* **首次下载 100MB**——没配 `HF_ENDPOINT` 镜像可能很慢/失败。
* **Chroma 是嵌入式**——单机足够；超大知识库（千万级块）换专用向量数据库（Milvus 等），接口约定类似（都实现 VectorStore）。

***

## 推荐资料（延伸阅读）

* [LangChain 官方文档 · 嵌入模型集成](https://docs.langchain.com/oss/python/integrations/embeddings/) —— Embeddings 接口与集成
* [LangChain 官方文档 · 向量库集成](https://docs.langchain.com/oss/python/integrations/vectorstores/) —— VectorStore 接口与官方支持清单
* [Chroma 官方文档](https://docs.trychroma.com/) —— 本教程使用的向量库，API 与配置

***

## 9. 未完待续

零件齐了：模板、模型、解析器、记忆、文档、切分、向量库、Retriever。**老板要的东西来了**：把检索 → 拼上下文 → 生成，串成一条完整链路——RAG。

但是：旧教程教的 `RetrievalQA` / `create_retrieval_chain` 已经**迁入 langchain-classic（弃用）**。第 09 篇教你用 **LCEL 管道**直接组装——这才是 1.x 官方路线。

→ [09 · RAG 组装](/doc/doc/enterprise-rag-course/09-RAG组装)
