上一篇聊了 RAG 的全链路,有读者问:「Embedding 到底是什么?把文本变成向量这个过程,底层发生了什么?」
这个问题问得好。面试里大家都背过「把文本映射到高维空间的稠密向量」,但真要解释清楚——为什么这个向量能代表语义?为什么余弦相似度能衡量两段文字是不是在聊同一件事——很多人就含糊过去了。
这篇从直觉开始,到选型,到代码落地,把 Embedding 和向量数据库这块拆干净。
一、Embedding 到底表示了什么
先讲一个最直觉的例子。
假设你要给动物建一个坐标系:
会飞
↑
鸟 | 蝙蝠
|
有毛 ←──────────────┼──────────────→ 无毛
|
狗 | 鳄鱼
↓
不会飞
在这个二维坐标系里,「狗」和「猫」的坐标很近(都在左下区),「狗」和「鱼」很远。这就是向量的直觉——把对象的属性编码成坐标,坐标越近,属性越相似。
真实的 Embedding 模型做的是同一件事,只是维度从 2 维变成了 768 维或 1024 维,每一维对应某种抽象的语义属性(不一定能用人类语言描述)。模型是从海量文本中自动学会如何分配这些坐标的——语义相近的词和句子,在这个高维空间里自然聚在一起。
几个关键直觉:
- 语义在方向,不在长度
「我很开心」和「今天心情特别好」在 768 维空间里,方向相近,但向量的长度(模)可能不同。这就是为什么用余弦相似度而不是欧氏距离——余弦只看两个向量的夹角,不管长度。
余弦相似度 = cos(θ) = (A · B) / (‖A‖ × ‖B‖)
夹角越小(接近 0°),相似度越接近 1;方向相反(180°),相似度为 -1。
- 与 TF-IDF 的本质区别
如果你做过传统搜索引擎,对 TF-IDF 不陌生。TF-IDF 是词频统计,没有语义理解——「快乐」和「高兴」在 TF-IDF 里是两个不同的词,毫无关联。
Embedding 是语义表示,「快乐」和「高兴」的向量很近,即使字面上不同。
TF-IDF(稀疏向量):
「快乐的狗」→ [0, 0, 0, 1, 0, 1, 0, ...] # 只有「快乐」「狗」对应位置为 1
Embedding(稠密向量):
「快乐的狗」→ [0.23, -0.18, 0.95, 0.02, ...] # 768 个连续浮点数,每个都有值
「高兴的小狗」→ [0.25, -0.16, 0.93, 0.04, ...] # 非常接近
这也是为什么 RAG 系统不用 TF-IDF 做语义检索——纯关键词召回会漏掉大量语义相关的内容。
- 向量化是有代价的
把一段 500 字的文档向量化,要调用 Embedding 模型做一次前向推理——计算量不小。100 万篇文档全量向量化,要认真估算时间和成本:
| 规模 | BGE-large(本地 GPU T4) | text-embedding-3-small(OpenAI API) |
|---|---|---|
| 10 万 chunk | ~5 分钟 | ~$0.002 |
| 100 万 chunk | ~50 分钟 | ~$0.02 |
| 1000 万 chunk | ~8 小时 | ~$0.2 |
OpenAI 的 text-embedding-3-small 价格是 $0.02/百万 Token,本地部署 bge-large 在 T4 GPU 上跑 batch=256 大概 500ms 出一批。向量化是一次性的离线成本,后续增量更新才是持续压力。
二、代码 Embedding:搭代码搜索系统的正确姿势
通用 Embedding 模型对代码效果不好。原因很直接:代码有自己的语法结构、变量命名习惯、API 调用模式,通用模型在自然语言语料上训练,对代码的语义理解浮于表面。
一个典型的坑:用通用 Embedding 搜索「Python 排序算法」,可能返回一堆聊「算法复杂度」的文档,而漏掉实际包含 sort() 调用的代码文件。
2026 年代码 Embedding 选型:
| 模型 | 特点 | 推荐场景 |
|---|---|---|
| voyage-code-3 (Voyage AI) | 跨语言代码最强,支持 Python/Java/Go/TS 等 20+ 语言 | 多语言代码库搜索、企业代码 RAG |
| text-embedding-3-large (OpenAI) | 通用但对代码有一定支持,上下文 8K | 代码和文档混合的知识库 |
| CodeBERT (微软开源) | 纯代码预训练,支持 Python/Java/JS 等 6 种语言 | 本地部署、数据不出境 |
| UniXcoder (微软开源) | 支持代码理解 + 代码生成,跨模态 | 代码搜索 + 代码补全系统 |
| jina-embeddings-v3 | 通用但对代码有适配,本地部署友好 | 轻量场景 |
选型建议:
你的代码库是什么语言?
├─ Python 为主 → voyage-code-3 或 CodeBERT(两者 Python 最强)
├─ 多语言混合 → voyage-code-3(覆盖最广)
│
需要本地部署(数据不出境)?
├─ 是 → CodeBERT 或 UniXcoder(微软开源)
└─ 否 → voyage-code-3(API,效果最好)
│
代码和文档混合的知识库?
└─ text-embedding-3-large(兼顾两者,但代码效果略差于专用模型)
实测对比(搜索「用 Python 实现二叉树的中序遍历」):
| 模型 | 排名 #1 结果 | 是否命中 |
|---|---|---|
| text-embedding-3-large | 「树形数据结构概述」(文档) | ❌ 漏掉代码 |
| voyage-code-3 | def inorder_traversal(root)... (代码) | ✅ 精准命中 |
| CodeBERT | def inorder_traversal(root)... (代码) | ✅ 精准命中 |
如果你在搭代码智能搜索或 AI Code Review 系统,代码专用 Embedding 是不可跳过的选型点。
# voyage-code-3 使用示例
import voyageai
vo = voyageai.Client(api_key="your-api-key")
# 代码文档向量化
code_snippets = [
"def quicksort(arr): ...",
"public void mergeSort(int[] arr) { ... }",
]
result = vo.embed(code_snippets, model="voyage-code-3", input_type="document")
embeddings = result.embeddings # List[List[float]],每个 1024 维
# 查询向量化(input_type="query" 与 document 不同)
query = "用 Python 实现快速排序"
query_result = vo.embed([query], model="voyage-code-3", input_type="query")
注意 voyage 区分 input_type="document" 和 input_type="query"——文档和查询用不同的编码方式,这是 Asymmetric Embedding 的设计,查询通常更短且语义模糊,文档通常更长且具体,分开编码效果更好。
三、向量数据库轻量选项:Chroma 什么时候用
前面写了 pgvector/Milvus/Qdrant 的详细对比,有读者问:「Chroma 呢?好多教程用这个。」
Chroma 的定位很明确:本地开发 / 快速原型 / 不需要运维的轻量场景。
# Chroma 5 行代码建一个知识库
import chromadb
client = chromadb.Client() # 纯内存模式,重启清空
# 或 chromadb.PersistentClient(path="./my_db") # 持久化到本地文件
collection = client.create_collection("my_docs")
collection.add(
documents=["Python 是一种解释型编程语言", "Go 适合高并发服务"],
ids=["doc1", "doc2"],
)
results = collection.query(query_texts=["什么语言适合写服务端?"], n_results=1)
print(results["documents"]) # [['Go 适合高并发服务']]
Chroma 内置了 Embedding 模型(默认用 all-MiniLM-L6-v2),不用自己处理向量化——传文本进去,它自动向量化再存储。这是它在教程里大量出现的原因:零配置,三行代码跑起来。
Chroma vs pgvector 决策:
| 场景 | 选哪个 | 理由 |
|---|---|---|
| 本地开发、调试 RAG 逻辑 | Chroma | 零运维,重启即清空,调试友好 |
| 写教程、演示 Demo | Chroma | 代码最简洁,读者上手快 |
| 生产环境,已有 PostgreSQL | pgvector | 复用基础设施,零额外运维 |
| 生产环境,需要 ACID 事务 | pgvector | 和业务数据在同一个 DB |
| 数据量 > 100 万,需要分布式 | Milvus/Qdrant | Chroma 单机,pgvector 千万级开始吃力 |
Chroma 的硬伤:不适合生产。它没有完善的高可用方案,集群支持有限,大规模下性能和稳定性都比不上 Qdrant/Milvus。把 Chroma 用在生产的团队,一旦数据量涨上去,迁移成本会很高。
结论:Chroma 是最好的「开发期向量库」,pgvector 是「最低运维成本的生产向量库」。
四、pgvector 实战:用 PostgreSQL 构建最小知识库
pgvector 是 PostgreSQL 的扩展,向量作为一种数据类型直接存在 PG 表里。这意味着:
- 向量索引和业务数据在同一个数据库,不用维护两套基础设施
- 支持完整的 SQL 操作——
WHERE、JOIN、事务全都可以用 - 权限控制直接复用 PostgreSQL 的行级安全(RLS)
- 运维团队已经熟悉 PG,学习成本几乎为零
适用场景:数据量 < 500 万向量,已有 PostgreSQL,团队不想引入新的基础设施组件。
4.1 环境准备
# 方式一:Docker(推荐本地开发)
docker run -d /
--name pgvector-demo /
-e POSTGRES_PASSWORD=yourpassword /
-p 5432:5432 /
pgvector/pgvector:pg16
# 方式二:已有 PostgreSQL,安装扩展
# Ubuntu/Debian:
sudo apt install postgresql-16-pgvector
# macOS (Homebrew):
brew install pgvector
-- 连接 PostgreSQL 后,开启扩展
CREATE EXTENSION IF NOT EXISTS vector;
4.2 建表与索引
-- 知识库文档表(以 RAG 客服场景为例)
CREATE TABLE knowledge_chunks (
id BIGSERIAL PRIMARY KEY,
source_file TEXT NOT NULL, -- 来源文档
section TEXT, -- 章节标题
content TEXT NOT NULL, -- 原文
embedding VECTOR(1024) NOT NULL, -- 向量(bge-m3 输出 1024 维)
metadata JSONB DEFAULT '{}', -- 可扩展元数据(权限/部门/时间等)
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 创建 HNSW 索引(推荐,查询更快)
-- 余弦相似度用 vector_cosine_ops
CREATE INDEX ON knowledge_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 或者 IVFFlat(内存更省,精度略低)
-- CREATE INDEX ON knowledge_chunks
-- USING ivfflat (embedding vector_cosine_ops)
-- WITH (lists = 100);
-- 元数据过滤加速(给高频过滤字段建普通索引)
CREATE INDEX ON knowledge_chunks ((metadata->>'dept'));
CREATE INDEX ON knowledge_chunks (created_at);
选 HNSW 还是 IVFFlat?
- HNSW:查询更快(P99 < 20ms),召回率更高(97%+),支持增量写入,首选
- IVFFlat:内存更省,但需要先有足量数据才能训练(建议 > 10 万条再用),
lists参数设sqrt(N)左右
4.3 Python 写入向量
import psycopg2
from psycopg2.extras import execute_values
from sentence_transformers import SentenceTransformer
from typing import List
import json
# 连接配置
conn = psycopg2.connect(
host="localhost",
database="postgres",
user="postgres",
password="yourpassword",
)
conn.autocommit = False
# Embedding 模型(中文场景用 bge-large-zh-v1.5 或 bge-m3)
embedder = SentenceTransformer("BAAI/bge-large-zh-v1.5")
def ingest_chunks(chunks: List[dict]):
"""
批量写入文档块。
chunks 格式:[{"content": "...", "source_file": "...", "section": "...", "metadata": {...}}]
"""
texts = [c["content"] for c in chunks]
# 批量向量化(batch 推理比逐条快 10x+)
embeddings = embedder.encode(
texts,
batch_size=64,
normalize_embeddings=True, # L2 归一化,余弦相似度等价内积
show_progress_bar=True,
)
rows = [
(
c["source_file"],
c.get("section"),
c["content"],
embedding.tolist(), # VECTOR 类型接受 Python list
json.dumps(c.get("metadata", {})),
)
for c, embedding in zip(chunks, embeddings)
]
with conn.cursor() as cur:
execute_values(
cur,
"""
INSERT INTO knowledge_chunks
(source_file, section, content, embedding, metadata)
VALUES %s
""",
rows,
template="(%s, %s, %s, %s::vector, %s::jsonb)",
)
conn.commit()
print(f"已写入 {len(rows)} 条")
# 使用示例
ingest_chunks([
{
"content": "退换货政策:自签收之日起 7 天内可申请无理由退货,需保持原包装完好。",
"source_file": "policy/refund.md",
"section": "退换货",
"metadata": {"dept": "客服", "version": "2026-Q2"},
},
{
"content": "物流异常处理:若显示已签收但未收到,请 48 小时内联系客服并提供订单号。",
"source_file": "policy/logistics.md",
"section": "物流",
"metadata": {"dept": "物流", "version": "2026-Q2"},
},
])
4.4 语义查询
def semantic_search(
query: str,
top_k: int = 5,
dept_filter: str = None,
) -> List[dict]:
"""
语义检索:支持可选的部门元数据过滤。
"""
query_embedding = embedder.encode(
[query],
normalize_embeddings=True,
)[0].tolist()
# pgvector 的距离算子:
# <=> 余弦距离(1 - cosine_similarity),越小越相似
# <-> 欧氏距离(L2)
# <#> 内积的负值(归一化后与余弦等价)
sql = """
SELECT
id,
source_file,
section,
content,
metadata,
1 - (embedding <=> %s::vector) AS cosine_similarity
FROM knowledge_chunks
{where_clause}
ORDER BY embedding <=> %s::vector
LIMIT %s
"""
params = [query_embedding, query_embedding, top_k]
if dept_filter:
# JSONB 字段过滤:先走普通索引筛选,再做向量检索
where_clause = "WHERE metadata->>'dept' = %s"
params = [query_embedding, dept_filter, query_embedding, top_k]
sql = sql.format(where_clause=where_clause)
# 注意参数顺序:第一个 %s 是 WHERE 前的 embedding,第二个是 dept,
# 第三个是 ORDER BY 的 embedding,第四个是 LIMIT
sql = """
SELECT
id, source_file, section, content, metadata,
1 - (embedding <=> %s::vector) AS cosine_similarity
FROM knowledge_chunks
WHERE metadata->>'dept' = %s
ORDER BY embedding <=> %s::vector
LIMIT %s
"""
params = [query_embedding, dept_filter, query_embedding, top_k]
else:
sql = sql.format(where_clause="")
with conn.cursor() as cur:
cur.execute(sql, params)
rows = cur.fetchall()
return [
{
"id": r[0],
"source_file": r[1],
"section": r[2],
"content": r[3],
"metadata": r[4],
"similarity": float(r[5]),
}
for r in rows
]
# 使用示例
results = semantic_search("退货需要什么条件", top_k=3)
for r in results:
print(f"[{r['similarity']:.3f}] {r['source_file']} - {r['content'][:60]}")
# [0.921] policy/refund.md - 退换货政策:自签收之日起 7 天内可申请无理由退货...
4.5 与 LangChain 集成(三行接入)
如果你的项目已经在用 LangChain,pgvector 有官方集成,不需要手写 SQL:
from langchain_community.vectorstores import PGVector
from langchain_community.embeddings import HuggingFaceEmbeddings
embedder = HuggingFaceEmbeddings(model_name="BAAI/bge-large-zh-v1.5")
# CONNECTION_STRING 格式:postgresql+psycopg2://user:pwd@host/dbname
vectorstore = PGVector(
connection_string="postgresql+psycopg2://postgres:yourpassword@localhost/postgres",
embedding_function=embedder,
collection_name="knowledge_chunks",
)
# 写入文档
from langchain.schema import Document
vectorstore.add_documents([
Document(page_content="退换货政策...", metadata={"source": "policy/refund.md"}),
])
# 语义搜索
docs = vectorstore.similarity_search("退货条件", k=3)
LangChain 的 PGVector 会自动创建表和索引(默认用 IVFFlat)。如果想用 HNSW,需要在数据库里手动建索引,或等 LangChain 下游版本支持 HNSW 参数配置。
4.6 pgvector 生产注意事项
注意点 1:先写数据,再建 HNSW 索引
IVFFlat 在数据量不足时效果很差(需要先有足量数据做 K-Means);HNSW 没有这个限制,但如果你的数据会大量更新,建索引前先写完一批数据,再建索引,性能更好。
注意点 2:maintenance_work_mem 要调大
HNSW 索引构建很耗内存,默认的 64MB 通常不够:
-- 建索引前临时调大,建完恢复
SET maintenance_work_mem = '1GB';
CREATE INDEX ON knowledge_chunks USING hnsw (embedding vector_cosine_ops);
RESET maintenance_work_mem;
注意点 3:查询时设 ef_search
HNSW 索引的 ef_search 参数控制查询精度,默认值 40 在百万级数据时召回率可能不够:
-- 会话级设置,提高查询召回率
SET hnsw.ef_search = 100;
注意点 4:数据量超 500 万考虑迁移
pgvector 是单机方案,超过 500 万向量后 P99 延迟开始显著上升,超过 1000 万就该考虑迁 Qdrant 或 Milvus 了。迁移成本:把数据导出重新向量化写入,通常一个周末搞定。
五、一张表总结选型
| 需求 | 推荐方案 | 理由 |
|---|---|---|
| 本地开发调试 | Chroma | 零运维,三行代码起 |
| 生产环境 < 100 万向量 + 已有 PG | pgvector | 复用基础设施,零额外运维 |
| 生产环境,需要 ACID 事务 | pgvector | 向量和业务数据同库 |
| 生产环境 100 万-5000 万 | Qdrant | Rust 性能 + Payload 过滤无损 |
| 生产环境 > 5000 万,分布式 | Milvus | 唯一验证过十亿级的开源方案 |
| 多语言代码库搜索 | voyage-code-3 + 任意向量库 | 代码语义理解最强 |
| 代码数据不出境 | CodeBERT + pgvector | 全链路本地 |
结尾
今天这篇覆盖了几个容易被忽视的点:
- Embedding 的方向才是语义,长度没有意义——这是余弦相似度的根本原因
- 代码 Embedding 要用专用模型,通用模型对代码语义理解浮于表面
- Chroma 是开发期的最佳拍档,但别在生产用它
- pgvector 是 500 万以下规模的最低阻力方案,建库代码加上去不超过 50 行
下一篇把检索策略聊完——混合检索、RRF、Reranker 怎么组合,以及查询改写(Query Rewriting)的工程实现。
这里给大家精心整理了一份全面的AI大模型学习资源,包括:AI大模型全套学习路线图(从入门到实战)、精品AI大模型学习书籍手册、视频教程、实战学习、面试题等,资料免费分享!
👇👇扫码免费领取全部内容👇👇

1. 成长路线图&学习规划
要学习一门新的技术,作为新手一定要先学习成长路线图,方向不对,努力白费。
这里,我们为新手和想要进一步提升的专业人士准备了一份详细的学习成长路线图和规划。可以说是最科学最系统的学习成长路线。

2. 大模型经典PDF书籍
书籍和学习文档资料是学习大模型过程中必不可少的,我们精选了一系列深入探讨大模型技术的书籍和学习文档,它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础。(书籍含电子版PDF)

3. 大模型视频教程
对于很多自学或者没有基础的同学来说,书籍这些纯文字类的学习教材会觉得比较晦涩难以理解,因此,我们提供了丰富的大模型视频教程,以动态、形象的方式展示技术概念,帮助你更快、更轻松地掌握核心知识。

4. 2026行业报告
行业分析主要包括对不同行业的现状、趋势、问题、机会等进行系统地调研和评估,以了解哪些行业更适合引入大模型的技术和应用,以及在哪些方面可以发挥大模型的优势。

5. 大模型项目实战
学以致用 ,当你的理论知识积累到一定程度,就需要通过项目实战,在实际操作中检验和巩固你所学到的知识,同时为你找工作和职业发展打下坚实的基础。

6. 大模型面试题
面试不仅是技术的较量,更需要充分的准备。
在你已经掌握了大模型技术之后,就需要开始准备面试,我们将提供精心整理的大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。

7. 资料领取:全套内容免费抱走,学 AI 不用再找第二份
不管你是 0 基础想入门 AI 大模型,还是有基础想冲刺大厂、了解行业趋势,这份资料都能满足你!
现在只需按照提示操作,就能免费领取:
👇👇扫码免费领取全部内容👇👇

2131



被折叠的 条评论
为什么被折叠?



